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 ---------------------- 395 // in initialization: 396 auto font = ctx.createFont("Roboto", "fonts/Roboto.ttf"); 397 enforce(font != -1, "Failed to load font!"); 398 foreach (fallbackPath; ["fonts/NotoSans.ttf"]) { 399 auto fallback = ctx.createFont("fallback", fallbackPath); 400 if (fallback == -1) { 401 stderr.writeln("Failed to load fallback font ", fallbackPath); 402 continue; 403 } 404 ctx.addFallbackFont(font, fallback); 405 } 406 407 // rendering: 408 ctx.fontFaceId = font; 409 //ctx.fontBlur = 3; 410 //ctx.textAlign = NVGTextAlign.H.Center; 411 ctx.fontSize = 20; 412 ctx.fillColor = NVGColor.white; 413 ctx.text(0, height, text); 414 ---------------------- 415 416 Font measure functions return values in local space, the calculations are 417 carried in the same resolution as the final rendering. This is done because 418 the text glyph positions are snapped to the nearest pixels sharp rendering. 419 420 The local space means that values are not rotated or scale as per the current 421 transformation. For example if you set font size to 12, which would mean that 422 line height is 16, then regardless of the current scaling and rotation, the 423 returned line height is always 16. Some measures may vary because of the scaling 424 since aforementioned pixel snapping. 425 426 While this may sound a little odd, the setup allows you to always render the 427 same way regardless of scaling. I.e. following works regardless of scaling: 428 429 ---------------------- 430 string txt = "Text me up."; 431 vg.textBounds(x, y, txt, bounds); 432 vg.beginPath(); 433 vg.roundedRect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1], 6); 434 vg.fill(); 435 ---------------------- 436 437 Note: currently only solid color fill is supported for text. 438 439 font_stash = 440 ## Low-Level Font Engine (FontStash) 441 442 FontStash is used to load fonts, to manage font atlases, and to get various text metrics. 443 You don't need any graphics context to use FontStash, so you can do things like text 444 layouting outside of your rendering code. Loaded fonts are refcounted, so it is cheap 445 to create new FontStash, copy fonts from NanoVega context into it, and use that new 446 FontStash to do some UI layouting, for example. Also note that you can get text metrics 447 without creating glyph bitmaps (by using [FONSTextBoundsIterator], for example); this way 448 you don't need to waste CPU and memory resources to render unneeded images into font atlas, 449 and you can layout alot of text very fast. 450 451 Note that "FontStash" is abbrevated as "FONS". So when you see some API that contains 452 word "fons" in it, this is not a typo, and it should not read "font" intead. 453 454 TODO for Ketmar: write some nice example code here, and finish documenting FontStash API. 455 */ 456 module arsd.nanovega; 457 458 /// This example shows how to do the NanoVega sample without the [NVGWindow] helper class. 459 unittest { 460 import arsd.simpledisplay; 461 462 import arsd.nanovega; 463 464 void main () { 465 NVGContext nvg; // our NanoVega context 466 467 // we need at least OpenGL3 with GLSL to use NanoVega, 468 // so let's tell simpledisplay about that 469 setOpenGLContextVersion(3, 0); 470 471 // now create OpenGL window 472 auto sdmain = new SimpleWindow(800, 600, "NanoVega Simple Sample", OpenGlOptions.yes, Resizability.allowResizing); 473 474 // we need to destroy NanoVega context on window close 475 // stricly speaking, it is not necessary, as nothing fatal 476 // will happen if you'll forget it, but let's be polite. 477 // note that we cannot do that *after* our window was closed, 478 // as we need alive OpenGL context to do proper cleanup. 479 sdmain.onClosing = delegate () { 480 nvg.kill(); 481 }; 482 483 // this is called just before our window will be shown for the first time. 484 // we must create NanoVega context here, as it needs to initialize 485 // internal OpenGL subsystem with valid OpenGL context. 486 sdmain.visibleForTheFirstTime = delegate () { 487 // yes, that's all 488 sdmain.setAsCurrentOpenGlContext(); 489 nvg = nvgCreateContext(); 490 if (nvg is null) assert(0, "cannot initialize NanoVega"); 491 }; 492 493 // this callback will be called when we will need to repaint our window 494 sdmain.redrawOpenGlScene = delegate () { 495 // fix viewport (we can do this in resize event, or here, it doesn't matter) 496 glViewport(0, 0, sdmain.width, sdmain.height); 497 498 // clear window 499 glClearColor(0, 0, 0, 0); 500 glClear(glNVGClearFlags); // use NanoVega API to get flags for OpenGL call 501 502 { 503 nvg.beginFrame(sdmain.width, sdmain.height); // begin rendering 504 scope(exit) nvg.endFrame(); // and flush render queue on exit 505 506 nvg.beginPath(); // start new path 507 nvg.roundedRect(20.5, 30.5, sdmain.width-40, sdmain.height-60, 8); // .5 to draw at pixel center (see NanoVega documentation) 508 // now set filling mode for our rectangle 509 // you can create colors using HTML syntax, or with convenient constants 510 nvg.fillPaint = nvg.linearGradient(20.5, 30.5, sdmain.width-40, sdmain.height-60, NVGColor("#f70"), NVGColor.green); 511 // now fill our rect 512 nvg.fill(); 513 // and draw a nice outline 514 nvg.strokeColor = NVGColor.white; 515 nvg.strokeWidth = 2; 516 nvg.stroke(); 517 // that's all, folks! 518 } 519 }; 520 521 sdmain.eventLoop(0, // no pulse timer required 522 delegate (KeyEvent event) { 523 if (event == "*-Q" || event == "Escape") { sdmain.close(); return; } // quit on Q, Ctrl+Q, and so on 524 }, 525 ); 526 527 flushGui(); // let OS do it's cleanup 528 } 529 } 530 531 private: 532 533 version(aliced) { 534 import iv.meta; 535 import iv.vfs; 536 } else { 537 private alias usize = size_t; 538 // i fear phobos! 539 private template Unqual(T) { 540 static if (is(T U == immutable U)) alias Unqual = U; 541 else static if (is(T U == shared inout const U)) alias Unqual = U; 542 else static if (is(T U == shared inout U)) alias Unqual = U; 543 else static if (is(T U == shared const U)) alias Unqual = U; 544 else static if (is(T U == shared U)) alias Unqual = U; 545 else static if (is(T U == inout const U)) alias Unqual = U; 546 else static if (is(T U == inout U)) alias Unqual = U; 547 else static if (is(T U == const U)) alias Unqual = U; 548 else alias Unqual = T; 549 } 550 private template isAnyCharType(T, bool unqual=false) { 551 static if (unqual) private alias UT = Unqual!T; else private alias UT = T; 552 enum isAnyCharType = is(UT == char) || is(UT == wchar) || is(UT == dchar); 553 } 554 private template isWideCharType(T, bool unqual=false) { 555 static if (unqual) private alias UT = Unqual!T; else private alias UT = T; 556 enum isWideCharType = is(UT == wchar) || is(UT == dchar); 557 } 558 } 559 version(nanovg_disable_vfs) { 560 enum NanoVegaHasIVVFS = false; 561 } else { 562 static if (is(typeof((){import iv.vfs;}))) { 563 enum NanoVegaHasIVVFS = true; 564 import iv.vfs; 565 } else { 566 enum NanoVegaHasIVVFS = false; 567 } 568 } 569 570 // ////////////////////////////////////////////////////////////////////////// // 571 // engine 572 // ////////////////////////////////////////////////////////////////////////// // 573 import core.stdc.stdlib : malloc, realloc, free; 574 import core.stdc.string : memset, memcpy, strlen; 575 import std.math : PI; 576 577 //version = nanovg_force_stb_ttf; 578 579 version(Posix) { 580 version = nanovg_use_freetype; 581 } else { 582 version = nanovg_disable_fontconfig; 583 } 584 585 version (bindbc) { 586 version = nanovg_builtin_fontconfig_bindings; 587 version = nanovg_bindbc_opengl_bindings; 588 version = nanovg_bindbc_freetype_bindings; 589 version(BindFT_Dynamic) 590 static assert(0, "AsumFace was too lazy to write the code for the dynamic bindbc freetype bindings"); 591 else { 592 version(BindFT_Static) {} 593 else 594 static assert(0, "well, duh. you got to pass the BindFT_Static version identifier to the compiler"); 595 } 596 } else version(aliced) { 597 version = nanovg_default_no_font_aa; 598 version = nanovg_builtin_fontconfig_bindings; 599 version = nanovg_builtin_freetype_bindings; 600 version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings 601 } else { 602 version (neverHave_bindbc_opengl) 603 version = nanovg_bindbc_opengl_bindings; 604 else 605 version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings 606 version (neverHave_bindbc_freetype) 607 version = nanovg_bindbc_freetype_bindings; 608 else 609 version = nanovg_builtin_freetype_bindings; 610 version = nanovg_builtin_fontconfig_bindings; 611 } 612 613 version(nanovg_disable_fontconfig) { 614 public enum NanoVegaHasFontConfig = false; 615 } else { 616 public enum NanoVegaHasFontConfig = true; 617 version(nanovg_builtin_fontconfig_bindings) {} else import iv.fontconfig; 618 } 619 620 //version = nanovg_bench_flatten; 621 622 /++ 623 Annotation to indicate the marked function is compatible with [arsd.script]. 624 625 626 Any function that takes a [Color] argument will be passed a string instead. 627 628 Scriptable Functions 629 ==================== 630 631 $(UDA_USES) 632 633 $(ALWAYS_DOCUMENT) 634 +/ 635 private enum scriptable = "arsd_jsvar_compatible"; 636 637 public: 638 alias NVG_PI = PI; 639 640 enum NanoVegaHasArsdColor = (is(typeof((){ import arsd.color; }))); 641 enum NanoVegaHasArsdImage = (is(typeof((){ import arsd.color; import arsd.image; }))); 642 643 static if (NanoVegaHasArsdColor) private import arsd.color; 644 static if (NanoVegaHasArsdImage) { 645 private import arsd.image; 646 } else { 647 void stbi_set_unpremultiply_on_load (int flag_true_if_should_unpremultiply) {} 648 void stbi_convert_iphone_png_to_rgb (int flag_true_if_should_convert) {} 649 ubyte* stbi_load (const(char)* filename, int* x, int* y, int* comp, int req_comp) { return null; } 650 ubyte* stbi_load_from_memory (const(void)* buffer, int len, int* x, int* y, int* comp, int req_comp) { return null; } 651 void stbi_image_free (void* retval_from_stbi_load) {} 652 } 653 654 version(nanovg_default_no_font_aa) { 655 __gshared bool NVG_INVERT_FONT_AA = false; 656 } else { 657 __gshared bool NVG_INVERT_FONT_AA = true; 658 } 659 660 661 /// this is branchless for ints on x86, and even for longs on x86_64 662 public ubyte nvgClampToByte(T) (T n) pure nothrow @safe @nogc if (__traits(isIntegral, T)) { 663 static if (__VERSION__ > 2067) pragma(inline, true); 664 static if (T.sizeof == 2 || T.sizeof == 4) { 665 static if (__traits(isUnsigned, T)) { 666 return cast(ubyte)(n&0xff|(255-((-cast(int)(n < 256))>>24))); 667 } else { 668 n &= -cast(int)(n >= 0); 669 return cast(ubyte)(n|((255-cast(int)n)>>31)); 670 } 671 } else static if (T.sizeof == 1) { 672 static assert(__traits(isUnsigned, T), "clampToByte: signed byte? no, really?"); 673 return cast(ubyte)n; 674 } else static if (T.sizeof == 8) { 675 static if (__traits(isUnsigned, T)) { 676 return cast(ubyte)(n&0xff|(255-((-cast(long)(n < 256))>>56))); 677 } else { 678 n &= -cast(long)(n >= 0); 679 return cast(ubyte)(n|((255-cast(long)n)>>63)); 680 } 681 } else { 682 static assert(false, "clampToByte: integer too big"); 683 } 684 } 685 686 687 /// NanoVega RGBA color 688 /// Group: color_utils 689 public align(1) struct NVGColor { 690 align(1): 691 public: 692 float[4] rgba = 0; /// default color is transparent (a=1 is opaque) 693 694 public: 695 @property string toString () const @safe { import std.string : format; return "NVGColor(%s,%s,%s,%s)".format(r, g, b, a); } 696 697 public: 698 enum transparent = NVGColor(0.0f, 0.0f, 0.0f, 0.0f); 699 enum k8orange = NVGColor(1.0f, 0.5f, 0.0f, 1.0f); 700 701 enum aliceblue = NVGColor(240, 248, 255); 702 enum antiquewhite = NVGColor(250, 235, 215); 703 enum aqua = NVGColor(0, 255, 255); 704 enum aquamarine = NVGColor(127, 255, 212); 705 enum azure = NVGColor(240, 255, 255); 706 enum beige = NVGColor(245, 245, 220); 707 enum bisque = NVGColor(255, 228, 196); 708 enum black = NVGColor(0, 0, 0); // basic color 709 enum blanchedalmond = NVGColor(255, 235, 205); 710 enum blue = NVGColor(0, 0, 255); // basic color 711 enum blueviolet = NVGColor(138, 43, 226); 712 enum brown = NVGColor(165, 42, 42); 713 enum burlywood = NVGColor(222, 184, 135); 714 enum cadetblue = NVGColor(95, 158, 160); 715 enum chartreuse = NVGColor(127, 255, 0); 716 enum chocolate = NVGColor(210, 105, 30); 717 enum coral = NVGColor(255, 127, 80); 718 enum cornflowerblue = NVGColor(100, 149, 237); 719 enum cornsilk = NVGColor(255, 248, 220); 720 enum crimson = NVGColor(220, 20, 60); 721 enum cyan = NVGColor(0, 255, 255); // basic color 722 enum darkblue = NVGColor(0, 0, 139); 723 enum darkcyan = NVGColor(0, 139, 139); 724 enum darkgoldenrod = NVGColor(184, 134, 11); 725 enum darkgray = NVGColor(169, 169, 169); 726 enum darkgreen = NVGColor(0, 100, 0); 727 enum darkgrey = NVGColor(169, 169, 169); 728 enum darkkhaki = NVGColor(189, 183, 107); 729 enum darkmagenta = NVGColor(139, 0, 139); 730 enum darkolivegreen = NVGColor(85, 107, 47); 731 enum darkorange = NVGColor(255, 140, 0); 732 enum darkorchid = NVGColor(153, 50, 204); 733 enum darkred = NVGColor(139, 0, 0); 734 enum darksalmon = NVGColor(233, 150, 122); 735 enum darkseagreen = NVGColor(143, 188, 143); 736 enum darkslateblue = NVGColor(72, 61, 139); 737 enum darkslategray = NVGColor(47, 79, 79); 738 enum darkslategrey = NVGColor(47, 79, 79); 739 enum darkturquoise = NVGColor(0, 206, 209); 740 enum darkviolet = NVGColor(148, 0, 211); 741 enum deeppink = NVGColor(255, 20, 147); 742 enum deepskyblue = NVGColor(0, 191, 255); 743 enum dimgray = NVGColor(105, 105, 105); 744 enum dimgrey = NVGColor(105, 105, 105); 745 enum dodgerblue = NVGColor(30, 144, 255); 746 enum firebrick = NVGColor(178, 34, 34); 747 enum floralwhite = NVGColor(255, 250, 240); 748 enum forestgreen = NVGColor(34, 139, 34); 749 enum fuchsia = NVGColor(255, 0, 255); 750 enum gainsboro = NVGColor(220, 220, 220); 751 enum ghostwhite = NVGColor(248, 248, 255); 752 enum gold = NVGColor(255, 215, 0); 753 enum goldenrod = NVGColor(218, 165, 32); 754 enum gray = NVGColor(128, 128, 128); // basic color 755 enum green = NVGColor(0, 128, 0); // basic color 756 enum greenyellow = NVGColor(173, 255, 47); 757 enum grey = NVGColor(128, 128, 128); // basic color 758 enum honeydew = NVGColor(240, 255, 240); 759 enum hotpink = NVGColor(255, 105, 180); 760 enum indianred = NVGColor(205, 92, 92); 761 enum indigo = NVGColor(75, 0, 130); 762 enum ivory = NVGColor(255, 255, 240); 763 enum khaki = NVGColor(240, 230, 140); 764 enum lavender = NVGColor(230, 230, 250); 765 enum lavenderblush = NVGColor(255, 240, 245); 766 enum lawngreen = NVGColor(124, 252, 0); 767 enum lemonchiffon = NVGColor(255, 250, 205); 768 enum lightblue = NVGColor(173, 216, 230); 769 enum lightcoral = NVGColor(240, 128, 128); 770 enum lightcyan = NVGColor(224, 255, 255); 771 enum lightgoldenrodyellow = NVGColor(250, 250, 210); 772 enum lightgray = NVGColor(211, 211, 211); 773 enum lightgreen = NVGColor(144, 238, 144); 774 enum lightgrey = NVGColor(211, 211, 211); 775 enum lightpink = NVGColor(255, 182, 193); 776 enum lightsalmon = NVGColor(255, 160, 122); 777 enum lightseagreen = NVGColor(32, 178, 170); 778 enum lightskyblue = NVGColor(135, 206, 250); 779 enum lightslategray = NVGColor(119, 136, 153); 780 enum lightslategrey = NVGColor(119, 136, 153); 781 enum lightsteelblue = NVGColor(176, 196, 222); 782 enum lightyellow = NVGColor(255, 255, 224); 783 enum lime = NVGColor(0, 255, 0); 784 enum limegreen = NVGColor(50, 205, 50); 785 enum linen = NVGColor(250, 240, 230); 786 enum magenta = NVGColor(255, 0, 255); // basic color 787 enum maroon = NVGColor(128, 0, 0); 788 enum mediumaquamarine = NVGColor(102, 205, 170); 789 enum mediumblue = NVGColor(0, 0, 205); 790 enum mediumorchid = NVGColor(186, 85, 211); 791 enum mediumpurple = NVGColor(147, 112, 219); 792 enum mediumseagreen = NVGColor(60, 179, 113); 793 enum mediumslateblue = NVGColor(123, 104, 238); 794 enum mediumspringgreen = NVGColor(0, 250, 154); 795 enum mediumturquoise = NVGColor(72, 209, 204); 796 enum mediumvioletred = NVGColor(199, 21, 133); 797 enum midnightblue = NVGColor(25, 25, 112); 798 enum mintcream = NVGColor(245, 255, 250); 799 enum mistyrose = NVGColor(255, 228, 225); 800 enum moccasin = NVGColor(255, 228, 181); 801 enum navajowhite = NVGColor(255, 222, 173); 802 enum navy = NVGColor(0, 0, 128); 803 enum oldlace = NVGColor(253, 245, 230); 804 enum olive = NVGColor(128, 128, 0); 805 enum olivedrab = NVGColor(107, 142, 35); 806 enum orange = NVGColor(255, 165, 0); 807 enum orangered = NVGColor(255, 69, 0); 808 enum orchid = NVGColor(218, 112, 214); 809 enum palegoldenrod = NVGColor(238, 232, 170); 810 enum palegreen = NVGColor(152, 251, 152); 811 enum paleturquoise = NVGColor(175, 238, 238); 812 enum palevioletred = NVGColor(219, 112, 147); 813 enum papayawhip = NVGColor(255, 239, 213); 814 enum peachpuff = NVGColor(255, 218, 185); 815 enum peru = NVGColor(205, 133, 63); 816 enum pink = NVGColor(255, 192, 203); 817 enum plum = NVGColor(221, 160, 221); 818 enum powderblue = NVGColor(176, 224, 230); 819 enum purple = NVGColor(128, 0, 128); 820 enum red = NVGColor(255, 0, 0); // basic color 821 enum rosybrown = NVGColor(188, 143, 143); 822 enum royalblue = NVGColor(65, 105, 225); 823 enum saddlebrown = NVGColor(139, 69, 19); 824 enum salmon = NVGColor(250, 128, 114); 825 enum sandybrown = NVGColor(244, 164, 96); 826 enum seagreen = NVGColor(46, 139, 87); 827 enum seashell = NVGColor(255, 245, 238); 828 enum sienna = NVGColor(160, 82, 45); 829 enum silver = NVGColor(192, 192, 192); 830 enum skyblue = NVGColor(135, 206, 235); 831 enum slateblue = NVGColor(106, 90, 205); 832 enum slategray = NVGColor(112, 128, 144); 833 enum slategrey = NVGColor(112, 128, 144); 834 enum snow = NVGColor(255, 250, 250); 835 enum springgreen = NVGColor(0, 255, 127); 836 enum steelblue = NVGColor(70, 130, 180); 837 enum tan = NVGColor(210, 180, 140); 838 enum teal = NVGColor(0, 128, 128); 839 enum thistle = NVGColor(216, 191, 216); 840 enum tomato = NVGColor(255, 99, 71); 841 enum turquoise = NVGColor(64, 224, 208); 842 enum violet = NVGColor(238, 130, 238); 843 enum wheat = NVGColor(245, 222, 179); 844 enum white = NVGColor(255, 255, 255); // basic color 845 enum whitesmoke = NVGColor(245, 245, 245); 846 enum yellow = NVGColor(255, 255, 0); // basic color 847 enum yellowgreen = NVGColor(154, 205, 50); 848 849 nothrow @safe @nogc: 850 public: 851 /// 852 this (ubyte ar, ubyte ag, ubyte ab, ubyte aa=255) pure { 853 pragma(inline, true); 854 r = ar/255.0f; 855 g = ag/255.0f; 856 b = ab/255.0f; 857 a = aa/255.0f; 858 } 859 860 /// 861 this (float ar, float ag, float ab, float aa=1.0f) pure { 862 pragma(inline, true); 863 r = ar; 864 g = ag; 865 b = ab; 866 a = aa; 867 } 868 869 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color) 870 this (uint c) pure { 871 pragma(inline, true); 872 r = (c&0xff)/255.0f; 873 g = ((c>>8)&0xff)/255.0f; 874 b = ((c>>16)&0xff)/255.0f; 875 a = ((c>>24)&0xff)/255.0f; 876 } 877 878 /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb" 879 this (const(char)[] srgb) { 880 static int c2d (char ch) pure nothrow @safe @nogc { 881 pragma(inline, true); 882 return 883 ch >= '0' && ch <= '9' ? ch-'0' : 884 ch >= 'A' && ch <= 'F' ? ch-'A'+10 : 885 ch >= 'a' && ch <= 'f' ? ch-'a'+10 : 886 -1; 887 } 888 int[8] digs; 889 int dc = -1; 890 foreach (immutable char ch; srgb) { 891 if (ch <= ' ') continue; 892 if (ch == '#') { 893 if (dc != -1) { dc = -1; break; } 894 dc = 0; 895 } else { 896 if (dc >= digs.length) { dc = -1; break; } 897 if ((digs[dc++] = c2d(ch)) < 0) { dc = -1; break; } 898 } 899 } 900 switch (dc) { 901 case 3: // rgb 902 a = 1.0f; 903 r = digs[0]/15.0f; 904 g = digs[1]/15.0f; 905 b = digs[2]/15.0f; 906 break; 907 case 4: // argb 908 a = digs[0]/15.0f; 909 r = digs[1]/15.0f; 910 g = digs[2]/15.0f; 911 b = digs[3]/15.0f; 912 break; 913 case 6: // rrggbb 914 a = 1.0f; 915 r = (digs[0]*16+digs[1])/255.0f; 916 g = (digs[2]*16+digs[3])/255.0f; 917 b = (digs[4]*16+digs[5])/255.0f; 918 break; 919 case 8: // aarrggbb 920 a = (digs[0]*16+digs[1])/255.0f; 921 r = (digs[2]*16+digs[3])/255.0f; 922 g = (digs[4]*16+digs[5])/255.0f; 923 b = (digs[6]*16+digs[7])/255.0f; 924 break; 925 default: 926 break; 927 } 928 } 929 930 /// Is this color completely opaque? 931 @property bool isOpaque () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] >= 1.0f); } 932 /// Is this color completely transparent? 933 @property bool isTransparent () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] <= 0.0f); } 934 935 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color) 936 @property uint asUint () const pure { 937 pragma(inline, true); 938 return 939 cast(uint)(r*255)| 940 (cast(uint)(g*255)<<8)| 941 (cast(uint)(b*255)<<16)| 942 (cast(uint)(a*255)<<24); 943 } 944 945 alias asUintABGR = asUint; /// Ditto. 946 947 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color) 948 static NVGColor fromUint (uint c) pure { pragma(inline, true); return NVGColor(c); } 949 950 alias fromUintABGR = fromUint; /// Ditto. 951 952 /// AARRGGBB 953 @property uint asUintARGB () const pure { 954 pragma(inline, true); 955 return 956 cast(uint)(b*255)| 957 (cast(uint)(g*255)<<8)| 958 (cast(uint)(r*255)<<16)| 959 (cast(uint)(a*255)<<24); 960 } 961 962 /// AARRGGBB 963 static NVGColor fromUintARGB (uint c) pure { pragma(inline, true); return NVGColor((c>>16)&0xff, (c>>8)&0xff, c&0xff, (c>>24)&0xff); } 964 965 @property ref inout(float) r () inout pure @trusted { pragma(inline, true); return rgba.ptr[0]; } /// 966 @property ref inout(float) g () inout pure @trusted { pragma(inline, true); return rgba.ptr[1]; } /// 967 @property ref inout(float) b () inout pure @trusted { pragma(inline, true); return rgba.ptr[2]; } /// 968 @property ref inout(float) a () inout pure @trusted { pragma(inline, true); return rgba.ptr[3]; } /// 969 970 ref NVGColor applyTint() (const scope auto ref NVGColor tint) nothrow @trusted @nogc { 971 if (tint.a == 0) return this; 972 foreach (immutable idx, ref float v; rgba[0..4]) { 973 v = nvg__clamp(v*tint.rgba.ptr[idx], 0.0f, 1.0f); 974 } 975 return this; 976 } 977 978 NVGHSL asHSL() (bool useWeightedLightness=false) const { pragma(inline, true); return NVGHSL.fromColor(this, useWeightedLightness); } /// 979 static fromHSL() (const scope auto ref NVGHSL hsl) { pragma(inline, true); return hsl.asColor; } /// 980 981 static if (NanoVegaHasArsdColor) { 982 Color toArsd () const { pragma(inline, true); return Color(cast(int)(r*255), cast(int)(g*255), cast(int)(b*255), cast(int)(a*255)); } /// 983 static NVGColor fromArsd (in Color c) { pragma(inline, true); return NVGColor(c.r, c.g, c.b, c.a); } /// 984 /// 985 this (in Color c) { 986 version(aliced) pragma(inline, true); 987 r = c.r/255.0f; 988 g = c.g/255.0f; 989 b = c.b/255.0f; 990 a = c.a/255.0f; 991 } 992 } 993 } 994 995 996 /// NanoVega A-HSL color 997 /// Group: color_utils 998 public align(1) struct NVGHSL { 999 align(1): 1000 float h=0, s=0, l=1, a=1; /// 1001 1002 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)); } 1003 1004 nothrow @safe @nogc: 1005 public: 1006 /// 1007 this (float ah, float as, float al, float aa=1) pure { pragma(inline, true); h = ah; s = as; l = al; a = aa; } 1008 1009 NVGColor asColor () const { pragma(inline, true); return nvgHSLA(h, s, l, a); } /// 1010 1011 // taken from Adam's arsd.color 1012 /** Converts an RGB color into an HSL triplet. 1013 * [useWeightedLightness] will try to get a better value for luminosity for the human eye, 1014 * which is more sensitive to green than red and more to red than blue. 1015 * If it is false, it just does average of the rgb. */ 1016 static NVGHSL fromColor() (const scope auto ref NVGColor c, bool useWeightedLightness=false) pure { 1017 NVGHSL res; 1018 res.a = c.a; 1019 float r1 = c.r; 1020 float g1 = c.g; 1021 float b1 = c.b; 1022 1023 float maxColor = r1; 1024 if (g1 > maxColor) maxColor = g1; 1025 if (b1 > maxColor) maxColor = b1; 1026 float minColor = r1; 1027 if (g1 < minColor) minColor = g1; 1028 if (b1 < minColor) minColor = b1; 1029 1030 res.l = (maxColor+minColor)/2; 1031 if (useWeightedLightness) { 1032 // the colors don't affect the eye equally 1033 // this is a little more accurate than plain HSL numbers 1034 res.l = 0.2126*r1+0.7152*g1+0.0722*b1; 1035 } 1036 if (maxColor != minColor) { 1037 if (res.l < 0.5) { 1038 res.s = (maxColor-minColor)/(maxColor+minColor); 1039 } else { 1040 res.s = (maxColor-minColor)/(2.0-maxColor-minColor); 1041 } 1042 if (r1 == maxColor) { 1043 res.h = (g1-b1)/(maxColor-minColor); 1044 } else if(g1 == maxColor) { 1045 res.h = 2.0+(b1-r1)/(maxColor-minColor); 1046 } else { 1047 res.h = 4.0+(r1-g1)/(maxColor-minColor); 1048 } 1049 } 1050 1051 res.h = res.h*60; 1052 if (res.h < 0) res.h += 360; 1053 res.h /= 360; 1054 1055 return res; 1056 } 1057 } 1058 1059 1060 //version = nanovega_debug_image_manager; 1061 //version = nanovega_debug_image_manager_rc; 1062 1063 /** NanoVega image handle. 1064 * 1065 * This is refcounted struct, so you don't need to do anything special to free it once it is allocated. 1066 * 1067 * Group: images 1068 */ 1069 struct NVGImage { 1070 enum isOpaqueStruct = true; 1071 private: 1072 NVGContext ctx; 1073 int id; // backend image id 1074 1075 public: 1076 /// 1077 this() (const scope auto ref NVGImage src) nothrow @trusted @nogc { 1078 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); } 1079 if (src.id > 0 && src.ctx !is null) { 1080 ctx = cast(NVGContext)src.ctx; 1081 id = src.id; 1082 ctx.nvg__imageIncRef(id); 1083 } 1084 } 1085 1086 /// 1087 ~this () nothrow @trusted @nogc { version(aliced) pragma(inline, true); clear(); } 1088 1089 /// 1090 this (this) nothrow @trusted @nogc { 1091 version(aliced) pragma(inline, true); 1092 if (id > 0 && ctx !is null) { 1093 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p postblit (imgid=%d)\n", &this, id); } 1094 ctx.nvg__imageIncRef(id); 1095 } 1096 } 1097 1098 /// 1099 void opAssign() (const scope auto ref NVGImage src) nothrow @trusted @nogc { 1100 if (src.id <= 0 || src.ctx is null) { 1101 clear(); 1102 } else { 1103 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); } 1104 if (src.id > 0 && src.ctx !is null) (cast(NVGContext)src.ctx).nvg__imageIncRef(src.id); 1105 if (id > 0 && ctx !is null) ctx.nvg__imageDecRef(id); 1106 ctx = cast(NVGContext)src.ctx; 1107 id = src.id; 1108 } 1109 } 1110 1111 /// Free this image. 1112 void clear () nothrow @trusted @nogc { 1113 if (id > 0 && ctx !is null) { 1114 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p cleared (imgid=%d)\n", &this, id); } 1115 ctx.nvg__imageDecRef(id); 1116 } 1117 id = 0; 1118 ctx = null; 1119 } 1120 1121 /// Is this image valid? 1122 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (id > 0 && ctx.valid); } 1123 1124 /// Is the given image valid and comes from the same context? 1125 @property bool isSameContext (const(NVGContext) actx) const pure nothrow @safe @nogc { pragma(inline, true); return (actx !is null && ctx is actx); } 1126 1127 /// Returns image width, or zero for invalid image. 1128 int width () const nothrow @trusted @nogc { 1129 int w = 0; 1130 if (valid) { 1131 int h = void; 1132 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h); 1133 } 1134 return w; 1135 } 1136 1137 /// Returns image height, or zero for invalid image. 1138 int height () const nothrow @trusted @nogc { 1139 int h = 0; 1140 if (valid) { 1141 int w = void; 1142 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h); 1143 } 1144 return h; 1145 } 1146 } 1147 1148 1149 /// Paint parameters for various fills. Don't change anything here! 1150 /// Group: render_styles 1151 public struct NVGPaint { 1152 enum isOpaqueStruct = true; 1153 1154 NVGMatrix xform; 1155 float[2] extent = 0.0f; 1156 float radius = 0.0f; 1157 float feather = 0.0f; 1158 NVGColor innerColor; /// this can be used to modulate images (fill/font) 1159 NVGColor middleColor; 1160 NVGColor outerColor; 1161 float midp = -1; // middle stop for 3-color gradient 1162 NVGImage image; 1163 bool simpleColor; /// if `true`, only innerColor is used, and this is solid-color paint 1164 1165 this() (const scope auto ref NVGPaint p) nothrow @trusted @nogc { 1166 xform = p.xform; 1167 extent[] = p.extent[]; 1168 radius = p.radius; 1169 feather = p.feather; 1170 innerColor = p.innerColor; 1171 middleColor = p.middleColor; 1172 midp = p.midp; 1173 outerColor = p.outerColor; 1174 image = p.image; 1175 simpleColor = p.simpleColor; 1176 } 1177 1178 void opAssign() (const scope auto ref NVGPaint p) nothrow @trusted @nogc { 1179 xform = p.xform; 1180 extent[] = p.extent[]; 1181 radius = p.radius; 1182 feather = p.feather; 1183 innerColor = p.innerColor; 1184 middleColor = p.middleColor; 1185 midp = p.midp; 1186 outerColor = p.outerColor; 1187 image = p.image; 1188 simpleColor = p.simpleColor; 1189 } 1190 1191 void clear () nothrow @trusted @nogc { 1192 version(aliced) pragma(inline, true); 1193 import core.stdc.string : memset; 1194 image.clear(); 1195 memset(&this, 0, this.sizeof); 1196 simpleColor = true; 1197 } 1198 } 1199 1200 /// Path winding. 1201 /// Group: paths 1202 public enum NVGWinding { 1203 CCW = 1, /// Winding for solid shapes 1204 CW = 2, /// Winding for holes 1205 } 1206 1207 /// Path solidity. 1208 /// Group: paths 1209 public enum NVGSolidity { 1210 Solid = 1, /// Solid shape (CCW winding). 1211 Hole = 2, /// Hole (CW winding). 1212 } 1213 1214 /// Line cap style. 1215 /// Group: render_styles 1216 public enum NVGLineCap { 1217 Butt, /// 1218 Round, /// 1219 Square, /// 1220 Bevel, /// 1221 Miter, /// 1222 } 1223 1224 /// Text align. 1225 /// Group: text_api 1226 public align(1) struct NVGTextAlign { 1227 align(1): 1228 /// Horizontal align. 1229 enum H : ubyte { 1230 Left = 0, /// Default, align text horizontally to left. 1231 Center = 1, /// Align text horizontally to center. 1232 Right = 2, /// Align text horizontally to right. 1233 } 1234 1235 /// Vertical align. 1236 enum V : ubyte { 1237 Baseline = 0, /// Default, align text vertically to baseline. 1238 Top = 1, /// Align text vertically to top. 1239 Middle = 2, /// Align text vertically to middle. 1240 Bottom = 3, /// Align text vertically to bottom. 1241 } 1242 1243 pure nothrow @safe @nogc: 1244 public: 1245 this (H h) { pragma(inline, true); value = h; } /// 1246 this (V v) { pragma(inline, true); value = cast(ubyte)(v<<4); } /// 1247 this (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1248 this (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1249 void reset () { pragma(inline, true); value = 0; } /// 1250 void reset (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1251 void reset (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1252 @property: 1253 bool left () const { pragma(inline, true); return ((value&0x0f) == H.Left); } /// 1254 void left (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Left : 0)); } /// 1255 bool center () const { pragma(inline, true); return ((value&0x0f) == H.Center); } /// 1256 void center (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Center : 0)); } /// 1257 bool right () const { pragma(inline, true); return ((value&0x0f) == H.Right); } /// 1258 void right (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Right : 0)); } /// 1259 // 1260 bool baseline () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Baseline); } /// 1261 void baseline (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Baseline<<4 : 0)); } /// 1262 bool top () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Top); } /// 1263 void top (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Top<<4 : 0)); } /// 1264 bool middle () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Middle); } /// 1265 void middle (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Middle<<4 : 0)); } /// 1266 bool bottom () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Bottom); } /// 1267 void bottom (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Bottom<<4 : 0)); } /// 1268 // 1269 H horizontal () const { pragma(inline, true); return cast(H)(value&0x0f); } /// 1270 void horizontal (H v) { pragma(inline, true); value = (value&0xf0)|v; } /// 1271 // 1272 V vertical () const { pragma(inline, true); return cast(V)((value>>4)&0x0f); } /// 1273 void vertical (V v) { pragma(inline, true); value = (value&0x0f)|cast(ubyte)(v<<4); } /// 1274 // 1275 private: 1276 ubyte value = 0; // low nibble: horizontal; high nibble: vertical 1277 } 1278 1279 /// Blending type. 1280 /// Group: composite_operation 1281 public enum NVGBlendFactor { 1282 Zero = 1<<0, /// 1283 One = 1<<1, /// 1284 SrcColor = 1<<2, /// 1285 OneMinusSrcColor = 1<<3, /// 1286 DstColor = 1<<4, /// 1287 OneMinusDstColor = 1<<5, /// 1288 SrcAlpha = 1<<6, /// 1289 OneMinusSrcAlpha = 1<<7, /// 1290 DstAlpha = 1<<8, /// 1291 OneMinusDstAlpha = 1<<9, /// 1292 SrcAlphaSaturate = 1<<10, /// 1293 } 1294 1295 /// Composite operation (HTML5-alike). 1296 /// Group: composite_operation 1297 public enum NVGCompositeOperation { 1298 SourceOver, /// 1299 SourceIn, /// 1300 SourceOut, /// 1301 SourceAtop, /// 1302 DestinationOver, /// 1303 DestinationIn, /// 1304 DestinationOut, /// 1305 DestinationAtop, /// 1306 Lighter, /// 1307 Copy, /// 1308 Xor, /// 1309 } 1310 1311 /// Composite operation state. 1312 /// Group: composite_operation 1313 public struct NVGCompositeOperationState { 1314 bool simple; /// `true`: use `glBlendFunc()` instead of `glBlendFuncSeparate()` 1315 NVGBlendFactor srcRGB; /// 1316 NVGBlendFactor dstRGB; /// 1317 NVGBlendFactor srcAlpha; /// 1318 NVGBlendFactor dstAlpha; /// 1319 } 1320 1321 /// Mask combining more 1322 /// Group: clipping 1323 public enum NVGClipMode { 1324 None, /// normal rendering (i.e. render path instead of modifying clip region) 1325 Union, /// old mask will be masked with the current one; this is the default mode for [clip] 1326 Or, /// new mask will be added to the current one (logical `OR` operation); 1327 Xor, /// new mask will be logically `XOR`ed with the current one 1328 Sub, /// "subtract" current path from mask 1329 Replace, /// replace current mask 1330 Add = Or, /// Synonym 1331 } 1332 1333 /// Glyph position info. 1334 /// Group: text_api 1335 public struct NVGGlyphPosition { 1336 usize strpos; /// Position of the glyph in the input string. 1337 float x; /// The x-coordinate of the logical glyph position. 1338 float minx, maxx; /// The bounds of the glyph shape. 1339 } 1340 1341 /// Text row storage. 1342 /// Group: text_api 1343 public struct NVGTextRow(CT) if (isAnyCharType!CT) { 1344 alias CharType = CT; 1345 const(CT)[] s; 1346 int start; /// Index in the input text where the row starts. 1347 int end; /// Index in the input text where the row ends (one past the last character). 1348 float width; /// Logical width of the row. 1349 float minx, maxx; /// Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending. 1350 /// Get rest of the string. 1351 @property const(CT)[] rest () const pure nothrow @trusted @nogc { pragma(inline, true); return (end <= s.length ? s[end..$] : null); } 1352 /// Get current row. 1353 @property const(CT)[] row () const pure nothrow @trusted @nogc { pragma(inline, true); return s[start..end]; } 1354 @property const(CT)[] string () const pure nothrow @trusted @nogc { pragma(inline, true); return s; } 1355 @property void string(CT) (const(CT)[] v) pure nothrow @trusted @nogc { pragma(inline, true); s = v; } 1356 } 1357 1358 /// Image creation flags. 1359 /// Group: images 1360 public enum NVGImageFlag : uint { 1361 None = 0, /// Nothing special. 1362 GenerateMipmaps = 1<<0, /// Generate mipmaps during creation of the image. 1363 RepeatX = 1<<1, /// Repeat image in X direction. 1364 RepeatY = 1<<2, /// Repeat image in Y direction. 1365 FlipY = 1<<3, /// Flips (inverses) image in Y direction when rendered. 1366 Premultiplied = 1<<4, /// Image data has premultiplied alpha. 1367 ClampToBorderX = 1<<5, /// Clamp image to border (instead of clamping to edge by default) 1368 ClampToBorderY = 1<<6, /// Clamp image to border (instead of clamping to edge by default) 1369 NoFiltering = 1<<8, /// use GL_NEAREST instead of GL_LINEAR. Only affects upscaling if GenerateMipmaps is active. 1370 Nearest = NoFiltering, /// compatibility with original NanoVG 1371 NoDelete = 1<<16,/// Do not delete GL texture handle. 1372 } 1373 1374 alias NVGImageFlags = NVGImageFlag; /// Backwards compatibility for [NVGImageFlag]. 1375 1376 1377 // ////////////////////////////////////////////////////////////////////////// // 1378 private: 1379 1380 static T* xdup(T) (const(T)* ptr, int count) nothrow @trusted @nogc { 1381 import core.stdc.stdlib : malloc; 1382 import core.stdc.string : memcpy; 1383 if (count == 0) return null; 1384 T* res = cast(T*)malloc(T.sizeof*count); 1385 if (res is null) assert(0, "NanoVega: out of memory"); 1386 memcpy(res, ptr, T.sizeof*count); 1387 return res; 1388 } 1389 1390 // Internal Render API 1391 enum NVGtexture { 1392 Alpha = 0x01, 1393 RGBA = 0x02, 1394 } 1395 1396 struct NVGscissor { 1397 NVGMatrix xform; 1398 float[2] extent = -1.0f; 1399 } 1400 1401 /// General NanoVega vertex struct. Contains geometry coordinates, and (sometimes unused) texture coordinates. 1402 public struct NVGVertex { 1403 float x, y, u, v; 1404 } 1405 1406 struct NVGpath { 1407 int first; 1408 int count; 1409 bool closed; 1410 int nbevel; 1411 NVGVertex* fill; 1412 int nfill; 1413 NVGVertex* stroke; 1414 int nstroke; 1415 NVGWinding mWinding; 1416 bool convex; 1417 bool cloned; 1418 1419 @disable this (this); // no copies 1420 void opAssign() (const scope auto ref NVGpath a) { static assert(0, "no copies!"); } 1421 1422 void clear () nothrow @trusted @nogc { 1423 import core.stdc.stdlib : free; 1424 import core.stdc.string : memset; 1425 if (cloned) { 1426 if (stroke !is null && stroke !is fill) free(stroke); 1427 if (fill !is null) free(fill); 1428 } 1429 memset(&this, 0, this.sizeof); 1430 } 1431 1432 // won't clear current path 1433 void copyFrom (const NVGpath* src) nothrow @trusted @nogc { 1434 import core.stdc.string : memcpy; 1435 assert(src !is null); 1436 memcpy(&this, src, NVGpath.sizeof); 1437 this.fill = xdup(src.fill, src.nfill); 1438 if (src.stroke is src.fill) { 1439 this.stroke = this.fill; 1440 } else { 1441 this.stroke = xdup(src.stroke, src.nstroke); 1442 } 1443 this.cloned = true; 1444 } 1445 1446 public @property const(NVGVertex)[] fillVertices () const pure nothrow @trusted @nogc { 1447 pragma(inline, true); 1448 return (nfill > 0 ? fill[0..nfill] : null); 1449 } 1450 1451 public @property const(NVGVertex)[] strokeVertices () const pure nothrow @trusted @nogc { 1452 pragma(inline, true); 1453 return (nstroke > 0 ? stroke[0..nstroke] : null); 1454 } 1455 1456 public @property NVGWinding winding () const pure nothrow @trusted @nogc { pragma(inline, true); return mWinding; } 1457 public @property bool complex () const pure nothrow @trusted @nogc { pragma(inline, true); return !convex; } 1458 } 1459 1460 1461 struct NVGparams { 1462 void* userPtr; 1463 bool edgeAntiAlias; 1464 bool fontAA; 1465 bool function (void* uptr) nothrow @trusted @nogc renderCreate; 1466 int function (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc renderCreateTexture; 1467 bool function (void* uptr, int image) nothrow @trusted @nogc renderTextureIncRef; 1468 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 1469 bool function (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc renderUpdateTexture; 1470 bool function (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc renderGetTextureSize; 1471 void function (void* uptr, int width, int height) nothrow @trusted @nogc renderViewport; // called in [beginFrame] 1472 void function (void* uptr) nothrow @trusted @nogc renderCancel; 1473 void function (void* uptr) nothrow @trusted @nogc renderFlush; 1474 void function (void* uptr) nothrow @trusted @nogc renderPushClip; // backend should support stack of at least [NVG_MAX_STATES] elements 1475 void function (void* uptr) nothrow @trusted @nogc renderPopClip; // backend should support stack of at least [NVG_MAX_STATES] elements 1476 void function (void* uptr) nothrow @trusted @nogc renderResetClip; // reset current clip region to `non-clipped` 1477 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; 1478 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; 1479 void function (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc renderTriangles; 1480 void function (void* uptr, const scope ref NVGMatrix mat) nothrow @trusted @nogc renderSetAffine; 1481 void function (void* uptr) nothrow @trusted @nogc renderDelete; 1482 } 1483 1484 // ////////////////////////////////////////////////////////////////////////// // 1485 private: 1486 1487 enum NVG_INIT_FONTIMAGE_SIZE = 512; 1488 enum NVG_MAX_FONTIMAGE_SIZE = 2048; 1489 enum NVG_MAX_FONTIMAGES = 4; 1490 1491 enum NVG_INIT_COMMANDS_SIZE = 256; 1492 enum NVG_INIT_POINTS_SIZE = 128; 1493 enum NVG_INIT_PATHS_SIZE = 16; 1494 enum NVG_INIT_VERTS_SIZE = 256; 1495 enum NVG_MAX_STATES = 32; 1496 1497 public enum NVG_KAPPA90 = 0.5522847493f; /// Length proportional to radius of a cubic bezier handle for 90deg arcs. 1498 enum NVG_MIN_FEATHER = 0.001f; // it should be greater than zero, 'cause it is used in shader for divisions 1499 1500 enum Command { 1501 MoveTo = 0, 1502 LineTo = 1, 1503 BezierTo = 2, 1504 Close = 3, 1505 Winding = 4, 1506 } 1507 1508 enum PointFlag : int { 1509 Corner = 0x01, 1510 Left = 0x02, 1511 Bevel = 0x04, 1512 InnerBevelPR = 0x08, 1513 } 1514 1515 struct NVGstate { 1516 NVGCompositeOperationState compositeOperation; 1517 bool shapeAntiAlias = true; 1518 NVGPaint fill; 1519 NVGPaint stroke; 1520 float strokeWidth = 1.0f; 1521 float miterLimit = 10.0f; 1522 NVGLineCap lineJoin = NVGLineCap.Miter; 1523 NVGLineCap lineCap = NVGLineCap.Butt; 1524 float alpha = 1.0f; 1525 NVGMatrix xform; 1526 NVGscissor scissor; 1527 float fontSize = 16.0f; 1528 float letterSpacing = 0.0f; 1529 float lineHeight = 1.0f; 1530 float fontBlur = 0.0f; 1531 NVGTextAlign textAlign; 1532 int fontId = 0; 1533 bool evenOddMode = false; // use even-odd filling rule (required for some svgs); otherwise use non-zero fill 1534 // dashing 1535 enum MaxDashes = 32; // max 16 dashes 1536 float[MaxDashes] dashes; 1537 uint dashCount = 0; 1538 uint lastFlattenDashCount = 0; 1539 float dashStart = 0; 1540 float totalDashLen; 1541 bool firstDashIsGap = false; 1542 // dasher state for flattener 1543 bool dasherActive = false; 1544 1545 void clearPaint () nothrow @trusted @nogc { 1546 fill.clear(); 1547 stroke.clear(); 1548 } 1549 } 1550 1551 struct NVGpoint { 1552 float x, y; 1553 float dx, dy; 1554 float len; 1555 float dmx, dmy; 1556 ubyte flags; 1557 } 1558 1559 struct NVGpathCache { 1560 NVGpoint* points; 1561 int npoints; 1562 int cpoints; 1563 NVGpath* paths; 1564 int npaths; 1565 int cpaths; 1566 NVGVertex* verts; 1567 int nverts; 1568 int cverts; 1569 float[4] bounds; 1570 // this is required for saved paths 1571 bool strokeReady; 1572 bool fillReady; 1573 float strokeAlphaMul; 1574 float strokeWidth; 1575 float fringeWidth; 1576 bool evenOddMode; 1577 NVGClipMode clipmode; 1578 // non-saved path will not have this 1579 float* commands; 1580 int ncommands; 1581 1582 @disable this (this); // no copies 1583 void opAssign() (const scope auto ref NVGpathCache a) { static assert(0, "no copies!"); } 1584 1585 // won't clear current path 1586 void copyFrom (const NVGpathCache* src) nothrow @trusted @nogc { 1587 import core.stdc.stdlib : malloc; 1588 import core.stdc.string : memcpy, memset; 1589 assert(src !is null); 1590 memcpy(&this, src, NVGpathCache.sizeof); 1591 this.points = xdup(src.points, src.npoints); 1592 this.cpoints = src.npoints; 1593 this.verts = xdup(src.verts, src.nverts); 1594 this.cverts = src.nverts; 1595 this.commands = xdup(src.commands, src.ncommands); 1596 if (src.npaths > 0) { 1597 this.paths = cast(NVGpath*)malloc(src.npaths*NVGpath.sizeof); 1598 memset(this.paths, 0, npaths*NVGpath.sizeof); 1599 foreach (immutable pidx; 0..npaths) this.paths[pidx].copyFrom(&src.paths[pidx]); 1600 this.cpaths = src.npaths; 1601 } else { 1602 this.npaths = this.cpaths = 0; 1603 } 1604 } 1605 1606 void clear () nothrow @trusted @nogc { 1607 import core.stdc.stdlib : free; 1608 import core.stdc.string : memset; 1609 if (paths !is null) { 1610 foreach (ref p; paths[0..npaths]) p.clear(); 1611 free(paths); 1612 } 1613 if (points !is null) free(points); 1614 if (verts !is null) free(verts); 1615 if (commands !is null) free(commands); 1616 memset(&this, 0, this.sizeof); 1617 } 1618 } 1619 1620 /// Pointer to opaque NanoVega context structure. 1621 /// Group: context_management 1622 public alias NVGContext = NVGcontextinternal*; 1623 1624 /// FontStash context 1625 /// Group: font_stash 1626 public alias FONSContext = FONScontextInternal*; 1627 1628 /// Returns FontStash context of the given NanoVega context. 1629 /// Group: font_stash 1630 public FONSContext fonsContext (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.fs : null); } 1631 1632 /// Returns scale that should be applied to FontStash parameters due to matrix transformations on the context (or 1) 1633 /// Group: font_stash 1634 public float fonsScale (NVGContext ctx) /*pure*/ nothrow @trusted @nogc { 1635 pragma(inline, true); 1636 return (ctx !is null && ctx.contextAlive && ctx.nstates > 0 ? nvg__getFontScale(&ctx.states.ptr[ctx.nstates-1])*ctx.devicePxRatio : 1); 1637 } 1638 1639 /// Setup FontStash from the given NanoVega context font parameters. Note that this will apply transformation scale too. 1640 /// Returns `false` if FontStash or NanoVega context is not active. 1641 /// Group: font_stash 1642 public bool setupFonsFrom (FONSContext stash, NVGContext ctx) nothrow @trusted @nogc { 1643 if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false; 1644 NVGstate* state = nvg__getState(ctx); 1645 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 1646 stash.size = state.fontSize*scale; 1647 stash.spacing = state.letterSpacing*scale; 1648 stash.blur = state.fontBlur*scale; 1649 stash.textAlign = state.textAlign; 1650 stash.fontId = state.fontId; 1651 return true; 1652 } 1653 1654 /// Setup NanoVega context font parameters from the given FontStash. Note that NanoVega can apply transformation scale later. 1655 /// Returns `false` if FontStash or NanoVega context is not active. 1656 /// Group: font_stash 1657 public bool setupCtxFrom (NVGContext ctx, FONSContext stash) nothrow @trusted @nogc { 1658 if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false; 1659 NVGstate* state = nvg__getState(ctx); 1660 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 1661 state.fontSize = stash.size; 1662 state.letterSpacing = stash.spacing; 1663 state.fontBlur = stash.blur; 1664 state.textAlign = stash.textAlign; 1665 state.fontId = stash.fontId; 1666 return true; 1667 } 1668 1669 /** Bezier curve rasterizer. 1670 * 1671 * De Casteljau Bezier rasterizer is faster, but currently rasterizing curves with cusps sligtly wrong. 1672 * It doesn't really matter in practice. 1673 * 1674 * AFD tesselator is somewhat slower, but does cusps better. 1675 * 1676 * McSeem rasterizer should have the best quality, bit it is the slowest method. Basically, you will 1677 * never notice any visial difference (and this code is not really debugged), so you probably should 1678 * not use it. It is there for further experiments. 1679 */ 1680 public enum NVGTesselation { 1681 DeCasteljau, /// default: standard well-known tesselation algorithm 1682 AFD, /// adaptive forward differencing 1683 DeCasteljauMcSeem, /// standard well-known tesselation algorithm, with improvements from Maxim Shemanarev; slowest one, but should give best results 1684 } 1685 1686 /// Default tesselator for Bezier curves. 1687 public __gshared NVGTesselation NVG_DEFAULT_TESSELATOR = NVGTesselation.DeCasteljau; 1688 1689 1690 // some public info 1691 1692 /// valid only inside [beginFrame]/[endFrame] 1693 /// Group: context_management 1694 public int width (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mWidth : 0); } 1695 1696 /// valid only inside [beginFrame]/[endFrame] 1697 /// Group: context_management 1698 public int height (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mHeight : 0); } 1699 1700 /// valid only inside [beginFrame]/[endFrame] 1701 /// Group: context_management 1702 public float devicePixelRatio (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.devicePxRatio : float.nan); } 1703 1704 /// Returns `true` if [beginFrame] was called, and neither [endFrame], nor [cancelFrame] were. 1705 /// Group: context_management 1706 public bool inFrame (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.nstates > 0 : false); } 1707 1708 // path autoregistration 1709 1710 /// [pickid] to stop autoregistration. 1711 /// Group: context_management 1712 public enum NVGNoPick = -1; 1713 1714 /// >=0: this pickid will be assigned to all filled/stroked paths 1715 /// Group: context_management 1716 public int pickid (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickId : NVGNoPick); } 1717 1718 /// >=0: this pickid will be assigned to all filled/stroked paths 1719 /// Group: context_management 1720 public void pickid (NVGContext ctx, int v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.pathPickId = v; } 1721 1722 /// pick autoregistration mode; see [NVGPickKind] 1723 /// Group: context_management 1724 public uint pickmode (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickRegistered&NVGPickKind.All : 0); } 1725 1726 /// pick autoregistration mode; see [NVGPickKind] 1727 /// Group: context_management 1728 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); } 1729 1730 // tesselator options 1731 1732 /// Get current Bezier tesselation mode. See [NVGTesselation]. 1733 /// Group: context_management 1734 public NVGTesselation tesselation (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.tesselatortype : NVGTesselation.DeCasteljau); } 1735 1736 /// Set current Bezier tesselation mode. See [NVGTesselation]. 1737 /// Group: context_management 1738 public void tesselation (NVGContext ctx, NVGTesselation v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.tesselatortype = v; } 1739 1740 1741 private struct NVGcontextinternal { 1742 private: 1743 NVGparams params; 1744 float* commands; 1745 int ccommands; 1746 int ncommands; 1747 float commandx, commandy; 1748 NVGstate[NVG_MAX_STATES] states; 1749 int nstates; 1750 NVGpathCache* cache; 1751 public float tessTol; 1752 public float angleTol; // 0.0f -- angle tolerance for McSeem Bezier rasterizer 1753 public float cuspLimit; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps) 1754 float distTol; 1755 public float fringeWidth; 1756 float devicePxRatio; 1757 FONSContext fs; 1758 NVGImage[NVG_MAX_FONTIMAGES] fontImages; 1759 int fontImageIdx; 1760 int drawCallCount; 1761 int fillTriCount; 1762 int strokeTriCount; 1763 int textTriCount; 1764 NVGTesselation tesselatortype; 1765 // picking API 1766 NVGpickScene* pickScene; 1767 int pathPickId; // >=0: register all paths for picking using this id 1768 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 1769 // path recording 1770 NVGPathSet recset; 1771 int recstart; // used to cancel recording 1772 bool recblockdraw; 1773 // internals 1774 NVGMatrix gpuAffine; 1775 int mWidth, mHeight; 1776 // image manager 1777 shared int imageCount; // number of alive images in this context 1778 bool contextAlive; // context can be dead, but still contain some images 1779 1780 @disable this (this); // no copies 1781 void opAssign() (const scope auto ref NVGcontextinternal a) { static assert(0, "no copies!"); } 1782 1783 // debug feature 1784 public @property int getImageCount () nothrow @trusted @nogc { 1785 import core.atomic; 1786 return atomicLoad(imageCount); 1787 } 1788 } 1789 1790 /** Returns number of tesselated pathes in context. 1791 * 1792 * Tesselated pathes are either triangle strips (for strokes), or 1793 * triangle fans (for fills). Note that NanoVega can generate some 1794 * surprising pathes (like fringe stroke for antialiasing, for example). 1795 * 1796 * One render path can contain vertices both for fill, and for stroke triangles. 1797 */ 1798 public int renderPathCount (NVGContext ctx) pure nothrow @trusted @nogc { 1799 pragma(inline, true); 1800 return (ctx !is null && ctx.contextAlive ? ctx.cache.npaths : 0); 1801 } 1802 1803 /** Get vertices of "fill" triangle fan for the given render path. Can return empty slice. 1804 * 1805 * $(WARNING Returned slice can be invalidated by any other NanoVega API call 1806 * (except the calls to render path accessors), and using it in such 1807 * case is UB. So copy vertices to freshly allocated array if you want 1808 * to keep them for further processing.) 1809 */ 1810 public const(NVGVertex)[] renderPathFillVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1811 pragma(inline, true); 1812 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].fillVertices : null); 1813 } 1814 1815 /** Get vertices of "stroke" triangle strip for the given render path. Can return empty slice. 1816 * 1817 * $(WARNING Returned slice can be invalidated by any other NanoVega API call 1818 * (except the calls to render path accessors), and using it in such 1819 * case is UB. So copy vertices to freshly allocated array if you want 1820 * to keep them for further processing.) 1821 */ 1822 public const(NVGVertex)[] renderPathStrokeVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1823 pragma(inline, true); 1824 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].strokeVertices : null); 1825 1826 } 1827 1828 /// Returns winding for the given render path. 1829 public NVGWinding renderPathWinding (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1830 pragma(inline, true); 1831 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].winding : NVGWinding.CCW); 1832 1833 } 1834 1835 /// Returns "complex path" flag for the given render path. 1836 public bool renderPathComplex (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1837 pragma(inline, true); 1838 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].complex : false); 1839 1840 } 1841 1842 void nvg__imageIncRef (NVGContext ctx, int imgid, bool increfInGL=true) nothrow @trusted @nogc { 1843 if (ctx !is null && imgid > 0) { 1844 import core.atomic : atomicOp; 1845 atomicOp!"+="(ctx.imageCount, 1); 1846 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[++]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); } 1847 if (ctx.contextAlive && increfInGL) ctx.params.renderTextureIncRef(ctx.params.userPtr, imgid); 1848 } 1849 } 1850 1851 void nvg__imageDecRef (NVGContext ctx, int imgid) nothrow @trusted @nogc { 1852 if (ctx !is null && imgid > 0) { 1853 import core.atomic : atomicOp; 1854 int icnt = atomicOp!"-="(ctx.imageCount, 1); 1855 if (icnt < 0) assert(0, "NanoVega: internal image refcounting error"); 1856 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[--]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); } 1857 if (ctx.contextAlive) ctx.params.renderDeleteTexture(ctx.params.userPtr, imgid); 1858 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); } 1859 if (!ctx.contextAlive && icnt == 0) { 1860 // it is finally safe to free context memory 1861 import core.stdc.stdlib : free; 1862 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("killed zombie context %p\n", ctx); } 1863 free(ctx); 1864 } 1865 } 1866 } 1867 1868 1869 public import core.stdc.math : 1870 nvg__sqrtf = sqrtf, 1871 nvg__modf = fmodf, 1872 nvg__sinf = sinf, 1873 nvg__cosf = cosf, 1874 nvg__tanf = tanf, 1875 nvg__atan2f = atan2f, 1876 nvg__acosf = acosf, 1877 nvg__ceilf = ceilf; 1878 1879 version(Windows) { 1880 public int nvg__lrintf (float f) nothrow @trusted @nogc { pragma(inline, true); return cast(int)(f+0.5); } 1881 } else { 1882 public import core.stdc.math : nvg__lrintf = lrintf; 1883 } 1884 1885 public auto nvg__min(T) (T a, T b) { pragma(inline, true); return (a < b ? a : b); } 1886 public auto nvg__max(T) (T a, T b) { pragma(inline, true); return (a > b ? a : b); } 1887 public auto nvg__clamp(T) (T a, T mn, T mx) { pragma(inline, true); return (a < mn ? mn : (a > mx ? mx : a)); } 1888 //float nvg__absf() (float a) { pragma(inline, true); return (a >= 0.0f ? a : -a); } 1889 public auto nvg__sign(T) (T a) { pragma(inline, true); return (a >= cast(T)0 ? cast(T)1 : cast(T)(-1)); } 1890 public float nvg__cross() (float dx0, float dy0, float dx1, float dy1) { pragma(inline, true); return (dx1*dy0-dx0*dy1); } 1891 1892 //public import core.stdc.math : nvg__absf = fabsf; 1893 public import core.math : nvg__absf = fabs; 1894 1895 1896 float nvg__normalize (float* x, float* y) nothrow @safe @nogc { 1897 float d = nvg__sqrtf((*x)*(*x)+(*y)*(*y)); 1898 if (d > 1e-6f) { 1899 immutable float id = 1.0f/d; 1900 *x *= id; 1901 *y *= id; 1902 } 1903 return d; 1904 } 1905 1906 void nvg__deletePathCache (ref NVGpathCache* c) nothrow @trusted @nogc { 1907 if (c !is null) { 1908 c.clear(); 1909 free(c); 1910 } 1911 } 1912 1913 NVGpathCache* nvg__allocPathCache () nothrow @trusted @nogc { 1914 NVGpathCache* c = cast(NVGpathCache*)malloc(NVGpathCache.sizeof); 1915 if (c is null) goto error; 1916 memset(c, 0, NVGpathCache.sizeof); 1917 1918 c.points = cast(NVGpoint*)malloc(NVGpoint.sizeof*NVG_INIT_POINTS_SIZE); 1919 if (c.points is null) goto error; 1920 assert(c.npoints == 0); 1921 c.cpoints = NVG_INIT_POINTS_SIZE; 1922 1923 c.paths = cast(NVGpath*)malloc(NVGpath.sizeof*NVG_INIT_PATHS_SIZE); 1924 if (c.paths is null) goto error; 1925 assert(c.npaths == 0); 1926 c.cpaths = NVG_INIT_PATHS_SIZE; 1927 1928 c.verts = cast(NVGVertex*)malloc(NVGVertex.sizeof*NVG_INIT_VERTS_SIZE); 1929 if (c.verts is null) goto error; 1930 assert(c.nverts == 0); 1931 c.cverts = NVG_INIT_VERTS_SIZE; 1932 1933 return c; 1934 1935 error: 1936 nvg__deletePathCache(c); 1937 return null; 1938 } 1939 1940 void nvg__setDevicePixelRatio (NVGContext ctx, float ratio) pure nothrow @safe @nogc { 1941 ctx.tessTol = 0.25f/ratio; 1942 ctx.distTol = 0.01f/ratio; 1943 ctx.fringeWidth = 1.0f/ratio; 1944 ctx.devicePxRatio = ratio; 1945 } 1946 1947 NVGCompositeOperationState nvg__compositeOperationState (NVGCompositeOperation op) pure nothrow @safe @nogc { 1948 NVGCompositeOperationState state; 1949 NVGBlendFactor sfactor, dfactor; 1950 1951 if (op == NVGCompositeOperation.SourceOver) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha;} 1952 else if (op == NVGCompositeOperation.SourceIn) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.Zero; } 1953 else if (op == NVGCompositeOperation.SourceOut) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.Zero; } 1954 else if (op == NVGCompositeOperation.SourceAtop) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } 1955 else if (op == NVGCompositeOperation.DestinationOver) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.One; } 1956 else if (op == NVGCompositeOperation.DestinationIn) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.SrcAlpha; } 1957 else if (op == NVGCompositeOperation.DestinationOut) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } 1958 else if (op == NVGCompositeOperation.DestinationAtop) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.SrcAlpha; } 1959 else if (op == NVGCompositeOperation.Lighter) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.One; } 1960 else if (op == NVGCompositeOperation.Copy) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.Zero; } 1961 else if (op == NVGCompositeOperation.Xor) { 1962 state.simple = false; 1963 state.srcRGB = NVGBlendFactor.OneMinusDstColor; 1964 state.srcAlpha = NVGBlendFactor.OneMinusDstAlpha; 1965 state.dstRGB = NVGBlendFactor.OneMinusSrcColor; 1966 state.dstAlpha = NVGBlendFactor.OneMinusSrcAlpha; 1967 return state; 1968 } 1969 else { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } // default value for invalid op: SourceOver 1970 1971 state.simple = true; 1972 state.srcAlpha = sfactor; 1973 state.dstAlpha = dfactor; 1974 return state; 1975 } 1976 1977 NVGstate* nvg__getState (NVGContext ctx) pure nothrow @trusted @nogc { 1978 pragma(inline, true); 1979 if (ctx is null || !ctx.contextAlive || ctx.nstates == 0) assert(0, "NanoVega: cannot perform commands on inactive context"); 1980 return &ctx.states.ptr[ctx.nstates-1]; 1981 } 1982 1983 // Constructor called by the render back-end. 1984 NVGContext createInternal (NVGparams* params) nothrow @trusted @nogc { 1985 FONSParams fontParams; 1986 NVGContext ctx = cast(NVGContext)malloc(NVGcontextinternal.sizeof); 1987 if (ctx is null) goto error; 1988 memset(ctx, 0, NVGcontextinternal.sizeof); 1989 1990 ctx.angleTol = 0; // angle tolerance for McSeem Bezier rasterizer 1991 ctx.cuspLimit = 0; // cusp limit for McSeem Bezier rasterizer (0: real cusps) 1992 1993 ctx.contextAlive = true; 1994 1995 ctx.params = *params; 1996 //ctx.fontImages[0..NVG_MAX_FONTIMAGES] = 0; 1997 1998 ctx.commands = cast(float*)malloc(float.sizeof*NVG_INIT_COMMANDS_SIZE); 1999 if (ctx.commands is null) goto error; 2000 ctx.ncommands = 0; 2001 ctx.ccommands = NVG_INIT_COMMANDS_SIZE; 2002 2003 ctx.cache = nvg__allocPathCache(); 2004 if (ctx.cache is null) goto error; 2005 2006 ctx.save(); 2007 ctx.reset(); 2008 2009 nvg__setDevicePixelRatio(ctx, 1.0f); 2010 ctx.mWidth = ctx.mHeight = 0; 2011 2012 if (!ctx.params.renderCreate(ctx.params.userPtr)) goto error; 2013 2014 // init font rendering 2015 memset(&fontParams, 0, fontParams.sizeof); 2016 fontParams.width = NVG_INIT_FONTIMAGE_SIZE; 2017 fontParams.height = NVG_INIT_FONTIMAGE_SIZE; 2018 fontParams.flags = FONSParams.Flag.ZeroTopLeft; 2019 fontParams.renderCreate = null; 2020 fontParams.renderUpdate = null; 2021 fontParams.renderDelete = null; 2022 fontParams.userPtr = null; 2023 ctx.fs = FONSContext.create(fontParams); 2024 if (ctx.fs is null) goto error; 2025 2026 // create font texture 2027 ctx.fontImages[0].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, fontParams.width, fontParams.height, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null); 2028 if (ctx.fontImages[0].id == 0) goto error; 2029 ctx.fontImages[0].ctx = ctx; 2030 ctx.nvg__imageIncRef(ctx.fontImages[0].id, false); // don't increment driver refcount 2031 ctx.fontImageIdx = 0; 2032 2033 ctx.pathPickId = -1; 2034 ctx.tesselatortype = NVG_DEFAULT_TESSELATOR; 2035 2036 return ctx; 2037 2038 error: 2039 ctx.deleteInternal(); 2040 return null; 2041 } 2042 2043 // Called by render backend. 2044 NVGparams* internalParams (NVGContext ctx) nothrow @trusted @nogc { 2045 return &ctx.params; 2046 } 2047 2048 // Destructor called by the render back-end. 2049 void deleteInternal (ref NVGContext ctx) nothrow @trusted @nogc { 2050 if (ctx is null) return; 2051 if (ctx.contextAlive) { 2052 if (ctx.commands !is null) free(ctx.commands); 2053 nvg__deletePathCache(ctx.cache); 2054 2055 if (ctx.fs) ctx.fs.kill(); 2056 2057 foreach (uint i; 0..NVG_MAX_FONTIMAGES) ctx.fontImages[i].clear(); 2058 2059 if (ctx.params.renderDelete !is null) ctx.params.renderDelete(ctx.params.userPtr); 2060 2061 if (ctx.pickScene !is null) nvg__deletePickScene(ctx.pickScene); 2062 2063 ctx.contextAlive = false; 2064 2065 import core.atomic : atomicLoad; 2066 if (atomicLoad(ctx.imageCount) == 0) { 2067 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("destroyed context %p\n", ctx); } 2068 free(ctx); 2069 } else { 2070 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("context %p is zombie now (%d image refs)\n", ctx, ctx.imageCount); } 2071 } 2072 } 2073 } 2074 2075 /// Delete NanoVega context. 2076 /// Group: context_management 2077 public void kill (ref NVGContext ctx) nothrow @trusted @nogc { 2078 if (ctx !is null) { 2079 ctx.deleteInternal(); 2080 ctx = null; 2081 } 2082 } 2083 2084 /// Returns `true` if the given context is not `null` and can be used for painting. 2085 /// Group: context_management 2086 public bool valid (in NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive); } 2087 2088 2089 // ////////////////////////////////////////////////////////////////////////// // 2090 // Frame Management 2091 2092 /** Begin drawing a new frame. 2093 * 2094 * Calls to NanoVega drawing API should be wrapped in [beginFrame] and [endFrame] 2095 * 2096 * [beginFrame] defines the size of the window to render to in relation currently 2097 * set viewport (i.e. glViewport on GL backends). Device pixel ration allows to 2098 * control the rendering on Hi-DPI devices. 2099 * 2100 * For example, GLFW returns two dimension for an opened window: window size and 2101 * frame buffer size. In that case you would set windowWidth/windowHeight to the window size, 2102 * devicePixelRatio to: `windowWidth/windowHeight`. 2103 * 2104 * Default ratio is `1`. 2105 * 2106 * Note that fractional ratio can (and will) distort your fonts and images. 2107 * 2108 * This call also resets pick marks (see picking API for non-rasterized paths), 2109 * path recording, and GPU affine transformatin matrix. 2110 * 2111 * see also [glNVGClearFlags], which returns necessary flags for [glClear]. 2112 * 2113 * Group: frame_management 2114 */ 2115 public void beginFrame (NVGContext ctx, int windowWidth, int windowHeight, float devicePixelRatio=1.0f) nothrow @trusted @nogc { 2116 import std.math : isNaN; 2117 /* 2118 printf("Tris: draws:%d fill:%d stroke:%d text:%d TOT:%d\n", 2119 ctx.drawCallCount, ctx.fillTriCount, ctx.strokeTriCount, ctx.textTriCount, 2120 ctx.fillTriCount+ctx.strokeTriCount+ctx.textTriCount); 2121 */ 2122 if (ctx.nstates > 0) ctx.cancelFrame(); 2123 2124 if (windowWidth < 1) windowWidth = 1; 2125 if (windowHeight < 1) windowHeight = 1; 2126 2127 if (isNaN(devicePixelRatio)) devicePixelRatio = (windowHeight > 0 ? cast(float)windowWidth/cast(float)windowHeight : 1024.0/768.0); 2128 2129 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint(); 2130 ctx.nstates = 0; 2131 ctx.save(); 2132 ctx.reset(); 2133 2134 nvg__setDevicePixelRatio(ctx, devicePixelRatio); 2135 2136 ctx.params.renderViewport(ctx.params.userPtr, windowWidth, windowHeight); 2137 ctx.mWidth = windowWidth; 2138 ctx.mHeight = windowHeight; 2139 2140 ctx.recset = null; 2141 ctx.recstart = -1; 2142 2143 ctx.pathPickId = NVGNoPick; 2144 ctx.pathPickRegistered = 0; 2145 2146 ctx.drawCallCount = 0; 2147 ctx.fillTriCount = 0; 2148 ctx.strokeTriCount = 0; 2149 ctx.textTriCount = 0; 2150 2151 ctx.ncommands = 0; 2152 ctx.pathPickRegistered = 0; 2153 nvg__clearPathCache(ctx); 2154 2155 ctx.gpuAffine = NVGMatrix.Identity; 2156 2157 nvg__pickBeginFrame(ctx, windowWidth, windowHeight); 2158 } 2159 2160 /// Cancels drawing the current frame. Cancels path recording. 2161 /// Group: frame_management 2162 public void cancelFrame (NVGContext ctx) nothrow @trusted @nogc { 2163 ctx.cancelRecording(); 2164 //ctx.mWidth = 0; 2165 //ctx.mHeight = 0; 2166 // cancel render queue 2167 ctx.params.renderCancel(ctx.params.userPtr); 2168 // clear saved states (this may free some textures) 2169 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint(); 2170 ctx.nstates = 0; 2171 } 2172 2173 /// Ends drawing the current frame (flushing remaining render state). Commits recorded paths. 2174 /// Group: frame_management 2175 public void endFrame (NVGContext ctx) nothrow @trusted @nogc { 2176 if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx); 2177 ctx.stopRecording(); 2178 //ctx.mWidth = 0; 2179 //ctx.mHeight = 0; 2180 // flush render queue 2181 NVGstate* state = nvg__getState(ctx); 2182 ctx.params.renderFlush(ctx.params.userPtr); 2183 if (ctx.fontImageIdx != 0) { 2184 auto fontImage = ctx.fontImages[ctx.fontImageIdx]; 2185 int j = 0, iw, ih; 2186 // delete images that smaller than current one 2187 if (!fontImage.valid) return; 2188 ctx.imageSize(fontImage, iw, ih); 2189 foreach (int i; 0..ctx.fontImageIdx) { 2190 if (ctx.fontImages[i].valid) { 2191 int nw, nh; 2192 ctx.imageSize(ctx.fontImages[i], nw, nh); 2193 if (nw < iw || nh < ih) { 2194 ctx.deleteImage(ctx.fontImages[i]); 2195 } else { 2196 ctx.fontImages[j++] = ctx.fontImages[i]; 2197 } 2198 } 2199 } 2200 // make current font image to first 2201 ctx.fontImages[j++] = ctx.fontImages[0]; 2202 ctx.fontImages[0] = fontImage; 2203 ctx.fontImageIdx = 0; 2204 // clear all images after j 2205 ctx.fontImages[j..NVG_MAX_FONTIMAGES] = NVGImage.init; 2206 } 2207 // clear saved states (this may free some textures) 2208 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint(); 2209 ctx.nstates = 0; 2210 } 2211 2212 2213 // ////////////////////////////////////////////////////////////////////////// // 2214 // Recording and Replaying Pathes 2215 2216 // Saved path set. 2217 // Group: path_recording 2218 public alias NVGPathSet = NVGPathSetS*; 2219 2220 2221 //TODO: save scissor info? 2222 struct NVGPathSetS { 2223 private: 2224 // either path cache, or text item 2225 static struct Node { 2226 NVGPaint paint; 2227 NVGpathCache* path; 2228 } 2229 2230 private: 2231 Node* nodes; 2232 int nnodes, cnodes; 2233 NVGpickScene* pickscene; 2234 //int npickscenes, cpickscenes; 2235 NVGContext svctx; // used to do some sanity checks, and to free resources 2236 2237 private: 2238 Node* allocNode () nothrow @trusted @nogc { 2239 import core.stdc.string : memset; 2240 // grow buffer if necessary 2241 if (nnodes+1 > cnodes) { 2242 import core.stdc.stdlib : realloc; 2243 int newsz = (cnodes == 0 ? 8 : cnodes <= 1024 ? cnodes*2 : cnodes+1024); 2244 nodes = cast(Node*)realloc(nodes, newsz*Node.sizeof); 2245 if (nodes is null) assert(0, "NanoVega: out of memory"); 2246 //memset(svp.caches+svp.ccaches, 0, (newsz-svp.ccaches)*NVGpathCache.sizeof); 2247 cnodes = newsz; 2248 } 2249 assert(nnodes < cnodes); 2250 memset(nodes+nnodes, 0, Node.sizeof); 2251 return &nodes[nnodes++]; 2252 } 2253 2254 Node* allocPathNode () nothrow @trusted @nogc { 2255 import core.stdc.stdlib : malloc; 2256 import core.stdc.string : memset; 2257 auto node = allocNode(); 2258 // allocate path cache 2259 auto pc = cast(NVGpathCache*)malloc(NVGpathCache.sizeof); 2260 if (pc is null) assert(0, "NanoVega: out of memory"); 2261 node.path = pc; 2262 return node; 2263 } 2264 2265 void clearNode (int idx) nothrow @trusted @nogc { 2266 if (idx < 0 || idx >= nnodes) return; 2267 Node* node = &nodes[idx]; 2268 if (svctx !is null && node.paint.image.valid) node.paint.image.clear(); 2269 if (node.path !is null) node.path.clear(); 2270 } 2271 2272 private: 2273 void takeCurrentPickScene (NVGContext ctx) nothrow @trusted @nogc { 2274 NVGpickScene* ps = ctx.pickScene; 2275 if (ps is null) return; // nothing to do 2276 if (ps.npaths == 0) return; // pick scene is empty 2277 ctx.pickScene = null; 2278 pickscene = ps; 2279 } 2280 2281 void replay (NVGContext ctx, const scope ref NVGColor fillTint, const scope ref NVGColor strokeTint) nothrow @trusted @nogc { 2282 NVGstate* state = nvg__getState(ctx); 2283 foreach (ref node; nodes[0..nnodes]) { 2284 if (auto cc = node.path) { 2285 if (cc.npaths <= 0) continue; 2286 2287 if (cc.fillReady) { 2288 NVGPaint fillPaint = node.paint; 2289 2290 // apply global alpha 2291 fillPaint.innerColor.a *= state.alpha; 2292 fillPaint.middleColor.a *= state.alpha; 2293 fillPaint.outerColor.a *= state.alpha; 2294 2295 fillPaint.innerColor.applyTint(fillTint); 2296 fillPaint.middleColor.applyTint(fillTint); 2297 fillPaint.outerColor.applyTint(fillTint); 2298 2299 ctx.params.renderFill(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &fillPaint, &state.scissor, cc.fringeWidth, cc.bounds.ptr, cc.paths, cc.npaths, cc.evenOddMode); 2300 2301 // count triangles 2302 foreach (int i; 0..cc.npaths) { 2303 NVGpath* path = &cc.paths[i]; 2304 ctx.fillTriCount += path.nfill-2; 2305 ctx.fillTriCount += path.nstroke-2; 2306 ctx.drawCallCount += 2; 2307 } 2308 } 2309 2310 if (cc.strokeReady) { 2311 NVGPaint strokePaint = node.paint; 2312 2313 strokePaint.innerColor.a *= cc.strokeAlphaMul; 2314 strokePaint.middleColor.a *= cc.strokeAlphaMul; 2315 strokePaint.outerColor.a *= cc.strokeAlphaMul; 2316 2317 // apply global alpha 2318 strokePaint.innerColor.a *= state.alpha; 2319 strokePaint.middleColor.a *= state.alpha; 2320 strokePaint.outerColor.a *= state.alpha; 2321 2322 strokePaint.innerColor.applyTint(strokeTint); 2323 strokePaint.middleColor.applyTint(strokeTint); 2324 strokePaint.outerColor.applyTint(strokeTint); 2325 2326 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &strokePaint, &state.scissor, cc.fringeWidth, cc.strokeWidth, cc.paths, cc.npaths); 2327 2328 // count triangles 2329 foreach (int i; 0..cc.npaths) { 2330 NVGpath* path = &cc.paths[i]; 2331 ctx.strokeTriCount += path.nstroke-2; 2332 ++ctx.drawCallCount; 2333 } 2334 } 2335 } 2336 } 2337 } 2338 2339 public: 2340 @disable this (this); // no copies 2341 void opAssign() (const scope auto ref NVGPathSetS a) { static assert(0, "no copies!"); } 2342 2343 // pick test 2344 // Call delegate [dg] for each path under the specified position (in no particular order). 2345 // Returns the id of the path for which delegate [dg] returned true or -1. 2346 // dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending). 2347 int hitTestDG(bool bestOrder=false, DG) (in float x, in float y, NVGPickKind kind, scope DG dg) if (IsGoodHitTestDG!DG || IsGoodHitTestInternalDG!DG) { 2348 if (pickscene is null) return -1; 2349 2350 NVGpickScene* ps = pickscene; 2351 int levelwidth = 1<<(ps.nlevels-1); 2352 int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth); 2353 int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth); 2354 int npicked = 0; 2355 2356 for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) { 2357 NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx]; 2358 while (pp !is null) { 2359 if (nvg__pickPathTestBounds(svctx, ps, pp, x, y)) { 2360 int hit = 0; 2361 if ((kind&NVGPickKind.Stroke) && (pp.flags&NVGPathFlags.Stroke)) hit = nvg__pickPathStroke(ps, pp, x, y); 2362 if (!hit && (kind&NVGPickKind.Fill) && (pp.flags&NVGPathFlags.Fill)) hit = nvg__pickPath(ps, pp, x, y); 2363 if (hit) { 2364 static if (IsGoodHitTestDG!DG) { 2365 static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) { 2366 if (dg(pp.id, cast(int)pp.order)) return pp.id; 2367 } else { 2368 dg(pp.id, cast(int)pp.order); 2369 } 2370 } else { 2371 static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) { 2372 if (dg(pp)) return pp.id; 2373 } else { 2374 dg(pp); 2375 } 2376 } 2377 } 2378 } 2379 pp = pp.next; 2380 } 2381 cellx >>= 1; 2382 celly >>= 1; 2383 levelwidth >>= 1; 2384 } 2385 2386 return -1; 2387 } 2388 2389 // Fills ids with a list of the top most hit ids under the specified position. 2390 // Returns the slice of [ids]. 2391 int[] hitTestAll (in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc { 2392 if (pickscene is null || ids.length == 0) return ids[0..0]; 2393 2394 int npicked = 0; 2395 NVGpickScene* ps = pickscene; 2396 2397 hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc { 2398 if (npicked == ps.cpicked) { 2399 int cpicked = ps.cpicked+ps.cpicked; 2400 NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked); 2401 if (picked is null) return true; // abort 2402 ps.cpicked = cpicked; 2403 ps.picked = picked; 2404 } 2405 ps.picked[npicked] = pp; 2406 ++npicked; 2407 return false; // go on 2408 }); 2409 2410 qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths); 2411 2412 assert(npicked >= 0); 2413 if (npicked > ids.length) npicked = cast(int)ids.length; 2414 foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id; 2415 2416 return ids[0..npicked]; 2417 } 2418 2419 // Returns the id of the pickable shape containing x,y or -1 if no shape was found. 2420 int hitTest (in float x, in float y, NVGPickKind kind) nothrow @trusted @nogc { 2421 if (pickscene is null) return -1; 2422 2423 int bestOrder = -1; 2424 int bestID = -1; 2425 2426 hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc { 2427 if (pp.order > bestOrder) { 2428 bestOrder = pp.order; 2429 bestID = pp.id; 2430 } 2431 }); 2432 2433 return bestID; 2434 } 2435 } 2436 2437 // Append current path to existing path set. Is is safe to call this with `null` [svp]. 2438 void appendCurrentPathToCache (NVGContext ctx, NVGPathSet svp, const scope ref NVGPaint paint) nothrow @trusted @nogc { 2439 if (ctx is null || svp is null) return; 2440 if (ctx !is svp.svctx) assert(0, "NanoVega: cannot save paths from different contexts"); 2441 if (ctx.ncommands == 0) { 2442 assert(ctx.cache.npaths == 0); 2443 return; 2444 } 2445 if (!ctx.cache.fillReady && !ctx.cache.strokeReady) return; 2446 2447 // tesselate current path 2448 //if (!ctx.cache.fillReady) nvg__prepareFill(ctx); 2449 //if (!ctx.cache.strokeReady) nvg__prepareStroke(ctx); 2450 2451 auto node = svp.allocPathNode(); 2452 NVGpathCache* cc = node.path; 2453 cc.copyFrom(ctx.cache); 2454 node.paint = paint; 2455 // copy path commands (we may need 'em for picking) 2456 version(all) { 2457 cc.ncommands = ctx.ncommands; 2458 if (cc.ncommands) { 2459 import core.stdc.stdlib : malloc; 2460 import core.stdc.string : memcpy; 2461 cc.commands = cast(float*)malloc(ctx.ncommands*float.sizeof); 2462 if (cc.commands is null) assert(0, "NanoVega: out of memory"); 2463 memcpy(cc.commands, ctx.commands, ctx.ncommands*float.sizeof); 2464 } else { 2465 cc.commands = null; 2466 } 2467 } 2468 } 2469 2470 // Create new empty path set. 2471 // Group: path_recording 2472 public NVGPathSet newPathSet (NVGContext ctx) nothrow @trusted @nogc { 2473 import core.stdc.stdlib : malloc; 2474 import core.stdc.string : memset; 2475 if (ctx is null) return null; 2476 NVGPathSet res = cast(NVGPathSet)malloc(NVGPathSetS.sizeof); 2477 if (res is null) assert(0, "NanoVega: out of memory"); 2478 memset(res, 0, NVGPathSetS.sizeof); 2479 res.svctx = ctx; 2480 return res; 2481 } 2482 2483 // Is the given path set empty? Empty path set can be `null`. 2484 // Group: path_recording 2485 public bool empty (NVGPathSet svp) pure nothrow @safe @nogc { pragma(inline, true); return (svp is null || svp.nnodes == 0); } 2486 2487 // Clear path set contents. Will release $(B some) allocated memory (this function is meant to clear something that will be reused). 2488 // Group: path_recording 2489 public void clear (NVGPathSet svp) nothrow @trusted @nogc { 2490 if (svp !is null) { 2491 import core.stdc.stdlib : free; 2492 foreach (immutable idx; 0.. svp.nnodes) svp.clearNode(idx); 2493 svp.nnodes = 0; 2494 } 2495 } 2496 2497 // Destroy path set (frees all allocated memory). 2498 // Group: path_recording 2499 public void kill (ref NVGPathSet svp) nothrow @trusted @nogc { 2500 if (svp !is null) { 2501 import core.stdc.stdlib : free; 2502 svp.clear(); 2503 if (svp.nodes !is null) free(svp.nodes); 2504 free(svp); 2505 if (svp.pickscene !is null) nvg__deletePickScene(svp.pickscene); 2506 svp = null; 2507 } 2508 } 2509 2510 // Start path recording. [svp] should be alive until recording is cancelled or stopped. 2511 // Group: path_recording 2512 public void startRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { 2513 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2514 ctx.stopRecording(); 2515 ctx.recset = svp; 2516 ctx.recstart = (svp !is null ? svp.nnodes : -1); 2517 ctx.recblockdraw = false; 2518 } 2519 2520 /* Start path recording. [svp] should be alive until recording is cancelled or stopped. 2521 * 2522 * This will block all rendering, so you can call your rendering functions to record paths without actual drawing. 2523 * Commiting or cancelling will re-enable rendering. 2524 * You can call this with `null` svp to block rendering without recording any paths. 2525 * 2526 * Group: path_recording 2527 */ 2528 public void startBlockingRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { 2529 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2530 ctx.stopRecording(); 2531 ctx.recset = svp; 2532 ctx.recstart = (svp !is null ? svp.nnodes : -1); 2533 ctx.recblockdraw = true; 2534 } 2535 2536 // Commit recorded paths. It is safe to call this when recording is not started. 2537 // Group: path_recording 2538 public void stopRecording (NVGContext ctx) nothrow @trusted @nogc { 2539 if (ctx.recset !is null && ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2540 if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx); 2541 ctx.recset = null; 2542 ctx.recstart = -1; 2543 ctx.recblockdraw = false; 2544 } 2545 2546 // Cancel path recording. 2547 // Group: path_recording 2548 public void cancelRecording (NVGContext ctx) nothrow @trusted @nogc { 2549 if (ctx.recset !is null) { 2550 if (ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2551 assert(ctx.recstart >= 0 && ctx.recstart <= ctx.recset.nnodes); 2552 foreach (immutable idx; ctx.recstart..ctx.recset.nnodes) ctx.recset.clearNode(idx); 2553 ctx.recset.nnodes = ctx.recstart; 2554 ctx.recset = null; 2555 ctx.recstart = -1; 2556 } 2557 ctx.recblockdraw = false; 2558 } 2559 2560 /* Replay saved path set. 2561 * 2562 * Replaying record while you're recording another one is undefined behavior. 2563 * 2564 * Group: path_recording 2565 */ 2566 public void replayRecording() (NVGContext ctx, NVGPathSet svp, const scope auto ref NVGColor fillTint, const scope auto ref NVGColor strokeTint) nothrow @trusted @nogc { 2567 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2568 svp.replay(ctx, fillTint, strokeTint); 2569 } 2570 2571 /// Ditto. 2572 public void replayRecording() (NVGContext ctx, NVGPathSet svp, const scope auto ref NVGColor fillTint) nothrow @trusted @nogc { ctx.replayRecording(svp, fillTint, NVGColor.transparent); } 2573 2574 /// Ditto. 2575 public void replayRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { ctx.replayRecording(svp, NVGColor.transparent, NVGColor.transparent); } 2576 2577 2578 // ////////////////////////////////////////////////////////////////////////// // 2579 // Composite operation 2580 2581 /// Sets the composite operation. 2582 /// Group: composite_operation 2583 public void globalCompositeOperation (NVGContext ctx, NVGCompositeOperation op) nothrow @trusted @nogc { 2584 NVGstate* state = nvg__getState(ctx); 2585 state.compositeOperation = nvg__compositeOperationState(op); 2586 } 2587 2588 /// Sets the composite operation with custom pixel arithmetic. 2589 /// Group: composite_operation 2590 public void globalCompositeBlendFunc (NVGContext ctx, NVGBlendFactor sfactor, NVGBlendFactor dfactor) nothrow @trusted @nogc { 2591 ctx.globalCompositeBlendFuncSeparate(sfactor, dfactor, sfactor, dfactor); 2592 } 2593 2594 /// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately. 2595 /// Group: composite_operation 2596 public void globalCompositeBlendFuncSeparate (NVGContext ctx, NVGBlendFactor srcRGB, NVGBlendFactor dstRGB, NVGBlendFactor srcAlpha, NVGBlendFactor dstAlpha) nothrow @trusted @nogc { 2597 NVGCompositeOperationState op; 2598 op.simple = false; 2599 op.srcRGB = srcRGB; 2600 op.dstRGB = dstRGB; 2601 op.srcAlpha = srcAlpha; 2602 op.dstAlpha = dstAlpha; 2603 NVGstate* state = nvg__getState(ctx); 2604 state.compositeOperation = op; 2605 } 2606 2607 2608 // ////////////////////////////////////////////////////////////////////////// // 2609 // Color utils 2610 2611 /// Returns a color value from string form. 2612 /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb" 2613 /// Group: color_utils 2614 public NVGColor nvgRGB (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); } 2615 2616 /// Ditto. 2617 public NVGColor nvgRGBA (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); } 2618 2619 /// Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f). 2620 /// Group: color_utils 2621 public NVGColor nvgRGB (int r, int g, int b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(nvgClampToByte(r), nvgClampToByte(g), nvgClampToByte(b), 255); } 2622 2623 /// Returns a color value from red, green, blue values. Alpha will be set to 1.0f. 2624 /// Group: color_utils 2625 public NVGColor nvgRGBf (float r, float g, float b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(r, g, b, 1.0f); } 2626 2627 /// Returns a color value from red, green, blue and alpha values. 2628 /// Group: color_utils 2629 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)); } 2630 2631 /// Returns a color value from red, green, blue and alpha values. 2632 /// Group: color_utils 2633 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); } 2634 2635 /// Returns new color with transparency (alpha) set to [a]. 2636 /// Group: color_utils 2637 public NVGColor nvgTransRGBA (NVGColor c, ubyte a) nothrow @trusted @nogc { 2638 pragma(inline, true); 2639 c.a = a/255.0f; 2640 return c; 2641 } 2642 2643 /// Ditto. 2644 public NVGColor nvgTransRGBAf (NVGColor c, float a) nothrow @trusted @nogc { 2645 pragma(inline, true); 2646 c.a = a; 2647 return c; 2648 } 2649 2650 /// Linearly interpolates from color c0 to c1, and returns resulting color value. 2651 /// Group: color_utils 2652 public NVGColor nvgLerpRGBA() (const scope auto ref NVGColor c0, const scope auto ref NVGColor c1, float u) nothrow @trusted @nogc { 2653 NVGColor cint = void; 2654 u = nvg__clamp(u, 0.0f, 1.0f); 2655 float oneminu = 1.0f-u; 2656 foreach (uint i; 0..4) cint.rgba.ptr[i] = c0.rgba.ptr[i]*oneminu+c1.rgba.ptr[i]*u; 2657 return cint; 2658 } 2659 2660 /* see below 2661 public NVGColor nvgHSL() (float h, float s, float l) { 2662 //pragma(inline, true); // alas 2663 return nvgHSLA(h, s, l, 255); 2664 } 2665 */ 2666 2667 float nvg__hue (float h, float m1, float m2) pure nothrow @safe @nogc { 2668 if (h < 0) h += 1; 2669 if (h > 1) h -= 1; 2670 if (h < 1.0f/6.0f) return m1+(m2-m1)*h*6.0f; 2671 if (h < 3.0f/6.0f) return m2; 2672 if (h < 4.0f/6.0f) return m1+(m2-m1)*(2.0f/3.0f-h)*6.0f; 2673 return m1; 2674 } 2675 2676 /// Returns color value specified by hue, saturation and lightness. 2677 /// HSL values are all in range [0..1], alpha will be set to 255. 2678 /// Group: color_utils 2679 public alias nvgHSL = nvgHSLA; // trick to allow inlining 2680 2681 /// Returns color value specified by hue, saturation and lightness and alpha. 2682 /// HSL values are all in range [0..1], alpha in range [0..255]. 2683 /// Group: color_utils 2684 public NVGColor nvgHSLA (float h, float s, float l, ubyte a=255) nothrow @trusted @nogc { 2685 pragma(inline, true); 2686 NVGColor col = void; 2687 h = nvg__modf(h, 1.0f); 2688 if (h < 0.0f) h += 1.0f; 2689 s = nvg__clamp(s, 0.0f, 1.0f); 2690 l = nvg__clamp(l, 0.0f, 1.0f); 2691 immutable float m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s); 2692 immutable float m1 = 2*l-m2; 2693 col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2694 col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f); 2695 col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2696 col.a = a/255.0f; 2697 return col; 2698 } 2699 2700 /// Returns color value specified by hue, saturation and lightness and alpha. 2701 /// HSL values and alpha are all in range [0..1]. 2702 /// Group: color_utils 2703 public NVGColor nvgHSLA (float h, float s, float l, float a) nothrow @trusted @nogc { 2704 // sorry for copypasta, it is for inliner 2705 static if (__VERSION__ >= 2072) pragma(inline, true); 2706 NVGColor col = void; 2707 h = nvg__modf(h, 1.0f); 2708 if (h < 0.0f) h += 1.0f; 2709 s = nvg__clamp(s, 0.0f, 1.0f); 2710 l = nvg__clamp(l, 0.0f, 1.0f); 2711 immutable m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s); 2712 immutable m1 = 2*l-m2; 2713 col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2714 col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f); 2715 col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2716 col.a = a; 2717 return col; 2718 } 2719 2720 2721 // ////////////////////////////////////////////////////////////////////////// // 2722 // Matrices and Transformations 2723 2724 /** Matrix class. 2725 * 2726 * Group: matrices 2727 */ 2728 public align(1) struct NVGMatrix { 2729 align(1): 2730 private: 2731 static immutable float[6] IdentityMat = [ 2732 1.0f, 0.0f, 2733 0.0f, 1.0f, 2734 0.0f, 0.0f, 2735 ]; 2736 2737 public: 2738 /// Matrix values. Initial value is identity matrix. 2739 float[6] mat = [ 2740 1.0f, 0.0f, 2741 0.0f, 1.0f, 2742 0.0f, 0.0f, 2743 ]; 2744 2745 public nothrow @trusted @nogc: 2746 /// Create Matrix with the given values. 2747 this (const(float)[] amat...) { 2748 pragma(inline, true); 2749 if (amat.length >= 6) { 2750 mat.ptr[0..6] = amat.ptr[0..6]; 2751 } else { 2752 mat.ptr[0..6] = 0; 2753 mat.ptr[0..amat.length] = amat[]; 2754 } 2755 } 2756 2757 /// Can be used to check validity of [inverted] result 2758 @property bool valid () const { import core.stdc.math : isfinite; return (isfinite(mat.ptr[0]) != 0); } 2759 2760 /// Returns `true` if this matrix is identity matrix. 2761 @property bool isIdentity () const { version(aliced) pragma(inline, true); return (mat[] == IdentityMat[]); } 2762 2763 /// Returns new inverse matrix. 2764 /// If inverted matrix cannot be calculated, `res.valid` fill be `false`. 2765 NVGMatrix inverted () const { 2766 NVGMatrix res = this; 2767 res.invert; 2768 return res; 2769 } 2770 2771 /// Inverts this matrix. 2772 /// If inverted matrix cannot be calculated, `this.valid` fill be `false`. 2773 ref NVGMatrix invert () return { 2774 float[6] inv = void; 2775 immutable double det = cast(double)mat.ptr[0]*mat.ptr[3]-cast(double)mat.ptr[2]*mat.ptr[1]; 2776 if (det > -1e-6 && det < 1e-6) { 2777 inv[] = float.nan; 2778 } else { 2779 immutable double invdet = 1.0/det; 2780 inv.ptr[0] = cast(float)(mat.ptr[3]*invdet); 2781 inv.ptr[2] = cast(float)(-mat.ptr[2]*invdet); 2782 inv.ptr[4] = cast(float)((cast(double)mat.ptr[2]*mat.ptr[5]-cast(double)mat.ptr[3]*mat.ptr[4])*invdet); 2783 inv.ptr[1] = cast(float)(-mat.ptr[1]*invdet); 2784 inv.ptr[3] = cast(float)(mat.ptr[0]*invdet); 2785 inv.ptr[5] = cast(float)((cast(double)mat.ptr[1]*mat.ptr[4]-cast(double)mat.ptr[0]*mat.ptr[5])*invdet); 2786 } 2787 mat.ptr[0..6] = inv.ptr[0..6]; 2788 return this; 2789 } 2790 2791 /// Sets this matrix to identity matrix. 2792 ref NVGMatrix identity () return { version(aliced) pragma(inline, true); mat[] = IdentityMat[]; return this; } 2793 2794 /// Translate this matrix. 2795 ref NVGMatrix translate (in float tx, in float ty) return { 2796 version(aliced) pragma(inline, true); 2797 return this.mul(Translated(tx, ty)); 2798 } 2799 2800 /// Scale this matrix. 2801 ref NVGMatrix scale (in float sx, in float sy) return { 2802 version(aliced) pragma(inline, true); 2803 return this.mul(Scaled(sx, sy)); 2804 } 2805 2806 /// Rotate this matrix. 2807 ref NVGMatrix rotate (in float a) return { 2808 version(aliced) pragma(inline, true); 2809 return this.mul(Rotated(a)); 2810 } 2811 2812 /// Skew this matrix by X axis. 2813 ref NVGMatrix skewX (in float a) return { 2814 version(aliced) pragma(inline, true); 2815 return this.mul(SkewedX(a)); 2816 } 2817 2818 /// Skew this matrix by Y axis. 2819 ref NVGMatrix skewY (in float a) return { 2820 version(aliced) pragma(inline, true); 2821 return this.mul(SkewedY(a)); 2822 } 2823 2824 /// Skew this matrix by both axes. 2825 ref NVGMatrix skewY (in float ax, in float ay) return { 2826 version(aliced) pragma(inline, true); 2827 return this.mul(SkewedXY(ax, ay)); 2828 } 2829 2830 /// Transform point with this matrix. `null` destinations are allowed. 2831 /// [sx] and [sy] is the source point. [dx] and [dy] may point to the same variables. 2832 void point (float* dx, float* dy, float sx, float sy) nothrow @trusted @nogc { 2833 version(aliced) pragma(inline, true); 2834 if (dx !is null) *dx = sx*mat.ptr[0]+sy*mat.ptr[2]+mat.ptr[4]; 2835 if (dy !is null) *dy = sx*mat.ptr[1]+sy*mat.ptr[3]+mat.ptr[5]; 2836 } 2837 2838 /// Transform point with this matrix. 2839 void point (ref float x, ref float y) nothrow @trusted @nogc { 2840 version(aliced) pragma(inline, true); 2841 immutable float nx = x*mat.ptr[0]+y*mat.ptr[2]+mat.ptr[4]; 2842 immutable float ny = x*mat.ptr[1]+y*mat.ptr[3]+mat.ptr[5]; 2843 x = nx; 2844 y = ny; 2845 } 2846 2847 /// Sets this matrix to the result of multiplication of `this` and [s] (this * S). 2848 ref NVGMatrix mul() (const scope auto ref NVGMatrix s) { 2849 immutable float t0 = mat.ptr[0]*s.mat.ptr[0]+mat.ptr[1]*s.mat.ptr[2]; 2850 immutable float t2 = mat.ptr[2]*s.mat.ptr[0]+mat.ptr[3]*s.mat.ptr[2]; 2851 immutable float t4 = mat.ptr[4]*s.mat.ptr[0]+mat.ptr[5]*s.mat.ptr[2]+s.mat.ptr[4]; 2852 mat.ptr[1] = mat.ptr[0]*s.mat.ptr[1]+mat.ptr[1]*s.mat.ptr[3]; 2853 mat.ptr[3] = mat.ptr[2]*s.mat.ptr[1]+mat.ptr[3]*s.mat.ptr[3]; 2854 mat.ptr[5] = mat.ptr[4]*s.mat.ptr[1]+mat.ptr[5]*s.mat.ptr[3]+s.mat.ptr[5]; 2855 mat.ptr[0] = t0; 2856 mat.ptr[2] = t2; 2857 mat.ptr[4] = t4; 2858 return this; 2859 } 2860 2861 /// Sets this matrix to the result of multiplication of [s] and `this` (S * this). 2862 /// Sets the transform to the result of multiplication of two transforms, of A = B*A. 2863 /// Group: matrices 2864 ref NVGMatrix premul() (const scope auto ref NVGMatrix s) { 2865 NVGMatrix s2 = s; 2866 s2.mul(this); 2867 mat[] = s2.mat[]; 2868 return this; 2869 } 2870 2871 /// Multiply this matrix by [s], return result as new matrix. 2872 /// Performs operations in this left-to-right order. 2873 NVGMatrix opBinary(string op="*") (const scope auto ref NVGMatrix s) const { 2874 version(aliced) pragma(inline, true); 2875 NVGMatrix res = this; 2876 res.mul(s); 2877 return res; 2878 } 2879 2880 /// Multiply this matrix by [s]. 2881 /// Performs operations in this left-to-right order. 2882 ref NVGMatrix opOpAssign(string op="*") (const scope auto ref NVGMatrix s) { 2883 version(aliced) pragma(inline, true); 2884 return this.mul(s); 2885 } 2886 2887 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. 2888 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. 2889 float rotation () const { pragma(inline, true); return nvg__atan2f(mat.ptr[1], mat.ptr[0]); } /// Returns rotation of this matrix. 2890 float tx () const { pragma(inline, true); return mat.ptr[4]; } /// Returns x translation of this matrix. 2891 float ty () const { pragma(inline, true); return mat.ptr[5]; } /// Returns y translation of this matrix. 2892 2893 ref NVGMatrix scaleX (in float v) return { pragma(inline, true); return scaleRotateTransform(v, scaleY, rotation, tx, ty); } /// Sets x scaling of this matrix. 2894 ref NVGMatrix scaleY (in float v) return { pragma(inline, true); return scaleRotateTransform(scaleX, v, rotation, tx, ty); } /// Sets y scaling of this matrix. 2895 ref NVGMatrix rotation (in float v) return { pragma(inline, true); return scaleRotateTransform(scaleX, scaleY, v, tx, ty); } /// Sets rotation of this matrix. 2896 ref NVGMatrix tx (in float v) return { pragma(inline, true); mat.ptr[4] = v; return this; } /// Sets x translation of this matrix. 2897 ref NVGMatrix ty (in float v) return { pragma(inline, true); mat.ptr[5] = v; return this; } /// Sets y translation of this matrix. 2898 2899 /// Utility function to be used in `setXXX()`. 2900 /// This is the same as doing: `mat.identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster 2901 ref NVGMatrix scaleRotateTransform (in float xscale, in float yscale, in float a, in float tx, in float ty) return { 2902 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a); 2903 mat.ptr[0] = xscale*cs; mat.ptr[1] = yscale*sn; 2904 mat.ptr[2] = xscale*-sn; mat.ptr[3] = yscale*cs; 2905 mat.ptr[4] = tx; mat.ptr[5] = ty; 2906 return this; 2907 } 2908 2909 /// This is the same as doing: `mat.identity.rotate(a).translate(tx, ty)`, only faster 2910 ref NVGMatrix rotateTransform (in float a, in float tx, in float ty) return { 2911 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a); 2912 mat.ptr[0] = cs; mat.ptr[1] = sn; 2913 mat.ptr[2] = -sn; mat.ptr[3] = cs; 2914 mat.ptr[4] = tx; mat.ptr[5] = ty; 2915 return this; 2916 } 2917 2918 /// Returns new identity matrix. 2919 static NVGMatrix Identity () { pragma(inline, true); return NVGMatrix.init; } 2920 2921 /// Returns new translation matrix. 2922 static NVGMatrix Translated (in float tx, in float ty) { 2923 version(aliced) pragma(inline, true); 2924 NVGMatrix res = void; 2925 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f; 2926 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f; 2927 res.mat.ptr[4] = tx; res.mat.ptr[5] = ty; 2928 return res; 2929 } 2930 2931 /// Returns new scaling matrix. 2932 static NVGMatrix Scaled (in float sx, in float sy) { 2933 version(aliced) pragma(inline, true); 2934 NVGMatrix res = void; 2935 res.mat.ptr[0] = sx; res.mat.ptr[1] = 0.0f; 2936 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = sy; 2937 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2938 return res; 2939 } 2940 2941 /// Returns new rotation matrix. Angle is specified in radians. 2942 static NVGMatrix Rotated (in float a) { 2943 version(aliced) pragma(inline, true); 2944 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a); 2945 NVGMatrix res = void; 2946 res.mat.ptr[0] = cs; res.mat.ptr[1] = sn; 2947 res.mat.ptr[2] = -sn; res.mat.ptr[3] = cs; 2948 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2949 return res; 2950 } 2951 2952 /// Returns new x-skewing matrix. Angle is specified in radians. 2953 static NVGMatrix SkewedX (in float a) { 2954 version(aliced) pragma(inline, true); 2955 NVGMatrix res = void; 2956 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f; 2957 res.mat.ptr[2] = nvg__tanf(a); res.mat.ptr[3] = 1.0f; 2958 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2959 return res; 2960 } 2961 2962 /// Returns new y-skewing matrix. Angle is specified in radians. 2963 static NVGMatrix SkewedY (in float a) { 2964 version(aliced) pragma(inline, true); 2965 NVGMatrix res = void; 2966 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(a); 2967 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f; 2968 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2969 return res; 2970 } 2971 2972 /// Returns new xy-skewing matrix. Angles are specified in radians. 2973 static NVGMatrix SkewedXY (in float ax, in float ay) { 2974 version(aliced) pragma(inline, true); 2975 NVGMatrix res = void; 2976 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(ay); 2977 res.mat.ptr[2] = nvg__tanf(ax); res.mat.ptr[3] = 1.0f; 2978 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2979 return res; 2980 } 2981 2982 /// Utility function to be used in `setXXX()`. 2983 /// This is the same as doing: `NVGMatrix.Identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster 2984 static NVGMatrix ScaledRotatedTransformed (in float xscale, in float yscale, in float a, in float tx, in float ty) { 2985 NVGMatrix res = void; 2986 res.scaleRotateTransform(xscale, yscale, a, tx, ty); 2987 return res; 2988 } 2989 2990 /// This is the same as doing: `NVGMatrix.Identity.rotate(a).translate(tx, ty)`, only faster 2991 static NVGMatrix RotatedTransformed (in float a, in float tx, in float ty) { 2992 NVGMatrix res = void; 2993 res.rotateTransform(a, tx, ty); 2994 return res; 2995 } 2996 } 2997 2998 2999 /// Converts degrees to radians. 3000 /// Group: matrices 3001 public float nvgDegToRad() (in float deg) pure nothrow @safe @nogc { pragma(inline, true); return deg/180.0f*NVG_PI; } 3002 3003 /// Converts radians to degrees. 3004 /// Group: matrices 3005 public float nvgRadToDeg() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad/NVG_PI*180.0f; } 3006 3007 public alias nvgDegrees = nvgDegToRad; /// Use this like `42.nvgDegrees` 3008 public float nvgRadians() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad; } /// Use this like `0.1.nvgRadians` 3009 3010 3011 // ////////////////////////////////////////////////////////////////////////// // 3012 void nvg__setPaintColor() (ref NVGPaint p, const scope auto ref NVGColor color) nothrow @trusted @nogc { 3013 p.clear(); 3014 p.xform.identity; 3015 p.radius = 0.0f; 3016 p.feather = 1.0f; 3017 p.innerColor = p.middleColor = p.outerColor = color; 3018 p.midp = -1; 3019 p.simpleColor = true; 3020 } 3021 3022 3023 // ////////////////////////////////////////////////////////////////////////// // 3024 // State handling 3025 3026 version(nanovega_debug_clipping) { 3027 public void nvgClipDumpOn (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, true); } 3028 public void nvgClipDumpOff (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, false); } 3029 } 3030 3031 /** Pushes and saves the current render state into a state stack. 3032 * A matching [restore] must be used to restore the state. 3033 * Returns `false` if state stack overflowed. 3034 * 3035 * Group: state_handling 3036 */ 3037 public bool save (NVGContext ctx) nothrow @trusted @nogc { 3038 if (ctx.nstates >= NVG_MAX_STATES) return false; 3039 if (ctx.nstates > 0) { 3040 //memcpy(&ctx.states[ctx.nstates], &ctx.states[ctx.nstates-1], NVGstate.sizeof); 3041 ctx.states[ctx.nstates] = ctx.states[ctx.nstates-1]; 3042 ctx.params.renderPushClip(ctx.params.userPtr); 3043 } 3044 ++ctx.nstates; 3045 return true; 3046 } 3047 3048 /// Pops and restores current render state. 3049 /// Group: state_handling 3050 public bool restore (NVGContext ctx) nothrow @trusted @nogc { 3051 if (ctx.nstates <= 1) return false; 3052 ctx.states[ctx.nstates-1].clearPaint(); 3053 ctx.params.renderPopClip(ctx.params.userPtr); 3054 --ctx.nstates; 3055 return true; 3056 } 3057 3058 /// Resets current render state to default values. Does not affect the render state stack. 3059 /// Group: state_handling 3060 public void reset (NVGContext ctx) nothrow @trusted @nogc { 3061 NVGstate* state = nvg__getState(ctx); 3062 state.clearPaint(); 3063 3064 nvg__setPaintColor(state.fill, nvgRGBA(255, 255, 255, 255)); 3065 nvg__setPaintColor(state.stroke, nvgRGBA(0, 0, 0, 255)); 3066 state.compositeOperation = nvg__compositeOperationState(NVGCompositeOperation.SourceOver); 3067 state.shapeAntiAlias = true; 3068 state.strokeWidth = 1.0f; 3069 state.miterLimit = 10.0f; 3070 state.lineCap = NVGLineCap.Butt; 3071 state.lineJoin = NVGLineCap.Miter; 3072 state.alpha = 1.0f; 3073 state.xform.identity; 3074 3075 state.scissor.extent[] = -1.0f; 3076 3077 state.fontSize = 16.0f; 3078 state.letterSpacing = 0.0f; 3079 state.lineHeight = 1.0f; 3080 state.fontBlur = 0.0f; 3081 state.textAlign.reset; 3082 state.fontId = 0; 3083 state.evenOddMode = false; 3084 state.dashCount = 0; 3085 state.lastFlattenDashCount = 0; 3086 state.dashStart = 0; 3087 state.firstDashIsGap = false; 3088 state.dasherActive = false; 3089 3090 ctx.params.renderResetClip(ctx.params.userPtr); 3091 } 3092 3093 /** Returns `true` if we have any room in state stack. 3094 * It is guaranteed to have at least 32 stack slots. 3095 * 3096 * Group: state_handling 3097 */ 3098 public bool canSave (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates < NVG_MAX_STATES); } 3099 3100 /** Returns `true` if we have any saved state. 3101 * 3102 * Group: state_handling 3103 */ 3104 public bool canRestore (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates > 1); } 3105 3106 /// Returns `true` if rendering is currently blocked. 3107 /// Group: state_handling 3108 public bool renderBlocked (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.recblockdraw : false); } 3109 3110 /// Blocks/unblocks rendering 3111 /// Group: state_handling 3112 public void renderBlocked (NVGContext ctx, bool v) pure nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null && ctx.contextAlive) ctx.recblockdraw = v; } 3113 3114 /// Blocks/unblocks rendering; returns previous state. 3115 /// Group: state_handling 3116 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; } 3117 3118 3119 // ////////////////////////////////////////////////////////////////////////// // 3120 // Render styles 3121 3122 /// Sets filling mode to "even-odd". 3123 /// Group: render_styles 3124 public void evenOddFill (NVGContext ctx) nothrow @trusted @nogc { 3125 NVGstate* state = nvg__getState(ctx); 3126 state.evenOddMode = true; 3127 } 3128 3129 /// Sets filling mode to "non-zero" (this is default mode). 3130 /// Group: render_styles 3131 public void nonZeroFill (NVGContext ctx) nothrow @trusted @nogc { 3132 NVGstate* state = nvg__getState(ctx); 3133 state.evenOddMode = false; 3134 } 3135 3136 /// Sets whether to draw antialias for [stroke] and [fill]. It's enabled by default. 3137 /// Group: render_styles 3138 public void shapeAntiAlias (NVGContext ctx, bool enabled) { 3139 NVGstate* state = nvg__getState(ctx); 3140 state.shapeAntiAlias = enabled; 3141 } 3142 3143 /// Sets the stroke width of the stroke style. 3144 /// Group: render_styles 3145 @scriptable 3146 public void strokeWidth (NVGContext ctx, float width) nothrow @trusted @nogc { 3147 NVGstate* state = nvg__getState(ctx); 3148 state.strokeWidth = width; 3149 } 3150 3151 /// Sets the miter limit of the stroke style. Miter limit controls when a sharp corner is beveled. 3152 /// Group: render_styles 3153 public void miterLimit (NVGContext ctx, float limit) nothrow @trusted @nogc { 3154 NVGstate* state = nvg__getState(ctx); 3155 state.miterLimit = limit; 3156 } 3157 3158 /// Sets how the end of the line (cap) is drawn, 3159 /// Can be one of: NVGLineCap.Butt (default), NVGLineCap.Round, NVGLineCap.Square. 3160 /// Group: render_styles 3161 public void lineCap (NVGContext ctx, NVGLineCap cap) nothrow @trusted @nogc { 3162 NVGstate* state = nvg__getState(ctx); 3163 state.lineCap = cap; 3164 } 3165 3166 /// Sets how sharp path corners are drawn. 3167 /// Can be one of NVGLineCap.Miter (default), NVGLineCap.Round, NVGLineCap.Bevel. 3168 /// Group: render_styles 3169 public void lineJoin (NVGContext ctx, NVGLineCap join) nothrow @trusted @nogc { 3170 NVGstate* state = nvg__getState(ctx); 3171 state.lineJoin = join; 3172 } 3173 3174 /// Sets stroke dashing, using (dash_length, gap_length) pairs. 3175 /// Current limit is 16 pairs. 3176 /// Resets dash start to zero. 3177 /// Group: render_styles 3178 public void setLineDash (NVGContext ctx, const(float)[] dashdata) nothrow @trusted @nogc { 3179 NVGstate* state = nvg__getState(ctx); 3180 state.dashCount = 0; 3181 state.dashStart = 0; 3182 state.firstDashIsGap = false; 3183 if (dashdata.length >= 2) { 3184 bool curFIsGap = true; // trick 3185 foreach (immutable idx, float f; dashdata) { 3186 curFIsGap = !curFIsGap; 3187 if (f < 0.01f) continue; // skip it 3188 if (idx == 0) { 3189 // register first dash 3190 state.firstDashIsGap = curFIsGap; 3191 state.dashes.ptr[state.dashCount++] = f; 3192 } else { 3193 if ((idx&1) != ((state.dashCount&1)^cast(uint)state.firstDashIsGap)) { 3194 // oops, continuation 3195 state.dashes[state.dashCount-1] += f; 3196 } else { 3197 if (state.dashCount == state.dashes.length) break; 3198 state.dashes[state.dashCount++] = f; 3199 } 3200 } 3201 } 3202 if (state.dashCount&1) { 3203 if (state.dashCount == 1) { 3204 state.dashCount = 0; 3205 } else { 3206 assert(state.dashCount < state.dashes.length); 3207 state.dashes[state.dashCount++] = 0; 3208 } 3209 } 3210 // calculate total dash path length 3211 state.totalDashLen = 0; 3212 foreach (float f; state.dashes.ptr[0..state.dashCount]) state.totalDashLen += f; 3213 if (state.totalDashLen < 0.01f) { 3214 state.dashCount = 0; // nothing to do 3215 } else { 3216 if (state.lastFlattenDashCount != 0) state.lastFlattenDashCount = uint.max; // force re-flattening 3217 } 3218 } 3219 } 3220 3221 public alias lineDash = setLineDash; /// Ditto. 3222 3223 /// Sets stroke dashing, using (dash_length, gap_length) pairs. 3224 /// Current limit is 16 pairs. 3225 /// Group: render_styles 3226 public void setLineDashStart (NVGContext ctx, in float dashStart) nothrow @trusted @nogc { 3227 NVGstate* state = nvg__getState(ctx); 3228 if (state.lastFlattenDashCount != 0 && state.dashStart != dashStart) { 3229 state.lastFlattenDashCount = uint.max; // force re-flattening 3230 } 3231 state.dashStart = dashStart; 3232 } 3233 3234 public alias lineDashStart = setLineDashStart; /// Ditto. 3235 3236 /// Sets the transparency applied to all rendered shapes. 3237 /// Already transparent paths will get proportionally more transparent as well. 3238 /// Group: render_styles 3239 public void globalAlpha (NVGContext ctx, float alpha) nothrow @trusted @nogc { 3240 NVGstate* state = nvg__getState(ctx); 3241 state.alpha = alpha; 3242 } 3243 3244 private void strokeColor() {} 3245 3246 static if (NanoVegaHasArsdColor) { 3247 /// Sets current stroke style to a solid color. 3248 /// Group: render_styles 3249 @scriptable 3250 public void strokeColor (NVGContext ctx, Color color) nothrow @trusted @nogc { 3251 NVGstate* state = nvg__getState(ctx); 3252 nvg__setPaintColor(state.stroke, NVGColor(color)); 3253 } 3254 } 3255 3256 /// Sets current stroke style to a solid color. 3257 /// Group: render_styles 3258 public void strokeColor() (NVGContext ctx, const scope auto ref NVGColor color) nothrow @trusted @nogc { 3259 NVGstate* state = nvg__getState(ctx); 3260 nvg__setPaintColor(state.stroke, color); 3261 } 3262 3263 @scriptable 3264 public void strokePaint(NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc { 3265 strokePaint(ctx, *paint); 3266 } 3267 3268 /// Sets current stroke style to a paint, which can be a one of the gradients or a pattern. 3269 /// Group: render_styles 3270 @scriptable 3271 public void strokePaint() (NVGContext ctx, const scope auto ref NVGPaint paint) nothrow @trusted @nogc { 3272 NVGstate* state = nvg__getState(ctx); 3273 state.stroke = paint; 3274 //nvgTransformMultiply(state.stroke.xform[], state.xform[]); 3275 state.stroke.xform.mul(state.xform); 3276 } 3277 3278 // this is a hack to work around https://issues.dlang.org/show_bug.cgi?id=16206 3279 // for scriptable reflection. it just needs to be declared first among the overloads 3280 private void fillColor (NVGContext ctx) nothrow @trusted @nogc { } 3281 3282 static if (NanoVegaHasArsdColor) { 3283 /// Sets current fill style to a solid color. 3284 /// Group: render_styles 3285 @scriptable 3286 public void fillColor (NVGContext ctx, Color color) nothrow @trusted @nogc { 3287 NVGstate* state = nvg__getState(ctx); 3288 nvg__setPaintColor(state.fill, NVGColor(color)); 3289 } 3290 } 3291 3292 /// Sets current fill style to a solid color. 3293 /// Group: render_styles 3294 public void fillColor() (NVGContext ctx, const scope auto ref NVGColor color) nothrow @trusted @nogc { 3295 NVGstate* state = nvg__getState(ctx); 3296 nvg__setPaintColor(state.fill, color); 3297 3298 } 3299 3300 @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) 3301 public void fillPaint (NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc { 3302 fillPaint(ctx, *paint); 3303 } 3304 3305 /// Sets current fill style to a paint, which can be a one of the gradients or a pattern. 3306 /// Group: render_styles 3307 @scriptable 3308 public void fillPaint() (NVGContext ctx, const scope auto ref NVGPaint paint) nothrow @trusted @nogc { 3309 NVGstate* state = nvg__getState(ctx); 3310 state.fill = paint; 3311 //nvgTransformMultiply(state.fill.xform[], state.xform[]); 3312 state.fill.xform.mul(state.xform); 3313 } 3314 3315 /// Sets current fill style to a multistop linear gradient. 3316 /// Group: render_styles 3317 public void fillPaint() (NVGContext ctx, const scope auto ref NVGLGS lgs) nothrow @trusted @nogc { 3318 if (!lgs.valid) { 3319 NVGPaint p = void; 3320 memset(&p, 0, p.sizeof); 3321 nvg__setPaintColor(p, NVGColor.red); 3322 ctx.fillPaint = p; 3323 } else if (lgs.midp >= -1) { 3324 //{ import core.stdc.stdio; printf("SIMPLE! midp=%f\n", cast(double)lgs.midp); } 3325 ctx.fillPaint = ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc); 3326 } else { 3327 ctx.fillPaint = ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid); 3328 } 3329 } 3330 3331 /// Returns current transformation matrix. 3332 /// Group: render_transformations 3333 public NVGMatrix currTransform (NVGContext ctx) pure nothrow @trusted @nogc { 3334 NVGstate* state = nvg__getState(ctx); 3335 return state.xform; 3336 } 3337 3338 /// Sets current transformation matrix. 3339 /// Group: render_transformations 3340 public void currTransform() (NVGContext ctx, const scope auto ref NVGMatrix m) nothrow @trusted @nogc { 3341 NVGstate* state = nvg__getState(ctx); 3342 state.xform = m; 3343 } 3344 3345 /// Resets current transform to an identity matrix. 3346 /// Group: render_transformations 3347 @scriptable 3348 public void resetTransform (NVGContext ctx) nothrow @trusted @nogc { 3349 NVGstate* state = nvg__getState(ctx); 3350 state.xform.identity; 3351 } 3352 3353 /// Premultiplies current coordinate system by specified matrix. 3354 /// Group: render_transformations 3355 public void transform() (NVGContext ctx, const scope auto ref NVGMatrix mt) nothrow @trusted @nogc { 3356 NVGstate* state = nvg__getState(ctx); 3357 //nvgTransformPremultiply(state.xform[], t[]); 3358 state.xform *= mt; 3359 } 3360 3361 /// Translates current coordinate system. 3362 /// Group: render_transformations 3363 @scriptable 3364 public void translate (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 3365 NVGstate* state = nvg__getState(ctx); 3366 //NVGMatrix t = void; 3367 //nvgTransformTranslate(t[], x, y); 3368 //nvgTransformPremultiply(state.xform[], t[]); 3369 state.xform.premul(NVGMatrix.Translated(x, y)); 3370 } 3371 3372 /// Rotates current coordinate system. Angle is specified in radians. 3373 /// Group: render_transformations 3374 @scriptable 3375 public void rotate (NVGContext ctx, in float angle) nothrow @trusted @nogc { 3376 NVGstate* state = nvg__getState(ctx); 3377 //NVGMatrix t = void; 3378 //nvgTransformRotate(t[], angle); 3379 //nvgTransformPremultiply(state.xform[], t[]); 3380 state.xform.premul(NVGMatrix.Rotated(angle)); 3381 } 3382 3383 /// Skews the current coordinate system along X axis. Angle is specified in radians. 3384 /// Group: render_transformations 3385 @scriptable 3386 public void skewX (NVGContext ctx, in float angle) nothrow @trusted @nogc { 3387 NVGstate* state = nvg__getState(ctx); 3388 //NVGMatrix t = void; 3389 //nvgTransformSkewX(t[], angle); 3390 //nvgTransformPremultiply(state.xform[], t[]); 3391 state.xform.premul(NVGMatrix.SkewedX(angle)); 3392 } 3393 3394 /// Skews the current coordinate system along Y axis. Angle is specified in radians. 3395 /// Group: render_transformations 3396 @scriptable 3397 public void skewY (NVGContext ctx, in float angle) nothrow @trusted @nogc { 3398 NVGstate* state = nvg__getState(ctx); 3399 //NVGMatrix t = void; 3400 //nvgTransformSkewY(t[], angle); 3401 //nvgTransformPremultiply(state.xform[], t[]); 3402 state.xform.premul(NVGMatrix.SkewedY(angle)); 3403 } 3404 3405 /// Scales the current coordinate system. 3406 /// Group: render_transformations 3407 @scriptable 3408 public void scale (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 3409 NVGstate* state = nvg__getState(ctx); 3410 //NVGMatrix t = void; 3411 //nvgTransformScale(t[], x, y); 3412 //nvgTransformPremultiply(state.xform[], t[]); 3413 state.xform.premul(NVGMatrix.Scaled(x, y)); 3414 } 3415 3416 3417 // ////////////////////////////////////////////////////////////////////////// // 3418 // Images 3419 3420 /// Creates image by loading it from the disk from specified file name. 3421 /// Returns handle to the image or 0 on error. 3422 /// Group: images 3423 public NVGImage createImage() (NVGContext ctx, const(char)[] filename, const(NVGImageFlag)[] imageFlagsList...) { 3424 static if (NanoVegaHasArsdImage) { 3425 import arsd.image; 3426 // do we have new arsd API to load images? 3427 static if (!is(typeof(MemoryImage.fromImageFile)) || !is(typeof(MemoryImage.clearInternal))) { 3428 static assert(0, "Sorry, your ARSD is too old. Please, update it."); 3429 } 3430 try { 3431 auto oimg = MemoryImage.fromImageFile(filename); 3432 if (auto img = cast(TrueColorImage)oimg) { 3433 scope(exit) oimg.clearInternal(); 3434 return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList); 3435 } else { 3436 TrueColorImage img = oimg.getAsTrueColorImage; 3437 scope(exit) img.clearInternal(); 3438 oimg.clearInternal(); // drop original image, as `getAsTrueColorImage()` MUST create a new one here 3439 oimg = null; 3440 return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList); 3441 } 3442 } catch (Exception) {} 3443 return NVGImage.init; 3444 } else { 3445 import std.internal.cstring; 3446 ubyte* img; 3447 int w, h, n; 3448 stbi_set_unpremultiply_on_load(1); 3449 stbi_convert_iphone_png_to_rgb(1); 3450 img = stbi_load(filename.tempCString, &w, &h, &n, 4); 3451 if (img is null) { 3452 //printf("Failed to load %s - %s\n", filename, stbi_failure_reason()); 3453 return NVGImage.init; 3454 } 3455 auto image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList); 3456 stbi_image_free(img); 3457 return image; 3458 } 3459 } 3460 3461 static if (NanoVegaHasArsdImage) { 3462 /// Creates image by loading it from the specified memory image. 3463 /// Returns handle to the image or 0 on error. 3464 /// Group: images 3465 public NVGImage createImageFromMemoryImage() (NVGContext ctx, MemoryImage img, const(NVGImageFlag)[] imageFlagsList...) { 3466 if (img is null) return NVGImage.init; 3467 if (auto tc = cast(TrueColorImage)img) { 3468 return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList); 3469 } else { 3470 auto tc = img.getAsTrueColorImage; 3471 scope(exit) tc.clearInternal(); // here, it is guaranteed that `tc` is newly allocated image, so it is safe to kill it 3472 return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList); 3473 } 3474 } 3475 } else { 3476 /// Creates image by loading it from the specified chunk of memory. 3477 /// Returns handle to the image or 0 on error. 3478 /// Group: images 3479 public NVGImage createImageMem() (NVGContext ctx, const(ubyte)* data, int ndata, const(NVGImageFlag)[] imageFlagsList...) { 3480 int w, h, n, image; 3481 ubyte* img = stbi_load_from_memory(data, ndata, &w, &h, &n, 4); 3482 if (img is null) { 3483 //printf("Failed to load %s - %s\n", filename, stbi_failure_reason()); 3484 return NVGImage.init; 3485 } 3486 image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList); 3487 stbi_image_free(img); 3488 return image; 3489 } 3490 } 3491 3492 /// Creates image from specified image data. 3493 /// Returns handle to the image or 0 on error. 3494 /// Group: images 3495 public NVGImage createImageRGBA (NVGContext ctx, int w, int h, const(void)[] data, const(NVGImageFlag)[] imageFlagsList...) nothrow @trusted @nogc { 3496 if (w < 1 || h < 1 || data.length < w*h*4) return NVGImage.init; 3497 uint imageFlags = 0; 3498 foreach (immutable uint flag; imageFlagsList) imageFlags |= flag; 3499 NVGImage res; 3500 res.id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.RGBA, w, h, imageFlags, cast(const(ubyte)*)data.ptr); 3501 if (res.id > 0) { 3502 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); } 3503 res.ctx = ctx; 3504 ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount 3505 } 3506 return res; 3507 } 3508 3509 /// Updates image data specified by image handle. 3510 /// Group: images 3511 public void updateImage() (NVGContext ctx, auto ref NVGImage image, const(void)[] data) nothrow @trusted @nogc { 3512 if (image.valid) { 3513 int w, h; 3514 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context"); 3515 ctx.params.renderGetTextureSize(ctx.params.userPtr, image.id, &w, &h); 3516 ctx.params.renderUpdateTexture(ctx.params.userPtr, image.id, 0, 0, w, h, cast(const(ubyte)*)data.ptr); 3517 } 3518 } 3519 3520 /// Returns the dimensions of a created image. 3521 /// Group: images 3522 public void imageSize() (NVGContext ctx, const scope auto ref NVGImage image, out int w, out int h) nothrow @trusted @nogc { 3523 if (image.valid) { 3524 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context"); 3525 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, image.id, &w, &h); 3526 } 3527 } 3528 3529 /// Deletes created image. 3530 /// Group: images 3531 public void deleteImage() (NVGContext ctx, ref NVGImage image) nothrow @trusted @nogc { 3532 if (ctx is null || !image.valid) return; 3533 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context"); 3534 image.clear(); 3535 } 3536 3537 3538 // ////////////////////////////////////////////////////////////////////////// // 3539 // Paints 3540 3541 private void linearGradient() {} // hack for dmd bug 3542 3543 static if (NanoVegaHasArsdColor) { 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 @scriptable 3551 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 { 3552 return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), NVGColor(ocol)); 3553 } 3554 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start 3555 * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in 3556 * range `(0..1)`, and ocol the end color. 3557 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3558 * 3559 * Group: paints 3560 */ 3561 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 { 3562 return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), midp, NVGColor(mcol), NVGColor(ocol)); 3563 } 3564 } 3565 3566 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates 3567 * of the linear gradient, icol specifies the start color and ocol the end color. 3568 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3569 * 3570 * Group: paints 3571 */ 3572 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 { 3573 enum large = 1e5f; 3574 3575 NVGPaint p = void; 3576 memset(&p, 0, p.sizeof); 3577 p.simpleColor = false; 3578 3579 // Calculate transform aligned to the line 3580 float dx = ex-sx; 3581 float dy = ey-sy; 3582 immutable float d = nvg__sqrtf(dx*dx+dy*dy); 3583 if (d > 0.0001f) { 3584 dx /= d; 3585 dy /= d; 3586 } else { 3587 dx = 0; 3588 dy = 1; 3589 } 3590 3591 p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx; 3592 p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy; 3593 p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large; 3594 3595 p.extent.ptr[0] = large; 3596 p.extent.ptr[1] = large+d*0.5f; 3597 3598 p.radius = 0.0f; 3599 3600 p.feather = nvg__max(NVG_MIN_FEATHER, d); 3601 3602 p.innerColor = p.middleColor = icol; 3603 p.outerColor = ocol; 3604 p.midp = -1; 3605 3606 return p; 3607 } 3608 3609 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start 3610 * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in 3611 * range `(0..1)`, and ocol the end color. 3612 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3613 * 3614 * Group: paints 3615 */ 3616 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 { 3617 enum large = 1e5f; 3618 3619 NVGPaint p = void; 3620 memset(&p, 0, p.sizeof); 3621 p.simpleColor = false; 3622 3623 // Calculate transform aligned to the line 3624 float dx = ex-sx; 3625 float dy = ey-sy; 3626 immutable float d = nvg__sqrtf(dx*dx+dy*dy); 3627 if (d > 0.0001f) { 3628 dx /= d; 3629 dy /= d; 3630 } else { 3631 dx = 0; 3632 dy = 1; 3633 } 3634 3635 p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx; 3636 p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy; 3637 p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large; 3638 3639 p.extent.ptr[0] = large; 3640 p.extent.ptr[1] = large+d*0.5f; 3641 3642 p.radius = 0.0f; 3643 3644 p.feather = nvg__max(NVG_MIN_FEATHER, d); 3645 3646 if (midp <= 0) { 3647 p.innerColor = p.middleColor = mcol; 3648 p.midp = -1; 3649 } else if (midp > 1) { 3650 p.innerColor = p.middleColor = icol; 3651 p.midp = -1; 3652 } else { 3653 p.innerColor = icol; 3654 p.middleColor = mcol; 3655 p.midp = midp; 3656 } 3657 p.outerColor = ocol; 3658 3659 return p; 3660 } 3661 3662 static if (NanoVegaHasArsdColor) { 3663 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify 3664 * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color. 3665 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3666 * 3667 * Group: paints 3668 */ 3669 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 { 3670 return ctx.radialGradient(cx, cy, inr, outr, NVGColor(icol), NVGColor(ocol)); 3671 } 3672 } 3673 3674 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify 3675 * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color. 3676 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3677 * 3678 * Group: paints 3679 */ 3680 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 { 3681 immutable float r = (inr+outr)*0.5f; 3682 immutable float f = (outr-inr); 3683 3684 NVGPaint p = void; 3685 memset(&p, 0, p.sizeof); 3686 p.simpleColor = false; 3687 3688 p.xform.identity; 3689 p.xform.mat.ptr[4] = cx; 3690 p.xform.mat.ptr[5] = cy; 3691 3692 p.extent.ptr[0] = r; 3693 p.extent.ptr[1] = r; 3694 3695 p.radius = r; 3696 3697 p.feather = nvg__max(NVG_MIN_FEATHER, f); 3698 3699 p.innerColor = p.middleColor = icol; 3700 p.outerColor = ocol; 3701 p.midp = -1; 3702 3703 return p; 3704 } 3705 3706 static if (NanoVegaHasArsdColor) { 3707 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering 3708 * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle, 3709 * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry 3710 * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient. 3711 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3712 * 3713 * Group: paints 3714 */ 3715 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 { 3716 return ctx.boxGradient(x, y, w, h, r, f, NVGColor(icol), NVGColor(ocol)); 3717 } 3718 } 3719 3720 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering 3721 * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle, 3722 * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry 3723 * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient. 3724 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3725 * 3726 * Group: paints 3727 */ 3728 public NVGPaint 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 { 3729 NVGPaint p = void; 3730 memset(&p, 0, p.sizeof); 3731 p.simpleColor = false; 3732 3733 p.xform.identity; 3734 p.xform.mat.ptr[4] = x+w*0.5f; 3735 p.xform.mat.ptr[5] = y+h*0.5f; 3736 3737 p.extent.ptr[0] = w*0.5f; 3738 p.extent.ptr[1] = h*0.5f; 3739 3740 p.radius = r; 3741 3742 p.feather = nvg__max(NVG_MIN_FEATHER, f); 3743 3744 p.innerColor = p.middleColor = icol; 3745 p.outerColor = ocol; 3746 p.midp = -1; 3747 3748 return p; 3749 } 3750 3751 /** Creates and returns an image pattern. Parameters `(cx, cy)` specify the left-top location of the image pattern, 3752 * `(w, h)` the size of one image, [angle] rotation around the top-left corner, [image] is handle to the image to render. 3753 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3754 * 3755 * Group: paints 3756 */ 3757 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 { 3758 NVGPaint p = void; 3759 memset(&p, 0, p.sizeof); 3760 p.simpleColor = false; 3761 3762 p.xform.identity.rotate(angle); 3763 p.xform.mat.ptr[4] = cx; 3764 p.xform.mat.ptr[5] = cy; 3765 3766 p.extent.ptr[0] = w; 3767 p.extent.ptr[1] = h; 3768 3769 p.image = image; 3770 3771 p.innerColor = p.middleColor = p.outerColor = nvgRGBAf(1, 1, 1, alpha); 3772 p.midp = -1; 3773 3774 return p; 3775 } 3776 3777 /// Linear gradient with multiple stops. 3778 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3779 /// Group: paints 3780 public struct NVGLGS { 3781 private: 3782 NVGColor ic, mc, oc; // inner, middle, out 3783 float midp; 3784 NVGImage imgid; 3785 // [imagePattern] arguments 3786 float cx, cy, dimx, dimy; // dimx and dimy are ex and ey for simple gradients 3787 public float angle; /// 3788 3789 public: 3790 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (imgid.valid || midp >= -1); } /// 3791 void clear () nothrow @safe @nogc { pragma(inline, true); imgid.clear(); midp = float.nan; } /// 3792 } 3793 3794 /** Returns [NVGPaint] for linear gradient with stops, created with [createLinearGradientWithStops]. 3795 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3796 * 3797 * $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3798 * Group: paints 3799 */ 3800 public NVGPaint asPaint() (NVGContext ctx, const scope auto ref NVGLGS lgs) nothrow @trusted @nogc { 3801 if (!lgs.valid) { 3802 NVGPaint p = void; 3803 memset(&p, 0, p.sizeof); 3804 nvg__setPaintColor(p, NVGColor.red); 3805 return p; 3806 } else if (lgs.midp >= -1) { 3807 return ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc); 3808 } else { 3809 return ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid); 3810 } 3811 } 3812 3813 /// Gradient Stop Point. 3814 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3815 /// Group: paints 3816 public struct NVGGradientStop { 3817 float offset = 0; /// [0..1] 3818 NVGColor color; /// 3819 3820 this() (in float aofs, const scope auto ref NVGColor aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = aclr; } /// 3821 static if (NanoVegaHasArsdColor) { 3822 this() (in float aofs, in Color aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = NVGColor(aclr); } /// 3823 } 3824 } 3825 3826 /// Create linear gradient data suitable to use with `linearGradient(res)`. 3827 /// Don't forget to destroy the result when you don't need it anymore with `ctx.kill(res);`. 3828 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3829 /// Group: paints 3830 public NVGLGS createLinearGradientWithStops (NVGContext ctx, in float sx, in float sy, in float ex, in float ey, const(NVGGradientStop)[] stops...) nothrow @trusted @nogc { 3831 // based on the code by Jorge Acereda <jacereda@gmail.com> 3832 enum NVG_GRADIENT_SAMPLES = 1024; 3833 static void gradientSpan (uint* dst, const(NVGGradientStop)* s0, const(NVGGradientStop)* s1) nothrow @trusted @nogc { 3834 immutable float s0o = nvg__clamp(s0.offset, 0.0f, 1.0f); 3835 immutable float s1o = nvg__clamp(s1.offset, 0.0f, 1.0f); 3836 uint s = cast(uint)(s0o*NVG_GRADIENT_SAMPLES); 3837 uint e = cast(uint)(s1o*NVG_GRADIENT_SAMPLES); 3838 uint sc = 0xffffffffU; 3839 uint sh = 24; 3840 uint r = cast(uint)(s0.color.rgba[0]*sc); 3841 uint g = cast(uint)(s0.color.rgba[1]*sc); 3842 uint b = cast(uint)(s0.color.rgba[2]*sc); 3843 uint a = cast(uint)(s0.color.rgba[3]*sc); 3844 uint dr = cast(uint)((s1.color.rgba[0]*sc-r)/(e-s)); 3845 uint dg = cast(uint)((s1.color.rgba[1]*sc-g)/(e-s)); 3846 uint db = cast(uint)((s1.color.rgba[2]*sc-b)/(e-s)); 3847 uint da = cast(uint)((s1.color.rgba[3]*sc-a)/(e-s)); 3848 dst += s; 3849 foreach (immutable _; s..e) { 3850 version(BigEndian) { 3851 *dst++ = ((r>>sh)<<24)+((g>>sh)<<16)+((b>>sh)<<8)+((a>>sh)<<0); 3852 } else { 3853 *dst++ = ((a>>sh)<<24)+((b>>sh)<<16)+((g>>sh)<<8)+((r>>sh)<<0); 3854 } 3855 r += dr; 3856 g += dg; 3857 b += db; 3858 a += da; 3859 } 3860 } 3861 3862 NVGLGS res; 3863 res.cx = sx; 3864 res.cy = sy; 3865 3866 if (stops.length == 2 && stops.ptr[0].offset <= 0 && stops.ptr[1].offset >= 1) { 3867 // create simple linear gradient 3868 res.ic = res.mc = stops.ptr[0].color; 3869 res.oc = stops.ptr[1].color; 3870 res.midp = -1; 3871 res.dimx = ex; 3872 res.dimy = ey; 3873 } else if (stops.length == 3 && stops.ptr[0].offset <= 0 && stops.ptr[2].offset >= 1) { 3874 // create simple linear gradient with middle stop 3875 res.ic = stops.ptr[0].color; 3876 res.mc = stops.ptr[1].color; 3877 res.oc = stops.ptr[2].color; 3878 res.midp = stops.ptr[1].offset; 3879 res.dimx = ex; 3880 res.dimy = ey; 3881 } else { 3882 // create image gradient 3883 uint[NVG_GRADIENT_SAMPLES] data = void; 3884 immutable float w = ex-sx; 3885 immutable float h = ey-sy; 3886 res.dimx = nvg__sqrtf(w*w+h*h); 3887 res.dimy = 1; //??? 3888 3889 res.angle = 3890 (/*nvg__absf(h) < 0.0001 ? 0 : 3891 nvg__absf(w) < 0.0001 ? 90.nvgDegrees :*/ 3892 nvg__atan2f(h/*ey-sy*/, w/*ex-sx*/)); 3893 3894 if (stops.length > 0) { 3895 auto s0 = NVGGradientStop(0, nvgRGBAf(0, 0, 0, 1)); 3896 auto s1 = NVGGradientStop(1, nvgRGBAf(1, 1, 1, 1)); 3897 if (stops.length > 64) stops = stops[0..64]; 3898 if (stops.length) { 3899 s0.color = stops[0].color; 3900 s1.color = stops[$-1].color; 3901 } 3902 gradientSpan(data.ptr, &s0, (stops.length ? stops.ptr : &s1)); 3903 foreach (immutable i; 0..stops.length-1) gradientSpan(data.ptr, stops.ptr+i, stops.ptr+i+1); 3904 gradientSpan(data.ptr, (stops.length ? stops.ptr+stops.length-1 : &s0), &s1); 3905 res.imgid = ctx.createImageRGBA(NVG_GRADIENT_SAMPLES, 1, data[]/*, NVGImageFlag.RepeatX, NVGImageFlag.RepeatY*/); 3906 } 3907 } 3908 return res; 3909 } 3910 3911 3912 // ////////////////////////////////////////////////////////////////////////// // 3913 // Scissoring 3914 3915 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform. 3916 /// Group: scissoring 3917 public void scissor (NVGContext ctx, in float x, in float y, float w, float h) nothrow @trusted @nogc { 3918 NVGstate* state = nvg__getState(ctx); 3919 3920 w = nvg__max(0.0f, w); 3921 h = nvg__max(0.0f, h); 3922 3923 state.scissor.xform.identity; 3924 state.scissor.xform.mat.ptr[4] = x+w*0.5f; 3925 state.scissor.xform.mat.ptr[5] = y+h*0.5f; 3926 //nvgTransformMultiply(state.scissor.xform[], state.xform[]); 3927 state.scissor.xform.mul(state.xform); 3928 3929 state.scissor.extent.ptr[0] = w*0.5f; 3930 state.scissor.extent.ptr[1] = h*0.5f; 3931 } 3932 3933 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform. 3934 /// Arguments: [x, y, w, h]* 3935 /// Group: scissoring 3936 public void scissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 3937 enum ArgC = 4; 3938 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [scissor] call"); 3939 if (args.length < ArgC) return; 3940 NVGstate* state = nvg__getState(ctx); 3941 const(float)* aptr = args.ptr; 3942 foreach (immutable idx; 0..args.length/ArgC) { 3943 immutable x = *aptr++; 3944 immutable y = *aptr++; 3945 immutable w = nvg__max(0.0f, *aptr++); 3946 immutable h = nvg__max(0.0f, *aptr++); 3947 3948 state.scissor.xform.identity; 3949 state.scissor.xform.mat.ptr[4] = x+w*0.5f; 3950 state.scissor.xform.mat.ptr[5] = y+h*0.5f; 3951 //nvgTransformMultiply(state.scissor.xform[], state.xform[]); 3952 state.scissor.xform.mul(state.xform); 3953 3954 state.scissor.extent.ptr[0] = w*0.5f; 3955 state.scissor.extent.ptr[1] = h*0.5f; 3956 } 3957 } 3958 3959 void nvg__isectRects (float* dst, float ax, float ay, float aw, float ah, float bx, float by, float bw, float bh) nothrow @trusted @nogc { 3960 immutable float minx = nvg__max(ax, bx); 3961 immutable float miny = nvg__max(ay, by); 3962 immutable float maxx = nvg__min(ax+aw, bx+bw); 3963 immutable float maxy = nvg__min(ay+ah, by+bh); 3964 dst[0] = minx; 3965 dst[1] = miny; 3966 dst[2] = nvg__max(0.0f, maxx-minx); 3967 dst[3] = nvg__max(0.0f, maxy-miny); 3968 } 3969 3970 /** Intersects current scissor rectangle with the specified rectangle. 3971 * The scissor rectangle is transformed by the current transform. 3972 * Note: in case the rotation of previous scissor rect differs from 3973 * the current one, the intersection will be done between the specified 3974 * rectangle and the previous scissor rectangle transformed in the current 3975 * transform space. The resulting shape is always rectangle. 3976 * 3977 * Group: scissoring 3978 */ 3979 public void intersectScissor (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc { 3980 NVGstate* state = nvg__getState(ctx); 3981 3982 // If no previous scissor has been set, set the scissor as current scissor. 3983 if (state.scissor.extent.ptr[0] < 0) { 3984 ctx.scissor(x, y, w, h); 3985 return; 3986 } 3987 3988 NVGMatrix pxform = void; 3989 NVGMatrix invxorm = void; 3990 float[4] rect = void; 3991 3992 // Transform the current scissor rect into current transform space. 3993 // If there is difference in rotation, this will be approximation. 3994 //memcpy(pxform.mat.ptr, state.scissor.xform.ptr, float.sizeof*6); 3995 pxform = state.scissor.xform; 3996 immutable float ex = state.scissor.extent.ptr[0]; 3997 immutable float ey = state.scissor.extent.ptr[1]; 3998 //nvgTransformInverse(invxorm[], state.xform[]); 3999 invxorm = state.xform.inverted; 4000 //nvgTransformMultiply(pxform[], invxorm[]); 4001 pxform.mul(invxorm); 4002 immutable float tex = ex*nvg__absf(pxform.mat.ptr[0])+ey*nvg__absf(pxform.mat.ptr[2]); 4003 immutable float tey = ex*nvg__absf(pxform.mat.ptr[1])+ey*nvg__absf(pxform.mat.ptr[3]); 4004 4005 // Intersect rects. 4006 nvg__isectRects(rect.ptr, pxform.mat.ptr[4]-tex, pxform.mat.ptr[5]-tey, tex*2, tey*2, x, y, w, h); 4007 4008 //ctx.scissor(rect.ptr[0], rect.ptr[1], rect.ptr[2], rect.ptr[3]); 4009 ctx.scissor(rect.ptr[0..4]); 4010 } 4011 4012 /** Intersects current scissor rectangle with the specified rectangle. 4013 * The scissor rectangle is transformed by the current transform. 4014 * Note: in case the rotation of previous scissor rect differs from 4015 * the current one, the intersection will be done between the specified 4016 * rectangle and the previous scissor rectangle transformed in the current 4017 * transform space. The resulting shape is always rectangle. 4018 * 4019 * Arguments: [x, y, w, h]* 4020 * 4021 * Group: scissoring 4022 */ 4023 public void intersectScissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 4024 enum ArgC = 4; 4025 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [intersectScissor] call"); 4026 if (args.length < ArgC) return; 4027 const(float)* aptr = args.ptr; 4028 foreach (immutable idx; 0..args.length/ArgC) { 4029 immutable x = *aptr++; 4030 immutable y = *aptr++; 4031 immutable w = *aptr++; 4032 immutable h = *aptr++; 4033 ctx.intersectScissor(x, y, w, h); 4034 } 4035 } 4036 4037 /// Reset and disables scissoring. 4038 /// Group: scissoring 4039 public void resetScissor (NVGContext ctx) nothrow @trusted @nogc { 4040 NVGstate* state = nvg__getState(ctx); 4041 state.scissor.xform.mat[] = 0.0f; 4042 state.scissor.extent[] = -1.0f; 4043 } 4044 4045 4046 // ////////////////////////////////////////////////////////////////////////// // 4047 // Render-Time Affine Transformations 4048 4049 /// Sets GPU affine transformatin matrix. Don't do scaling or skewing here. 4050 /// This matrix won't be saved/restored with context state save/restore operations, as it is not a part of that state. 4051 /// Group: gpu_affine 4052 public void affineGPU() (NVGContext ctx, const scope auto ref NVGMatrix mat) nothrow @trusted @nogc { 4053 ctx.gpuAffine = mat; 4054 ctx.params.renderSetAffine(ctx.params.userPtr, ctx.gpuAffine); 4055 } 4056 4057 /// Get current GPU affine transformatin matrix. 4058 /// Group: gpu_affine 4059 public NVGMatrix affineGPU (NVGContext ctx) nothrow @safe @nogc { 4060 pragma(inline, true); 4061 return ctx.gpuAffine; 4062 } 4063 4064 /// "Untransform" point using current GPU affine matrix. 4065 /// Group: gpu_affine 4066 public void gpuUntransformPoint (NVGContext ctx, float *dx, float *dy, in float x, in float y) nothrow @safe @nogc { 4067 if (ctx.gpuAffine.isIdentity) { 4068 if (dx !is null) *dx = x; 4069 if (dy !is null) *dy = y; 4070 } else { 4071 // inverse GPU transformation 4072 NVGMatrix igpu = ctx.gpuAffine.inverted; 4073 igpu.point(dx, dy, x, y); 4074 } 4075 } 4076 4077 4078 // ////////////////////////////////////////////////////////////////////////// // 4079 // rasterization (tesselation) code 4080 4081 int nvg__ptEquals (float x1, float y1, float x2, float y2, float tol) pure nothrow @safe @nogc { 4082 //pragma(inline, true); 4083 immutable float dx = x2-x1; 4084 immutable float dy = y2-y1; 4085 return dx*dx+dy*dy < tol*tol; 4086 } 4087 4088 float nvg__distPtSeg (float x, float y, float px, float py, float qx, float qy) pure nothrow @safe @nogc { 4089 immutable float pqx = qx-px; 4090 immutable float pqy = qy-py; 4091 float dx = x-px; 4092 float dy = y-py; 4093 immutable float d = pqx*pqx+pqy*pqy; 4094 float t = pqx*dx+pqy*dy; 4095 if (d > 0) t /= d; 4096 if (t < 0) t = 0; else if (t > 1) t = 1; 4097 dx = px+t*pqx-x; 4098 dy = py+t*pqy-y; 4099 return dx*dx+dy*dy; 4100 } 4101 4102 void nvg__appendCommands(bool useCommand=true) (NVGContext ctx, Command acmd, const(float)[] vals...) nothrow @trusted @nogc { 4103 int nvals = cast(int)vals.length; 4104 static if (useCommand) { 4105 enum addon = 1; 4106 } else { 4107 enum addon = 0; 4108 if (nvals == 0) return; // nothing to do 4109 } 4110 4111 NVGstate* state = nvg__getState(ctx); 4112 4113 if (ctx.ncommands+nvals+addon > ctx.ccommands) { 4114 //int ccommands = ctx.ncommands+nvals+ctx.ccommands/2; 4115 int ccommands = ((ctx.ncommands+(nvals+addon))|0xfff)+1; 4116 float* commands = cast(float*)realloc(ctx.commands, float.sizeof*ccommands); 4117 if (commands is null) assert(0, "NanoVega: out of memory"); 4118 ctx.commands = commands; 4119 ctx.ccommands = ccommands; 4120 assert(ctx.ncommands+(nvals+addon) <= ctx.ccommands); 4121 } 4122 4123 static if (!useCommand) acmd = cast(Command)vals.ptr[0]; 4124 4125 if (acmd != Command.Close && acmd != Command.Winding) { 4126 //assert(nvals+addon >= 3); 4127 ctx.commandx = vals.ptr[nvals-2]; 4128 ctx.commandy = vals.ptr[nvals-1]; 4129 } 4130 4131 // copy commands 4132 float* vp = ctx.commands+ctx.ncommands; 4133 static if (useCommand) { 4134 vp[0] = cast(float)acmd; 4135 if (nvals > 0) memcpy(vp+1, vals.ptr, nvals*float.sizeof); 4136 } else { 4137 memcpy(vp, vals.ptr, nvals*float.sizeof); 4138 } 4139 ctx.ncommands += nvals+addon; 4140 4141 // transform commands 4142 int i = nvals+addon; 4143 while (i > 0) { 4144 int nlen = 1; 4145 final switch (cast(Command)(*vp)) { 4146 case Command.MoveTo: 4147 case Command.LineTo: 4148 assert(i >= 3); 4149 state.xform.point(vp+1, vp+2, vp[1], vp[2]); 4150 nlen = 3; 4151 break; 4152 case Command.BezierTo: 4153 assert(i >= 7); 4154 state.xform.point(vp+1, vp+2, vp[1], vp[2]); 4155 state.xform.point(vp+3, vp+4, vp[3], vp[4]); 4156 state.xform.point(vp+5, vp+6, vp[5], vp[6]); 4157 nlen = 7; 4158 break; 4159 case Command.Close: 4160 nlen = 1; 4161 break; 4162 case Command.Winding: 4163 nlen = 2; 4164 break; 4165 } 4166 assert(nlen > 0 && nlen <= i); 4167 i -= nlen; 4168 vp += nlen; 4169 } 4170 } 4171 4172 void nvg__clearPathCache (NVGContext ctx) nothrow @trusted @nogc { 4173 // no need to clear paths, as data is not copied there 4174 //foreach (ref p; ctx.cache.paths[0..ctx.cache.npaths]) p.clear(); 4175 ctx.cache.npoints = 0; 4176 ctx.cache.npaths = 0; 4177 ctx.cache.fillReady = ctx.cache.strokeReady = false; 4178 ctx.cache.clipmode = NVGClipMode.None; 4179 } 4180 4181 NVGpath* nvg__lastPath (NVGContext ctx) nothrow @trusted @nogc { 4182 return (ctx.cache.npaths > 0 ? &ctx.cache.paths[ctx.cache.npaths-1] : null); 4183 } 4184 4185 void nvg__addPath (NVGContext ctx) nothrow @trusted @nogc { 4186 import core.stdc.stdlib : realloc; 4187 import core.stdc.string : memset; 4188 4189 if (ctx.cache.npaths+1 > ctx.cache.cpaths) { 4190 int cpaths = ctx.cache.npaths+1+ctx.cache.cpaths/2; 4191 NVGpath* paths = cast(NVGpath*)realloc(ctx.cache.paths, NVGpath.sizeof*cpaths); 4192 if (paths is null) assert(0, "NanoVega: out of memory"); 4193 ctx.cache.paths = paths; 4194 ctx.cache.cpaths = cpaths; 4195 } 4196 4197 NVGpath* path = &ctx.cache.paths[ctx.cache.npaths++]; 4198 memset(path, 0, NVGpath.sizeof); 4199 path.first = ctx.cache.npoints; 4200 path.mWinding = NVGWinding.CCW; 4201 } 4202 4203 NVGpoint* nvg__lastPoint (NVGContext ctx) nothrow @trusted @nogc { 4204 return (ctx.cache.npoints > 0 ? &ctx.cache.points[ctx.cache.npoints-1] : null); 4205 } 4206 4207 void nvg__addPoint (NVGContext ctx, float x, float y, int flags) nothrow @trusted @nogc { 4208 NVGpath* path = nvg__lastPath(ctx); 4209 if (path is null) return; 4210 4211 if (path.count > 0 && ctx.cache.npoints > 0) { 4212 NVGpoint* pt = nvg__lastPoint(ctx); 4213 if (nvg__ptEquals(pt.x, pt.y, x, y, ctx.distTol)) { 4214 pt.flags |= flags; 4215 return; 4216 } 4217 } 4218 4219 if (ctx.cache.npoints+1 > ctx.cache.cpoints) { 4220 int cpoints = ctx.cache.npoints+1+ctx.cache.cpoints/2; 4221 NVGpoint* points = cast(NVGpoint*)realloc(ctx.cache.points, NVGpoint.sizeof*cpoints); 4222 if (points is null) return; 4223 ctx.cache.points = points; 4224 ctx.cache.cpoints = cpoints; 4225 } 4226 4227 NVGpoint* pt = &ctx.cache.points[ctx.cache.npoints]; 4228 memset(pt, 0, (*pt).sizeof); 4229 pt.x = x; 4230 pt.y = y; 4231 pt.flags = cast(ubyte)flags; 4232 4233 ++ctx.cache.npoints; 4234 ++path.count; 4235 } 4236 4237 void nvg__closePath (NVGContext ctx) nothrow @trusted @nogc { 4238 NVGpath* path = nvg__lastPath(ctx); 4239 if (path is null) return; 4240 path.closed = true; 4241 } 4242 4243 void nvg__pathWinding (NVGContext ctx, NVGWinding winding) nothrow @trusted @nogc { 4244 NVGpath* path = nvg__lastPath(ctx); 4245 if (path is null) return; 4246 path.mWinding = winding; 4247 } 4248 4249 float nvg__getAverageScale() (const scope auto ref NVGMatrix t) /*pure*/ nothrow @trusted @nogc { 4250 immutable float sx = nvg__sqrtf(t.mat.ptr[0]*t.mat.ptr[0]+t.mat.ptr[2]*t.mat.ptr[2]); 4251 immutable float sy = nvg__sqrtf(t.mat.ptr[1]*t.mat.ptr[1]+t.mat.ptr[3]*t.mat.ptr[3]); 4252 return (sx+sy)*0.5f; 4253 } 4254 4255 NVGVertex* nvg__allocTempVerts (NVGContext ctx, int nverts) nothrow @trusted @nogc { 4256 if (nverts > ctx.cache.cverts) { 4257 int cverts = (nverts+0xff)&~0xff; // Round up to prevent allocations when things change just slightly. 4258 NVGVertex* verts = cast(NVGVertex*)realloc(ctx.cache.verts, NVGVertex.sizeof*cverts); 4259 if (verts is null) return null; 4260 ctx.cache.verts = verts; 4261 ctx.cache.cverts = cverts; 4262 } 4263 4264 return ctx.cache.verts; 4265 } 4266 4267 float nvg__triarea2 (float ax, float ay, float bx, float by, float cx, float cy) pure nothrow @safe @nogc { 4268 immutable float abx = bx-ax; 4269 immutable float aby = by-ay; 4270 immutable float acx = cx-ax; 4271 immutable float acy = cy-ay; 4272 return acx*aby-abx*acy; 4273 } 4274 4275 float nvg__polyArea (NVGpoint* pts, int npts) nothrow @trusted @nogc { 4276 float area = 0; 4277 foreach (int i; 2..npts) { 4278 NVGpoint* a = &pts[0]; 4279 NVGpoint* b = &pts[i-1]; 4280 NVGpoint* c = &pts[i]; 4281 area += nvg__triarea2(a.x, a.y, b.x, b.y, c.x, c.y); 4282 } 4283 return area*0.5f; 4284 } 4285 4286 void nvg__polyReverse (NVGpoint* pts, int npts) nothrow @trusted @nogc { 4287 NVGpoint tmp = void; 4288 int i = 0, j = npts-1; 4289 while (i < j) { 4290 tmp = pts[i]; 4291 pts[i] = pts[j]; 4292 pts[j] = tmp; 4293 ++i; 4294 --j; 4295 } 4296 } 4297 4298 void nvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc { 4299 vtx.x = x; 4300 vtx.y = y; 4301 vtx.u = u; 4302 vtx.v = v; 4303 } 4304 4305 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 { 4306 if (level > 10) return; 4307 4308 // check for collinear points, and use AFD tesselator on such curves (it is WAY faster for this case) 4309 /* 4310 if (level == 0 && ctx.tesselatortype == NVGTesselation.Combined) { 4311 static bool collinear (in float v0x, in float v0y, in float v1x, in float v1y, in float v2x, in float v2y) nothrow @trusted @nogc { 4312 immutable float cz = (v1x-v0x)*(v2y-v0y)-(v2x-v0x)*(v1y-v0y); 4313 return (nvg__absf(cz*cz) <= 0.01f); // arbitrary number, seems to work ok with NanoSVG output 4314 } 4315 if (collinear(x1, y1, x2, y2, x3, y3) && collinear(x2, y2, x3, y3, x3, y4)) { 4316 //{ import core.stdc.stdio; printf("AFD fallback!\n"); } 4317 ctx.nvg__tesselateBezierAFD(x1, y1, x2, y2, x3, y3, x4, y4, type); 4318 return; 4319 } 4320 } 4321 */ 4322 4323 immutable float x12 = (x1+x2)*0.5f; 4324 immutable float y12 = (y1+y2)*0.5f; 4325 immutable float x23 = (x2+x3)*0.5f; 4326 immutable float y23 = (y2+y3)*0.5f; 4327 immutable float x34 = (x3+x4)*0.5f; 4328 immutable float y34 = (y3+y4)*0.5f; 4329 immutable float x123 = (x12+x23)*0.5f; 4330 immutable float y123 = (y12+y23)*0.5f; 4331 4332 immutable float dx = x4-x1; 4333 immutable float dy = y4-y1; 4334 immutable float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx)); 4335 immutable float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx)); 4336 4337 if ((d2+d3)*(d2+d3) < ctx.tessTol*(dx*dx+dy*dy)) { 4338 nvg__addPoint(ctx, x4, y4, type); 4339 return; 4340 } 4341 4342 immutable float x234 = (x23+x34)*0.5f; 4343 immutable float y234 = (y23+y34)*0.5f; 4344 immutable float x1234 = (x123+x234)*0.5f; 4345 immutable float y1234 = (y123+y234)*0.5f; 4346 4347 // "taxicab" / "manhattan" check for flat curves 4348 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) { 4349 nvg__addPoint(ctx, x1234, y1234, type); 4350 return; 4351 } 4352 4353 nvg__tesselateBezier(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0); 4354 nvg__tesselateBezier(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type); 4355 } 4356 4357 // based on the ideas and code of Maxim Shemanarev. Rest in Peace, bro! 4358 // see http://www.antigrain.com/research/adaptive_bezier/index.html 4359 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 { 4360 enum CollinearEPS = 0.00000001f; // 0.00001f; 4361 enum AngleTolEPS = 0.01f; 4362 4363 static float distSquared (in float x1, in float y1, in float x2, in float y2) pure nothrow @safe @nogc { 4364 pragma(inline, true); 4365 immutable float dx = x2-x1; 4366 immutable float dy = y2-y1; 4367 return dx*dx+dy*dy; 4368 } 4369 4370 if (level == 0) { 4371 nvg__addPoint(ctx, x1, y1, 0); 4372 nvg__tesselateBezierMcSeem(ctx, x1, y1, x2, y2, x3, y3, x4, y4, 1, type); 4373 nvg__addPoint(ctx, x4, y4, type); 4374 return; 4375 } 4376 4377 if (level >= 32) return; // recurse limit; practically, it should be never reached, but... 4378 4379 // calculate all the mid-points of the line segments 4380 immutable float x12 = (x1+x2)*0.5f; 4381 immutable float y12 = (y1+y2)*0.5f; 4382 immutable float x23 = (x2+x3)*0.5f; 4383 immutable float y23 = (y2+y3)*0.5f; 4384 immutable float x34 = (x3+x4)*0.5f; 4385 immutable float y34 = (y3+y4)*0.5f; 4386 immutable float x123 = (x12+x23)*0.5f; 4387 immutable float y123 = (y12+y23)*0.5f; 4388 immutable float x234 = (x23+x34)*0.5f; 4389 immutable float y234 = (y23+y34)*0.5f; 4390 immutable float x1234 = (x123+x234)*0.5f; 4391 immutable float y1234 = (y123+y234)*0.5f; 4392 4393 // try to approximate the full cubic curve by a single straight line 4394 immutable float dx = x4-x1; 4395 immutable float dy = y4-y1; 4396 4397 float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx)); 4398 float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx)); 4399 //immutable float da1, da2, k; 4400 4401 final switch ((cast(int)(d2 > CollinearEPS)<<1)+cast(int)(d3 > CollinearEPS)) { 4402 case 0: 4403 // all collinear or p1 == p4 4404 float k = dx*dx+dy*dy; 4405 if (k == 0) { 4406 d2 = distSquared(x1, y1, x2, y2); 4407 d3 = distSquared(x4, y4, x3, y3); 4408 } else { 4409 k = 1.0f/k; 4410 float da1 = x2-x1; 4411 float da2 = y2-y1; 4412 d2 = k*(da1*dx+da2*dy); 4413 da1 = x3-x1; 4414 da2 = y3-y1; 4415 d3 = k*(da1*dx+da2*dy); 4416 if (d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1) { 4417 // Simple collinear case, 1---2---3---4 4418 // We can leave just two endpoints 4419 return; 4420 } 4421 if (d2 <= 0) d2 = distSquared(x2, y2, x1, y1); 4422 else if (d2 >= 1) d2 = distSquared(x2, y2, x4, y4); 4423 else d2 = distSquared(x2, y2, x1+d2*dx, y1+d2*dy); 4424 4425 if (d3 <= 0) d3 = distSquared(x3, y3, x1, y1); 4426 else if (d3 >= 1) d3 = distSquared(x3, y3, x4, y4); 4427 else d3 = distSquared(x3, y3, x1+d3*dx, y1+d3*dy); 4428 } 4429 if (d2 > d3) { 4430 if (d2 < ctx.tessTol) { 4431 nvg__addPoint(ctx, x2, y2, type); 4432 return; 4433 } 4434 } if (d3 < ctx.tessTol) { 4435 nvg__addPoint(ctx, x3, y3, type); 4436 return; 4437 } 4438 break; 4439 case 1: 4440 // p1,p2,p4 are collinear, p3 is significant 4441 if (d3*d3 <= ctx.tessTol*(dx*dx+dy*dy)) { 4442 if (ctx.angleTol < AngleTolEPS) { 4443 nvg__addPoint(ctx, x23, y23, type); 4444 return; 4445 } else { 4446 // angle condition 4447 float da1 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-nvg__atan2f(y3-y2, x3-x2)); 4448 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1; 4449 if (da1 < ctx.angleTol) { 4450 nvg__addPoint(ctx, x2, y2, type); 4451 nvg__addPoint(ctx, x3, y3, type); 4452 return; 4453 } 4454 if (ctx.cuspLimit != 0.0) { 4455 if (da1 > ctx.cuspLimit) { 4456 nvg__addPoint(ctx, x3, y3, type); 4457 return; 4458 } 4459 } 4460 } 4461 } 4462 break; 4463 case 2: 4464 // p1,p3,p4 are collinear, p2 is significant 4465 if (d2*d2 <= ctx.tessTol*(dx*dx+dy*dy)) { 4466 if (ctx.angleTol < AngleTolEPS) { 4467 nvg__addPoint(ctx, x23, y23, type); 4468 return; 4469 } else { 4470 // angle condition 4471 float da1 = nvg__absf(nvg__atan2f(y3-y2, x3-x2)-nvg__atan2f(y2-y1, x2-x1)); 4472 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1; 4473 if (da1 < ctx.angleTol) { 4474 nvg__addPoint(ctx, x2, y2, type); 4475 nvg__addPoint(ctx, x3, y3, type); 4476 return; 4477 } 4478 if (ctx.cuspLimit != 0.0) { 4479 if (da1 > ctx.cuspLimit) { 4480 nvg__addPoint(ctx, x2, y2, type); 4481 return; 4482 } 4483 } 4484 } 4485 } 4486 break; 4487 case 3: 4488 // regular case 4489 if ((d2+d3)*(d2+d3) <= ctx.tessTol*(dx*dx+dy*dy)) { 4490 // if the curvature doesn't exceed the distance tolerance value, we tend to finish subdivisions 4491 if (ctx.angleTol < AngleTolEPS) { 4492 nvg__addPoint(ctx, x23, y23, type); 4493 return; 4494 } else { 4495 // angle and cusp condition 4496 immutable float k = nvg__atan2f(y3-y2, x3-x2); 4497 float da1 = nvg__absf(k-nvg__atan2f(y2-y1, x2-x1)); 4498 float da2 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-k); 4499 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1; 4500 if (da2 >= NVG_PI) da2 = 2*NVG_PI-da2; 4501 if (da1+da2 < ctx.angleTol) { 4502 // finally we can stop the recursion 4503 nvg__addPoint(ctx, x23, y23, type); 4504 return; 4505 } 4506 if (ctx.cuspLimit != 0.0) { 4507 if (da1 > ctx.cuspLimit) { 4508 nvg__addPoint(ctx, x2, y2, type); 4509 return; 4510 } 4511 if (da2 > ctx.cuspLimit) { 4512 nvg__addPoint(ctx, x3, y3, type); 4513 return; 4514 } 4515 } 4516 } 4517 } 4518 break; 4519 } 4520 4521 // continue subdivision 4522 nvg__tesselateBezierMcSeem(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0); 4523 nvg__tesselateBezierMcSeem(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type); 4524 } 4525 4526 4527 // Adaptive forward differencing for bezier tesselation. 4528 // See Lien, Sheue-Ling, Michael Shantz, and Vaughan Pratt. 4529 // "Adaptive forward differencing for rendering curves and surfaces." 4530 // ACM SIGGRAPH Computer Graphics. Vol. 21. No. 4. ACM, 1987. 4531 // original code by Taylor Holliday <taylor@audulus.com> 4532 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 { 4533 enum AFD_ONE = (1<<10); 4534 4535 // power basis 4536 immutable float ax = -x1+3*x2-3*x3+x4; 4537 immutable float ay = -y1+3*y2-3*y3+y4; 4538 immutable float bx = 3*x1-6*x2+3*x3; 4539 immutable float by = 3*y1-6*y2+3*y3; 4540 immutable float cx = -3*x1+3*x2; 4541 immutable float cy = -3*y1+3*y2; 4542 4543 // Transform to forward difference basis (stepsize 1) 4544 float px = x1; 4545 float py = y1; 4546 float dx = ax+bx+cx; 4547 float dy = ay+by+cy; 4548 float ddx = 6*ax+2*bx; 4549 float ddy = 6*ay+2*by; 4550 float dddx = 6*ax; 4551 float dddy = 6*ay; 4552 4553 //printf("dx: %f, dy: %f\n", dx, dy); 4554 //printf("ddx: %f, ddy: %f\n", ddx, ddy); 4555 //printf("dddx: %f, dddy: %f\n", dddx, dddy); 4556 4557 int t = 0; 4558 int dt = AFD_ONE; 4559 4560 immutable float tol = ctx.tessTol*4; 4561 4562 while (t < AFD_ONE) { 4563 // Flatness measure. 4564 float d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy; 4565 4566 // printf("d: %f, th: %f\n", d, th); 4567 4568 // Go to higher resolution if we're moving a lot or overshooting the end. 4569 while ((d > tol && dt > 1) || (t+dt > AFD_ONE)) { 4570 // printf("up\n"); 4571 4572 // Apply L to the curve. Increase curve resolution. 4573 dx = 0.5f*dx-(1.0f/8.0f)*ddx+(1.0f/16.0f)*dddx; 4574 dy = 0.5f*dy-(1.0f/8.0f)*ddy+(1.0f/16.0f)*dddy; 4575 ddx = (1.0f/4.0f)*ddx-(1.0f/8.0f)*dddx; 4576 ddy = (1.0f/4.0f)*ddy-(1.0f/8.0f)*dddy; 4577 dddx = (1.0f/8.0f)*dddx; 4578 dddy = (1.0f/8.0f)*dddy; 4579 4580 // Half the stepsize. 4581 dt >>= 1; 4582 4583 // Recompute d 4584 d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy; 4585 } 4586 4587 // Go to lower resolution if we're really flat 4588 // and we aren't going to overshoot the end. 4589 // XXX: tol/32 is just a guess for when we are too flat. 4590 while ((d > 0 && d < tol/32.0f && dt < AFD_ONE) && (t+2*dt <= AFD_ONE)) { 4591 // printf("down\n"); 4592 4593 // Apply L^(-1) to the curve. Decrease curve resolution. 4594 dx = 2*dx+ddx; 4595 dy = 2*dy+ddy; 4596 ddx = 4*ddx+4*dddx; 4597 ddy = 4*ddy+4*dddy; 4598 dddx = 8*dddx; 4599 dddy = 8*dddy; 4600 4601 // Double the stepsize. 4602 dt <<= 1; 4603 4604 // Recompute d 4605 d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy; 4606 } 4607 4608 // Forward differencing. 4609 px += dx; 4610 py += dy; 4611 dx += ddx; 4612 dy += ddy; 4613 ddx += dddx; 4614 ddy += dddy; 4615 4616 // Output a point. 4617 nvg__addPoint(ctx, px, py, (t > 0 ? type : 0)); 4618 4619 // Advance along the curve. 4620 t += dt; 4621 4622 // Ensure we don't overshoot. 4623 assert(t <= AFD_ONE); 4624 } 4625 } 4626 4627 4628 void nvg__dashLastPath (NVGContext ctx) nothrow @trusted @nogc { 4629 import core.stdc.stdlib : realloc; 4630 import core.stdc.string : memcpy; 4631 4632 NVGpathCache* cache = ctx.cache; 4633 if (cache.npaths == 0) return; 4634 4635 NVGpath* path = nvg__lastPath(ctx); 4636 if (path is null) return; 4637 4638 NVGstate* state = nvg__getState(ctx); 4639 if (!state.dasherActive) return; 4640 4641 static NVGpoint* pts = null; 4642 static uint ptsCount = 0; 4643 static uint ptsSize = 0; 4644 4645 if (path.count < 2) return; // just in case 4646 4647 // copy path points (reserve one point for closed pathes) 4648 if (ptsSize < path.count+1) { 4649 ptsSize = cast(uint)(path.count+1); 4650 pts = cast(NVGpoint*)realloc(pts, ptsSize*NVGpoint.sizeof); 4651 if (pts is null) assert(0, "NanoVega: out of memory"); 4652 } 4653 ptsCount = cast(uint)path.count; 4654 memcpy(pts, &cache.points[path.first], ptsCount*NVGpoint.sizeof); 4655 // add closing point for closed pathes 4656 if (path.closed && !nvg__ptEquals(pts[0].x, pts[0].y, pts[ptsCount-1].x, pts[ptsCount-1].y, ctx.distTol)) { 4657 pts[ptsCount++] = pts[0]; 4658 } 4659 4660 // remove last path (with its points) 4661 --cache.npaths; 4662 cache.npoints -= path.count; 4663 4664 // add stroked pathes 4665 const(float)* dashes = state.dashes.ptr; 4666 immutable uint dashCount = state.dashCount; 4667 float currDashStart = 0; 4668 uint currDashIdx = 0; 4669 immutable bool firstIsGap = state.firstDashIsGap; 4670 4671 // calculate lengthes 4672 { 4673 NVGpoint* v1 = &pts[0]; 4674 NVGpoint* v2 = &pts[1]; 4675 foreach (immutable _; 0..ptsCount) { 4676 float dx = v2.x-v1.x; 4677 float dy = v2.y-v1.y; 4678 v1.len = nvg__normalize(&dx, &dy); 4679 v1 = v2++; 4680 } 4681 } 4682 4683 void calcDashStart (float ds) { 4684 if (ds < 0) { 4685 ds = ds%state.totalDashLen; 4686 while (ds < 0) ds += state.totalDashLen; 4687 } 4688 currDashIdx = 0; 4689 currDashStart = 0; 4690 while (ds > 0) { 4691 if (ds > dashes[currDashIdx]) { 4692 ds -= dashes[currDashIdx]; 4693 ++currDashIdx; 4694 currDashStart = 0; 4695 if (currDashIdx >= dashCount) currDashIdx = 0; 4696 } else { 4697 currDashStart = ds; 4698 ds = 0; 4699 } 4700 } 4701 } 4702 4703 calcDashStart(state.dashStart); 4704 4705 uint srcPointIdx = 1; 4706 const(NVGpoint)* v1 = &pts[0]; 4707 const(NVGpoint)* v2 = &pts[1]; 4708 float currRest = v1.len; 4709 nvg__addPath(ctx); 4710 nvg__addPoint(ctx, v1.x, v1.y, PointFlag.Corner); 4711 4712 void fixLastPoint () { 4713 auto lpt = nvg__lastPath(ctx); 4714 if (lpt !is null && lpt.count > 0) { 4715 // fix last point 4716 if (auto lps = nvg__lastPoint(ctx)) lps.flags = PointFlag.Corner; 4717 // fix first point 4718 NVGpathCache* cache = ctx.cache; 4719 cache.points[lpt.first].flags = PointFlag.Corner; 4720 } 4721 } 4722 4723 for (;;) { 4724 immutable float dlen = dashes[currDashIdx]; 4725 if (dlen == 0) { 4726 ++currDashIdx; 4727 if (currDashIdx >= dashCount) currDashIdx = 0; 4728 continue; 4729 } 4730 immutable float dashRest = dlen-currDashStart; 4731 if ((currDashIdx&1) != firstIsGap) { 4732 // this is "moveto" command, so create new path 4733 fixLastPoint(); 4734 nvg__addPath(ctx); 4735 } 4736 if (currRest > dashRest) { 4737 currRest -= dashRest; 4738 ++currDashIdx; 4739 if (currDashIdx >= dashCount) currDashIdx = 0; 4740 currDashStart = 0; 4741 nvg__addPoint(ctx, 4742 v2.x-(v2.x-v1.x)*currRest/v1.len, 4743 v2.y-(v2.y-v1.y)*currRest/v1.len, 4744 PointFlag.Corner 4745 ); 4746 } else { 4747 currDashStart += currRest; 4748 nvg__addPoint(ctx, v2.x, v2.y, v1.flags); //k8:fix flags here? 4749 ++srcPointIdx; 4750 v1 = v2; 4751 currRest = v1.len; 4752 if (srcPointIdx >= ptsCount) break; 4753 v2 = &pts[srcPointIdx]; 4754 } 4755 } 4756 fixLastPoint(); 4757 } 4758 4759 4760 version(nanovg_bench_flatten) import iv.timer : Timer; 4761 4762 void nvg__flattenPaths(bool asStroke) (NVGContext ctx) nothrow @trusted @nogc { 4763 version(nanovg_bench_flatten) { 4764 Timer timer; 4765 char[128] tmbuf; 4766 int bzcount; 4767 } 4768 NVGpathCache* cache = ctx.cache; 4769 NVGstate* state = nvg__getState(ctx); 4770 4771 // check if we already did flattening 4772 static if (asStroke) { 4773 if (state.dashCount >= 2) { 4774 if (cache.npaths > 0 && state.lastFlattenDashCount == state.dashCount) return; // already flattened 4775 state.dasherActive = true; 4776 state.lastFlattenDashCount = state.dashCount; 4777 } else { 4778 if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened 4779 state.dasherActive = false; 4780 state.lastFlattenDashCount = 0; 4781 } 4782 } else { 4783 if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened 4784 state.lastFlattenDashCount = 0; // so next stroke flattening will redo it 4785 state.dasherActive = false; 4786 } 4787 4788 // clear path cache 4789 cache.npaths = 0; 4790 cache.npoints = 0; 4791 4792 // flatten 4793 version(nanovg_bench_flatten) timer.restart(); 4794 int i = 0; 4795 while (i < ctx.ncommands) { 4796 final switch (cast(Command)ctx.commands[i]) { 4797 case Command.MoveTo: 4798 //assert(i+3 <= ctx.ncommands); 4799 static if (asStroke) { 4800 if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx); 4801 } 4802 nvg__addPath(ctx); 4803 const p = &ctx.commands[i+1]; 4804 nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner); 4805 i += 3; 4806 break; 4807 case Command.LineTo: 4808 //assert(i+3 <= ctx.ncommands); 4809 const p = &ctx.commands[i+1]; 4810 nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner); 4811 i += 3; 4812 break; 4813 case Command.BezierTo: 4814 //assert(i+7 <= ctx.ncommands); 4815 const last = nvg__lastPoint(ctx); 4816 if (last !is null) { 4817 const cp1 = &ctx.commands[i+1]; 4818 const cp2 = &ctx.commands[i+3]; 4819 const p = &ctx.commands[i+5]; 4820 if (ctx.tesselatortype == NVGTesselation.DeCasteljau) { 4821 nvg__tesselateBezier(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner); 4822 } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) { 4823 nvg__tesselateBezierMcSeem(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner); 4824 } else { 4825 nvg__tesselateBezierAFD(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], PointFlag.Corner); 4826 } 4827 version(nanovg_bench_flatten) ++bzcount; 4828 } 4829 i += 7; 4830 break; 4831 case Command.Close: 4832 //assert(i+1 <= ctx.ncommands); 4833 nvg__closePath(ctx); 4834 i += 1; 4835 break; 4836 case Command.Winding: 4837 //assert(i+2 <= ctx.ncommands); 4838 nvg__pathWinding(ctx, cast(NVGWinding)ctx.commands[i+1]); 4839 i += 2; 4840 break; 4841 } 4842 } 4843 static if (asStroke) { 4844 if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx); 4845 } 4846 version(nanovg_bench_flatten) {{ 4847 timer.stop(); 4848 auto xb = timer.toBuffer(tmbuf[]); 4849 import core.stdc.stdio : printf; 4850 printf("flattening time: [%.*s] (%d beziers)\n", cast(uint)xb.length, xb.ptr, bzcount); 4851 }} 4852 4853 cache.bounds.ptr[0] = cache.bounds.ptr[1] = float.max; 4854 cache.bounds.ptr[2] = cache.bounds.ptr[3] = -float.max; 4855 4856 // calculate the direction and length of line segments 4857 version(nanovg_bench_flatten) timer.restart(); 4858 foreach (int j; 0..cache.npaths) { 4859 NVGpath* path = &cache.paths[j]; 4860 NVGpoint* pts = &cache.points[path.first]; 4861 4862 // if the first and last points are the same, remove the last, mark as closed path 4863 NVGpoint* p0 = &pts[path.count-1]; 4864 NVGpoint* p1 = &pts[0]; 4865 if (nvg__ptEquals(p0.x, p0.y, p1.x, p1.y, ctx.distTol)) { 4866 --path.count; 4867 p0 = &pts[path.count-1]; 4868 path.closed = true; 4869 } 4870 4871 // enforce winding 4872 if (path.count > 2) { 4873 immutable float area = nvg__polyArea(pts, path.count); 4874 if (path.mWinding == NVGWinding.CCW && area < 0.0f) nvg__polyReverse(pts, path.count); 4875 if (path.mWinding == NVGWinding.CW && area > 0.0f) nvg__polyReverse(pts, path.count); 4876 } 4877 4878 foreach (immutable _; 0..path.count) { 4879 // calculate segment direction and length 4880 p0.dx = p1.x-p0.x; 4881 p0.dy = p1.y-p0.y; 4882 p0.len = nvg__normalize(&p0.dx, &p0.dy); 4883 // update bounds 4884 cache.bounds.ptr[0] = nvg__min(cache.bounds.ptr[0], p0.x); 4885 cache.bounds.ptr[1] = nvg__min(cache.bounds.ptr[1], p0.y); 4886 cache.bounds.ptr[2] = nvg__max(cache.bounds.ptr[2], p0.x); 4887 cache.bounds.ptr[3] = nvg__max(cache.bounds.ptr[3], p0.y); 4888 // advance 4889 p0 = p1++; 4890 } 4891 } 4892 version(nanovg_bench_flatten) {{ 4893 timer.stop(); 4894 auto xb = timer.toBuffer(tmbuf[]); 4895 import core.stdc.stdio : printf; 4896 printf("segment calculation time: [%.*s]\n", cast(uint)xb.length, xb.ptr); 4897 }} 4898 } 4899 4900 int nvg__curveDivs (float r, float arc, float tol) nothrow @trusted @nogc { 4901 immutable float da = nvg__acosf(r/(r+tol))*2.0f; 4902 return nvg__max(2, cast(int)nvg__ceilf(arc/da)); 4903 } 4904 4905 void nvg__chooseBevel (int bevel, NVGpoint* p0, NVGpoint* p1, float w, float* x0, float* y0, float* x1, float* y1) nothrow @trusted @nogc { 4906 if (bevel) { 4907 *x0 = p1.x+p0.dy*w; 4908 *y0 = p1.y-p0.dx*w; 4909 *x1 = p1.x+p1.dy*w; 4910 *y1 = p1.y-p1.dx*w; 4911 } else { 4912 *x0 = p1.x+p1.dmx*w; 4913 *y0 = p1.y+p1.dmy*w; 4914 *x1 = p1.x+p1.dmx*w; 4915 *y1 = p1.y+p1.dmy*w; 4916 } 4917 } 4918 4919 NVGVertex* nvg__roundJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, int ncap, float fringe) nothrow @trusted @nogc { 4920 float dlx0 = p0.dy; 4921 float dly0 = -p0.dx; 4922 float dlx1 = p1.dy; 4923 float dly1 = -p1.dx; 4924 //NVG_NOTUSED(fringe); 4925 4926 if (p1.flags&PointFlag.Left) { 4927 float lx0 = void, ly0 = void, lx1 = void, ly1 = void; 4928 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1); 4929 immutable float a0 = nvg__atan2f(-dly0, -dlx0); 4930 float a1 = nvg__atan2f(-dly1, -dlx1); 4931 if (a1 > a0) a1 -= NVG_PI*2; 4932 4933 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4934 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4935 4936 int n = nvg__clamp(cast(int)nvg__ceilf(((a0-a1)/NVG_PI)*ncap), 2, ncap); 4937 for (int i = 0; i < n; ++i) { 4938 float u = i/cast(float)(n-1); 4939 float a = a0+u*(a1-a0); 4940 float rx = p1.x+nvg__cosf(a)*rw; 4941 float ry = p1.y+nvg__sinf(a)*rw; 4942 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4943 nvg__vset(dst, rx, ry, ru, 1); ++dst; 4944 } 4945 4946 nvg__vset(dst, lx1, ly1, lu, 1); ++dst; 4947 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4948 4949 } else { 4950 float rx0 = void, ry0 = void, rx1 = void, ry1 = void; 4951 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1); 4952 immutable float a0 = nvg__atan2f(dly0, dlx0); 4953 float a1 = nvg__atan2f(dly1, dlx1); 4954 if (a1 < a0) a1 += NVG_PI*2; 4955 4956 nvg__vset(dst, p1.x+dlx0*rw, p1.y+dly0*rw, lu, 1); ++dst; 4957 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4958 4959 int n = nvg__clamp(cast(int)nvg__ceilf(((a1-a0)/NVG_PI)*ncap), 2, ncap); 4960 for (int i = 0; i < n; i++) { 4961 float u = i/cast(float)(n-1); 4962 float a = a0+u*(a1-a0); 4963 float lx = p1.x+nvg__cosf(a)*lw; 4964 float ly = p1.y+nvg__sinf(a)*lw; 4965 nvg__vset(dst, lx, ly, lu, 1); ++dst; 4966 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4967 } 4968 4969 nvg__vset(dst, p1.x+dlx1*rw, p1.y+dly1*rw, lu, 1); ++dst; 4970 nvg__vset(dst, rx1, ry1, ru, 1); ++dst; 4971 4972 } 4973 return dst; 4974 } 4975 4976 NVGVertex* nvg__bevelJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, float fringe) nothrow @trusted @nogc { 4977 float rx0, ry0, rx1, ry1; 4978 float lx0, ly0, lx1, ly1; 4979 float dlx0 = p0.dy; 4980 float dly0 = -p0.dx; 4981 float dlx1 = p1.dy; 4982 float dly1 = -p1.dx; 4983 //NVG_NOTUSED(fringe); 4984 4985 if (p1.flags&PointFlag.Left) { 4986 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1); 4987 4988 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4989 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4990 4991 if (p1.flags&PointFlag.Bevel) { 4992 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4993 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4994 4995 nvg__vset(dst, lx1, ly1, lu, 1); ++dst; 4996 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4997 } else { 4998 rx0 = p1.x-p1.dmx*rw; 4999 ry0 = p1.y-p1.dmy*rw; 5000 5001 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 5002 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 5003 5004 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 5005 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 5006 5007 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 5008 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 5009 } 5010 5011 nvg__vset(dst, lx1, ly1, lu, 1); ++dst; 5012 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 5013 5014 } else { 5015 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1); 5016 5017 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst; 5018 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 5019 5020 if (p1.flags&PointFlag.Bevel) { 5021 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst; 5022 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 5023 5024 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst; 5025 nvg__vset(dst, rx1, ry1, ru, 1); ++dst; 5026 } else { 5027 lx0 = p1.x+p1.dmx*lw; 5028 ly0 = p1.y+p1.dmy*lw; 5029 5030 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst; 5031 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 5032 5033 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 5034 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 5035 5036 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst; 5037 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 5038 } 5039 5040 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst; 5041 nvg__vset(dst, rx1, ry1, ru, 1); ++dst; 5042 } 5043 5044 return dst; 5045 } 5046 5047 NVGVertex* nvg__buttCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc { 5048 immutable float px = p.x-dx*d; 5049 immutable float py = p.y-dy*d; 5050 immutable float dlx = dy; 5051 immutable float dly = -dx; 5052 nvg__vset(dst, px+dlx*w-dx*aa, py+dly*w-dy*aa, 0, 0); ++dst; 5053 nvg__vset(dst, px-dlx*w-dx*aa, py-dly*w-dy*aa, 1, 0); ++dst; 5054 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5055 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5056 return dst; 5057 } 5058 5059 NVGVertex* nvg__buttCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc { 5060 immutable float px = p.x+dx*d; 5061 immutable float py = p.y+dy*d; 5062 immutable float dlx = dy; 5063 immutable float dly = -dx; 5064 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5065 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5066 nvg__vset(dst, px+dlx*w+dx*aa, py+dly*w+dy*aa, 0, 0); ++dst; 5067 nvg__vset(dst, px-dlx*w+dx*aa, py-dly*w+dy*aa, 1, 0); ++dst; 5068 return dst; 5069 } 5070 5071 NVGVertex* nvg__roundCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc { 5072 immutable float px = p.x; 5073 immutable float py = p.y; 5074 immutable float dlx = dy; 5075 immutable float dly = -dx; 5076 //NVG_NOTUSED(aa); 5077 immutable float ncpf = cast(float)(ncap-1); 5078 foreach (int i; 0..ncap) { 5079 float a = i/*/cast(float)(ncap-1)*//ncpf*NVG_PI; 5080 float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w; 5081 nvg__vset(dst, px-dlx*ax-dx*ay, py-dly*ax-dy*ay, 0, 1); ++dst; 5082 nvg__vset(dst, px, py, 0.5f, 1); ++dst; 5083 } 5084 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5085 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5086 return dst; 5087 } 5088 5089 NVGVertex* nvg__roundCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc { 5090 immutable float px = p.x; 5091 immutable float py = p.y; 5092 immutable float dlx = dy; 5093 immutable float dly = -dx; 5094 //NVG_NOTUSED(aa); 5095 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5096 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5097 immutable float ncpf = cast(float)(ncap-1); 5098 foreach (int i; 0..ncap) { 5099 float a = i/*cast(float)(ncap-1)*//ncpf*NVG_PI; 5100 float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w; 5101 nvg__vset(dst, px, py, 0.5f, 1); ++dst; 5102 nvg__vset(dst, px-dlx*ax+dx*ay, py-dly*ax+dy*ay, 0, 1); ++dst; 5103 } 5104 return dst; 5105 } 5106 5107 void nvg__calculateJoins (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc { 5108 NVGpathCache* cache = ctx.cache; 5109 float iw = 0.0f; 5110 5111 if (w > 0.0f) iw = 1.0f/w; 5112 5113 // Calculate which joins needs extra vertices to append, and gather vertex count. 5114 foreach (int i; 0..cache.npaths) { 5115 NVGpath* path = &cache.paths[i]; 5116 NVGpoint* pts = &cache.points[path.first]; 5117 NVGpoint* p0 = &pts[path.count-1]; 5118 NVGpoint* p1 = &pts[0]; 5119 int nleft = 0; 5120 5121 path.nbevel = 0; 5122 5123 foreach (int j; 0..path.count) { 5124 //float dlx0, dly0, dlx1, dly1, dmr2, cross, limit; 5125 immutable float dlx0 = p0.dy; 5126 immutable float dly0 = -p0.dx; 5127 immutable float dlx1 = p1.dy; 5128 immutable float dly1 = -p1.dx; 5129 // Calculate extrusions 5130 p1.dmx = (dlx0+dlx1)*0.5f; 5131 p1.dmy = (dly0+dly1)*0.5f; 5132 immutable float dmr2 = p1.dmx*p1.dmx+p1.dmy*p1.dmy; 5133 if (dmr2 > 0.000001f) { 5134 float scale = 1.0f/dmr2; 5135 if (scale > 600.0f) scale = 600.0f; 5136 p1.dmx *= scale; 5137 p1.dmy *= scale; 5138 } 5139 5140 // Clear flags, but keep the corner. 5141 p1.flags = (p1.flags&PointFlag.Corner) ? PointFlag.Corner : 0; 5142 5143 // Keep track of left turns. 5144 immutable float cross = p1.dx*p0.dy-p0.dx*p1.dy; 5145 if (cross > 0.0f) { 5146 nleft++; 5147 p1.flags |= PointFlag.Left; 5148 } 5149 5150 // Calculate if we should use bevel or miter for inner join. 5151 immutable float limit = nvg__max(1.01f, nvg__min(p0.len, p1.len)*iw); 5152 if ((dmr2*limit*limit) < 1.0f) p1.flags |= PointFlag.InnerBevelPR; 5153 5154 // Check to see if the corner needs to be beveled. 5155 if (p1.flags&PointFlag.Corner) { 5156 if ((dmr2*miterLimit*miterLimit) < 1.0f || lineJoin == NVGLineCap.Bevel || lineJoin == NVGLineCap.Round) { 5157 p1.flags |= PointFlag.Bevel; 5158 } 5159 } 5160 5161 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) path.nbevel++; 5162 5163 p0 = p1++; 5164 } 5165 5166 path.convex = (nleft == path.count); 5167 } 5168 } 5169 5170 void nvg__expandStroke (NVGContext ctx, float w, int lineCap, int lineJoin, float miterLimit) nothrow @trusted @nogc { 5171 NVGpathCache* cache = ctx.cache; 5172 immutable float aa = ctx.fringeWidth; 5173 int ncap = nvg__curveDivs(w, NVG_PI, ctx.tessTol); // Calculate divisions per half circle. 5174 5175 nvg__calculateJoins(ctx, w, lineJoin, miterLimit); 5176 5177 // Calculate max vertex usage. 5178 int cverts = 0; 5179 foreach (int i; 0..cache.npaths) { 5180 NVGpath* path = &cache.paths[i]; 5181 immutable bool loop = path.closed; 5182 if (lineJoin == NVGLineCap.Round) { 5183 cverts += (path.count+path.nbevel*(ncap+2)+1)*2; // plus one for loop 5184 } else { 5185 cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop 5186 } 5187 if (!loop) { 5188 // space for caps 5189 if (lineCap == NVGLineCap.Round) { 5190 cverts += (ncap*2+2)*2; 5191 } else { 5192 cverts += (3+3)*2; 5193 } 5194 } 5195 } 5196 5197 NVGVertex* verts = nvg__allocTempVerts(ctx, cverts); 5198 if (verts is null) return; 5199 5200 foreach (int i; 0..cache.npaths) { 5201 NVGpath* path = &cache.paths[i]; 5202 NVGpoint* pts = &cache.points[path.first]; 5203 NVGpoint* p0; 5204 NVGpoint* p1; 5205 int s, e; 5206 5207 path.fill = null; 5208 path.nfill = 0; 5209 5210 // Calculate fringe or stroke 5211 immutable bool loop = path.closed; 5212 NVGVertex* dst = verts; 5213 path.stroke = dst; 5214 5215 if (loop) { 5216 // Looping 5217 p0 = &pts[path.count-1]; 5218 p1 = &pts[0]; 5219 s = 0; 5220 e = path.count; 5221 } else { 5222 // Add cap 5223 p0 = &pts[0]; 5224 p1 = &pts[1]; 5225 s = 1; 5226 e = path.count-1; 5227 } 5228 5229 if (!loop) { 5230 // Add cap 5231 float dx = p1.x-p0.x; 5232 float dy = p1.y-p0.y; 5233 nvg__normalize(&dx, &dy); 5234 if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapStart(dst, p0, dx, dy, w, -aa*0.5f, aa); 5235 else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapStart(dst, p0, dx, dy, w, w-aa, aa); 5236 else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapStart(dst, p0, dx, dy, w, ncap, aa); 5237 } 5238 5239 foreach (int j; s..e) { 5240 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) { 5241 if (lineJoin == NVGLineCap.Round) { 5242 dst = nvg__roundJoin(dst, p0, p1, w, w, 0, 1, ncap, aa); 5243 } else { 5244 dst = nvg__bevelJoin(dst, p0, p1, w, w, 0, 1, aa); 5245 } 5246 } else { 5247 nvg__vset(dst, p1.x+(p1.dmx*w), p1.y+(p1.dmy*w), 0, 1); ++dst; 5248 nvg__vset(dst, p1.x-(p1.dmx*w), p1.y-(p1.dmy*w), 1, 1); ++dst; 5249 } 5250 p0 = p1++; 5251 } 5252 5253 if (loop) { 5254 // Loop it 5255 nvg__vset(dst, verts[0].x, verts[0].y, 0, 1); ++dst; 5256 nvg__vset(dst, verts[1].x, verts[1].y, 1, 1); ++dst; 5257 } else { 5258 // Add cap 5259 float dx = p1.x-p0.x; 5260 float dy = p1.y-p0.y; 5261 nvg__normalize(&dx, &dy); 5262 if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, -aa*0.5f, aa); 5263 else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, w-aa, aa); 5264 else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapEnd(dst, p1, dx, dy, w, ncap, aa); 5265 } 5266 5267 path.nstroke = cast(int)(dst-verts); 5268 5269 verts = dst; 5270 } 5271 } 5272 5273 void nvg__expandFill (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc { 5274 NVGpathCache* cache = ctx.cache; 5275 immutable float aa = ctx.fringeWidth; 5276 bool fringe = (w > 0.0f); 5277 5278 nvg__calculateJoins(ctx, w, lineJoin, miterLimit); 5279 5280 // Calculate max vertex usage. 5281 int cverts = 0; 5282 foreach (int i; 0..cache.npaths) { 5283 NVGpath* path = &cache.paths[i]; 5284 cverts += path.count+path.nbevel+1; 5285 if (fringe) cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop 5286 } 5287 5288 NVGVertex* verts = nvg__allocTempVerts(ctx, cverts); 5289 if (verts is null) return; 5290 5291 bool convex = (cache.npaths == 1 && cache.paths[0].convex); 5292 5293 foreach (int i; 0..cache.npaths) { 5294 NVGpath* path = &cache.paths[i]; 5295 NVGpoint* pts = &cache.points[path.first]; 5296 5297 // Calculate shape vertices. 5298 immutable float woff = 0.5f*aa; 5299 NVGVertex* dst = verts; 5300 path.fill = dst; 5301 5302 if (fringe) { 5303 // Looping 5304 NVGpoint* p0 = &pts[path.count-1]; 5305 NVGpoint* p1 = &pts[0]; 5306 foreach (int j; 0..path.count) { 5307 if (p1.flags&PointFlag.Bevel) { 5308 immutable float dlx0 = p0.dy; 5309 immutable float dly0 = -p0.dx; 5310 immutable float dlx1 = p1.dy; 5311 immutable float dly1 = -p1.dx; 5312 if (p1.flags&PointFlag.Left) { 5313 immutable float lx = p1.x+p1.dmx*woff; 5314 immutable float ly = p1.y+p1.dmy*woff; 5315 nvg__vset(dst, lx, ly, 0.5f, 1); ++dst; 5316 } else { 5317 immutable float lx0 = p1.x+dlx0*woff; 5318 immutable float ly0 = p1.y+dly0*woff; 5319 immutable float lx1 = p1.x+dlx1*woff; 5320 immutable float ly1 = p1.y+dly1*woff; 5321 nvg__vset(dst, lx0, ly0, 0.5f, 1); ++dst; 5322 nvg__vset(dst, lx1, ly1, 0.5f, 1); ++dst; 5323 } 5324 } else { 5325 nvg__vset(dst, p1.x+(p1.dmx*woff), p1.y+(p1.dmy*woff), 0.5f, 1); ++dst; 5326 } 5327 p0 = p1++; 5328 } 5329 } else { 5330 foreach (int j; 0..path.count) { 5331 nvg__vset(dst, pts[j].x, pts[j].y, 0.5f, 1); 5332 ++dst; 5333 } 5334 } 5335 5336 path.nfill = cast(int)(dst-verts); 5337 verts = dst; 5338 5339 // Calculate fringe 5340 if (fringe) { 5341 float lw = w+woff; 5342 immutable float rw = w-woff; 5343 float lu = 0; 5344 immutable float ru = 1; 5345 dst = verts; 5346 path.stroke = dst; 5347 5348 // Create only half a fringe for convex shapes so that 5349 // the shape can be rendered without stenciling. 5350 if (convex) { 5351 lw = woff; // This should generate the same vertex as fill inset above. 5352 lu = 0.5f; // Set outline fade at middle. 5353 } 5354 5355 // Looping 5356 NVGpoint* p0 = &pts[path.count-1]; 5357 NVGpoint* p1 = &pts[0]; 5358 5359 foreach (int j; 0..path.count) { 5360 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) { 5361 dst = nvg__bevelJoin(dst, p0, p1, lw, rw, lu, ru, ctx.fringeWidth); 5362 } else { 5363 nvg__vset(dst, p1.x+(p1.dmx*lw), p1.y+(p1.dmy*lw), lu, 1); ++dst; 5364 nvg__vset(dst, p1.x-(p1.dmx*rw), p1.y-(p1.dmy*rw), ru, 1); ++dst; 5365 } 5366 p0 = p1++; 5367 } 5368 5369 // Loop it 5370 nvg__vset(dst, verts[0].x, verts[0].y, lu, 1); ++dst; 5371 nvg__vset(dst, verts[1].x, verts[1].y, ru, 1); ++dst; 5372 5373 path.nstroke = cast(int)(dst-verts); 5374 verts = dst; 5375 } else { 5376 path.stroke = null; 5377 path.nstroke = 0; 5378 } 5379 } 5380 } 5381 5382 5383 // ////////////////////////////////////////////////////////////////////////// // 5384 // Paths 5385 5386 /// Clears the current path and sub-paths. 5387 /// Group: paths 5388 @scriptable 5389 public void beginPath (NVGContext ctx) nothrow @trusted @nogc { 5390 ctx.ncommands = 0; 5391 ctx.pathPickRegistered &= NVGPickKind.All; // reset "registered" flags 5392 nvg__clearPathCache(ctx); 5393 } 5394 5395 public alias newPath = beginPath; /// Ditto. 5396 5397 /// Starts new sub-path with specified point as first point. 5398 /// Group: paths 5399 @scriptable 5400 public void moveTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 5401 nvg__appendCommands(ctx, Command.MoveTo, x, y); 5402 } 5403 5404 /// Starts new sub-path with specified point as first point. 5405 /// Arguments: [x, y]* 5406 /// Group: paths 5407 public void moveTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5408 enum ArgC = 2; 5409 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [moveTo] call"); 5410 if (args.length < ArgC) return; 5411 nvg__appendCommands(ctx, Command.MoveTo, args[$-2..$]); 5412 } 5413 5414 /// Adds line segment from the last point in the path to the specified point. 5415 /// Group: paths 5416 @scriptable 5417 public void lineTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 5418 nvg__appendCommands(ctx, Command.LineTo, x, y); 5419 } 5420 5421 /// Adds line segment from the last point in the path to the specified point. 5422 /// Arguments: [x, y]* 5423 /// Group: paths 5424 public void lineTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5425 enum ArgC = 2; 5426 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [lineTo] call"); 5427 if (args.length < ArgC) return; 5428 foreach (immutable idx; 0..args.length/ArgC) { 5429 nvg__appendCommands(ctx, Command.LineTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]); 5430 } 5431 } 5432 5433 /// Adds cubic bezier segment from last point in the path via two control points to the specified point. 5434 /// Group: paths 5435 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 { 5436 nvg__appendCommands(ctx, Command.BezierTo, c1x, c1y, c2x, c2y, x, y); 5437 } 5438 5439 /// Adds cubic bezier segment from last point in the path via two control points to the specified point. 5440 /// Arguments: [c1x, c1y, c2x, c2y, x, y]* 5441 /// Group: paths 5442 public void bezierTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5443 enum ArgC = 6; 5444 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [bezierTo] call"); 5445 if (args.length < ArgC) return; 5446 foreach (immutable idx; 0..args.length/ArgC) { 5447 nvg__appendCommands(ctx, Command.BezierTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]); 5448 } 5449 } 5450 5451 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point. 5452 /// Group: paths 5453 public void quadTo (NVGContext ctx, in float cx, in float cy, in float x, in float y) nothrow @trusted @nogc { 5454 immutable float x0 = ctx.commandx; 5455 immutable float y0 = ctx.commandy; 5456 nvg__appendCommands(ctx, 5457 Command.BezierTo, 5458 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0), 5459 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y), 5460 x, y, 5461 ); 5462 } 5463 5464 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point. 5465 /// Arguments: [cx, cy, x, y]* 5466 /// Group: paths 5467 public void quadTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5468 enum ArgC = 4; 5469 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [quadTo] call"); 5470 if (args.length < ArgC) return; 5471 const(float)* aptr = args.ptr; 5472 foreach (immutable idx; 0..args.length/ArgC) { 5473 immutable float x0 = ctx.commandx; 5474 immutable float y0 = ctx.commandy; 5475 immutable float cx = *aptr++; 5476 immutable float cy = *aptr++; 5477 immutable float x = *aptr++; 5478 immutable float y = *aptr++; 5479 nvg__appendCommands(ctx, 5480 Command.BezierTo, 5481 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0), 5482 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y), 5483 x, y, 5484 ); 5485 } 5486 } 5487 5488 /// Adds an arc segment at the corner defined by the last path point, and two specified points. 5489 /// Group: paths 5490 public void arcTo (NVGContext ctx, in float x1, in float y1, in float x2, in float y2, in float radius) nothrow @trusted @nogc { 5491 if (ctx.ncommands == 0) return; 5492 5493 immutable float x0 = ctx.commandx; 5494 immutable float y0 = ctx.commandy; 5495 5496 // handle degenerate cases 5497 if (nvg__ptEquals(x0, y0, x1, y1, ctx.distTol) || 5498 nvg__ptEquals(x1, y1, x2, y2, ctx.distTol) || 5499 nvg__distPtSeg(x1, y1, x0, y0, x2, y2) < ctx.distTol*ctx.distTol || 5500 radius < ctx.distTol) 5501 { 5502 ctx.lineTo(x1, y1); 5503 return; 5504 } 5505 5506 // calculate tangential circle to lines (x0, y0)-(x1, y1) and (x1, y1)-(x2, y2) 5507 float dx0 = x0-x1; 5508 float dy0 = y0-y1; 5509 float dx1 = x2-x1; 5510 float dy1 = y2-y1; 5511 nvg__normalize(&dx0, &dy0); 5512 nvg__normalize(&dx1, &dy1); 5513 immutable float a = nvg__acosf(dx0*dx1+dy0*dy1); 5514 immutable float d = radius/nvg__tanf(a/2.0f); 5515 5516 //printf("a=%f° d=%f\n", a/NVG_PI*180.0f, d); 5517 5518 if (d > 10000.0f) { 5519 ctx.lineTo(x1, y1); 5520 return; 5521 } 5522 5523 float cx = void, cy = void, a0 = void, a1 = void; 5524 NVGWinding dir; 5525 if (nvg__cross(dx0, dy0, dx1, dy1) > 0.0f) { 5526 cx = x1+dx0*d+dy0*radius; 5527 cy = y1+dy0*d+-dx0*radius; 5528 a0 = nvg__atan2f(dx0, -dy0); 5529 a1 = nvg__atan2f(-dx1, dy1); 5530 dir = NVGWinding.CW; 5531 //printf("CW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f); 5532 } else { 5533 cx = x1+dx0*d+-dy0*radius; 5534 cy = y1+dy0*d+dx0*radius; 5535 a0 = nvg__atan2f(-dx0, dy0); 5536 a1 = nvg__atan2f(dx1, -dy1); 5537 dir = NVGWinding.CCW; 5538 //printf("CCW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f); 5539 } 5540 5541 ctx.arc(dir, cx, cy, radius, a0, a1); // first is line 5542 } 5543 5544 5545 /// Adds an arc segment at the corner defined by the last path point, and two specified points. 5546 /// Arguments: [x1, y1, x2, y2, radius]* 5547 /// Group: paths 5548 public void arcTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5549 enum ArgC = 5; 5550 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arcTo] call"); 5551 if (args.length < ArgC) return; 5552 if (ctx.ncommands == 0) return; 5553 const(float)* aptr = args.ptr; 5554 foreach (immutable idx; 0..args.length/ArgC) { 5555 immutable float x0 = ctx.commandx; 5556 immutable float y0 = ctx.commandy; 5557 immutable float x1 = *aptr++; 5558 immutable float y1 = *aptr++; 5559 immutable float x2 = *aptr++; 5560 immutable float y2 = *aptr++; 5561 immutable float radius = *aptr++; 5562 ctx.arcTo(x1, y1, x2, y2, radius); 5563 } 5564 } 5565 5566 /// Closes current sub-path with a line segment. 5567 /// Group: paths 5568 @scriptable 5569 public void closePath (NVGContext ctx) nothrow @trusted @nogc { 5570 nvg__appendCommands(ctx, Command.Close); 5571 } 5572 5573 /// Sets the current sub-path winding, see NVGWinding and NVGSolidity. 5574 /// Group: paths 5575 public void pathWinding (NVGContext ctx, NVGWinding dir) nothrow @trusted @nogc { 5576 nvg__appendCommands(ctx, Command.Winding, cast(float)dir); 5577 } 5578 5579 /// Ditto. 5580 public void pathWinding (NVGContext ctx, NVGSolidity dir) nothrow @trusted @nogc { 5581 nvg__appendCommands(ctx, Command.Winding, cast(float)dir); 5582 } 5583 5584 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r, 5585 * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW). 5586 * Angles are specified in radians. 5587 * 5588 * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo 5589 * 5590 * Group: paths 5591 */ 5592 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 { 5593 static assert(mode == "original" || mode == "move" || mode == "line"); 5594 5595 float[3+5*7+100] vals = void; 5596 //int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo); 5597 static if (mode == "original") { 5598 immutable int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo); 5599 } else static if (mode == "move") { 5600 enum move = Command.MoveTo; 5601 } else static if (mode == "line") { 5602 enum move = Command.LineTo; 5603 } else { 5604 static assert(0, "wtf?!"); 5605 } 5606 5607 // Clamp angles 5608 float da = a1-a0; 5609 if (dir == NVGWinding.CW) { 5610 if (nvg__absf(da) >= NVG_PI*2) { 5611 da = NVG_PI*2; 5612 } else { 5613 while (da < 0.0f) da += NVG_PI*2; 5614 } 5615 } else { 5616 if (nvg__absf(da) >= NVG_PI*2) { 5617 da = -NVG_PI*2; 5618 } else { 5619 while (da > 0.0f) da -= NVG_PI*2; 5620 } 5621 } 5622 5623 // Split arc into max 90 degree segments. 5624 immutable int ndivs = nvg__max(1, nvg__min(cast(int)(nvg__absf(da)/(NVG_PI*0.5f)+0.5f), 5)); 5625 immutable float hda = (da/cast(float)ndivs)/2.0f; 5626 float kappa = nvg__absf(4.0f/3.0f*(1.0f-nvg__cosf(hda))/nvg__sinf(hda)); 5627 5628 if (dir == NVGWinding.CCW) kappa = -kappa; 5629 5630 int nvals = 0; 5631 float px = 0, py = 0, ptanx = 0, ptany = 0; 5632 foreach (int i; 0..ndivs+1) { 5633 immutable float a = a0+da*(i/cast(float)ndivs); 5634 immutable float dx = nvg__cosf(a); 5635 immutable float dy = nvg__sinf(a); 5636 immutable float x = cx+dx*r; 5637 immutable float y = cy+dy*r; 5638 immutable float tanx = -dy*r*kappa; 5639 immutable float tany = dx*r*kappa; 5640 5641 if (i == 0) { 5642 if (vals.length-nvals < 3) { 5643 // flush 5644 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command 5645 nvals = 0; 5646 } 5647 vals.ptr[nvals++] = cast(float)move; 5648 vals.ptr[nvals++] = x; 5649 vals.ptr[nvals++] = y; 5650 } else { 5651 if (vals.length-nvals < 7) { 5652 // flush 5653 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command 5654 nvals = 0; 5655 } 5656 vals.ptr[nvals++] = Command.BezierTo; 5657 vals.ptr[nvals++] = px+ptanx; 5658 vals.ptr[nvals++] = py+ptany; 5659 vals.ptr[nvals++] = x-tanx; 5660 vals.ptr[nvals++] = y-tany; 5661 vals.ptr[nvals++] = x; 5662 vals.ptr[nvals++] = y; 5663 } 5664 px = x; 5665 py = y; 5666 ptanx = tanx; 5667 ptany = tany; 5668 } 5669 5670 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command 5671 } 5672 5673 5674 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r, 5675 * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW). 5676 * Angles are specified in radians. 5677 * 5678 * Arguments: [cx, cy, r, a0, a1]* 5679 * 5680 * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo 5681 * 5682 * Group: paths 5683 */ 5684 public void arc(string mode="original") (NVGContext ctx, NVGWinding dir, in float[] args) nothrow @trusted @nogc { 5685 static assert(mode == "original" || mode == "move" || mode == "line"); 5686 enum ArgC = 5; 5687 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arc] call"); 5688 if (args.length < ArgC) return; 5689 const(float)* aptr = args.ptr; 5690 foreach (immutable idx; 0..args.length/ArgC) { 5691 immutable cx = *aptr++; 5692 immutable cy = *aptr++; 5693 immutable r = *aptr++; 5694 immutable a0 = *aptr++; 5695 immutable a1 = *aptr++; 5696 ctx.arc!mode(dir, cx, cy, r, a0, a1); 5697 } 5698 } 5699 5700 /// Creates new rectangle shaped sub-path. 5701 /// Group: paths 5702 @scriptable 5703 public void rect (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc { 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 /// Creates new rectangle shaped sub-path. 5714 /// Arguments: [x, y, w, h]* 5715 /// Group: paths 5716 public void rect (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5717 enum ArgC = 4; 5718 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [rect] call"); 5719 if (args.length < ArgC) return; 5720 const(float)* aptr = args.ptr; 5721 foreach (immutable idx; 0..args.length/ArgC) { 5722 immutable x = *aptr++; 5723 immutable y = *aptr++; 5724 immutable w = *aptr++; 5725 immutable h = *aptr++; 5726 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5727 Command.MoveTo, x, y, 5728 Command.LineTo, x, y+h, 5729 Command.LineTo, x+w, y+h, 5730 Command.LineTo, x+w, y, 5731 Command.Close, 5732 ); 5733 } 5734 } 5735 5736 /// Creates new rounded rectangle shaped sub-path. 5737 /// Group: paths 5738 @scriptable 5739 public void roundedRect (NVGContext ctx, in float x, in float y, in float w, in float h, in float radius) nothrow @trusted @nogc { 5740 ctx.roundedRectVarying(x, y, w, h, radius, radius, radius, radius); 5741 } 5742 5743 /// Creates new rounded rectangle shaped sub-path. 5744 /// Arguments: [x, y, w, h, radius]* 5745 /// Group: paths 5746 public void roundedRect (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5747 enum ArgC = 5; 5748 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRect] call"); 5749 if (args.length < ArgC) return; 5750 const(float)* aptr = args.ptr; 5751 foreach (immutable idx; 0..args.length/ArgC) { 5752 immutable x = *aptr++; 5753 immutable y = *aptr++; 5754 immutable w = *aptr++; 5755 immutable h = *aptr++; 5756 immutable r = *aptr++; 5757 ctx.roundedRectVarying(x, y, w, h, r, r, r, r); 5758 } 5759 } 5760 5761 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it. 5762 /// Group: paths 5763 @scriptable 5764 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 { 5765 if (rw < 0.1f || rh < 0.1f) { 5766 rect(ctx, x, y, w, h); 5767 } else { 5768 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5769 Command.MoveTo, x+rw, y, 5770 Command.LineTo, x+w-rw, y, 5771 Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh, 5772 Command.LineTo, x+w, y+h-rh, 5773 Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h, 5774 Command.LineTo, x+rw, y+h, 5775 Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh, 5776 Command.LineTo, x, y+rh, 5777 Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y, 5778 Command.Close, 5779 ); 5780 } 5781 } 5782 5783 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it. 5784 /// Arguments: [x, y, w, h, rw, rh]* 5785 /// Group: paths 5786 public void roundedRectEllipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5787 enum ArgC = 6; 5788 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectEllipse] call"); 5789 if (args.length < ArgC) return; 5790 const(float)* aptr = args.ptr; 5791 foreach (immutable idx; 0..args.length/ArgC) { 5792 immutable x = *aptr++; 5793 immutable y = *aptr++; 5794 immutable w = *aptr++; 5795 immutable h = *aptr++; 5796 immutable rw = *aptr++; 5797 immutable rh = *aptr++; 5798 if (rw < 0.1f || rh < 0.1f) { 5799 rect(ctx, x, y, w, h); 5800 } else { 5801 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5802 Command.MoveTo, x+rw, y, 5803 Command.LineTo, x+w-rw, y, 5804 Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh, 5805 Command.LineTo, x+w, y+h-rh, 5806 Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h, 5807 Command.LineTo, x+rw, y+h, 5808 Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh, 5809 Command.LineTo, x, y+rh, 5810 Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y, 5811 Command.Close, 5812 ); 5813 } 5814 } 5815 } 5816 5817 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner. 5818 /// Group: paths 5819 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 { 5820 if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) { 5821 ctx.rect(x, y, w, h); 5822 } else { 5823 immutable float halfw = nvg__absf(w)*0.5f; 5824 immutable float halfh = nvg__absf(h)*0.5f; 5825 immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h); 5826 immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h); 5827 immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h); 5828 immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h); 5829 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5830 Command.MoveTo, x, y+ryTL, 5831 Command.LineTo, x, y+h-ryBL, 5832 Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h, 5833 Command.LineTo, x+w-rxBR, y+h, 5834 Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR, 5835 Command.LineTo, x+w, y+ryTR, 5836 Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y, 5837 Command.LineTo, x+rxTL, y, 5838 Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL, 5839 Command.Close, 5840 ); 5841 } 5842 } 5843 5844 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner. 5845 /// Arguments: [x, y, w, h, radTopLeft, radTopRight, radBottomRight, radBottomLeft]* 5846 /// Group: paths 5847 public void roundedRectVarying (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5848 enum ArgC = 8; 5849 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectVarying] call"); 5850 if (args.length < ArgC) return; 5851 const(float)* aptr = args.ptr; 5852 foreach (immutable idx; 0..args.length/ArgC) { 5853 immutable x = *aptr++; 5854 immutable y = *aptr++; 5855 immutable w = *aptr++; 5856 immutable h = *aptr++; 5857 immutable radTopLeft = *aptr++; 5858 immutable radTopRight = *aptr++; 5859 immutable radBottomRight = *aptr++; 5860 immutable radBottomLeft = *aptr++; 5861 if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) { 5862 ctx.rect(x, y, w, h); 5863 } else { 5864 immutable float halfw = nvg__absf(w)*0.5f; 5865 immutable float halfh = nvg__absf(h)*0.5f; 5866 immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h); 5867 immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h); 5868 immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h); 5869 immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h); 5870 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5871 Command.MoveTo, x, y+ryTL, 5872 Command.LineTo, x, y+h-ryBL, 5873 Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h, 5874 Command.LineTo, x+w-rxBR, y+h, 5875 Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR, 5876 Command.LineTo, x+w, y+ryTR, 5877 Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y, 5878 Command.LineTo, x+rxTL, y, 5879 Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL, 5880 Command.Close, 5881 ); 5882 } 5883 } 5884 } 5885 5886 /// Creates new ellipse shaped sub-path. 5887 /// Group: paths 5888 public void ellipse (NVGContext ctx, in float cx, in float cy, in float rx, in float ry) nothrow @trusted @nogc { 5889 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5890 Command.MoveTo, cx-rx, cy, 5891 Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry, 5892 Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy, 5893 Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry, 5894 Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy, 5895 Command.Close, 5896 ); 5897 } 5898 5899 /// Creates new ellipse shaped sub-path. 5900 /// Arguments: [cx, cy, rx, ry]* 5901 /// Group: paths 5902 public void ellipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5903 enum ArgC = 4; 5904 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [ellipse] call"); 5905 if (args.length < ArgC) return; 5906 const(float)* aptr = args.ptr; 5907 foreach (immutable idx; 0..args.length/ArgC) { 5908 immutable cx = *aptr++; 5909 immutable cy = *aptr++; 5910 immutable rx = *aptr++; 5911 immutable ry = *aptr++; 5912 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5913 Command.MoveTo, cx-rx, cy, 5914 Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry, 5915 Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy, 5916 Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry, 5917 Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy, 5918 Command.Close, 5919 ); 5920 } 5921 } 5922 5923 /// Creates new circle shaped sub-path. 5924 /// Group: paths 5925 public void circle (NVGContext ctx, in float cx, in float cy, in float r) nothrow @trusted @nogc { 5926 ctx.ellipse(cx, cy, r, r); 5927 } 5928 5929 /// Creates new circle shaped sub-path. 5930 /// Arguments: [cx, cy, r]* 5931 /// Group: paths 5932 public void circle (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5933 enum ArgC = 3; 5934 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [circle] call"); 5935 if (args.length < ArgC) return; 5936 const(float)* aptr = args.ptr; 5937 foreach (immutable idx; 0..args.length/ArgC) { 5938 immutable cx = *aptr++; 5939 immutable cy = *aptr++; 5940 immutable r = *aptr++; 5941 ctx.ellipse(cx, cy, r, r); 5942 } 5943 } 5944 5945 // Debug function to dump cached path data. 5946 debug public void debugDumpPathCache (NVGContext ctx) nothrow @trusted @nogc { 5947 import core.stdc.stdio : printf; 5948 const(NVGpath)* path; 5949 printf("Dumping %d cached paths\n", ctx.cache.npaths); 5950 for (int i = 0; i < ctx.cache.npaths; ++i) { 5951 path = &ctx.cache.paths[i]; 5952 printf("-Path %d\n", i); 5953 if (path.nfill) { 5954 printf("-fill: %d\n", path.nfill); 5955 for (int j = 0; j < path.nfill; ++j) printf("%f\t%f\n", path.fill[j].x, path.fill[j].y); 5956 } 5957 if (path.nstroke) { 5958 printf("-stroke: %d\n", path.nstroke); 5959 for (int j = 0; j < path.nstroke; ++j) printf("%f\t%f\n", path.stroke[j].x, path.stroke[j].y); 5960 } 5961 } 5962 } 5963 5964 // Flatten path, prepare it for fill operation. 5965 void nvg__prepareFill (NVGContext ctx) nothrow @trusted @nogc { 5966 NVGpathCache* cache = ctx.cache; 5967 NVGstate* state = nvg__getState(ctx); 5968 5969 nvg__flattenPaths!false(ctx); 5970 5971 if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) { 5972 nvg__expandFill(ctx, ctx.fringeWidth, NVGLineCap.Miter, 2.4f); 5973 } else { 5974 nvg__expandFill(ctx, 0.0f, NVGLineCap.Miter, 2.4f); 5975 } 5976 5977 cache.evenOddMode = state.evenOddMode; 5978 cache.fringeWidth = ctx.fringeWidth; 5979 cache.fillReady = true; 5980 cache.strokeReady = false; 5981 cache.clipmode = NVGClipMode.None; 5982 } 5983 5984 // Flatten path, prepare it for stroke operation. 5985 void nvg__prepareStroke (NVGContext ctx) nothrow @trusted @nogc { 5986 NVGstate* state = nvg__getState(ctx); 5987 NVGpathCache* cache = ctx.cache; 5988 5989 nvg__flattenPaths!true(ctx); 5990 5991 immutable float scale = nvg__getAverageScale(state.xform); 5992 float strokeWidth = nvg__clamp(state.strokeWidth*scale, 0.0f, 200.0f); 5993 5994 if (strokeWidth < ctx.fringeWidth) { 5995 // If the stroke width is less than pixel size, use alpha to emulate coverage. 5996 // Since coverage is area, scale by alpha*alpha. 5997 immutable float alpha = nvg__clamp(strokeWidth/ctx.fringeWidth, 0.0f, 1.0f); 5998 cache.strokeAlphaMul = alpha*alpha; 5999 strokeWidth = ctx.fringeWidth; 6000 } else { 6001 cache.strokeAlphaMul = 1.0f; 6002 } 6003 cache.strokeWidth = strokeWidth; 6004 6005 if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) { 6006 nvg__expandStroke(ctx, strokeWidth*0.5f+ctx.fringeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit); 6007 } else { 6008 nvg__expandStroke(ctx, strokeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit); 6009 } 6010 6011 cache.fringeWidth = ctx.fringeWidth; 6012 cache.fillReady = false; 6013 cache.strokeReady = true; 6014 cache.clipmode = NVGClipMode.None; 6015 } 6016 6017 /// Fills the current path with current fill style. 6018 /// Group: paths 6019 @scriptable 6020 public void fill (NVGContext ctx) nothrow @trusted @nogc { 6021 NVGstate* state = nvg__getState(ctx); 6022 6023 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) { 6024 ctx.pathPickRegistered |= NVGPickKind.Fill<<16; 6025 ctx.currFillHitId = ctx.pathPickId; 6026 } 6027 6028 nvg__prepareFill(ctx); 6029 6030 // apply global alpha 6031 NVGPaint fillPaint = state.fill; 6032 fillPaint.innerColor.a *= state.alpha; 6033 fillPaint.middleColor.a *= state.alpha; 6034 fillPaint.outerColor.a *= state.alpha; 6035 6036 ctx.appendCurrentPathToCache(ctx.recset, state.fill); 6037 6038 if (ctx.recblockdraw) return; 6039 6040 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); 6041 6042 // count triangles 6043 foreach (int i; 0..ctx.cache.npaths) { 6044 NVGpath* path = &ctx.cache.paths[i]; 6045 ctx.fillTriCount += path.nfill-2; 6046 ctx.fillTriCount += path.nstroke-2; 6047 ctx.drawCallCount += 2; 6048 } 6049 } 6050 6051 /// Fills the current path with current stroke style. 6052 /// Group: paths 6053 @scriptable 6054 public void stroke (NVGContext ctx) nothrow @trusted @nogc { 6055 NVGstate* state = nvg__getState(ctx); 6056 6057 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) { 6058 ctx.pathPickRegistered |= NVGPickKind.Stroke<<16; 6059 ctx.currStrokeHitId = ctx.pathPickId; 6060 } 6061 6062 nvg__prepareStroke(ctx); 6063 6064 NVGpathCache* cache = ctx.cache; 6065 6066 NVGPaint strokePaint = state.stroke; 6067 strokePaint.innerColor.a *= cache.strokeAlphaMul; 6068 strokePaint.middleColor.a *= cache.strokeAlphaMul; 6069 strokePaint.outerColor.a *= cache.strokeAlphaMul; 6070 6071 // apply global alpha 6072 strokePaint.innerColor.a *= state.alpha; 6073 strokePaint.middleColor.a *= state.alpha; 6074 strokePaint.outerColor.a *= state.alpha; 6075 6076 ctx.appendCurrentPathToCache(ctx.recset, state.stroke); 6077 6078 if (ctx.recblockdraw) return; 6079 6080 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths); 6081 6082 // count triangles 6083 foreach (int i; 0..ctx.cache.npaths) { 6084 NVGpath* path = &ctx.cache.paths[i]; 6085 ctx.strokeTriCount += path.nstroke-2; 6086 ++ctx.drawCallCount; 6087 } 6088 } 6089 6090 /// Sets current path as clipping region. 6091 /// Group: clipping 6092 public void clip (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc { 6093 NVGstate* state = nvg__getState(ctx); 6094 6095 if (aclipmode == NVGClipMode.None) return; 6096 if (ctx.recblockdraw) return; //??? 6097 6098 if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr); 6099 6100 /* 6101 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) { 6102 ctx.pathPickRegistered |= NVGPickKind.Fill<<16; 6103 ctx.currFillHitId = ctx.pathPickId; 6104 } 6105 */ 6106 6107 nvg__prepareFill(ctx); 6108 6109 // apply global alpha 6110 NVGPaint fillPaint = state.fill; 6111 fillPaint.innerColor.a *= state.alpha; 6112 fillPaint.middleColor.a *= state.alpha; 6113 fillPaint.outerColor.a *= state.alpha; 6114 6115 //ctx.appendCurrentPathToCache(ctx.recset, state.fill); 6116 6117 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); 6118 6119 // count triangles 6120 foreach (int i; 0..ctx.cache.npaths) { 6121 NVGpath* path = &ctx.cache.paths[i]; 6122 ctx.fillTriCount += path.nfill-2; 6123 ctx.fillTriCount += path.nstroke-2; 6124 ctx.drawCallCount += 2; 6125 } 6126 } 6127 6128 /// Sets current path as clipping region. 6129 /// Group: clipping 6130 public alias clipFill = clip; 6131 6132 /// Sets current path' stroke as clipping region. 6133 /// Group: clipping 6134 public void clipStroke (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc { 6135 NVGstate* state = nvg__getState(ctx); 6136 6137 if (aclipmode == NVGClipMode.None) return; 6138 if (ctx.recblockdraw) return; //??? 6139 6140 if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr); 6141 6142 /* 6143 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) { 6144 ctx.pathPickRegistered |= NVGPickKind.Stroke<<16; 6145 ctx.currStrokeHitId = ctx.pathPickId; 6146 } 6147 */ 6148 6149 nvg__prepareStroke(ctx); 6150 6151 NVGpathCache* cache = ctx.cache; 6152 6153 NVGPaint strokePaint = state.stroke; 6154 strokePaint.innerColor.a *= cache.strokeAlphaMul; 6155 strokePaint.middleColor.a *= cache.strokeAlphaMul; 6156 strokePaint.outerColor.a *= cache.strokeAlphaMul; 6157 6158 // apply global alpha 6159 strokePaint.innerColor.a *= state.alpha; 6160 strokePaint.middleColor.a *= state.alpha; 6161 strokePaint.outerColor.a *= state.alpha; 6162 6163 //ctx.appendCurrentPathToCache(ctx.recset, state.stroke); 6164 6165 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, aclipmode, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths); 6166 6167 // count triangles 6168 foreach (int i; 0..ctx.cache.npaths) { 6169 NVGpath* path = &ctx.cache.paths[i]; 6170 ctx.strokeTriCount += path.nstroke-2; 6171 ++ctx.drawCallCount; 6172 } 6173 } 6174 6175 6176 // ////////////////////////////////////////////////////////////////////////// // 6177 // Picking API 6178 6179 // most of the code is by Michael Wynne <mike@mikesspace.net> 6180 // https://github.com/memononen/nanovg/pull/230 6181 // https://github.com/MikeWW/nanovg 6182 6183 /// Pick type query. Used in [hitTest] and [hitTestAll]. 6184 /// Group: picking_api 6185 public enum NVGPickKind : ubyte { 6186 Fill = 0x01, /// 6187 Stroke = 0x02, /// 6188 All = 0x03, /// 6189 } 6190 6191 /// Marks the fill of the current path as pickable with the specified id. 6192 /// Note that you can create and mark path without rasterizing it. 6193 /// Group: picking_api 6194 public void currFillHitId (NVGContext ctx, int id) nothrow @trusted @nogc { 6195 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6196 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/false); 6197 nvg__pickSceneInsert(ps, pp); 6198 } 6199 6200 public alias currFillPickId = currFillHitId; /// Ditto. 6201 6202 /// Marks the stroke of the current path as pickable with the specified id. 6203 /// Note that you can create and mark path without rasterizing it. 6204 /// Group: picking_api 6205 public void currStrokeHitId (NVGContext ctx, int id) nothrow @trusted @nogc { 6206 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6207 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/true); 6208 nvg__pickSceneInsert(ps, pp); 6209 } 6210 6211 public alias currStrokePickId = currStrokeHitId; /// Ditto. 6212 6213 // Marks the saved path set (fill) as pickable with the specified id. 6214 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!) 6215 // Group: picking_api 6216 /+ 6217 public void pathSetFillHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc { 6218 if (svp is null) return; 6219 if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context"); 6220 foreach (ref cp; svp.caches[0..svp.ncaches]) { 6221 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6222 NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/false); 6223 nvg__pickSceneInsert(ps, pp); 6224 } 6225 } 6226 +/ 6227 6228 // Marks the saved path set (stroke) as pickable with the specified id. 6229 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!) 6230 // Group: picking_api 6231 /+ 6232 public void pathSetStrokeHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc { 6233 if (svp is null) return; 6234 if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context"); 6235 foreach (ref cp; svp.caches[0..svp.ncaches]) { 6236 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6237 NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/true); 6238 nvg__pickSceneInsert(ps, pp); 6239 } 6240 } 6241 +/ 6242 6243 private template IsGoodHitTestDG(DG) { 6244 enum IsGoodHitTestDG = 6245 __traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); }) || 6246 __traits(compiles, (){ DG dg; dg(cast(int)42, cast(int)666); }); 6247 } 6248 6249 private template IsGoodHitTestInternalDG(DG) { 6250 enum IsGoodHitTestInternalDG = 6251 __traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); }) || 6252 __traits(compiles, (){ DG dg; NVGpickPath* pp; dg(pp); }); 6253 } 6254 6255 /// Call delegate [dg] for each path under the specified position (in no particular order). 6256 /// Returns the id of the path for which delegate [dg] returned true or [NVGNoPick]. 6257 /// dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending). 6258 /// Group: picking_api 6259 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) { 6260 if (ctx.pickScene is null || ctx.pickScene.npaths == 0 || (kind&NVGPickKind.All) == 0) return -1; 6261 6262 NVGpickScene* ps = ctx.pickScene; 6263 int levelwidth = 1<<(ps.nlevels-1); 6264 int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth); 6265 int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth); 6266 int npicked = 0; 6267 6268 // if we are interested only in most-toplevel path, there is no reason to check paths with worser order. 6269 // but we cannot just get out on the first path found, 'cause we are using quad tree to speed up bounds 6270 // checking, so path walking order is not guaranteed. 6271 static if (bestOrder) { 6272 int lastBestOrder = int.min; 6273 } 6274 6275 //{ import core.stdc.stdio; printf("npaths=%d\n", ps.npaths); } 6276 for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) { 6277 for (NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx]; pp !is null; pp = pp.next) { 6278 //{ 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); } 6279 static if (bestOrder) { 6280 // reject earlier paths 6281 if (pp.order <= lastBestOrder) continue; // not interesting 6282 } 6283 immutable uint kpx = kind&pp.flags&3; 6284 if (kpx == 0) continue; // not interesting 6285 if (!nvg__pickPathTestBounds(ctx, ps, pp, x, y)) continue; // not interesting 6286 //{ import core.stdc.stdio; printf("in bounds!\n"); } 6287 int hit = 0; 6288 if (kpx&NVGPickKind.Stroke) hit = nvg__pickPathStroke(ps, pp, x, y); 6289 if (!hit && (kpx&NVGPickKind.Fill)) hit = nvg__pickPath(ps, pp, x, y); 6290 if (!hit) continue; 6291 //{ import core.stdc.stdio; printf(" HIT!\n"); } 6292 static if (bestOrder) lastBestOrder = pp.order; 6293 static if (IsGoodHitTestDG!DG) { 6294 static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) { 6295 if (dg(pp.id, cast(int)pp.order)) return pp.id; 6296 } else { 6297 dg(pp.id, cast(int)pp.order); 6298 } 6299 } else { 6300 static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) { 6301 if (dg(pp)) return pp.id; 6302 } else { 6303 dg(pp); 6304 } 6305 } 6306 } 6307 cellx >>= 1; 6308 celly >>= 1; 6309 levelwidth >>= 1; 6310 } 6311 6312 return -1; 6313 } 6314 6315 /// Fills ids with a list of the top most hit ids (from bottom to top) under the specified position. 6316 /// Returns the slice of [ids]. 6317 /// Group: picking_api 6318 public int[] hitTestAll (NVGContext ctx, in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc { 6319 if (ctx.pickScene is null || ids.length == 0) return ids[0..0]; 6320 6321 int npicked = 0; 6322 NVGpickScene* ps = ctx.pickScene; 6323 6324 ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc { 6325 if (npicked == ps.cpicked) { 6326 int cpicked = ps.cpicked+ps.cpicked; 6327 NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked); 6328 if (picked is null) return true; // abort 6329 ps.cpicked = cpicked; 6330 ps.picked = picked; 6331 } 6332 ps.picked[npicked] = pp; 6333 ++npicked; 6334 return false; // go on 6335 }); 6336 6337 qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths); 6338 6339 assert(npicked >= 0); 6340 if (npicked > ids.length) npicked = cast(int)ids.length; 6341 foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id; 6342 6343 return ids[0..npicked]; 6344 } 6345 6346 /// Returns the id of the pickable shape containing x,y or [NVGNoPick] if no shape was found. 6347 /// Group: picking_api 6348 public int hitTest (NVGContext ctx, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc { 6349 if (ctx.pickScene is null) return -1; 6350 6351 int bestOrder = int.min; 6352 int bestID = -1; 6353 6354 ctx.hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) { 6355 if (pp.order > bestOrder) { 6356 bestOrder = pp.order; 6357 bestID = pp.id; 6358 } 6359 }); 6360 6361 return bestID; 6362 } 6363 6364 /// Returns `true` if the path with the given id contains x,y. 6365 /// Group: picking_api 6366 public bool hitTestForId (NVGContext ctx, in int id, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc { 6367 if (ctx.pickScene is null || id == NVGNoPick) return false; 6368 6369 bool res = false; 6370 6371 ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) { 6372 if (pp.id == id) { 6373 res = true; 6374 return true; // stop 6375 } 6376 return false; // continue 6377 }); 6378 6379 return res; 6380 } 6381 6382 /// Returns `true` if the given point is within the fill of the currently defined path. 6383 /// This operation can be done before rasterizing the current path. 6384 /// Group: picking_api 6385 public bool hitTestCurrFill (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 6386 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6387 int oldnpoints = ps.npoints; 6388 int oldnsegments = ps.nsegments; 6389 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/false); 6390 if (pp is null) return false; // oops 6391 scope(exit) { 6392 nvg__freePickPath(ps, pp); 6393 ps.npoints = oldnpoints; 6394 ps.nsegments = oldnsegments; 6395 } 6396 return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPath(ps, pp, x, y) : false); 6397 } 6398 6399 public alias isPointInPath = hitTestCurrFill; /// Ditto. 6400 6401 /// Returns `true` if the given point is within the stroke of the currently defined path. 6402 /// This operation can be done before rasterizing the current path. 6403 /// Group: picking_api 6404 public bool hitTestCurrStroke (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 6405 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6406 int oldnpoints = ps.npoints; 6407 int oldnsegments = ps.nsegments; 6408 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/true); 6409 if (pp is null) return false; // oops 6410 scope(exit) { 6411 nvg__freePickPath(ps, pp); 6412 ps.npoints = oldnpoints; 6413 ps.nsegments = oldnsegments; 6414 } 6415 return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPathStroke(ps, pp, x, y) : false); 6416 } 6417 6418 6419 nothrow @trusted @nogc { 6420 extern(C) { 6421 private alias _compare_fp_t = int function (const void*, const void*) nothrow @nogc; 6422 private extern(C) void qsort (scope void* base, size_t nmemb, size_t size, _compare_fp_t compar) nothrow @nogc; 6423 6424 extern(C) int nvg__comparePaths (const void* a, const void* b) { 6425 return (*cast(const(NVGpickPath)**)b).order-(*cast(const(NVGpickPath)**)a).order; 6426 } 6427 } 6428 6429 enum NVGPickEPS = 0.0001f; 6430 6431 // Segment flags 6432 enum NVGSegmentFlags { 6433 Corner = 1, 6434 Bevel = 2, 6435 InnerBevel = 4, 6436 Cap = 8, 6437 Endcap = 16, 6438 } 6439 6440 // Path flags 6441 enum NVGPathFlags : ushort { 6442 Fill = NVGPickKind.Fill, 6443 Stroke = NVGPickKind.Stroke, 6444 Scissor = 0x80, 6445 } 6446 6447 struct NVGsegment { 6448 int firstPoint; // Index into NVGpickScene.points 6449 short type; // NVG_LINETO or NVG_BEZIERTO 6450 short flags; // Flags relate to the corner between the prev segment and this one. 6451 float[4] bounds; 6452 float[2] startDir; // Direction at t == 0 6453 float[2] endDir; // Direction at t == 1 6454 float[2] miterDir; // Direction of miter of corner between the prev segment and this one. 6455 } 6456 6457 struct NVGpickSubPath { 6458 short winding; // TODO: Merge to flag field 6459 bool closed; // TODO: Merge to flag field 6460 6461 int firstSegment; // Index into NVGpickScene.segments 6462 int nsegments; 6463 6464 float[4] bounds; 6465 6466 NVGpickSubPath* next; 6467 } 6468 6469 struct NVGpickPath { 6470 int id; 6471 short flags; 6472 short order; 6473 float strokeWidth; 6474 float miterLimit; 6475 short lineCap; 6476 short lineJoin; 6477 bool evenOddMode; 6478 6479 float[4] bounds; 6480 int scissor; // Indexes into ps->points and defines scissor rect as XVec, YVec and Center 6481 6482 NVGpickSubPath* subPaths; 6483 NVGpickPath* next; 6484 NVGpickPath* cellnext; 6485 } 6486 6487 struct NVGpickScene { 6488 int npaths; 6489 6490 NVGpickPath* paths; // Linked list of paths 6491 NVGpickPath* lastPath; // The last path in the paths linked list (the first path added) 6492 NVGpickPath* freePaths; // Linked list of free paths 6493 6494 NVGpickSubPath* freeSubPaths; // Linked list of free sub paths 6495 6496 int width; 6497 int height; 6498 6499 // Points for all path sub paths. 6500 float* points; 6501 int npoints; 6502 int cpoints; 6503 6504 // Segments for all path sub paths 6505 NVGsegment* segments; 6506 int nsegments; 6507 int csegments; 6508 6509 // Implicit quadtree 6510 float xdim; // Width / (1 << nlevels) 6511 float ydim; // Height / (1 << nlevels) 6512 int ncells; // Total number of cells in all levels 6513 int nlevels; 6514 NVGpickPath*** levels; // Index: [Level][LevelY * LevelW + LevelX] Value: Linked list of paths 6515 6516 // Temp storage for picking 6517 int cpicked; 6518 NVGpickPath** picked; 6519 } 6520 6521 6522 // bounds utilities 6523 void nvg__initBounds (ref float[4] bounds) { 6524 bounds.ptr[0] = bounds.ptr[1] = float.max; 6525 bounds.ptr[2] = bounds.ptr[3] = -float.max; 6526 } 6527 6528 void nvg__expandBounds (ref float[4] bounds, const(float)* points, int npoints) { 6529 npoints *= 2; 6530 for (int i = 0; i < npoints; i += 2) { 6531 bounds.ptr[0] = nvg__min(bounds.ptr[0], points[i]); 6532 bounds.ptr[1] = nvg__min(bounds.ptr[1], points[i+1]); 6533 bounds.ptr[2] = nvg__max(bounds.ptr[2], points[i]); 6534 bounds.ptr[3] = nvg__max(bounds.ptr[3], points[i+1]); 6535 } 6536 } 6537 6538 void nvg__unionBounds (ref float[4] bounds, const scope ref float[4] boundsB) { 6539 bounds.ptr[0] = nvg__min(bounds.ptr[0], boundsB.ptr[0]); 6540 bounds.ptr[1] = nvg__min(bounds.ptr[1], boundsB.ptr[1]); 6541 bounds.ptr[2] = nvg__max(bounds.ptr[2], boundsB.ptr[2]); 6542 bounds.ptr[3] = nvg__max(bounds.ptr[3], boundsB.ptr[3]); 6543 } 6544 6545 void nvg__intersectBounds (ref float[4] bounds, const scope ref float[4] boundsB) { 6546 bounds.ptr[0] = nvg__max(boundsB.ptr[0], bounds.ptr[0]); 6547 bounds.ptr[1] = nvg__max(boundsB.ptr[1], bounds.ptr[1]); 6548 bounds.ptr[2] = nvg__min(boundsB.ptr[2], bounds.ptr[2]); 6549 bounds.ptr[3] = nvg__min(boundsB.ptr[3], bounds.ptr[3]); 6550 6551 bounds.ptr[2] = nvg__max(bounds.ptr[0], bounds.ptr[2]); 6552 bounds.ptr[3] = nvg__max(bounds.ptr[1], bounds.ptr[3]); 6553 } 6554 6555 bool nvg__pointInBounds (in float x, in float y, const scope ref float[4] bounds) { 6556 pragma(inline, true); 6557 return (x >= bounds.ptr[0] && x <= bounds.ptr[2] && y >= bounds.ptr[1] && y <= bounds.ptr[3]); 6558 } 6559 6560 // building paths & sub paths 6561 int nvg__pickSceneAddPoints (NVGpickScene* ps, const(float)* xy, int n) { 6562 import core.stdc.string : memcpy; 6563 if (ps.npoints+n > ps.cpoints) { 6564 import core.stdc.stdlib : realloc; 6565 int cpoints = ps.npoints+n+(ps.cpoints<<1); 6566 float* points = cast(float*)realloc(ps.points, float.sizeof*2*cpoints); 6567 if (points is null) assert(0, "NanoVega: out of memory"); 6568 ps.points = points; 6569 ps.cpoints = cpoints; 6570 } 6571 int i = ps.npoints; 6572 if (xy !is null) memcpy(&ps.points[i*2], xy, float.sizeof*2*n); 6573 ps.npoints += n; 6574 return i; 6575 } 6576 6577 void nvg__pickSubPathAddSegment (NVGpickScene* ps, NVGpickSubPath* psp, int firstPoint, int type, short flags) { 6578 NVGsegment* seg = null; 6579 if (ps.nsegments == ps.csegments) { 6580 int csegments = 1+ps.csegments+(ps.csegments<<1); 6581 NVGsegment* segments = cast(NVGsegment*)realloc(ps.segments, NVGsegment.sizeof*csegments); 6582 if (segments is null) assert(0, "NanoVega: out of memory"); 6583 ps.segments = segments; 6584 ps.csegments = csegments; 6585 } 6586 6587 if (psp.firstSegment == -1) psp.firstSegment = ps.nsegments; 6588 6589 seg = &ps.segments[ps.nsegments]; 6590 ++ps.nsegments; 6591 seg.firstPoint = firstPoint; 6592 seg.type = cast(short)type; 6593 seg.flags = flags; 6594 ++psp.nsegments; 6595 6596 nvg__segmentDir(ps, psp, seg, 0, seg.startDir); 6597 nvg__segmentDir(ps, psp, seg, 1, seg.endDir); 6598 } 6599 6600 void nvg__segmentDir (NVGpickScene* ps, NVGpickSubPath* psp, NVGsegment* seg, float t, ref float[2] d) { 6601 const(float)* points = &ps.points[seg.firstPoint*2]; 6602 immutable float x0 = points[0*2+0], x1 = points[1*2+0]; 6603 immutable float y0 = points[0*2+1], y1 = points[1*2+1]; 6604 switch (seg.type) { 6605 case Command.LineTo: 6606 d.ptr[0] = x1-x0; 6607 d.ptr[1] = y1-y0; 6608 nvg__normalize(&d.ptr[0], &d.ptr[1]); 6609 break; 6610 case Command.BezierTo: 6611 immutable float x2 = points[2*2+0]; 6612 immutable float y2 = points[2*2+1]; 6613 immutable float x3 = points[3*2+0]; 6614 immutable float y3 = points[3*2+1]; 6615 6616 immutable float omt = 1.0f-t; 6617 immutable float omt2 = omt*omt; 6618 immutable float t2 = t*t; 6619 6620 d.ptr[0] = 6621 3.0f*omt2*(x1-x0)+ 6622 6.0f*omt*t*(x2-x1)+ 6623 3.0f*t2*(x3-x2); 6624 6625 d.ptr[1] = 6626 3.0f*omt2*(y1-y0)+ 6627 6.0f*omt*t*(y2-y1)+ 6628 3.0f*t2*(y3-y2); 6629 6630 nvg__normalize(&d.ptr[0], &d.ptr[1]); 6631 break; 6632 default: 6633 break; 6634 } 6635 } 6636 6637 void nvg__pickSubPathAddFillSupports (NVGpickScene* ps, NVGpickSubPath* psp) { 6638 if (psp.firstSegment == -1) return; 6639 NVGsegment* segments = &ps.segments[psp.firstSegment]; 6640 for (int s = 0; s < psp.nsegments; ++s) { 6641 NVGsegment* seg = &segments[s]; 6642 const(float)* points = &ps.points[seg.firstPoint*2]; 6643 if (seg.type == Command.LineTo) { 6644 nvg__initBounds(seg.bounds); 6645 nvg__expandBounds(seg.bounds, points, 2); 6646 } else { 6647 nvg__bezierBounds(points, seg.bounds); 6648 } 6649 } 6650 } 6651 6652 void nvg__pickSubPathAddStrokeSupports (NVGpickScene* ps, NVGpickSubPath* psp, float strokeWidth, int lineCap, int lineJoin, float miterLimit) { 6653 if (psp.firstSegment == -1) return; 6654 immutable bool closed = psp.closed; 6655 const(float)* points = ps.points; 6656 NVGsegment* seg = null; 6657 NVGsegment* segments = &ps.segments[psp.firstSegment]; 6658 int nsegments = psp.nsegments; 6659 NVGsegment* prevseg = (closed ? &segments[psp.nsegments-1] : null); 6660 6661 int ns = 0; // nsupports 6662 float[32] supportingPoints = void; 6663 int firstPoint, lastPoint; 6664 6665 if (!closed) { 6666 segments[0].flags |= NVGSegmentFlags.Cap; 6667 segments[nsegments-1].flags |= NVGSegmentFlags.Endcap; 6668 } 6669 6670 for (int s = 0; s < nsegments; ++s) { 6671 seg = &segments[s]; 6672 nvg__initBounds(seg.bounds); 6673 6674 firstPoint = seg.firstPoint*2; 6675 lastPoint = firstPoint+(seg.type == Command.LineTo ? 2 : 6); 6676 6677 ns = 0; 6678 6679 // First two supporting points are either side of the start point 6680 supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[1]*strokeWidth; 6681 supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.startDir.ptr[0]*strokeWidth; 6682 6683 supportingPoints.ptr[ns++] = points[firstPoint]+seg.startDir.ptr[1]*strokeWidth; 6684 supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[0]*strokeWidth; 6685 6686 // Second two supporting points are either side of the end point 6687 supportingPoints.ptr[ns++] = points[lastPoint]-seg.endDir.ptr[1]*strokeWidth; 6688 supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[0]*strokeWidth; 6689 6690 supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[1]*strokeWidth; 6691 supportingPoints.ptr[ns++] = points[lastPoint+1]-seg.endDir.ptr[0]*strokeWidth; 6692 6693 if ((seg.flags&NVGSegmentFlags.Corner) && prevseg !is null) { 6694 seg.miterDir.ptr[0] = 0.5f*(-prevseg.endDir.ptr[1]-seg.startDir.ptr[1]); 6695 seg.miterDir.ptr[1] = 0.5f*(prevseg.endDir.ptr[0]+seg.startDir.ptr[0]); 6696 6697 immutable float M2 = seg.miterDir.ptr[0]*seg.miterDir.ptr[0]+seg.miterDir.ptr[1]*seg.miterDir.ptr[1]; 6698 6699 if (M2 > 0.000001f) { 6700 float scale = 1.0f/M2; 6701 if (scale > 600.0f) scale = 600.0f; 6702 seg.miterDir.ptr[0] *= scale; 6703 seg.miterDir.ptr[1] *= scale; 6704 } 6705 6706 //NVG_PICK_DEBUG_VECTOR_SCALE(&points[firstPoint], seg.miterDir, 10); 6707 6708 // Add an additional support at the corner on the other line 6709 supportingPoints.ptr[ns++] = points[firstPoint]-prevseg.endDir.ptr[1]*strokeWidth; 6710 supportingPoints.ptr[ns++] = points[firstPoint+1]+prevseg.endDir.ptr[0]*strokeWidth; 6711 6712 if (lineJoin == NVGLineCap.Miter || lineJoin == NVGLineCap.Bevel) { 6713 // Set a corner as beveled if the join type is bevel or mitered and 6714 // miterLimit is hit. 6715 if (lineJoin == NVGLineCap.Bevel || (M2*miterLimit*miterLimit) < 1.0f) { 6716 seg.flags |= NVGSegmentFlags.Bevel; 6717 } else { 6718 // Corner is mitered - add miter point as a support 6719 supportingPoints.ptr[ns++] = points[firstPoint]+seg.miterDir.ptr[0]*strokeWidth; 6720 supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.miterDir.ptr[1]*strokeWidth; 6721 } 6722 } else if (lineJoin == NVGLineCap.Round) { 6723 // ... and at the midpoint of the corner arc 6724 float[2] vertexN = [ -seg.startDir.ptr[0]+prevseg.endDir.ptr[0], -seg.startDir.ptr[1]+prevseg.endDir.ptr[1] ]; 6725 nvg__normalize(&vertexN[0], &vertexN[1]); 6726 6727 supportingPoints.ptr[ns++] = points[firstPoint]+vertexN[0]*strokeWidth; 6728 supportingPoints.ptr[ns++] = points[firstPoint+1]+vertexN[1]*strokeWidth; 6729 } 6730 } 6731 6732 if (seg.flags&NVGSegmentFlags.Cap) { 6733 switch (lineCap) { 6734 case NVGLineCap.Butt: 6735 // supports for butt already added 6736 break; 6737 case NVGLineCap.Square: 6738 // square cap supports are just the original two supports moved out along the direction 6739 supportingPoints.ptr[ns++] = supportingPoints.ptr[0]-seg.startDir.ptr[0]*strokeWidth; 6740 supportingPoints.ptr[ns++] = supportingPoints.ptr[1]-seg.startDir.ptr[1]*strokeWidth; 6741 supportingPoints.ptr[ns++] = supportingPoints.ptr[2]-seg.startDir.ptr[0]*strokeWidth; 6742 supportingPoints.ptr[ns++] = supportingPoints.ptr[3]-seg.startDir.ptr[1]*strokeWidth; 6743 break; 6744 case NVGLineCap.Round: 6745 // add one additional support for the round cap along the dir 6746 supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[0]*strokeWidth; 6747 supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[1]*strokeWidth; 6748 break; 6749 default: 6750 break; 6751 } 6752 } 6753 6754 if (seg.flags&NVGSegmentFlags.Endcap) { 6755 // end supporting points, either side of line 6756 int end = 4; 6757 switch(lineCap) { 6758 case NVGLineCap.Butt: 6759 // supports for butt already added 6760 break; 6761 case NVGLineCap.Square: 6762 // square cap supports are just the original two supports moved out along the direction 6763 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+0]+seg.endDir.ptr[0]*strokeWidth; 6764 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+1]+seg.endDir.ptr[1]*strokeWidth; 6765 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+2]+seg.endDir.ptr[0]*strokeWidth; 6766 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+3]+seg.endDir.ptr[1]*strokeWidth; 6767 break; 6768 case NVGLineCap.Round: 6769 // add one additional support for the round cap along the dir 6770 supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[0]*strokeWidth; 6771 supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[1]*strokeWidth; 6772 break; 6773 default: 6774 break; 6775 } 6776 } 6777 6778 nvg__expandBounds(seg.bounds, supportingPoints.ptr, ns/2); 6779 6780 prevseg = seg; 6781 } 6782 } 6783 6784 NVGpickPath* nvg__pickPathCreate (NVGContext context, const(float)[] acommands, int id, bool forStroke) { 6785 NVGpickScene* ps = nvg__pickSceneGet(context); 6786 if (ps is null) return null; 6787 6788 int i = 0; 6789 6790 int ncommands = cast(int)acommands.length; 6791 const(float)* commands = acommands.ptr; 6792 6793 NVGpickPath* pp = null; 6794 NVGpickSubPath* psp = null; 6795 float[2] start = void; 6796 int firstPoint; 6797 6798 //bool hasHoles = false; 6799 NVGpickSubPath* prev = null; 6800 6801 float[8] points = void; 6802 float[2] inflections = void; 6803 int ninflections = 0; 6804 6805 NVGstate* state = nvg__getState(context); 6806 float[4] totalBounds = void; 6807 NVGsegment* segments = null; 6808 const(NVGsegment)* seg = null; 6809 NVGpickSubPath *curpsp; 6810 6811 pp = nvg__allocPickPath(ps); 6812 if (pp is null) return null; 6813 6814 pp.id = id; 6815 6816 bool hasPoints = false; 6817 6818 void closeIt () { 6819 if (psp is null || !hasPoints) return; 6820 if (ps.points[(ps.npoints-1)*2] != start.ptr[0] || ps.points[(ps.npoints-1)*2+1] != start.ptr[1]) { 6821 firstPoint = nvg__pickSceneAddPoints(ps, start.ptr, 1); 6822 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, Command.LineTo, NVGSegmentFlags.Corner); 6823 } 6824 psp.closed = true; 6825 } 6826 6827 while (i < ncommands) { 6828 int cmd = cast(int)commands[i++]; 6829 switch (cmd) { 6830 case Command.MoveTo: // one coordinate pair 6831 const(float)* tfxy = commands+i; 6832 i += 2; 6833 6834 // new starting point 6835 start.ptr[0..2] = tfxy[0..2]; 6836 6837 // start a new path for each sub path to handle sub paths that intersect other sub paths 6838 prev = psp; 6839 psp = nvg__allocPickSubPath(ps); 6840 if (psp is null) { psp = prev; break; } 6841 psp.firstSegment = -1; 6842 psp.winding = NVGSolidity.Solid; 6843 psp.next = prev; 6844 6845 nvg__pickSceneAddPoints(ps, tfxy, 1); 6846 hasPoints = true; 6847 break; 6848 case Command.LineTo: // one coordinate pair 6849 const(float)* tfxy = commands+i; 6850 i += 2; 6851 firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 1); 6852 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner); 6853 hasPoints = true; 6854 break; 6855 case Command.BezierTo: // three coordinate pairs 6856 const(float)* tfxy = commands+i; 6857 i += 3*2; 6858 6859 // Split the curve at it's dx==0 or dy==0 inflection points. 6860 // Thus: 6861 // A horizontal line only ever interects the curves once. 6862 // and 6863 // Finding the closest point on any curve converges more reliably. 6864 6865 // NOTE: We could just split on dy==0 here. 6866 6867 memcpy(&points.ptr[0], &ps.points[(ps.npoints-1)*2], float.sizeof*2); 6868 memcpy(&points.ptr[2], tfxy, float.sizeof*2*3); 6869 6870 ninflections = 0; 6871 nvg__bezierInflections(points.ptr, 1, &ninflections, inflections.ptr); 6872 nvg__bezierInflections(points.ptr, 0, &ninflections, inflections.ptr); 6873 6874 if (ninflections) { 6875 float previnfl = 0; 6876 float[8] pointsA = void, pointsB = void; 6877 6878 nvg__smallsort(inflections.ptr, ninflections); 6879 6880 for (int infl = 0; infl < ninflections; ++infl) { 6881 if (nvg__absf(inflections.ptr[infl]-previnfl) < NVGPickEPS) continue; 6882 6883 immutable float t = (inflections.ptr[infl]-previnfl)*(1.0f/(1.0f-previnfl)); 6884 6885 previnfl = inflections.ptr[infl]; 6886 6887 nvg__splitBezier(points.ptr, t, pointsA.ptr, pointsB.ptr); 6888 6889 firstPoint = nvg__pickSceneAddPoints(ps, &pointsA.ptr[2], 3); 6890 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, (infl == 0) ? NVGSegmentFlags.Corner : 0); 6891 6892 memcpy(points.ptr, pointsB.ptr, float.sizeof*8); 6893 } 6894 6895 firstPoint = nvg__pickSceneAddPoints(ps, &pointsB.ptr[2], 3); 6896 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, 0); 6897 } else { 6898 firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 3); 6899 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner); 6900 } 6901 hasPoints = true; 6902 break; 6903 case Command.Close: 6904 closeIt(); 6905 break; 6906 case Command.Winding: 6907 psp.winding = cast(short)cast(int)commands[i]; 6908 //if (psp.winding == NVGSolidity.Hole) hasHoles = true; 6909 i += 1; 6910 break; 6911 default: 6912 break; 6913 } 6914 } 6915 6916 // force-close filled paths 6917 if (psp !is null && !forStroke && hasPoints && !psp.closed) closeIt(); 6918 6919 pp.flags = (forStroke ? NVGPathFlags.Stroke : NVGPathFlags.Fill); 6920 pp.subPaths = psp; 6921 pp.strokeWidth = state.strokeWidth*0.5f; 6922 pp.miterLimit = state.miterLimit; 6923 pp.lineCap = cast(short)state.lineCap; 6924 pp.lineJoin = cast(short)state.lineJoin; 6925 pp.evenOddMode = nvg__getState(context).evenOddMode; 6926 6927 nvg__initBounds(totalBounds); 6928 6929 for (curpsp = psp; curpsp; curpsp = curpsp.next) { 6930 if (forStroke) { 6931 nvg__pickSubPathAddStrokeSupports(ps, curpsp, pp.strokeWidth, pp.lineCap, pp.lineJoin, pp.miterLimit); 6932 } else { 6933 nvg__pickSubPathAddFillSupports(ps, curpsp); 6934 } 6935 6936 if (curpsp.firstSegment == -1) continue; 6937 segments = &ps.segments[curpsp.firstSegment]; 6938 nvg__initBounds(curpsp.bounds); 6939 for (int s = 0; s < curpsp.nsegments; ++s) { 6940 seg = &segments[s]; 6941 //NVG_PICK_DEBUG_BOUNDS(seg.bounds); 6942 nvg__unionBounds(curpsp.bounds, seg.bounds); 6943 } 6944 6945 nvg__unionBounds(totalBounds, curpsp.bounds); 6946 } 6947 6948 // Store the scissor rect if present. 6949 if (state.scissor.extent.ptr[0] != -1.0f) { 6950 // Use points storage to store the scissor data 6951 pp.scissor = nvg__pickSceneAddPoints(ps, null, 4); 6952 float* scissor = &ps.points[pp.scissor*2]; 6953 6954 //memcpy(scissor, state.scissor.xform.ptr, 6*float.sizeof); 6955 scissor[0..6] = state.scissor.xform.mat[]; 6956 memcpy(scissor+6, state.scissor.extent.ptr, 2*float.sizeof); 6957 6958 pp.flags |= NVGPathFlags.Scissor; 6959 } 6960 6961 memcpy(pp.bounds.ptr, totalBounds.ptr, float.sizeof*4); 6962 6963 return pp; 6964 } 6965 6966 6967 // Struct management 6968 NVGpickPath* nvg__allocPickPath (NVGpickScene* ps) { 6969 NVGpickPath* pp = ps.freePaths; 6970 if (pp !is null) { 6971 ps.freePaths = pp.next; 6972 } else { 6973 pp = cast(NVGpickPath*)malloc(NVGpickPath.sizeof); 6974 } 6975 memset(pp, 0, NVGpickPath.sizeof); 6976 return pp; 6977 } 6978 6979 // Put a pick path and any sub paths (back) to the free lists. 6980 void nvg__freePickPath (NVGpickScene* ps, NVGpickPath* pp) { 6981 // Add all sub paths to the sub path free list. 6982 // Finds the end of the path sub paths, links that to the current 6983 // sub path free list head and replaces the head ptr with the 6984 // head path sub path entry. 6985 NVGpickSubPath* psp = null; 6986 for (psp = pp.subPaths; psp !is null && psp.next !is null; psp = psp.next) {} 6987 6988 if (psp) { 6989 psp.next = ps.freeSubPaths; 6990 ps.freeSubPaths = pp.subPaths; 6991 } 6992 pp.subPaths = null; 6993 6994 // Add the path to the path freelist 6995 pp.next = ps.freePaths; 6996 ps.freePaths = pp; 6997 if (pp.next is null) ps.lastPath = pp; 6998 } 6999 7000 NVGpickSubPath* nvg__allocPickSubPath (NVGpickScene* ps) { 7001 NVGpickSubPath* psp = ps.freeSubPaths; 7002 if (psp !is null) { 7003 ps.freeSubPaths = psp.next; 7004 } else { 7005 psp = cast(NVGpickSubPath*)malloc(NVGpickSubPath.sizeof); 7006 if (psp is null) return null; 7007 } 7008 memset(psp, 0, NVGpickSubPath.sizeof); 7009 return psp; 7010 } 7011 7012 void nvg__returnPickSubPath (NVGpickScene* ps, NVGpickSubPath* psp) { 7013 psp.next = ps.freeSubPaths; 7014 ps.freeSubPaths = psp; 7015 } 7016 7017 NVGpickScene* nvg__allocPickScene () { 7018 NVGpickScene* ps = cast(NVGpickScene*)malloc(NVGpickScene.sizeof); 7019 if (ps is null) return null; 7020 memset(ps, 0, NVGpickScene.sizeof); 7021 ps.nlevels = 5; 7022 return ps; 7023 } 7024 7025 void nvg__deletePickScene (NVGpickScene* ps) { 7026 NVGpickPath* pp; 7027 NVGpickSubPath* psp; 7028 7029 // Add all paths (and thus sub paths) to the free list(s). 7030 while (ps.paths !is null) { 7031 pp = ps.paths.next; 7032 nvg__freePickPath(ps, ps.paths); 7033 ps.paths = pp; 7034 } 7035 7036 // Delete all paths 7037 while (ps.freePaths !is null) { 7038 pp = ps.freePaths; 7039 ps.freePaths = pp.next; 7040 while (pp.subPaths !is null) { 7041 psp = pp.subPaths; 7042 pp.subPaths = psp.next; 7043 free(psp); 7044 } 7045 free(pp); 7046 } 7047 7048 // Delete all sub paths 7049 while (ps.freeSubPaths !is null) { 7050 psp = ps.freeSubPaths.next; 7051 free(ps.freeSubPaths); 7052 ps.freeSubPaths = psp; 7053 } 7054 7055 ps.npoints = 0; 7056 ps.nsegments = 0; 7057 7058 if (ps.levels !is null) { 7059 free(ps.levels[0]); 7060 free(ps.levels); 7061 } 7062 7063 if (ps.picked !is null) free(ps.picked); 7064 if (ps.points !is null) free(ps.points); 7065 if (ps.segments !is null) free(ps.segments); 7066 7067 free(ps); 7068 } 7069 7070 NVGpickScene* nvg__pickSceneGet (NVGContext ctx) { 7071 if (ctx.pickScene is null) ctx.pickScene = nvg__allocPickScene(); 7072 return ctx.pickScene; 7073 } 7074 7075 7076 // Applies Casteljau's algorithm to a cubic bezier for a given parameter t 7077 // points is 4 points (8 floats) 7078 // lvl1 is 3 points (6 floats) 7079 // lvl2 is 2 points (4 floats) 7080 // lvl3 is 1 point (2 floats) 7081 void nvg__casteljau (const(float)* points, float t, float* lvl1, float* lvl2, float* lvl3) { 7082 enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0; 7083 enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1; 7084 7085 // Level 1 7086 lvl1[x0] = (points[x1]-points[x0])*t+points[x0]; 7087 lvl1[y0] = (points[y1]-points[y0])*t+points[y0]; 7088 7089 lvl1[x1] = (points[x2]-points[x1])*t+points[x1]; 7090 lvl1[y1] = (points[y2]-points[y1])*t+points[y1]; 7091 7092 lvl1[x2] = (points[x3]-points[x2])*t+points[x2]; 7093 lvl1[y2] = (points[y3]-points[y2])*t+points[y2]; 7094 7095 // Level 2 7096 lvl2[x0] = (lvl1[x1]-lvl1[x0])*t+lvl1[x0]; 7097 lvl2[y0] = (lvl1[y1]-lvl1[y0])*t+lvl1[y0]; 7098 7099 lvl2[x1] = (lvl1[x2]-lvl1[x1])*t+lvl1[x1]; 7100 lvl2[y1] = (lvl1[y2]-lvl1[y1])*t+lvl1[y1]; 7101 7102 // Level 3 7103 lvl3[x0] = (lvl2[x1]-lvl2[x0])*t+lvl2[x0]; 7104 lvl3[y0] = (lvl2[y1]-lvl2[y0])*t+lvl2[y0]; 7105 } 7106 7107 // Calculates a point on a bezier at point t. 7108 void nvg__bezierEval (const(float)* points, float t, ref float[2] tpoint) { 7109 immutable float omt = 1-t; 7110 immutable float omt3 = omt*omt*omt; 7111 immutable float omt2 = omt*omt; 7112 immutable float t3 = t*t*t; 7113 immutable float t2 = t*t; 7114 7115 tpoint.ptr[0] = 7116 points[0]*omt3+ 7117 points[2]*3.0f*omt2*t+ 7118 points[4]*3.0f*omt*t2+ 7119 points[6]*t3; 7120 7121 tpoint.ptr[1] = 7122 points[1]*omt3+ 7123 points[3]*3.0f*omt2*t+ 7124 points[5]*3.0f*omt*t2+ 7125 points[7]*t3; 7126 } 7127 7128 // Splits a cubic bezier curve into two parts at point t. 7129 void nvg__splitBezier (const(float)* points, float t, float* pointsA, float* pointsB) { 7130 enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0; 7131 enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1; 7132 7133 float[6] lvl1 = void; 7134 float[4] lvl2 = void; 7135 float[2] lvl3 = void; 7136 7137 nvg__casteljau(points, t, lvl1.ptr, lvl2.ptr, lvl3.ptr); 7138 7139 // First half 7140 pointsA[x0] = points[x0]; 7141 pointsA[y0] = points[y0]; 7142 7143 pointsA[x1] = lvl1.ptr[x0]; 7144 pointsA[y1] = lvl1.ptr[y0]; 7145 7146 pointsA[x2] = lvl2.ptr[x0]; 7147 pointsA[y2] = lvl2.ptr[y0]; 7148 7149 pointsA[x3] = lvl3.ptr[x0]; 7150 pointsA[y3] = lvl3.ptr[y0]; 7151 7152 // Second half 7153 pointsB[x0] = lvl3.ptr[x0]; 7154 pointsB[y0] = lvl3.ptr[y0]; 7155 7156 pointsB[x1] = lvl2.ptr[x1]; 7157 pointsB[y1] = lvl2.ptr[y1]; 7158 7159 pointsB[x2] = lvl1.ptr[x2]; 7160 pointsB[y2] = lvl1.ptr[y2]; 7161 7162 pointsB[x3] = points[x3]; 7163 pointsB[y3] = points[y3]; 7164 } 7165 7166 // Calculates the inflection points in coordinate coord (X = 0, Y = 1) of a cubic bezier. 7167 // Appends any found inflection points to the array inflections and increments *ninflections. 7168 // So finds the parameters where dx/dt or dy/dt is 0 7169 void nvg__bezierInflections (const(float)* points, int coord, int* ninflections, float* inflections) { 7170 immutable float v0 = points[0*2+coord], v1 = points[1*2+coord], v2 = points[2*2+coord], v3 = points[3*2+coord]; 7171 float[2] t = void; 7172 int nvalid = *ninflections; 7173 7174 immutable float a = 3.0f*( -v0+3.0f*v1-3.0f*v2+v3 ); 7175 immutable float b = 6.0f*( v0-2.0f*v1+v2 ); 7176 immutable float c = 3.0f*( v1-v0 ); 7177 7178 float d = b*b-4.0f*a*c; 7179 if (nvg__absf(d-0.0f) < NVGPickEPS) { 7180 // Zero or one root 7181 t.ptr[0] = -b/2.0f*a; 7182 if (t.ptr[0] > NVGPickEPS && t.ptr[0] < (1.0f-NVGPickEPS)) { 7183 inflections[nvalid] = t.ptr[0]; 7184 ++nvalid; 7185 } 7186 } else if (d > NVGPickEPS) { 7187 // zero, one or two roots 7188 d = nvg__sqrtf(d); 7189 7190 t.ptr[0] = (-b+d)/(2.0f*a); 7191 t.ptr[1] = (-b-d)/(2.0f*a); 7192 7193 for (int i = 0; i < 2; ++i) { 7194 if (t.ptr[i] > NVGPickEPS && t.ptr[i] < (1.0f-NVGPickEPS)) { 7195 inflections[nvalid] = t.ptr[i]; 7196 ++nvalid; 7197 } 7198 } 7199 } else { 7200 // zero roots 7201 } 7202 7203 *ninflections = nvalid; 7204 } 7205 7206 // Sort a small number of floats in ascending order (0 < n < 6) 7207 void nvg__smallsort (float* values, int n) { 7208 bool bSwapped = true; 7209 for (int j = 0; j < n-1 && bSwapped; ++j) { 7210 bSwapped = false; 7211 for (int i = 0; i < n-1; ++i) { 7212 if (values[i] > values[i+1]) { 7213 auto tmp = values[i]; 7214 values[i] = values[i+1]; 7215 values[i+1] = tmp; 7216 } 7217 } 7218 } 7219 } 7220 7221 // Calculates the bounding rect of a given cubic bezier curve. 7222 void nvg__bezierBounds (const(float)* points, ref float[4] bounds) { 7223 float[4] inflections = void; 7224 int ninflections = 0; 7225 float[2] tpoint = void; 7226 7227 nvg__initBounds(bounds); 7228 7229 // Include start and end points in bounds 7230 nvg__expandBounds(bounds, &points[0], 1); 7231 nvg__expandBounds(bounds, &points[6], 1); 7232 7233 // Calculate dx==0 and dy==0 inflection points and add them to the bounds 7234 7235 nvg__bezierInflections(points, 0, &ninflections, inflections.ptr); 7236 nvg__bezierInflections(points, 1, &ninflections, inflections.ptr); 7237 7238 foreach (immutable int i; 0..ninflections) { 7239 nvg__bezierEval(points, inflections[i], tpoint); 7240 nvg__expandBounds(bounds, tpoint.ptr, 1); 7241 } 7242 } 7243 7244 // Checks to see if a line originating from x,y along the +ve x axis 7245 // intersects the given line (points[0],points[1]) -> (points[2], points[3]). 7246 // Returns `true` on intersection. 7247 // Horizontal lines are never hit. 7248 bool nvg__intersectLine (const(float)* points, float x, float y) { 7249 immutable float x1 = points[0]; 7250 immutable float y1 = points[1]; 7251 immutable float x2 = points[2]; 7252 immutable float y2 = points[3]; 7253 immutable float d = y2-y1; 7254 if (d > NVGPickEPS || d < -NVGPickEPS) { 7255 immutable float s = (x2-x1)/d; 7256 immutable float lineX = x1+(y-y1)*s; 7257 return (lineX > x); 7258 } else { 7259 return false; 7260 } 7261 } 7262 7263 // Checks to see if a line originating from x,y along the +ve x axis intersects the given bezier. 7264 // It is assumed that the line originates from within the bounding box of 7265 // the bezier and that the curve has no dy=0 inflection points. 7266 // Returns the number of intersections found (which is either 1 or 0). 7267 int nvg__intersectBezier (const(float)* points, float x, float y) { 7268 immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0]; 7269 immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1]; 7270 7271 if (y0 == y1 && y1 == y2 && y2 == y3) return 0; 7272 7273 // Initial t guess 7274 float t = void; 7275 if (y3 != y0) t = (y-y0)/(y3-y0); 7276 else if (x3 != x0) t = (x-x0)/(x3-x0); 7277 else t = 0.5f; 7278 7279 // A few Newton iterations 7280 for (int iter = 0; iter < 6; ++iter) { 7281 immutable float omt = 1-t; 7282 immutable float omt2 = omt*omt; 7283 immutable float t2 = t*t; 7284 immutable float omt3 = omt2*omt; 7285 immutable float t3 = t2*t; 7286 7287 immutable float ty = y0*omt3 + 7288 y1*3.0f*omt2*t + 7289 y2*3.0f*omt*t2 + 7290 y3*t3; 7291 7292 // Newton iteration 7293 immutable float dty = 3.0f*omt2*(y1-y0) + 7294 6.0f*omt*t*(y2-y1) + 7295 3.0f*t2*(y3-y2); 7296 7297 // dty will never == 0 since: 7298 // Either omt, omt2 are zero OR t2 is zero 7299 // y0 != y1 != y2 != y3 (checked above) 7300 t = t-(ty-y)/dty; 7301 } 7302 7303 { 7304 immutable float omt = 1-t; 7305 immutable float omt2 = omt*omt; 7306 immutable float t2 = t*t; 7307 immutable float omt3 = omt2*omt; 7308 immutable float t3 = t2*t; 7309 7310 immutable float tx = 7311 x0*omt3+ 7312 x1*3.0f*omt2*t+ 7313 x2*3.0f*omt*t2+ 7314 x3*t3; 7315 7316 return (tx > x ? 1 : 0); 7317 } 7318 } 7319 7320 // Finds the closest point on a line to a given point 7321 void nvg__closestLine (const(float)* points, float x, float y, float* closest, float* ot) { 7322 immutable float x1 = points[0]; 7323 immutable float y1 = points[1]; 7324 immutable float x2 = points[2]; 7325 immutable float y2 = points[3]; 7326 immutable float pqx = x2-x1; 7327 immutable float pqz = y2-y1; 7328 immutable float dx = x-x1; 7329 immutable float dz = y-y1; 7330 immutable float d = pqx*pqx+pqz*pqz; 7331 float t = pqx*dx+pqz*dz; 7332 if (d > 0) t /= d; 7333 if (t < 0) t = 0; else if (t > 1) t = 1; 7334 closest[0] = x1+t*pqx; 7335 closest[1] = y1+t*pqz; 7336 *ot = t; 7337 } 7338 7339 // Finds the closest point on a curve for a given point (x,y). 7340 // Assumes that the curve has no dx==0 or dy==0 inflection points. 7341 void nvg__closestBezier (const(float)* points, float x, float y, float* closest, float *ot) { 7342 immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0]; 7343 immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1]; 7344 7345 // This assumes that the curve has no dy=0 inflection points. 7346 7347 // Initial t guess 7348 float t = 0.5f; 7349 7350 // A few Newton iterations 7351 for (int iter = 0; iter < 6; ++iter) { 7352 immutable float omt = 1-t; 7353 immutable float omt2 = omt*omt; 7354 immutable float t2 = t*t; 7355 immutable float omt3 = omt2*omt; 7356 immutable float t3 = t2*t; 7357 7358 immutable float ty = 7359 y0*omt3+ 7360 y1*3.0f*omt2*t+ 7361 y2*3.0f*omt*t2+ 7362 y3*t3; 7363 7364 immutable float tx = 7365 x0*omt3+ 7366 x1*3.0f*omt2*t+ 7367 x2*3.0f*omt*t2+ 7368 x3*t3; 7369 7370 // Newton iteration 7371 immutable float dty = 7372 3.0f*omt2*(y1-y0)+ 7373 6.0f*omt*t*(y2-y1)+ 7374 3.0f*t2*(y3-y2); 7375 7376 immutable float ddty = 7377 6.0f*omt*(y2-2.0f*y1+y0)+ 7378 6.0f*t*(y3-2.0f*y2+y1); 7379 7380 immutable float dtx = 7381 3.0f*omt2*(x1-x0)+ 7382 6.0f*omt*t*(x2-x1)+ 7383 3.0f*t2*(x3-x2); 7384 7385 immutable float ddtx = 7386 6.0f*omt*(x2-2.0f*x1+x0)+ 7387 6.0f*t*(x3-2.0f*x2+x1); 7388 7389 immutable float errorx = tx-x; 7390 immutable float errory = ty-y; 7391 7392 immutable float n = errorx*dtx+errory*dty; 7393 if (n == 0) break; 7394 7395 immutable float d = dtx*dtx+dty*dty+errorx*ddtx+errory*ddty; 7396 if (d != 0) t = t-n/d; else break; 7397 } 7398 7399 t = nvg__max(0, nvg__min(1.0, t)); 7400 *ot = t; 7401 { 7402 immutable float omt = 1-t; 7403 immutable float omt2 = omt*omt; 7404 immutable float t2 = t*t; 7405 immutable float omt3 = omt2*omt; 7406 immutable float t3 = t2*t; 7407 7408 immutable float ty = 7409 y0*omt3+ 7410 y1*3.0f*omt2*t+ 7411 y2*3.0f*omt*t2+ 7412 y3*t3; 7413 7414 immutable float tx = 7415 x0*omt3+ 7416 x1*3.0f*omt2*t+ 7417 x2*3.0f*omt*t2+ 7418 x3*t3; 7419 7420 closest[0] = tx; 7421 closest[1] = ty; 7422 } 7423 } 7424 7425 // Returns: 7426 // 1 If (x,y) is contained by the stroke of the path 7427 // 0 If (x,y) is not contained by the path. 7428 int nvg__pickSubPathStroke (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, float strokeWidth, int lineCap, int lineJoin) { 7429 if (!nvg__pointInBounds(x, y, psp.bounds)) return 0; 7430 if (psp.firstSegment == -1) return 0; 7431 7432 float[2] closest = void; 7433 float[2] d = void; 7434 float t = void; 7435 7436 // trace a line from x,y out along the positive x axis and count the number of intersections 7437 int nsegments = psp.nsegments; 7438 const(NVGsegment)* seg = ps.segments+psp.firstSegment; 7439 const(NVGsegment)* prevseg = (psp.closed ? &ps.segments[psp.firstSegment+nsegments-1] : null); 7440 immutable float strokeWidthSqd = strokeWidth*strokeWidth; 7441 7442 for (int s = 0; s < nsegments; ++s, prevseg = seg, ++seg) { 7443 if (nvg__pointInBounds(x, y, seg.bounds)) { 7444 // Line potentially hits stroke. 7445 switch (seg.type) { 7446 case Command.LineTo: 7447 nvg__closestLine(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t); 7448 break; 7449 case Command.BezierTo: 7450 nvg__closestBezier(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t); 7451 break; 7452 default: 7453 continue; 7454 } 7455 7456 d.ptr[0] = x-closest.ptr[0]; 7457 d.ptr[1] = y-closest.ptr[1]; 7458 7459 if ((t >= NVGPickEPS && t <= 1.0f-NVGPickEPS) || 7460 (seg.flags&(NVGSegmentFlags.Corner|NVGSegmentFlags.Cap|NVGSegmentFlags.Endcap)) == 0 || 7461 (lineJoin == NVGLineCap.Round)) 7462 { 7463 // Closest point is in the middle of the line/curve, at a rounded join/cap 7464 // or at a smooth join 7465 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1]; 7466 if (distSqd < strokeWidthSqd) return 1; 7467 } else if ((t > 1.0f-NVGPickEPS && (seg.flags&NVGSegmentFlags.Endcap)) || 7468 (t < NVGPickEPS && (seg.flags&NVGSegmentFlags.Cap))) { 7469 switch (lineCap) { 7470 case NVGLineCap.Butt: 7471 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1]; 7472 immutable float dirD = (t < NVGPickEPS ? 7473 -(d.ptr[0]*seg.startDir.ptr[0]+d.ptr[1]*seg.startDir.ptr[1]) : 7474 d.ptr[0]*seg.endDir.ptr[0]+d.ptr[1]*seg.endDir.ptr[1]); 7475 if (dirD < -NVGPickEPS && distSqd < strokeWidthSqd) return 1; 7476 break; 7477 case NVGLineCap.Square: 7478 if (nvg__absf(d.ptr[0]) < strokeWidth && nvg__absf(d.ptr[1]) < strokeWidth) return 1; 7479 break; 7480 case NVGLineCap.Round: 7481 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1]; 7482 if (distSqd < strokeWidthSqd) return 1; 7483 break; 7484 default: 7485 break; 7486 } 7487 } else if (seg.flags&NVGSegmentFlags.Corner) { 7488 // Closest point is at a corner 7489 const(NVGsegment)* seg0, seg1; 7490 7491 if (t < NVGPickEPS) { 7492 seg0 = prevseg; 7493 seg1 = seg; 7494 } else { 7495 seg0 = seg; 7496 seg1 = (s == nsegments-1 ? &ps.segments[psp.firstSegment] : seg+1); 7497 } 7498 7499 if (!(seg1.flags&NVGSegmentFlags.Bevel)) { 7500 immutable float prevNDist = -seg0.endDir.ptr[1]*d.ptr[0]+seg0.endDir.ptr[0]*d.ptr[1]; 7501 immutable float curNDist = seg1.startDir.ptr[1]*d.ptr[0]-seg1.startDir.ptr[0]*d.ptr[1]; 7502 if (nvg__absf(prevNDist) < strokeWidth && nvg__absf(curNDist) < strokeWidth) return 1; 7503 } else { 7504 d.ptr[0] -= -seg1.startDir.ptr[1]*strokeWidth; 7505 d.ptr[1] -= +seg1.startDir.ptr[0]*strokeWidth; 7506 if (seg1.miterDir.ptr[0]*d.ptr[0]+seg1.miterDir.ptr[1]*d.ptr[1] < 0) return 1; 7507 } 7508 } 7509 } 7510 } 7511 7512 return 0; 7513 } 7514 7515 // Returns: 7516 // 1 If (x,y) is contained by the path and the path is solid. 7517 // -1 If (x,y) is contained by the path and the path is a hole. 7518 // 0 If (x,y) is not contained by the path. 7519 int nvg__pickSubPath (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, bool evenOddMode) { 7520 if (!nvg__pointInBounds(x, y, psp.bounds)) return 0; 7521 if (psp.firstSegment == -1) return 0; 7522 7523 const(NVGsegment)* seg = &ps.segments[psp.firstSegment]; 7524 int nsegments = psp.nsegments; 7525 int nintersections = 0; 7526 7527 // trace a line from x,y out along the positive x axis and count the number of intersections 7528 for (int s = 0; s < nsegments; ++s, ++seg) { 7529 if ((seg.bounds.ptr[1]-NVGPickEPS) < y && 7530 (seg.bounds.ptr[3]-NVGPickEPS) > y && 7531 seg.bounds.ptr[2] > x) 7532 { 7533 // Line hits the box. 7534 switch (seg.type) { 7535 case Command.LineTo: 7536 if (seg.bounds.ptr[0] > x) { 7537 // line originates outside the box 7538 ++nintersections; 7539 } else { 7540 // line originates inside the box 7541 nintersections += nvg__intersectLine(&ps.points[seg.firstPoint*2], x, y); 7542 } 7543 break; 7544 case Command.BezierTo: 7545 if (seg.bounds.ptr[0] > x) { 7546 // line originates outside the box 7547 ++nintersections; 7548 } else { 7549 // line originates inside the box 7550 nintersections += nvg__intersectBezier(&ps.points[seg.firstPoint*2], x, y); 7551 } 7552 break; 7553 default: 7554 break; 7555 } 7556 } 7557 } 7558 7559 if (evenOddMode) { 7560 return nintersections; 7561 } else { 7562 return (nintersections&1 ? (psp.winding == NVGSolidity.Solid ? 1 : -1) : 0); 7563 } 7564 } 7565 7566 bool nvg__pickPath (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) { 7567 int pickCount = 0; 7568 const(NVGpickSubPath)* psp = pp.subPaths; 7569 while (psp !is null) { 7570 pickCount += nvg__pickSubPath(ps, psp, x, y, pp.evenOddMode); 7571 psp = psp.next; 7572 } 7573 return ((pp.evenOddMode ? pickCount&1 : pickCount) != 0); 7574 } 7575 7576 bool nvg__pickPathStroke (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) { 7577 const(NVGpickSubPath)* psp = pp.subPaths; 7578 while (psp !is null) { 7579 if (nvg__pickSubPathStroke(ps, psp, x, y, pp.strokeWidth, pp.lineCap, pp.lineJoin)) return true; 7580 psp = psp.next; 7581 } 7582 return false; 7583 } 7584 7585 bool nvg__pickPathTestBounds (NVGContext ctx, const NVGpickScene* ps, const NVGpickPath* pp, float x, float y) { 7586 if (nvg__pointInBounds(x, y, pp.bounds)) { 7587 //{ import core.stdc.stdio; printf(" (0): in bounds!\n"); } 7588 if (pp.flags&NVGPathFlags.Scissor) { 7589 const(float)* scissor = &ps.points[pp.scissor*2]; 7590 // untransform scissor translation 7591 float stx = void, sty = void; 7592 ctx.gpuUntransformPoint(&stx, &sty, scissor[4], scissor[5]); 7593 immutable float rx = x-stx; 7594 immutable float ry = y-sty; 7595 //{ 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]); } 7596 if (nvg__absf((scissor[0]*rx)+(scissor[1]*ry)) > scissor[6] || 7597 nvg__absf((scissor[2]*rx)+(scissor[3]*ry)) > scissor[7]) 7598 { 7599 //{ import core.stdc.stdio; printf(" (1): scissor reject!\n"); } 7600 return false; 7601 } 7602 } 7603 return true; 7604 } 7605 return false; 7606 } 7607 7608 int nvg__countBitsUsed (uint v) pure { 7609 pragma(inline, true); 7610 import core.bitop : bsr; 7611 return (v != 0 ? bsr(v)+1 : 0); 7612 } 7613 7614 void nvg__pickSceneInsert (NVGpickScene* ps, NVGpickPath* pp) { 7615 if (ps is null || pp is null) return; 7616 7617 int[4] cellbounds; 7618 int base = ps.nlevels-1; 7619 int level; 7620 int levelwidth; 7621 int levelshift; 7622 int levelx; 7623 int levely; 7624 NVGpickPath** cell = null; 7625 7626 // Bit tricks for inserting into an implicit quadtree. 7627 7628 // Calc bounds of path in cells at the lowest level 7629 cellbounds.ptr[0] = cast(int)(pp.bounds.ptr[0]/ps.xdim); 7630 cellbounds.ptr[1] = cast(int)(pp.bounds.ptr[1]/ps.ydim); 7631 cellbounds.ptr[2] = cast(int)(pp.bounds.ptr[2]/ps.xdim); 7632 cellbounds.ptr[3] = cast(int)(pp.bounds.ptr[3]/ps.ydim); 7633 7634 // Find which bits differ between the min/max x/y coords 7635 cellbounds.ptr[0] ^= cellbounds.ptr[2]; 7636 cellbounds.ptr[1] ^= cellbounds.ptr[3]; 7637 7638 // Use the number of bits used (countBitsUsed(x) == sizeof(int) * 8 - clz(x); 7639 // to calculate the level to insert at (the level at which the bounds fit in a single cell) 7640 level = nvg__min(base-nvg__countBitsUsed(cellbounds.ptr[0]), base-nvg__countBitsUsed(cellbounds.ptr[1])); 7641 if (level < 0) level = 0; 7642 //{ 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]); } 7643 //level = 0; 7644 7645 // Find the correct cell in the chosen level, clamping to the edges. 7646 levelwidth = 1<<level; 7647 levelshift = (ps.nlevels-level)-1; 7648 levelx = nvg__clamp(cellbounds.ptr[2]>>levelshift, 0, levelwidth-1); 7649 levely = nvg__clamp(cellbounds.ptr[3]>>levelshift, 0, levelwidth-1); 7650 7651 // Insert the path into the linked list at that cell. 7652 cell = &ps.levels[level][levely*levelwidth+levelx]; 7653 7654 pp.cellnext = *cell; 7655 *cell = pp; 7656 7657 if (ps.paths is null) ps.lastPath = pp; 7658 pp.next = ps.paths; 7659 ps.paths = pp; 7660 7661 // Store the order (depth) of the path for picking ops. 7662 pp.order = cast(short)ps.npaths; 7663 ++ps.npaths; 7664 } 7665 7666 void nvg__pickBeginFrame (NVGContext ctx, int width, int height) { 7667 NVGpickScene* ps = nvg__pickSceneGet(ctx); 7668 7669 //NVG_PICK_DEBUG_NEWFRAME(); 7670 7671 // Return all paths & sub paths from last frame to the free list 7672 while (ps.paths !is null) { 7673 NVGpickPath* pp = ps.paths.next; 7674 nvg__freePickPath(ps, ps.paths); 7675 ps.paths = pp; 7676 } 7677 7678 ps.paths = null; 7679 ps.npaths = 0; 7680 7681 // Store the screen metrics for the quadtree 7682 ps.width = width; 7683 ps.height = height; 7684 7685 immutable float lowestSubDiv = cast(float)(1<<(ps.nlevels-1)); 7686 ps.xdim = cast(float)width/lowestSubDiv; 7687 ps.ydim = cast(float)height/lowestSubDiv; 7688 7689 // Allocate the quadtree if required. 7690 if (ps.levels is null) { 7691 int ncells = 1; 7692 7693 ps.levels = cast(NVGpickPath***)malloc((NVGpickPath**).sizeof*ps.nlevels); 7694 for (int l = 0; l < ps.nlevels; ++l) { 7695 int leveldim = 1<<l; 7696 ncells += leveldim*leveldim; 7697 } 7698 7699 ps.levels[0] = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ncells); 7700 7701 int cell = 1; 7702 for (int l = 1; l < ps.nlevels; ++l) { 7703 ps.levels[l] = &ps.levels[0][cell]; 7704 int leveldim = 1<<l; 7705 cell += leveldim*leveldim; 7706 } 7707 7708 ps.ncells = ncells; 7709 } 7710 memset(ps.levels[0], 0, ps.ncells*(NVGpickPath*).sizeof); 7711 7712 // Allocate temporary storage for nvgHitTestAll results if required. 7713 if (ps.picked is null) { 7714 ps.cpicked = 16; 7715 ps.picked = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ps.cpicked); 7716 } 7717 7718 ps.npoints = 0; 7719 ps.nsegments = 0; 7720 } 7721 } // nothrow @trusted @nogc 7722 7723 7724 /// Return outline of the current path. Returned outline is not flattened. 7725 /// Group: paths 7726 public NVGPathOutline getCurrPathOutline (NVGContext ctx) nothrow @trusted @nogc { 7727 if (ctx is null || !ctx.contextAlive || ctx.ncommands == 0) return NVGPathOutline.init; 7728 7729 auto res = NVGPathOutline.createNew(); 7730 7731 const(float)[] acommands = ctx.commands[0..ctx.ncommands]; 7732 int ncommands = cast(int)acommands.length; 7733 const(float)* commands = acommands.ptr; 7734 7735 float cx = 0, cy = 0; 7736 float[2] start = void; 7737 float[4] totalBounds = [float.max, float.max, -float.max, -float.max]; 7738 float[8] bcp = void; // bezier curve points; used to calculate bounds 7739 7740 void addToBounds (in float x, in float y) nothrow @trusted @nogc { 7741 totalBounds.ptr[0] = nvg__min(totalBounds.ptr[0], x); 7742 totalBounds.ptr[1] = nvg__min(totalBounds.ptr[1], y); 7743 totalBounds.ptr[2] = nvg__max(totalBounds.ptr[2], x); 7744 totalBounds.ptr[3] = nvg__max(totalBounds.ptr[3], y); 7745 } 7746 7747 bool hasPoints = false; 7748 7749 void closeIt () nothrow @trusted @nogc { 7750 if (!hasPoints) return; 7751 if (cx != start.ptr[0] || cy != start.ptr[1]) { 7752 res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo); 7753 res.ds.putArgs(start[]); 7754 cx = start.ptr[0]; 7755 cy = start.ptr[1]; 7756 addToBounds(cx, cy); 7757 } 7758 } 7759 7760 int i = 0; 7761 while (i < ncommands) { 7762 int cmd = cast(int)commands[i++]; 7763 switch (cmd) { 7764 case Command.MoveTo: // one coordinate pair 7765 const(float)* tfxy = commands+i; 7766 i += 2; 7767 // add command 7768 res.ds.putCommand(NVGPathOutline.Command.Kind.MoveTo); 7769 res.ds.putArgs(tfxy[0..2]); 7770 // new starting point 7771 start.ptr[0..2] = tfxy[0..2]; 7772 cx = tfxy[0]; 7773 cy = tfxy[0]; 7774 addToBounds(cx, cy); 7775 hasPoints = true; 7776 break; 7777 case Command.LineTo: // one coordinate pair 7778 const(float)* tfxy = commands+i; 7779 i += 2; 7780 // add command 7781 res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo); 7782 res.ds.putArgs(tfxy[0..2]); 7783 cx = tfxy[0]; 7784 cy = tfxy[0]; 7785 addToBounds(cx, cy); 7786 hasPoints = true; 7787 break; 7788 case Command.BezierTo: // three coordinate pairs 7789 const(float)* tfxy = commands+i; 7790 i += 3*2; 7791 // add command 7792 res.ds.putCommand(NVGPathOutline.Command.Kind.BezierTo); 7793 res.ds.putArgs(tfxy[0..6]); 7794 // bounds 7795 bcp.ptr[0] = cx; 7796 bcp.ptr[1] = cy; 7797 bcp.ptr[2..8] = tfxy[0..6]; 7798 nvg__bezierBounds(bcp.ptr, totalBounds); 7799 cx = tfxy[4]; 7800 cy = tfxy[5]; 7801 hasPoints = true; 7802 break; 7803 case Command.Close: 7804 closeIt(); 7805 hasPoints = false; 7806 break; 7807 case Command.Winding: 7808 //psp.winding = cast(short)cast(int)commands[i]; 7809 i += 1; 7810 break; 7811 default: 7812 break; 7813 } 7814 } 7815 7816 res.ds.bounds[] = totalBounds[]; 7817 return res; 7818 } 7819 7820 7821 // ////////////////////////////////////////////////////////////////////////// // 7822 // Text 7823 7824 /** Creates font by loading it from the disk from specified file name. 7825 * Returns handle to the font or FONS_INVALID (aka -1) on error. 7826 * Use "fontname:noaa" as [name] to turn off antialiasing (if font driver supports that). 7827 * 7828 * On POSIX systems it is possible to use fontconfig font names too. 7829 * `:noaa` in font path is still allowed, but it must be the last option. 7830 * 7831 * Group: text_api 7832 */ 7833 public int createFont (NVGContext ctx, const(char)[] name, const(char)[] path) nothrow @trusted { 7834 return ctx.fs.addFont(name, path, ctx.params.fontAA); 7835 } 7836 7837 /** Creates font by loading it from the specified memory chunk. 7838 * Returns handle to the font or FONS_INVALID (aka -1) on error. 7839 * Won't free data on error. 7840 * 7841 * Group: text_api 7842 */ 7843 public int createFontMem (NVGContext ctx, const(char)[] name, ubyte* data, int ndata, bool freeData) nothrow @trusted @nogc { 7844 return ctx.fs.addFontMem(name, data, ndata, freeData, ctx.params.fontAA); 7845 } 7846 7847 /// Add fonts from another context. 7848 /// This is more effective than reloading fonts, 'cause font data will be shared. 7849 /// Group: text_api 7850 public void addFontsFrom (NVGContext ctx, NVGContext source) nothrow @trusted @nogc { 7851 if (ctx is null || source is null) return; 7852 ctx.fs.addFontsFrom(source.fs); 7853 } 7854 7855 /// Adds the font with id `fallback` onto the font with id `base` for characters 7856 /// that are not found in `base`'s character set. May be called multiple times, 7857 /// up to an internal maximum count per font. 7858 /// Group: text_api 7859 /// Returns: `true` if successful, `false` if not 7860 public bool addFallbackFont (NVGContext ctx, int base, int fallback) nothrow @trusted @nogc { 7861 if (ctx is null) return false; 7862 return ctx.fs.addFallbackFont(base, fallback); 7863 } 7864 7865 /// Finds a loaded font of specified name, and returns handle to it, or FONS_INVALID (aka -1) if the font is not found. 7866 /// Group: text_api 7867 public int findFont (NVGContext ctx, const(char)[] name) nothrow @trusted @nogc { 7868 pragma(inline, true); 7869 return (name.length == 0 ? FONS_INVALID : ctx.fs.getFontByName(name)); 7870 } 7871 7872 /// Sets the font size of current text style. 7873 /// Group: text_api 7874 public void fontSize (NVGContext ctx, float size) nothrow @trusted @nogc { 7875 pragma(inline, true); 7876 nvg__getState(ctx).fontSize = size; 7877 } 7878 7879 /// Gets the font size of current text style. 7880 /// Group: text_api 7881 public float fontSize (NVGContext ctx) nothrow @trusted @nogc { 7882 pragma(inline, true); 7883 return nvg__getState(ctx).fontSize; 7884 } 7885 7886 /// Sets the blur of current text style. 7887 /// Group: text_api 7888 public void fontBlur (NVGContext ctx, float blur) nothrow @trusted @nogc { 7889 pragma(inline, true); 7890 nvg__getState(ctx).fontBlur = blur; 7891 } 7892 7893 /// Gets the blur of current text style. 7894 /// Group: text_api 7895 public float fontBlur (NVGContext ctx) nothrow @trusted @nogc { 7896 pragma(inline, true); 7897 return nvg__getState(ctx).fontBlur; 7898 } 7899 7900 /// Sets the letter spacing of current text style. 7901 /// Group: text_api 7902 public void textLetterSpacing (NVGContext ctx, float spacing) nothrow @trusted @nogc { 7903 pragma(inline, true); 7904 nvg__getState(ctx).letterSpacing = spacing; 7905 } 7906 7907 /// Gets the letter spacing of current text style. 7908 /// Group: text_api 7909 public float textLetterSpacing (NVGContext ctx) nothrow @trusted @nogc { 7910 pragma(inline, true); 7911 return nvg__getState(ctx).letterSpacing; 7912 } 7913 7914 /// Sets the proportional line height of current text style. The line height is specified as multiple of font size. 7915 /// Group: text_api 7916 public void textLineHeight (NVGContext ctx, float lineHeight) nothrow @trusted @nogc { 7917 pragma(inline, true); 7918 nvg__getState(ctx).lineHeight = lineHeight; 7919 } 7920 7921 /// Gets the proportional line height of current text style. The line height is specified as multiple of font size. 7922 /// Group: text_api 7923 public float textLineHeight (NVGContext ctx) nothrow @trusted @nogc { 7924 pragma(inline, true); 7925 return nvg__getState(ctx).lineHeight; 7926 } 7927 7928 /// Sets the text align of current text style, see [NVGTextAlign] for options. 7929 /// Group: text_api 7930 public void textAlign (NVGContext ctx, NVGTextAlign talign) nothrow @trusted @nogc { 7931 pragma(inline, true); 7932 nvg__getState(ctx).textAlign = talign; 7933 } 7934 7935 /// Ditto. 7936 public void textAlign (NVGContext ctx, NVGTextAlign.H h) nothrow @trusted @nogc { 7937 pragma(inline, true); 7938 nvg__getState(ctx).textAlign.horizontal = h; 7939 } 7940 7941 /// Ditto. 7942 public void textAlign (NVGContext ctx, NVGTextAlign.V v) nothrow @trusted @nogc { 7943 pragma(inline, true); 7944 nvg__getState(ctx).textAlign.vertical = v; 7945 } 7946 7947 /// Ditto. 7948 public void textAlign (NVGContext ctx, NVGTextAlign.H h, NVGTextAlign.V v) nothrow @trusted @nogc { 7949 pragma(inline, true); 7950 nvg__getState(ctx).textAlign.reset(h, v); 7951 } 7952 7953 /// Ditto. 7954 public void textAlign (NVGContext ctx, NVGTextAlign.V v, NVGTextAlign.H h) nothrow @trusted @nogc { 7955 pragma(inline, true); 7956 nvg__getState(ctx).textAlign.reset(h, v); 7957 } 7958 7959 /// Gets the text align of current text style, see [NVGTextAlign] for options. 7960 /// Group: text_api 7961 public NVGTextAlign textAlign (NVGContext ctx) nothrow @trusted @nogc { 7962 pragma(inline, true); 7963 return nvg__getState(ctx).textAlign; 7964 } 7965 7966 /// Sets the font face based on specified id of current text style. 7967 /// Group: text_api 7968 public void fontFaceId (NVGContext ctx, int font) nothrow @trusted @nogc { 7969 pragma(inline, true); 7970 nvg__getState(ctx).fontId = font; 7971 } 7972 7973 /// Gets the font face based on specified id of current text style. 7974 /// Group: text_api 7975 public int fontFaceId (NVGContext ctx) nothrow @trusted @nogc { 7976 pragma(inline, true); 7977 return nvg__getState(ctx).fontId; 7978 } 7979 7980 /** Sets the font face based on specified name of current text style. 7981 * 7982 * The underlying implementation is using O(1) data structure to lookup 7983 * font names, so you probably should use this function instead of [fontFaceId] 7984 * to make your code more robust and less error-prone. 7985 * 7986 * Group: text_api 7987 */ 7988 public void fontFace (NVGContext ctx, const(char)[] font) nothrow @trusted @nogc { 7989 pragma(inline, true); 7990 nvg__getState(ctx).fontId = ctx.fs.getFontByName(font); 7991 } 7992 7993 static if (is(typeof(&fons__nvg__toPath))) { 7994 public enum NanoVegaHasCharToPath = true; /// 7995 } else { 7996 public enum NanoVegaHasCharToPath = false; /// 7997 } 7998 7999 /// Adds glyph outlines to the current path. Vertical 0 is baseline. 8000 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead. 8001 /// Returns `false` if there is no such glyph, or current font is not scalable. 8002 /// Group: text_api 8003 public bool charToPath (NVGContext ctx, dchar dch, float[] bounds=null) nothrow @trusted @nogc { 8004 NVGstate* state = nvg__getState(ctx); 8005 ctx.fs.fontId = state.fontId; 8006 return ctx.fs.toPath(ctx, dch, bounds); 8007 } 8008 8009 static if (is(typeof(&fons__nvg__bounds))) { 8010 public enum NanoVegaHasCharPathBounds = true; /// 8011 } else { 8012 public enum NanoVegaHasCharPathBounds = false; /// 8013 } 8014 8015 /// Returns bounds of the glyph outlines. Vertical 0 is baseline. 8016 /// The glyph is not scaled in any way. 8017 /// Returns `false` if there is no such glyph, or current font is not scalable. 8018 /// Group: text_api 8019 public bool charPathBounds (NVGContext ctx, dchar dch, float[] bounds) nothrow @trusted @nogc { 8020 NVGstate* state = nvg__getState(ctx); 8021 ctx.fs.fontId = state.fontId; 8022 return ctx.fs.getPathBounds(dch, bounds); 8023 } 8024 8025 /** [charOutline] will return [NVGPathOutline]. 8026 8027 some usage samples: 8028 8029 --- 8030 float[4] bounds = void; 8031 8032 nvg.scale(0.5, 0.5); 8033 nvg.translate(500, 800); 8034 nvg.evenOddFill; 8035 8036 nvg.newPath(); 8037 nvg.charToPath('&', bounds[]); 8038 conwriteln(bounds[]); 8039 nvg.fillPaint(nvg.linearGradient(0, 0, 600, 600, NVGColor("#f70"), NVGColor("#ff0"))); 8040 nvg.strokeColor(NVGColor("#0f0")); 8041 nvg.strokeWidth = 3; 8042 nvg.fill(); 8043 nvg.stroke(); 8044 // glyph bounds 8045 nvg.newPath(); 8046 nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]); 8047 nvg.strokeColor(NVGColor("#00f")); 8048 nvg.stroke(); 8049 8050 nvg.newPath(); 8051 nvg.charToPath('g', bounds[]); 8052 conwriteln(bounds[]); 8053 nvg.fill(); 8054 nvg.strokeColor(NVGColor("#0f0")); 8055 nvg.stroke(); 8056 // glyph bounds 8057 nvg.newPath(); 8058 nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]); 8059 nvg.strokeColor(NVGColor("#00f")); 8060 nvg.stroke(); 8061 8062 nvg.newPath(); 8063 nvg.moveTo(0, 0); 8064 nvg.lineTo(600, 0); 8065 nvg.strokeColor(NVGColor("#0ff")); 8066 nvg.stroke(); 8067 8068 if (auto ol = nvg.charOutline('Q')) { 8069 scope(exit) ol.kill(); 8070 nvg.newPath(); 8071 conwriteln("==== length: ", ol.length, " ===="); 8072 foreach (const ref cmd; ol.commands) { 8073 //conwriteln(" ", cmd.code, ": ", cmd.args[]); 8074 assert(cmd.valid); 8075 final switch (cmd.code) { 8076 case cmd.Kind.MoveTo: nvg.moveTo(cmd.args[0], cmd.args[1]); break; 8077 case cmd.Kind.LineTo: nvg.lineTo(cmd.args[0], cmd.args[1]); break; 8078 case cmd.Kind.QuadTo: nvg.quadTo(cmd.args[0], cmd.args[1], cmd.args[2], cmd.args[3]); break; 8079 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; 8080 } 8081 } 8082 nvg.strokeColor(NVGColor("#f00")); 8083 nvg.stroke(); 8084 } 8085 --- 8086 8087 Group: text_api 8088 */ 8089 public struct NVGPathOutline { 8090 private nothrow @trusted @nogc: 8091 struct DataStore { 8092 uint rc; // refcount 8093 ubyte* data; 8094 uint used; 8095 uint size; 8096 uint ccount; // number of commands 8097 float[4] bounds = 0; /// outline bounds 8098 nothrow @trusted @nogc: 8099 void putBytes (const(void)[] b) { 8100 if (b.length == 0) return; 8101 if (b.length >= int.max/8) assert(0, "NanoVega: out of memory"); 8102 if (int.max/8-used < b.length) assert(0, "NanoVega: out of memory"); 8103 if (used+cast(uint)b.length > size) { 8104 import core.stdc.stdlib : realloc; 8105 uint newsz = size; 8106 while (newsz < used+cast(uint)b.length) newsz = (newsz == 0 ? 1024 : newsz < 32768 ? newsz*2 : newsz+8192); 8107 assert(used+cast(uint)b.length <= newsz); 8108 data = cast(ubyte*)realloc(data, newsz); 8109 if (data is null) assert(0, "NanoVega: out of memory"); 8110 size = newsz; 8111 } 8112 import core.stdc.string : memcpy; 8113 memcpy(data+used, b.ptr, b.length); 8114 used += cast(uint)b.length; 8115 } 8116 void putCommand (ubyte cmd) { pragma(inline, true); ++ccount; putBytes((&cmd)[0..1]); } 8117 void putArgs (const(float)[] f...) { pragma(inline, true); putBytes(f[]); } 8118 } 8119 8120 static void incRef (DataStore* ds) { 8121 pragma(inline, true); 8122 if (ds !is null) { 8123 ++ds.rc; 8124 //{ import core.stdc.stdio; printf("ods(%p): incref: newrc=%u\n", ds, ds.rc); } 8125 } 8126 } 8127 8128 static void decRef (DataStore* ds) { 8129 version(aliced) pragma(inline, true); 8130 if (ds !is null) { 8131 //{ import core.stdc.stdio; printf("ods(%p): decref: newrc=%u\n", ds, ds.rc-1); } 8132 if (--ds.rc == 0) { 8133 import core.stdc.stdlib : free; 8134 import core.stdc.string : memset; 8135 if (ds.data !is null) free(ds.data); 8136 memset(ds, 0, DataStore.sizeof); // just in case 8137 free(ds); 8138 //{ import core.stdc.stdio; printf(" ods(%p): killed.\n"); } 8139 } 8140 } 8141 } 8142 8143 private: 8144 static NVGPathOutline createNew () { 8145 import core.stdc.stdlib : malloc; 8146 import core.stdc.string : memset; 8147 auto ds = cast(DataStore*)malloc(DataStore.sizeof); 8148 if (ds is null) assert(0, "NanoVega: out of memory"); 8149 memset(ds, 0, DataStore.sizeof); 8150 ds.rc = 1; 8151 NVGPathOutline res; 8152 res.dsaddr = cast(usize)ds; 8153 return res; 8154 } 8155 8156 private: 8157 usize dsaddr; // fool GC 8158 8159 @property inout(DataStore)* ds () inout pure { pragma(inline, true); return cast(DataStore*)dsaddr; } 8160 8161 public: 8162 /// commands 8163 static struct Command { 8164 /// 8165 enum Kind : ubyte { 8166 MoveTo, /// 8167 LineTo, /// 8168 QuadTo, /// 8169 BezierTo, /// 8170 End, /// no more commands (this command is not `valid`!) 8171 8172 } 8173 Kind code; /// 8174 const(float)[] args; /// 8175 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (code >= Kind.min && code < Kind.End && args.length >= 2); } /// 8176 8177 static uint arglen (Kind code) pure nothrow @safe @nogc { 8178 pragma(inline, true); 8179 return 8180 code == Kind.MoveTo || code == Kind.LineTo ? 2 : 8181 code == Kind.QuadTo ? 4 : 8182 code == Kind.BezierTo ? 6 : 8183 0; 8184 } 8185 8186 /// perform NanoVega command with stored data. 8187 void perform (NVGContext ctx) const nothrow @trusted @nogc { 8188 if (ctx is null) return; 8189 final switch (code) { 8190 case Kind.MoveTo: if (args.length > 1) ctx.moveTo(args.ptr[0..2]); break; 8191 case Kind.LineTo: if (args.length > 1) ctx.lineTo(args.ptr[0..2]); break; 8192 case Kind.QuadTo: if (args.length > 3) ctx.quadTo(args.ptr[0..4]); break; 8193 case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(args.ptr[0..6]); break; 8194 case Kind.End: break; 8195 } 8196 } 8197 8198 /// perform NanoVega command with stored data, transforming points with [xform] transformation matrix. 8199 void perform() (NVGContext ctx, const scope auto ref NVGMatrix xform) const nothrow @trusted @nogc { 8200 if (ctx is null || !valid) return; 8201 float[6] pts = void; 8202 pts[0..args.length] = args[]; 8203 foreach (immutable pidx; 0..args.length/2) xform.point(pts.ptr[pidx*2+0], pts.ptr[pidx*2+1]); 8204 final switch (code) { 8205 case Kind.MoveTo: if (args.length > 1) ctx.moveTo(pts.ptr[0..2]); break; 8206 case Kind.LineTo: if (args.length > 1) ctx.lineTo(pts.ptr[0..2]); break; 8207 case Kind.QuadTo: if (args.length > 3) ctx.quadTo(pts.ptr[0..4]); break; 8208 case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(pts.ptr[0..6]); break; 8209 case Kind.End: break; 8210 } 8211 } 8212 } 8213 8214 public: 8215 /// Create new path with quadratic bezier (first command is MoveTo, second command is QuadTo). 8216 static NVGPathOutline createNewQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) { 8217 auto res = createNew(); 8218 res.ds.putCommand(Command.Kind.MoveTo); 8219 res.ds.putArgs(x0, y0); 8220 res.ds.putCommand(Command.Kind.QuadTo); 8221 res.ds.putArgs(cx, cy, x, y); 8222 return res; 8223 } 8224 8225 /// Create new path with cubic bezier (first command is MoveTo, second command is BezierTo). 8226 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) { 8227 auto res = createNew(); 8228 res.ds.putCommand(Command.Kind.MoveTo); 8229 res.ds.putArgs(x1, y1); 8230 res.ds.putCommand(Command.Kind.BezierTo); 8231 res.ds.putArgs(x2, y2, x3, y3, x4, y4); 8232 return res; 8233 } 8234 8235 public: 8236 this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); } 8237 ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); } 8238 8239 void opAssign() (const scope auto ref NVGPathOutline a) { 8240 incRef(cast(DataStore*)a.dsaddr); 8241 decRef(cast(DataStore*)dsaddr); 8242 dsaddr = a.dsaddr; 8243 } 8244 8245 /// Clear storage. 8246 void clear () { 8247 pragma(inline, true); 8248 decRef(ds); 8249 dsaddr = 0; 8250 } 8251 8252 /// Is this outline empty? 8253 @property empty () const pure { pragma(inline, true); return (dsaddr == 0 || ds.ccount == 0); } 8254 8255 /// Returns number of commands in outline. 8256 @property int length () const pure { pragma(inline, true); return (dsaddr ? ds.ccount : 0); } 8257 8258 /// Returns "flattened" path. Flattened path consists of only two commands kinds: MoveTo and LineTo. 8259 NVGPathOutline flatten () const { pragma(inline, true); return flattenInternal(null); } 8260 8261 /// Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo. 8262 NVGPathOutline flatten() (const scope auto ref NVGMatrix mt) const { pragma(inline, true); return flattenInternal(&mt); } 8263 8264 // Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo. 8265 private NVGPathOutline flattenInternal (scope NVGMatrix* tfm) const { 8266 import core.stdc.string : memset; 8267 8268 NVGPathOutline res; 8269 if (dsaddr == 0 || ds.ccount == 0) { res = this; return res; } // nothing to do 8270 8271 // check if we need to flatten the path 8272 if (tfm is null) { 8273 bool dowork = false; 8274 foreach (const ref cs; commands) { 8275 if (cs.code != Command.Kind.MoveTo && cs.code != Command.Kind.LineTo) { 8276 dowork = true; 8277 break; 8278 } 8279 } 8280 if (!dowork) { res = this; return res; } // nothing to do 8281 } 8282 8283 NVGcontextinternal ctx; 8284 memset(&ctx, 0, ctx.sizeof); 8285 ctx.cache = nvg__allocPathCache(); 8286 scope(exit) { 8287 import core.stdc.stdlib : free; 8288 nvg__deletePathCache(ctx.cache); 8289 } 8290 8291 ctx.tessTol = 0.25f; 8292 ctx.angleTol = 0; // 0 -- angle tolerance for McSeem Bezier rasterizer 8293 ctx.cuspLimit = 0; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps) 8294 ctx.distTol = 0.01f; 8295 ctx.tesselatortype = NVGTesselation.DeCasteljau; 8296 8297 nvg__addPath(&ctx); // we need this for `nvg__addPoint()` 8298 8299 // has some curves or transformations, convert path 8300 res = createNew(); 8301 float[8] args = void; 8302 8303 res.ds.bounds = [float.max, float.max, -float.max, -float.max]; 8304 8305 float lastX = float.max, lastY = float.max; 8306 bool lastWasMove = false; 8307 8308 void addPoint (float x, float y, Command.Kind cmd=Command.Kind.LineTo) nothrow @trusted @nogc { 8309 if (tfm !is null) tfm.point(x, y); 8310 bool isMove = (cmd == Command.Kind.MoveTo); 8311 if (isMove) { 8312 // moveto 8313 if (lastWasMove && nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return; 8314 } else { 8315 // lineto 8316 if (nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return; 8317 } 8318 lastWasMove = isMove; 8319 lastX = x; 8320 lastY = y; 8321 res.ds.putCommand(cmd); 8322 res.ds.putArgs(x, y); 8323 res.ds.bounds.ptr[0] = nvg__min(res.ds.bounds.ptr[0], x); 8324 res.ds.bounds.ptr[1] = nvg__min(res.ds.bounds.ptr[1], y); 8325 res.ds.bounds.ptr[2] = nvg__max(res.ds.bounds.ptr[2], x); 8326 res.ds.bounds.ptr[3] = nvg__max(res.ds.bounds.ptr[3], y); 8327 } 8328 8329 // sorry for this pasta 8330 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 { 8331 ctx.cache.npoints = 0; 8332 if (ctx.tesselatortype == NVGTesselation.DeCasteljau) { 8333 nvg__tesselateBezier(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner); 8334 } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) { 8335 nvg__tesselateBezierMcSeem(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner); 8336 } else { 8337 nvg__tesselateBezierAFD(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, PointFlag.Corner); 8338 } 8339 // add generated points 8340 foreach (const ref pt; ctx.cache.points[0..ctx.cache.npoints]) addPoint(pt.x, pt.y); 8341 } 8342 8343 void flattenQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) { 8344 flattenBezier( 8345 x0, y0, 8346 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0), 8347 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y), 8348 x, y, 8349 0, 8350 ); 8351 } 8352 8353 float cx = 0, cy = 0; 8354 foreach (const ref cs; commands) { 8355 switch (cs.code) { 8356 case Command.Kind.LineTo: 8357 case Command.Kind.MoveTo: 8358 addPoint(cs.args[0], cs.args[1], cs.code); 8359 cx = cs.args[0]; 8360 cy = cs.args[1]; 8361 break; 8362 case Command.Kind.QuadTo: 8363 flattenQuad(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3]); 8364 cx = cs.args[2]; 8365 cy = cs.args[3]; 8366 break; 8367 case Command.Kind.BezierTo: 8368 flattenBezier(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3], cs.args[4], cs.args[5], 0); 8369 cx = cs.args[4]; 8370 cy = cs.args[5]; 8371 break; 8372 default: 8373 break; 8374 } 8375 } 8376 8377 return res; 8378 } 8379 8380 /// Returns forward range with all glyph commands. 8381 auto commands () const nothrow @trusted @nogc { 8382 static struct Range { 8383 private nothrow @trusted @nogc: 8384 usize dsaddr; 8385 uint cpos; // current position in data 8386 uint cleft; // number of commands left 8387 @property const(ubyte)* data () inout pure { pragma(inline, true); return (dsaddr ? (cast(DataStore*)dsaddr).data : null); } 8388 public: 8389 this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); } 8390 ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); } 8391 void opAssign() (const scope auto ref Range a) { 8392 incRef(cast(DataStore*)a.dsaddr); 8393 decRef(cast(DataStore*)dsaddr); 8394 dsaddr = a.dsaddr; 8395 cpos = a.cpos; 8396 cleft = a.cleft; 8397 } 8398 float[4] bounds () const pure { float[4] res = 0; pragma(inline, true); if (dsaddr) res[] = (cast(DataStore*)dsaddr).bounds[]; return res; } /// outline bounds 8399 @property bool empty () const pure { pragma(inline, true); return (cleft == 0); } 8400 @property int length () const pure { pragma(inline, true); return cleft; } 8401 @property Range save () const { pragma(inline, true); Range res = this; return res; } 8402 @property Command front () const { 8403 Command res = void; 8404 if (cleft > 0) { 8405 res.code = cast(Command.Kind)data[cpos]; 8406 switch (res.code) { 8407 case Command.Kind.MoveTo: 8408 case Command.Kind.LineTo: 8409 res.args = (cast(const(float*))(data+cpos+1))[0..1*2]; 8410 break; 8411 case Command.Kind.QuadTo: 8412 res.args = (cast(const(float*))(data+cpos+1))[0..2*2]; 8413 break; 8414 case Command.Kind.BezierTo: 8415 res.args = (cast(const(float*))(data+cpos+1))[0..3*2]; 8416 break; 8417 default: 8418 res.code = Command.Kind.End; 8419 res.args = null; 8420 break; 8421 } 8422 } else { 8423 res.code = Command.Kind.End; 8424 res.args = null; 8425 } 8426 return res; 8427 } 8428 void popFront () { 8429 if (cleft <= 1) { cleft = 0; return; } // don't waste time skipping last command 8430 --cleft; 8431 switch (data[cpos]) { 8432 case Command.Kind.MoveTo: 8433 case Command.Kind.LineTo: 8434 cpos += 1+1*2*cast(uint)float.sizeof; 8435 break; 8436 case Command.Kind.QuadTo: 8437 cpos += 1+2*2*cast(uint)float.sizeof; 8438 break; 8439 case Command.Kind.BezierTo: 8440 cpos += 1+3*2*cast(uint)float.sizeof; 8441 break; 8442 default: 8443 cleft = 0; 8444 break; 8445 } 8446 } 8447 } 8448 if (dsaddr) { 8449 incRef(cast(DataStore*)dsaddr); // range anchors it 8450 return Range(dsaddr, 0, ds.ccount); 8451 } else { 8452 return Range.init; 8453 } 8454 } 8455 } 8456 8457 public alias NVGGlyphOutline = NVGPathOutline; /// For backwards compatibility. 8458 8459 /// Destroy glyph outiline and free allocated memory. 8460 /// Group: text_api 8461 public void kill (ref NVGPathOutline ol) nothrow @trusted @nogc { 8462 pragma(inline, true); 8463 ol.clear(); 8464 } 8465 8466 static if (is(typeof(&fons__nvg__toOutline))) { 8467 public enum NanoVegaHasCharOutline = true; /// 8468 } else { 8469 public enum NanoVegaHasCharOutline = false; /// 8470 } 8471 8472 /// Returns glyph outlines as array of commands. Vertical 0 is baseline. 8473 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead. 8474 /// Returns `null` if there is no such glyph, or current font is not scalable. 8475 /// Group: text_api 8476 public NVGPathOutline charOutline (NVGContext ctx, dchar dch) nothrow @trusted @nogc { 8477 import core.stdc.stdlib : malloc; 8478 import core.stdc.string : memcpy; 8479 NVGstate* state = nvg__getState(ctx); 8480 ctx.fs.fontId = state.fontId; 8481 auto oline = NVGPathOutline.createNew(); 8482 if (!ctx.fs.toOutline(dch, oline.ds)) oline.clear(); 8483 return oline; 8484 } 8485 8486 8487 float nvg__quantize (float a, float d) pure nothrow @safe @nogc { 8488 pragma(inline, true); 8489 return (cast(int)(a/d+0.5f))*d; 8490 } 8491 8492 float nvg__getFontScale (NVGstate* state) /*pure*/ nothrow @safe @nogc { 8493 pragma(inline, true); 8494 return nvg__min(nvg__quantize(nvg__getAverageScale(state.xform), 0.01f), 4.0f); 8495 } 8496 8497 void nvg__flushTextTexture (NVGContext ctx) nothrow @trusted @nogc { 8498 int[4] dirty = void; 8499 if (ctx.fs.validateTexture(dirty.ptr)) { 8500 auto fontImage = &ctx.fontImages[ctx.fontImageIdx]; 8501 // Update texture 8502 if (fontImage.valid) { 8503 int iw, ih; 8504 const(ubyte)* data = ctx.fs.getTextureData(&iw, &ih); 8505 int x = dirty[0]; 8506 int y = dirty[1]; 8507 int w = dirty[2]-dirty[0]; 8508 int h = dirty[3]-dirty[1]; 8509 ctx.params.renderUpdateTexture(ctx.params.userPtr, fontImage.id, x, y, w, h, data); 8510 } 8511 } 8512 } 8513 8514 bool nvg__allocTextAtlas (NVGContext ctx) nothrow @trusted @nogc { 8515 int iw, ih; 8516 nvg__flushTextTexture(ctx); 8517 if (ctx.fontImageIdx >= NVG_MAX_FONTIMAGES-1) return false; 8518 // if next fontImage already have a texture 8519 if (ctx.fontImages[ctx.fontImageIdx+1].valid) { 8520 ctx.imageSize(ctx.fontImages[ctx.fontImageIdx+1], iw, ih); 8521 } else { 8522 // calculate the new font image size and create it 8523 ctx.imageSize(ctx.fontImages[ctx.fontImageIdx], iw, ih); 8524 if (iw > ih) ih *= 2; else iw *= 2; 8525 if (iw > NVG_MAX_FONTIMAGE_SIZE || ih > NVG_MAX_FONTIMAGE_SIZE) iw = ih = NVG_MAX_FONTIMAGE_SIZE; 8526 ctx.fontImages[ctx.fontImageIdx+1].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, iw, ih, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null); 8527 if (ctx.fontImages[ctx.fontImageIdx+1].id > 0) { 8528 ctx.fontImages[ctx.fontImageIdx+1].ctx = ctx; 8529 ctx.nvg__imageIncRef(ctx.fontImages[ctx.fontImageIdx+1].id, false); // don't increment driver refcount 8530 } 8531 } 8532 ++ctx.fontImageIdx; 8533 ctx.fs.resetAtlas(iw, ih); 8534 return true; 8535 } 8536 8537 void nvg__renderText (NVGContext ctx, NVGVertex* verts, int nverts) nothrow @trusted @nogc { 8538 NVGstate* state = nvg__getState(ctx); 8539 NVGPaint paint = state.fill; 8540 8541 // Render triangles. 8542 paint.image = ctx.fontImages[ctx.fontImageIdx]; 8543 8544 // Apply global alpha 8545 paint.innerColor.a *= state.alpha; 8546 paint.middleColor.a *= state.alpha; 8547 paint.outerColor.a *= state.alpha; 8548 8549 ctx.params.renderTriangles(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &paint, &state.scissor, verts, nverts); 8550 8551 ++ctx.drawCallCount; 8552 ctx.textTriCount += nverts/3; 8553 } 8554 8555 /// Draws text string at specified location. Returns next x position. 8556 /// Group: text_api 8557 public float text(T) (NVGContext ctx, float x, float y, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) { 8558 NVGstate* state = nvg__getState(ctx); 8559 FONSTextIter!T iter, prevIter; 8560 FONSQuad q; 8561 NVGVertex* verts; 8562 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8563 float invscale = 1.0f/scale; 8564 int cverts = 0; 8565 int nverts = 0; 8566 8567 if (state.fontId == FONS_INVALID) return x; 8568 if (str.length == 0) return x; 8569 8570 ctx.fs.size = state.fontSize*scale; 8571 ctx.fs.spacing = state.letterSpacing*scale; 8572 ctx.fs.blur = state.fontBlur*scale; 8573 ctx.fs.textAlign = state.textAlign; 8574 ctx.fs.fontId = state.fontId; 8575 8576 cverts = nvg__max(2, cast(int)(str.length))*6; // conservative estimate 8577 verts = nvg__allocTempVerts(ctx, cverts); 8578 if (verts is null) return x; 8579 8580 if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Required)) return x; 8581 prevIter = iter; 8582 while (iter.next(q)) { 8583 float[4*2] c = void; 8584 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph? 8585 if (nverts != 0) { 8586 // TODO: add back-end bit to do this just once per frame 8587 nvg__flushTextTexture(ctx); 8588 nvg__renderText(ctx, verts, nverts); 8589 nverts = 0; 8590 } 8591 if (!nvg__allocTextAtlas(ctx)) break; // no memory :( 8592 iter = prevIter; 8593 iter.next(q); // try again 8594 if (iter.prevGlyphIndex < 0) { 8595 // still can not find glyph, try replacement 8596 iter = prevIter; 8597 if (!iter.getDummyChar(q)) break; 8598 } 8599 } 8600 prevIter = iter; 8601 // transform corners 8602 state.xform.point(&c[0], &c[1], q.x0*invscale, q.y0*invscale); 8603 state.xform.point(&c[2], &c[3], q.x1*invscale, q.y0*invscale); 8604 state.xform.point(&c[4], &c[5], q.x1*invscale, q.y1*invscale); 8605 state.xform.point(&c[6], &c[7], q.x0*invscale, q.y1*invscale); 8606 // create triangles 8607 if (nverts+6 <= cverts) { 8608 nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts; 8609 nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts; 8610 nvg__vset(&verts[nverts], c[2], c[3], q.s1, q.t0); ++nverts; 8611 nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts; 8612 nvg__vset(&verts[nverts], c[6], c[7], q.s0, q.t1); ++nverts; 8613 nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts; 8614 } 8615 } 8616 8617 // TODO: add back-end bit to do this just once per frame 8618 if (nverts > 0) { 8619 nvg__flushTextTexture(ctx); 8620 nvg__renderText(ctx, verts, nverts); 8621 } 8622 8623 return iter.nextx/scale; 8624 } 8625 8626 /** Draws multi-line text string at specified location wrapped at the specified width. 8627 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 8628 * Words longer than the max width are slit at nearest character (i.e. no hyphenation). 8629 * 8630 * Group: text_api 8631 */ 8632 public void textBox(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) { 8633 NVGstate* state = nvg__getState(ctx); 8634 if (state.fontId == FONS_INVALID) return; 8635 8636 NVGTextRow!T[2] rows; 8637 auto oldAlign = state.textAlign; 8638 scope(exit) state.textAlign = oldAlign; 8639 auto halign = state.textAlign.horizontal; 8640 float lineh = 0; 8641 8642 ctx.textMetrics(null, null, &lineh); 8643 state.textAlign.horizontal = NVGTextAlign.H.Left; 8644 for (;;) { 8645 auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]); 8646 //{ import core.stdc.stdio : printf; printf("slen=%u; rlen=%u; bw=%f\n", cast(uint)str.length, cast(uint)rres.length, cast(double)breakRowWidth); } 8647 if (rres.length == 0) break; 8648 foreach (ref row; rres) { 8649 final switch (halign) { 8650 case NVGTextAlign.H.Left: ctx.text(x, y, row.row); break; 8651 case NVGTextAlign.H.Center: ctx.text(x+breakRowWidth*0.5f-row.width*0.5f, y, row.row); break; 8652 case NVGTextAlign.H.Right: ctx.text(x+breakRowWidth-row.width, y, row.row); break; 8653 } 8654 y += lineh*state.lineHeight; 8655 } 8656 str = rres[$-1].rest; 8657 } 8658 } 8659 8660 private template isGoodPositionDelegate(DG) { 8661 private DG dg; 8662 static if (is(typeof({ NVGGlyphPosition pos; bool res = dg(pos); })) || 8663 is(typeof({ NVGGlyphPosition pos; dg(pos); }))) 8664 enum isGoodPositionDelegate = true; 8665 else 8666 enum isGoodPositionDelegate = false; 8667 } 8668 8669 /** Calculates the glyph x positions of the specified text. 8670 * Measured values are returned in local coordinate space. 8671 * 8672 * Group: text_api 8673 */ 8674 public NVGGlyphPosition[] textGlyphPositions(T) (NVGContext ctx, float x, float y, const(T)[] str, NVGGlyphPosition[] positions) nothrow @trusted @nogc 8675 if (isAnyCharType!T) 8676 { 8677 if (str.length == 0 || positions.length == 0) return positions[0..0]; 8678 usize posnum; 8679 auto len = ctx.textGlyphPositions(x, y, str, (const scope ref NVGGlyphPosition pos) { 8680 positions.ptr[posnum++] = pos; 8681 return (posnum < positions.length); 8682 }); 8683 return positions[0..len]; 8684 } 8685 8686 /// Ditto. 8687 public int textGlyphPositions(T, DG) (NVGContext ctx, float x, float y, const(T)[] str, scope DG dg) 8688 if (isAnyCharType!T && isGoodPositionDelegate!DG) 8689 { 8690 import std.traits : ReturnType; 8691 static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true; 8692 8693 NVGstate* state = nvg__getState(ctx); 8694 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8695 float invscale = 1.0f/scale; 8696 FONSTextIter!T iter, prevIter; 8697 FONSQuad q; 8698 int npos = 0; 8699 8700 if (str.length == 0) return 0; 8701 8702 ctx.fs.size = state.fontSize*scale; 8703 ctx.fs.spacing = state.letterSpacing*scale; 8704 ctx.fs.blur = state.fontBlur*scale; 8705 ctx.fs.textAlign = state.textAlign; 8706 ctx.fs.fontId = state.fontId; 8707 8708 if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Optional)) return npos; 8709 prevIter = iter; 8710 while (iter.next(q)) { 8711 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph? 8712 if (!nvg__allocTextAtlas(ctx)) break; // no memory 8713 iter = prevIter; 8714 iter.next(q); // try again 8715 if (iter.prevGlyphIndex < 0) { 8716 // still can not find glyph, try replacement 8717 iter = prevIter; 8718 if (!iter.getDummyChar(q)) break; 8719 } 8720 } 8721 prevIter = iter; 8722 NVGGlyphPosition position = void; //WARNING! 8723 position.strpos = cast(usize)(iter.stringp-str.ptr); 8724 position.x = iter.x*invscale; 8725 position.minx = nvg__min(iter.x, q.x0)*invscale; 8726 position.maxx = nvg__max(iter.nextx, q.x1)*invscale; 8727 ++npos; 8728 static if (RetBool) { if (!dg(position)) return npos; } else dg(position); 8729 } 8730 8731 return npos; 8732 } 8733 8734 private template isGoodRowDelegate(CT, DG) { 8735 private DG dg; 8736 static if (is(typeof({ NVGTextRow!CT row; bool res = dg(row); })) || 8737 is(typeof({ NVGTextRow!CT row; dg(row); }))) 8738 enum isGoodRowDelegate = true; 8739 else 8740 enum isGoodRowDelegate = false; 8741 } 8742 8743 /** Breaks the specified text into lines. 8744 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 8745 * Words longer than the max width are slit at nearest character (i.e. no hyphenation). 8746 * 8747 * Group: text_api 8748 */ 8749 public NVGTextRow!T[] textBreakLines(T) (NVGContext ctx, const(T)[] str, float breakRowWidth, NVGTextRow!T[] rows) nothrow @trusted @nogc 8750 if (isAnyCharType!T) 8751 { 8752 if (rows.length == 0) return rows; 8753 if (rows.length > int.max-1) rows = rows[0..int.max-1]; 8754 int nrow = 0; 8755 auto count = ctx.textBreakLines(str, breakRowWidth, (const scope ref NVGTextRow!T row) { 8756 rows[nrow++] = row; 8757 return (nrow < rows.length); 8758 }); 8759 return rows[0..count]; 8760 } 8761 8762 /** Breaks the specified text into lines. 8763 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 8764 * Words longer than the max width are slit at nearest character (i.e. no hyphenation). 8765 * Returns number of rows. 8766 * 8767 * Group: text_api 8768 */ 8769 public int textBreakLines(T, DG) (NVGContext ctx, const(T)[] str, float breakRowWidth, scope DG dg) 8770 if (isAnyCharType!T && isGoodRowDelegate!(T, DG)) 8771 { 8772 import std.traits : ReturnType; 8773 static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true; 8774 8775 enum NVGcodepointType : int { 8776 Space, 8777 NewLine, 8778 Char, 8779 } 8780 8781 NVGstate* state = nvg__getState(ctx); 8782 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8783 float invscale = 1.0f/scale; 8784 FONSTextIter!T iter, prevIter; 8785 FONSQuad q; 8786 int nrows = 0; 8787 float rowStartX = 0; 8788 float rowWidth = 0; 8789 float rowMinX = 0; 8790 float rowMaxX = 0; 8791 int rowStart = 0; 8792 int rowEnd = 0; 8793 int wordStart = 0; 8794 float wordStartX = 0; 8795 float wordMinX = 0; 8796 int breakEnd = 0; 8797 float breakWidth = 0; 8798 float breakMaxX = 0; 8799 int type = NVGcodepointType.Space, ptype = NVGcodepointType.Space; 8800 uint pcodepoint = 0; 8801 8802 if (state.fontId == FONS_INVALID) return 0; 8803 if (str.length == 0 || dg is null) return 0; 8804 8805 ctx.fs.size = state.fontSize*scale; 8806 ctx.fs.spacing = state.letterSpacing*scale; 8807 ctx.fs.blur = state.fontBlur*scale; 8808 ctx.fs.textAlign = state.textAlign; 8809 ctx.fs.fontId = state.fontId; 8810 8811 breakRowWidth *= scale; 8812 8813 enum Phase { 8814 Normal, // searching for breaking point 8815 SkipBlanks, // skip leading blanks 8816 } 8817 Phase phase = Phase.SkipBlanks; // don't skip blanks on first line 8818 8819 if (!iter.setup(ctx.fs, 0, 0, str, FONSBitmapFlag.Optional)) return 0; 8820 prevIter = iter; 8821 while (iter.next(q)) { 8822 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph? 8823 if (!nvg__allocTextAtlas(ctx)) break; // no memory 8824 iter = prevIter; 8825 iter.next(q); // try again 8826 if (iter.prevGlyphIndex < 0) { 8827 // still can not find glyph, try replacement 8828 iter = prevIter; 8829 if (!iter.getDummyChar(q)) break; 8830 } 8831 } 8832 prevIter = iter; 8833 switch (iter.codepoint) { 8834 case 9: // \t 8835 case 11: // \v 8836 case 12: // \f 8837 case 32: // space 8838 case 0x00a0: // NBSP 8839 type = NVGcodepointType.Space; 8840 break; 8841 case 10: // \n 8842 type = (pcodepoint == 13 ? NVGcodepointType.Space : NVGcodepointType.NewLine); 8843 break; 8844 case 13: // \r 8845 type = (pcodepoint == 10 ? NVGcodepointType.Space : NVGcodepointType.NewLine); 8846 break; 8847 case 0x0085: // NEL 8848 case 0x2028: // Line Separator 8849 case 0x2029: // Paragraph Separator 8850 type = NVGcodepointType.NewLine; 8851 break; 8852 default: 8853 type = NVGcodepointType.Char; 8854 break; 8855 } 8856 if (phase == Phase.SkipBlanks) { 8857 // fix row start 8858 rowStart = cast(int)(iter.stringp-str.ptr); 8859 rowEnd = rowStart; 8860 rowStartX = iter.x; 8861 rowWidth = iter.nextx-rowStartX; // q.x1-rowStartX; 8862 rowMinX = q.x0-rowStartX; 8863 rowMaxX = q.x1-rowStartX; 8864 wordStart = rowStart; 8865 wordStartX = iter.x; 8866 wordMinX = q.x0-rowStartX; 8867 breakEnd = rowStart; 8868 breakWidth = 0.0; 8869 breakMaxX = 0.0; 8870 if (type == NVGcodepointType.Space) continue; 8871 phase = Phase.Normal; 8872 } 8873 8874 if (type == NVGcodepointType.NewLine) { 8875 // always handle new lines 8876 NVGTextRow!T row; 8877 row.string = str; 8878 row.start = rowStart; 8879 row.end = rowEnd; 8880 row.width = rowWidth*invscale; 8881 row.minx = rowMinX*invscale; 8882 row.maxx = rowMaxX*invscale; 8883 ++nrows; 8884 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8885 phase = Phase.SkipBlanks; 8886 } else { 8887 float nextWidth = iter.nextx-rowStartX; 8888 // track last non-white space character 8889 if (type == NVGcodepointType.Char) { 8890 rowEnd = cast(int)(iter.nextp-str.ptr); 8891 rowWidth = iter.nextx-rowStartX; 8892 rowMaxX = q.x1-rowStartX; 8893 } 8894 // track last end of a word 8895 if (ptype == NVGcodepointType.Char && type == NVGcodepointType.Space) { 8896 breakEnd = cast(int)(iter.stringp-str.ptr); 8897 breakWidth = rowWidth; 8898 breakMaxX = rowMaxX; 8899 } 8900 // track last beginning of a word 8901 if (ptype == NVGcodepointType.Space && type == NVGcodepointType.Char) { 8902 wordStart = cast(int)(iter.stringp-str.ptr); 8903 wordStartX = iter.x; 8904 wordMinX = q.x0-rowStartX; 8905 } 8906 // break to new line when a character is beyond break width 8907 if (type == NVGcodepointType.Char && nextWidth > breakRowWidth) { 8908 // the run length is too long, need to break to new line 8909 NVGTextRow!T row; 8910 row.string = str; 8911 if (breakEnd == rowStart) { 8912 // the current word is longer than the row length, just break it from here 8913 row.start = rowStart; 8914 row.end = cast(int)(iter.stringp-str.ptr); 8915 row.width = rowWidth*invscale; 8916 row.minx = rowMinX*invscale; 8917 row.maxx = rowMaxX*invscale; 8918 ++nrows; 8919 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8920 rowStartX = iter.x; 8921 rowStart = cast(int)(iter.stringp-str.ptr); 8922 rowEnd = cast(int)(iter.nextp-str.ptr); 8923 rowWidth = iter.nextx-rowStartX; 8924 rowMinX = q.x0-rowStartX; 8925 rowMaxX = q.x1-rowStartX; 8926 wordStart = rowStart; 8927 wordStartX = iter.x; 8928 wordMinX = q.x0-rowStartX; 8929 } else { 8930 // break the line from the end of the last word, and start new line from the beginning of the new 8931 //{ import core.stdc.stdio : printf; printf("rowStart=%u; rowEnd=%u; breakEnd=%u; len=%u\n", rowStart, rowEnd, breakEnd, cast(uint)str.length); } 8932 row.start = rowStart; 8933 row.end = breakEnd; 8934 row.width = breakWidth*invscale; 8935 row.minx = rowMinX*invscale; 8936 row.maxx = breakMaxX*invscale; 8937 ++nrows; 8938 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8939 rowStartX = wordStartX; 8940 rowStart = wordStart; 8941 rowEnd = cast(int)(iter.nextp-str.ptr); 8942 rowWidth = iter.nextx-rowStartX; 8943 rowMinX = wordMinX; 8944 rowMaxX = q.x1-rowStartX; 8945 // no change to the word start 8946 } 8947 // set null break point 8948 breakEnd = rowStart; 8949 breakWidth = 0.0; 8950 breakMaxX = 0.0; 8951 } 8952 } 8953 8954 pcodepoint = iter.codepoint; 8955 ptype = type; 8956 } 8957 8958 // break the line from the end of the last word, and start new line from the beginning of the new 8959 if (phase != Phase.SkipBlanks && rowStart < str.length) { 8960 //{ import core.stdc.stdio : printf; printf(" rowStart=%u; len=%u\n", rowStart, cast(uint)str.length); } 8961 NVGTextRow!T row; 8962 row.string = str; 8963 row.start = rowStart; 8964 row.end = cast(int)str.length; 8965 row.width = rowWidth*invscale; 8966 row.minx = rowMinX*invscale; 8967 row.maxx = rowMaxX*invscale; 8968 ++nrows; 8969 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8970 } 8971 8972 return nrows; 8973 } 8974 8975 /** Returns iterator which you can use to calculate text bounds and advancement. 8976 * This is usable when you need to do some text layouting with wrapping, to avoid 8977 * guesswork ("will advancement for this space stay the same?"), and Schlemiel's 8978 * algorithm. Note that you can copy the returned struct to save iterator state. 8979 * 8980 * You can check if iterator is valid with [valid] property, put new chars with 8981 * [put] method, get current advance with [advance] property, and current 8982 * bounds with `getBounds(ref float[4] bounds)` method. 8983 * 8984 * $(WARNING Don't change font parameters while iterating! Or use [restoreFont] method.) 8985 * 8986 * Group: text_api 8987 */ 8988 public struct TextBoundsIterator { 8989 private: 8990 NVGContext ctx; 8991 FONSTextBoundsIterator fsiter; // fontstash iterator 8992 float scale, invscale, xscaled, yscaled; 8993 // font settings 8994 float fsSize, fsSpacing, fsBlur; 8995 int fsFontId; 8996 NVGTextAlign fsAlign; 8997 8998 public: 8999 /// Setups iteration. Takes current font parameters from the given NanoVega context. 9000 this (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc { reset(actx, ax, ay); } 9001 9002 /// Resets iteration. Takes current font parameters from the given NanoVega context. 9003 void reset (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc { 9004 fsiter = fsiter.init; 9005 this = this.init; 9006 if (actx is null) return; 9007 NVGstate* state = nvg__getState(actx); 9008 if (state is null) return; 9009 if (state.fontId == FONS_INVALID) { ctx = null; return; } 9010 9011 ctx = actx; 9012 scale = nvg__getFontScale(state)*ctx.devicePxRatio; 9013 invscale = 1.0f/scale; 9014 9015 fsSize = state.fontSize*scale; 9016 fsSpacing = state.letterSpacing*scale; 9017 fsBlur = state.fontBlur*scale; 9018 fsAlign = state.textAlign; 9019 fsFontId = state.fontId; 9020 restoreFont(); 9021 9022 xscaled = ax*scale; 9023 yscaled = ay*scale; 9024 fsiter.reset(ctx.fs, xscaled, yscaled); 9025 } 9026 9027 /// Restart iteration. Will not restore font. 9028 void restart () nothrow @trusted @nogc { 9029 if (ctx !is null) fsiter.reset(ctx.fs, xscaled, yscaled); 9030 } 9031 9032 /// Restore font settings for the context. 9033 void restoreFont () nothrow @trusted @nogc { 9034 if (ctx !is null) { 9035 ctx.fs.size = fsSize; 9036 ctx.fs.spacing = fsSpacing; 9037 ctx.fs.blur = fsBlur; 9038 ctx.fs.textAlign = fsAlign; 9039 ctx.fs.fontId = fsFontId; 9040 } 9041 } 9042 9043 /// Is this iterator valid? 9044 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null); } 9045 9046 /// Add chars. 9047 void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) { pragma(inline, true); if (ctx !is null) fsiter.put(str[]); } 9048 9049 /// Returns current advance 9050 @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null ? fsiter.advance*invscale : 0); } 9051 9052 /// Returns current text bounds. 9053 void getBounds (ref float[4] bounds) nothrow @trusted @nogc { 9054 if (ctx !is null) { 9055 fsiter.getBounds(bounds); 9056 ctx.fs.getLineBounds(yscaled, &bounds[1], &bounds[3]); 9057 bounds[0] *= invscale; 9058 bounds[1] *= invscale; 9059 bounds[2] *= invscale; 9060 bounds[3] *= invscale; 9061 } else { 9062 bounds[] = 0; 9063 } 9064 } 9065 9066 /// Returns current horizontal text bounds. 9067 void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc { 9068 if (ctx !is null) { 9069 fsiter.getHBounds(xmin, xmax); 9070 xmin *= invscale; 9071 xmax *= invscale; 9072 } 9073 } 9074 9075 /// Returns current vertical text bounds. 9076 void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc { 9077 if (ctx !is null) { 9078 //fsiter.getVBounds(ymin, ymax); 9079 ctx.fs.getLineBounds(yscaled, &ymin, &ymax); 9080 ymin *= invscale; 9081 ymax *= invscale; 9082 } 9083 } 9084 } 9085 9086 /// Returns font line height (without line spacing), measured in local coordinate space. 9087 /// Group: text_api 9088 public float textFontHeight (NVGContext ctx) nothrow @trusted @nogc { 9089 float res = void; 9090 ctx.textMetrics(null, null, &res); 9091 return res; 9092 } 9093 9094 /// Returns font ascender (positive), measured in local coordinate space. 9095 /// Group: text_api 9096 public float textFontAscender (NVGContext ctx) nothrow @trusted @nogc { 9097 float res = void; 9098 ctx.textMetrics(&res, null, null); 9099 return res; 9100 } 9101 9102 /// Returns font descender (negative), measured in local coordinate space. 9103 /// Group: text_api 9104 public float textFontDescender (NVGContext ctx) nothrow @trusted @nogc { 9105 float res = void; 9106 ctx.textMetrics(null, &res, null); 9107 return res; 9108 } 9109 9110 /** Measures the specified text string. Returns horizontal and vertical sizes of the measured text. 9111 * Measured values are returned in local coordinate space. 9112 * 9113 * Group: text_api 9114 */ 9115 public void textExtents(T) (NVGContext ctx, const(T)[] str, float *w, float *h) nothrow @trusted @nogc if (isAnyCharType!T) { 9116 float[4] bnd = void; 9117 ctx.textBounds(0, 0, str, bnd[]); 9118 if (!ctx.fs.getFontAA(nvg__getState(ctx).fontId)) { 9119 if (w !is null) *w = nvg__lrintf(bnd.ptr[2]-bnd.ptr[0]); 9120 if (h !is null) *h = nvg__lrintf(bnd.ptr[3]-bnd.ptr[1]); 9121 } else { 9122 if (w !is null) *w = bnd.ptr[2]-bnd.ptr[0]; 9123 if (h !is null) *h = bnd.ptr[3]-bnd.ptr[1]; 9124 } 9125 } 9126 9127 /** Measures the specified text string. Returns horizontal size of the measured text. 9128 * Measured values are returned in local coordinate space. 9129 * 9130 * Group: text_api 9131 */ 9132 public float textWidth(T) (NVGContext ctx, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) { 9133 float w = void; 9134 ctx.textExtents(str, &w, null); 9135 return w; 9136 } 9137 9138 /** Measures the specified text string. Parameter bounds should be a float[4], 9139 * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax] 9140 * Returns the horizontal advance of the measured text (i.e. where the next character should drawn). 9141 * Measured values are returned in local coordinate space. 9142 * 9143 * Group: text_api 9144 */ 9145 public float textBounds(T) (NVGContext ctx, float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc 9146 if (isAnyCharType!T) 9147 { 9148 NVGstate* state = nvg__getState(ctx); 9149 9150 if (state.fontId == FONS_INVALID) { 9151 bounds[] = 0; 9152 return 0; 9153 } 9154 9155 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 9156 ctx.fs.size = state.fontSize*scale; 9157 ctx.fs.spacing = state.letterSpacing*scale; 9158 ctx.fs.blur = state.fontBlur*scale; 9159 ctx.fs.textAlign = state.textAlign; 9160 ctx.fs.fontId = state.fontId; 9161 9162 float[4] b = void; 9163 immutable float width = ctx.fs.getTextBounds(x*scale, y*scale, str, b[]); 9164 immutable float invscale = 1.0f/scale; 9165 if (bounds.length) { 9166 // use line bounds for height 9167 ctx.fs.getLineBounds(y*scale, b.ptr+1, b.ptr+3); 9168 if (bounds.length > 0) bounds.ptr[0] = b.ptr[0]*invscale; 9169 if (bounds.length > 1) bounds.ptr[1] = b.ptr[1]*invscale; 9170 if (bounds.length > 2) bounds.ptr[2] = b.ptr[2]*invscale; 9171 if (bounds.length > 3) bounds.ptr[3] = b.ptr[3]*invscale; 9172 } 9173 return width*invscale; 9174 } 9175 9176 /// Ditto. 9177 public void textBoxBounds(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str, float[] bounds) if (isAnyCharType!T) { 9178 NVGstate* state = nvg__getState(ctx); 9179 NVGTextRow!T[2] rows; 9180 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 9181 float invscale = 1.0f/scale; 9182 float lineh = 0, rminy = 0, rmaxy = 0; 9183 float minx, miny, maxx, maxy; 9184 9185 if (state.fontId == FONS_INVALID) { 9186 bounds[] = 0; 9187 return; 9188 } 9189 9190 auto oldAlign = state.textAlign; 9191 scope(exit) state.textAlign = oldAlign; 9192 auto halign = state.textAlign.horizontal; 9193 9194 ctx.textMetrics(null, null, &lineh); 9195 state.textAlign.horizontal = NVGTextAlign.H.Left; 9196 9197 minx = maxx = x; 9198 miny = maxy = y; 9199 9200 ctx.fs.size = state.fontSize*scale; 9201 ctx.fs.spacing = state.letterSpacing*scale; 9202 ctx.fs.blur = state.fontBlur*scale; 9203 ctx.fs.textAlign = state.textAlign; 9204 ctx.fs.fontId = state.fontId; 9205 ctx.fs.getLineBounds(0, &rminy, &rmaxy); 9206 rminy *= invscale; 9207 rmaxy *= invscale; 9208 9209 for (;;) { 9210 auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]); 9211 if (rres.length == 0) break; 9212 foreach (ref row; rres) { 9213 float rminx, rmaxx, dx = 0; 9214 // horizontal bounds 9215 final switch (halign) { 9216 case NVGTextAlign.H.Left: dx = 0; break; 9217 case NVGTextAlign.H.Center: dx = breakRowWidth*0.5f-row.width*0.5f; break; 9218 case NVGTextAlign.H.Right: dx = breakRowWidth-row.width; break; 9219 } 9220 rminx = x+row.minx+dx; 9221 rmaxx = x+row.maxx+dx; 9222 minx = nvg__min(minx, rminx); 9223 maxx = nvg__max(maxx, rmaxx); 9224 // vertical bounds 9225 miny = nvg__min(miny, y+rminy); 9226 maxy = nvg__max(maxy, y+rmaxy); 9227 y += lineh*state.lineHeight; 9228 } 9229 str = rres[$-1].rest; 9230 } 9231 9232 if (bounds.length) { 9233 if (bounds.length > 0) bounds.ptr[0] = minx; 9234 if (bounds.length > 1) bounds.ptr[1] = miny; 9235 if (bounds.length > 2) bounds.ptr[2] = maxx; 9236 if (bounds.length > 3) bounds.ptr[3] = maxy; 9237 } 9238 } 9239 9240 /// Returns the vertical metrics based on the current text style. Measured values are returned in local coordinate space. 9241 /// Group: text_api 9242 public void textMetrics (NVGContext ctx, float* ascender, float* descender, float* lineh) nothrow @trusted @nogc { 9243 NVGstate* state = nvg__getState(ctx); 9244 9245 if (state.fontId == FONS_INVALID) { 9246 if (ascender !is null) *ascender *= 0; 9247 if (descender !is null) *descender *= 0; 9248 if (lineh !is null) *lineh *= 0; 9249 return; 9250 } 9251 9252 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 9253 immutable float invscale = 1.0f/scale; 9254 9255 ctx.fs.size = state.fontSize*scale; 9256 ctx.fs.spacing = state.letterSpacing*scale; 9257 ctx.fs.blur = state.fontBlur*scale; 9258 ctx.fs.textAlign = state.textAlign; 9259 ctx.fs.fontId = state.fontId; 9260 9261 ctx.fs.getVertMetrics(ascender, descender, lineh); 9262 if (ascender !is null) *ascender *= invscale; 9263 if (descender !is null) *descender *= invscale; 9264 if (lineh !is null) *lineh *= invscale; 9265 } 9266 9267 9268 // ////////////////////////////////////////////////////////////////////////// // 9269 // fontstash 9270 // ////////////////////////////////////////////////////////////////////////// // 9271 import core.stdc.stdlib : malloc, realloc, free; 9272 import core.stdc.string : memset, memcpy, strncpy, strcmp, strlen; 9273 import core.stdc.stdio : FILE, fopen, fclose, fseek, ftell, fread, SEEK_END, SEEK_SET; 9274 9275 public: 9276 // welcome to version hell! 9277 version(nanovg_force_stb_ttf) { 9278 } else { 9279 version(nanovg_force_detect) {} else version(nanovg_use_freetype) { version = nanovg_use_freetype_ii; } 9280 } 9281 version(nanovg_ignore_iv_stb_ttf) enum nanovg_ignore_iv_stb_ttf = true; else enum nanovg_ignore_iv_stb_ttf = false; 9282 //version(nanovg_ignore_mono); 9283 9284 version(nanovg_force_stb_ttf) { 9285 private enum NanoVegaForceFreeType = false; 9286 } else { 9287 version (nanovg_builtin_freetype_bindings) { 9288 version(Posix) { 9289 private enum NanoVegaForceFreeType = true; 9290 } else { 9291 private enum NanoVegaForceFreeType = false; 9292 } 9293 } else { 9294 version(Posix) { 9295 private enum NanoVegaForceFreeType = true; 9296 } else { 9297 private enum NanoVegaForceFreeType = false; 9298 } 9299 } 9300 } 9301 9302 version(nanovg_use_freetype_ii) { 9303 enum NanoVegaIsUsingSTBTTF = false; 9304 //pragma(msg, "iv.freetype: forced"); 9305 } else { 9306 static if (NanoVegaForceFreeType) { 9307 enum NanoVegaIsUsingSTBTTF = false; 9308 } else { 9309 static if (!nanovg_ignore_iv_stb_ttf && __traits(compiles, { import iv.stb.ttf; })) { 9310 import iv.stb.ttf; 9311 enum NanoVegaIsUsingSTBTTF = true; 9312 version(nanovg_report_stb_ttf) pragma(msg, "iv.stb.ttf"); 9313 } else static if (__traits(compiles, { import arsd.ttf; })) { 9314 import arsd.ttf; 9315 enum NanoVegaIsUsingSTBTTF = true; 9316 version(nanovg_report_stb_ttf) pragma(msg, "arsd.ttf"); 9317 } else static if (__traits(compiles, { import stb_truetype; })) { 9318 import stb_truetype; 9319 enum NanoVegaIsUsingSTBTTF = true; 9320 version(nanovg_report_stb_ttf) pragma(msg, "stb_truetype"); 9321 } else static if (__traits(compiles, { import iv.freetype; })) { 9322 version (nanovg_builtin_freetype_bindings) { 9323 enum NanoVegaIsUsingSTBTTF = false; 9324 version = nanovg_builtin_freetype_bindings; 9325 } else { 9326 import iv.freetype; 9327 enum NanoVegaIsUsingSTBTTF = false; 9328 } 9329 version(nanovg_report_stb_ttf) pragma(msg, "freetype"); 9330 } else { 9331 static assert(0, "no stb_ttf/iv.freetype found!"); 9332 } 9333 } 9334 } 9335 9336 9337 // ////////////////////////////////////////////////////////////////////////// // 9338 //version = nanovg_ft_mono; 9339 9340 /// Invald font id. 9341 /// Group: font_stash 9342 public enum FONS_INVALID = -1; 9343 9344 public enum FONSBitmapFlag : uint { 9345 Required = 0, 9346 Optional = 1, 9347 } 9348 9349 public enum FONSError : int { 9350 NoError = 0, 9351 AtlasFull = 1, // Font atlas is full. 9352 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. 9353 StatesOverflow = 3, // Calls to fonsPushState has created too large stack, if you need deep state stack bump up FONS_MAX_STATES. 9354 StatesUnderflow = 4, // Trying to pop too many states fonsPopState(). 9355 } 9356 9357 /// Initial parameters for new FontStash. 9358 /// Group: font_stash 9359 public struct FONSParams { 9360 enum Flag : uint { 9361 ZeroTopLeft = 0U, // default 9362 ZeroBottomLeft = 1U, 9363 } 9364 int width, height; 9365 Flag flags = Flag.ZeroTopLeft; 9366 void* userPtr; 9367 bool function (void* uptr, int width, int height) nothrow @trusted @nogc renderCreate; 9368 int function (void* uptr, int width, int height) nothrow @trusted @nogc renderResize; 9369 void function (void* uptr, int* rect, const(ubyte)* data) nothrow @trusted @nogc renderUpdate; 9370 void function (void* uptr) nothrow @trusted @nogc renderDelete; 9371 @property bool isZeroTopLeft () const pure nothrow @trusted @nogc { pragma(inline, true); return ((flags&Flag.ZeroBottomLeft) == 0); } 9372 } 9373 9374 //TODO: document this 9375 public struct FONSQuad { 9376 float x0=0, y0=0, s0=0, t0=0; 9377 float x1=0, y1=0, s1=0, t1=0; 9378 } 9379 9380 //TODO: document this 9381 public struct FONSTextIter(CT) if (isAnyCharType!CT) { 9382 alias CharType = CT; 9383 float x=0, y=0, nextx=0, nexty=0, scale=0, spacing=0; 9384 uint codepoint; 9385 short isize, iblur; 9386 FONSContext stash; 9387 FONSfont* font; 9388 int prevGlyphIndex; 9389 const(CT)* s; // string 9390 const(CT)* n; // next 9391 const(CT)* e; // end 9392 FONSBitmapFlag bitmapOption; 9393 static if (is(CT == char)) { 9394 uint utf8state; 9395 } 9396 9397 this (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc { setup(astash, ax, ay, astr, abitmapOption); } 9398 ~this () nothrow @trusted @nogc { pragma(inline, true); static if (is(CT == char)) utf8state = 0; s = n = e = null; } 9399 9400 @property const(CT)* stringp () const pure nothrow @trusted @nogc { pragma(inline, true); return s; } 9401 @property const(CT)* nextp () const pure nothrow @trusted @nogc { pragma(inline, true); return n; } 9402 @property const(CT)* endp () const pure nothrow @trusted @nogc { pragma(inline, true); return e; } 9403 9404 bool setup (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc { 9405 import core.stdc.string : memset; 9406 9407 memset(&this, 0, this.sizeof); 9408 if (astash is null) return false; 9409 9410 FONSstate* state = astash.getState; 9411 9412 if (state.font < 0 || state.font >= astash.nfonts) return false; 9413 font = astash.fonts[state.font]; 9414 if (font is null || font.fdata is null) return false; 9415 9416 isize = cast(short)(state.size*10.0f); 9417 iblur = cast(short)state.blur; 9418 scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f); 9419 9420 // align horizontally 9421 if (state.talign.left) { 9422 // empty 9423 } else if (state.talign.right) { 9424 immutable float width = astash.getTextBounds(ax, ay, astr, null); 9425 ax -= width; 9426 } else if (state.talign.center) { 9427 immutable float width = astash.getTextBounds(ax, ay, astr, null); 9428 ax -= width*0.5f; 9429 } 9430 9431 // align vertically 9432 ay += astash.getVertAlign(font, state.talign, isize); 9433 9434 x = nextx = ax; 9435 y = nexty = ay; 9436 spacing = state.spacing; 9437 9438 if (astr.ptr is null) { 9439 static if (is(CharType == char)) astr = ""; 9440 else static if (is(CharType == wchar)) astr = ""w; 9441 else static if (is(CharType == dchar)) astr = ""d; 9442 else static assert(0, "wtf?!"); 9443 } 9444 s = astr.ptr; 9445 n = astr.ptr; 9446 e = astr.ptr+astr.length; 9447 9448 codepoint = 0; 9449 prevGlyphIndex = -1; 9450 bitmapOption = abitmapOption; 9451 stash = astash; 9452 9453 return true; 9454 } 9455 9456 bool getDummyChar (ref FONSQuad quad) nothrow @trusted @nogc { 9457 if (stash is null || font is null) return false; 9458 // get glyph and quad 9459 x = nextx; 9460 y = nexty; 9461 FONSglyph* glyph = stash.getGlyph(font, 0xFFFD, isize, iblur, bitmapOption); 9462 if (glyph !is null) { 9463 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad); 9464 prevGlyphIndex = glyph.index; 9465 return true; 9466 } else { 9467 prevGlyphIndex = -1; 9468 return false; 9469 } 9470 } 9471 9472 bool next (ref FONSQuad quad) nothrow @trusted @nogc { 9473 if (stash is null || font is null) return false; 9474 FONSglyph* glyph = null; 9475 static if (is(CharType == char)) { 9476 const(char)* str = this.n; 9477 this.s = this.n; 9478 if (str is this.e) return false; 9479 const(char)* e = this.e; 9480 for (; str !is e; ++str) { 9481 /*if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue;*/ 9482 mixin(DecUtfMixin!("this.utf8state", "this.codepoint", "*cast(const(ubyte)*)str")); 9483 if (utf8state) continue; 9484 ++str; // 'cause we'll break anyway 9485 // get glyph and quad 9486 x = nextx; 9487 y = nexty; 9488 glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption); 9489 if (glyph !is null) { 9490 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad); 9491 prevGlyphIndex = glyph.index; 9492 } else { 9493 prevGlyphIndex = -1; 9494 } 9495 break; 9496 } 9497 this.n = str; 9498 } else { 9499 const(CharType)* str = this.n; 9500 this.s = this.n; 9501 if (str is this.e) return false; 9502 codepoint = cast(uint)(*str++); 9503 if (codepoint > dchar.max) codepoint = 0xFFFD; 9504 // get glyph and quad 9505 x = nextx; 9506 y = nexty; 9507 glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption); 9508 if (glyph !is null) { 9509 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad); 9510 prevGlyphIndex = glyph.index; 9511 } else { 9512 prevGlyphIndex = -1; 9513 } 9514 this.n = str; 9515 } 9516 return true; 9517 } 9518 } 9519 9520 9521 // ////////////////////////////////////////////////////////////////////////// // 9522 //static if (!HasAST) version = nanovg_use_freetype_ii_x; 9523 9524 /*version(nanovg_use_freetype_ii_x)*/ static if (!NanoVegaIsUsingSTBTTF) { 9525 version(nanovg_builtin_freetype_bindings) { 9526 pragma(lib, "freetype"); 9527 private extern(C) nothrow @trusted @nogc { 9528 private import core.stdc.config : c_long, c_ulong; 9529 alias FT_Pos = c_long; 9530 // config/ftconfig.h 9531 alias FT_Int16 = short; 9532 alias FT_UInt16 = ushort; 9533 alias FT_Int32 = int; 9534 alias FT_UInt32 = uint; 9535 alias FT_Fast = int; 9536 alias FT_UFast = uint; 9537 alias FT_Int64 = long; 9538 alias FT_Uint64 = ulong; 9539 // fttypes.h 9540 alias FT_Bool = ubyte; 9541 alias FT_FWord = short; 9542 alias FT_UFWord = ushort; 9543 alias FT_Char = char; 9544 alias FT_Byte = ubyte; 9545 alias FT_Bytes = FT_Byte*; 9546 alias FT_Tag = FT_UInt32; 9547 alias FT_String = char; 9548 alias FT_Short = short; 9549 alias FT_UShort = ushort; 9550 alias FT_Int = int; 9551 alias FT_UInt = uint; 9552 alias FT_Long = c_long; 9553 alias FT_ULong = c_ulong; 9554 alias FT_F2Dot14 = short; 9555 alias FT_F26Dot6 = c_long; 9556 alias FT_Fixed = c_long; 9557 alias FT_Error = int; 9558 alias FT_Pointer = void*; 9559 alias FT_Offset = usize; 9560 alias FT_PtrDist = ptrdiff_t; 9561 9562 struct FT_UnitVector { 9563 FT_F2Dot14 x; 9564 FT_F2Dot14 y; 9565 } 9566 9567 struct FT_Matrix { 9568 FT_Fixed xx, xy; 9569 FT_Fixed yx, yy; 9570 } 9571 9572 struct FT_Data { 9573 const(FT_Byte)* pointer; 9574 FT_Int length; 9575 } 9576 alias FT_Face = FT_FaceRec*; 9577 struct FT_FaceRec { 9578 FT_Long num_faces; 9579 FT_Long face_index; 9580 FT_Long face_flags; 9581 FT_Long style_flags; 9582 FT_Long num_glyphs; 9583 FT_String* family_name; 9584 FT_String* style_name; 9585 FT_Int num_fixed_sizes; 9586 FT_Bitmap_Size* available_sizes; 9587 FT_Int num_charmaps; 9588 FT_CharMap* charmaps; 9589 FT_Generic generic; 9590 FT_BBox bbox; 9591 FT_UShort units_per_EM; 9592 FT_Short ascender; 9593 FT_Short descender; 9594 FT_Short height; 9595 FT_Short max_advance_width; 9596 FT_Short max_advance_height; 9597 FT_Short underline_position; 9598 FT_Short underline_thickness; 9599 FT_GlyphSlot glyph; 9600 FT_Size size; 9601 FT_CharMap charmap; 9602 FT_Driver driver; 9603 FT_Memory memory; 9604 FT_Stream stream; 9605 FT_ListRec sizes_list; 9606 FT_Generic autohint; 9607 void* extensions; 9608 FT_Face_Internal internal; 9609 } 9610 struct FT_Bitmap_Size { 9611 FT_Short height; 9612 FT_Short width; 9613 FT_Pos size; 9614 FT_Pos x_ppem; 9615 FT_Pos y_ppem; 9616 } 9617 alias FT_CharMap = FT_CharMapRec*; 9618 struct FT_CharMapRec { 9619 FT_Face face; 9620 FT_Encoding encoding; 9621 FT_UShort platform_id; 9622 FT_UShort encoding_id; 9623 } 9624 extern(C) nothrow @nogc { alias FT_Generic_Finalizer = void function (void* object); } 9625 struct FT_Generic { 9626 void* data; 9627 FT_Generic_Finalizer finalizer; 9628 } 9629 struct FT_Vector { 9630 FT_Pos x; 9631 FT_Pos y; 9632 } 9633 struct FT_BBox { 9634 FT_Pos xMin, yMin; 9635 FT_Pos xMax, yMax; 9636 } 9637 alias FT_Pixel_Mode = int; 9638 enum { 9639 FT_PIXEL_MODE_NONE = 0, 9640 FT_PIXEL_MODE_MONO, 9641 FT_PIXEL_MODE_GRAY, 9642 FT_PIXEL_MODE_GRAY2, 9643 FT_PIXEL_MODE_GRAY4, 9644 FT_PIXEL_MODE_LCD, 9645 FT_PIXEL_MODE_LCD_V, 9646 FT_PIXEL_MODE_MAX 9647 } 9648 struct FT_Bitmap { 9649 uint rows; 9650 uint width; 9651 int pitch; 9652 ubyte* buffer; 9653 ushort num_grays; 9654 ubyte pixel_mode; 9655 ubyte palette_mode; 9656 void* palette; 9657 } 9658 struct FT_Outline { 9659 short n_contours; 9660 short n_points; 9661 FT_Vector* points; 9662 byte* tags; 9663 short* contours; 9664 int flags; 9665 } 9666 alias FT_GlyphSlot = FT_GlyphSlotRec*; 9667 struct FT_GlyphSlotRec { 9668 FT_Library library; 9669 FT_Face face; 9670 FT_GlyphSlot next; 9671 FT_UInt reserved; 9672 FT_Generic generic; 9673 FT_Glyph_Metrics metrics; 9674 FT_Fixed linearHoriAdvance; 9675 FT_Fixed linearVertAdvance; 9676 FT_Vector advance; 9677 FT_Glyph_Format format; 9678 FT_Bitmap bitmap; 9679 FT_Int bitmap_left; 9680 FT_Int bitmap_top; 9681 FT_Outline outline; 9682 FT_UInt num_subglyphs; 9683 FT_SubGlyph subglyphs; 9684 void* control_data; 9685 c_long control_len; 9686 FT_Pos lsb_delta; 9687 FT_Pos rsb_delta; 9688 void* other; 9689 FT_Slot_Internal internal; 9690 } 9691 alias FT_Size = FT_SizeRec*; 9692 struct FT_SizeRec { 9693 FT_Face face; 9694 FT_Generic generic; 9695 FT_Size_Metrics metrics; 9696 FT_Size_Internal internal; 9697 } 9698 alias FT_Encoding = FT_Tag; 9699 alias FT_Face_Internal = void*; 9700 alias FT_Driver = void*; 9701 alias FT_Memory = void*; 9702 alias FT_Stream = void*; 9703 alias FT_Library = void*; 9704 alias FT_SubGlyph = void*; 9705 alias FT_Slot_Internal = void*; 9706 alias FT_Size_Internal = void*; 9707 alias FT_ListNode = FT_ListNodeRec*; 9708 alias FT_List = FT_ListRec*; 9709 struct FT_ListNodeRec { 9710 FT_ListNode prev; 9711 FT_ListNode next; 9712 void* data; 9713 } 9714 struct FT_ListRec { 9715 FT_ListNode head; 9716 FT_ListNode tail; 9717 } 9718 struct FT_Glyph_Metrics { 9719 FT_Pos width; 9720 FT_Pos height; 9721 FT_Pos horiBearingX; 9722 FT_Pos horiBearingY; 9723 FT_Pos horiAdvance; 9724 FT_Pos vertBearingX; 9725 FT_Pos vertBearingY; 9726 FT_Pos vertAdvance; 9727 } 9728 alias FT_Glyph_Format = FT_Tag; 9729 FT_Tag FT_MAKE_TAG (char x1, char x2, char x3, char x4) pure nothrow @safe @nogc { 9730 pragma(inline, true); 9731 return cast(FT_UInt32)((x1<<24)|(x2<<16)|(x3<<8)|x4); 9732 } 9733 enum : FT_Tag { 9734 FT_GLYPH_FORMAT_NONE = 0, 9735 FT_GLYPH_FORMAT_COMPOSITE = FT_MAKE_TAG('c','o','m','p'), 9736 FT_GLYPH_FORMAT_BITMAP = FT_MAKE_TAG('b','i','t','s'), 9737 FT_GLYPH_FORMAT_OUTLINE = FT_MAKE_TAG('o','u','t','l'), 9738 FT_GLYPH_FORMAT_PLOTTER = FT_MAKE_TAG('p','l','o','t'), 9739 } 9740 struct FT_Size_Metrics { 9741 FT_UShort x_ppem; 9742 FT_UShort y_ppem; 9743 9744 FT_Fixed x_scale; 9745 FT_Fixed y_scale; 9746 9747 FT_Pos ascender; 9748 FT_Pos descender; 9749 FT_Pos height; 9750 FT_Pos max_advance; 9751 } 9752 enum FT_LOAD_DEFAULT = 0x0U; 9753 enum FT_LOAD_NO_SCALE = 1U<<0; 9754 enum FT_LOAD_NO_HINTING = 1U<<1; 9755 enum FT_LOAD_RENDER = 1U<<2; 9756 enum FT_LOAD_NO_BITMAP = 1U<<3; 9757 enum FT_LOAD_VERTICAL_LAYOUT = 1U<<4; 9758 enum FT_LOAD_FORCE_AUTOHINT = 1U<<5; 9759 enum FT_LOAD_CROP_BITMAP = 1U<<6; 9760 enum FT_LOAD_PEDANTIC = 1U<<7; 9761 enum FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH = 1U<<9; 9762 enum FT_LOAD_NO_RECURSE = 1U<<10; 9763 enum FT_LOAD_IGNORE_TRANSFORM = 1U<<11; 9764 enum FT_LOAD_MONOCHROME = 1U<<12; 9765 enum FT_LOAD_LINEAR_DESIGN = 1U<<13; 9766 enum FT_LOAD_NO_AUTOHINT = 1U<<15; 9767 enum FT_LOAD_COLOR = 1U<<20; 9768 enum FT_LOAD_COMPUTE_METRICS = 1U<<21; 9769 enum FT_FACE_FLAG_KERNING = 1U<<6; 9770 alias FT_Kerning_Mode = int; 9771 enum /*FT_Kerning_Mode*/ { 9772 FT_KERNING_DEFAULT = 0, 9773 FT_KERNING_UNFITTED, 9774 FT_KERNING_UNSCALED 9775 } 9776 extern(C) nothrow @nogc { 9777 alias FT_Outline_MoveToFunc = int function (const(FT_Vector)*, void*); 9778 alias FT_Outline_LineToFunc = int function (const(FT_Vector)*, void*); 9779 alias FT_Outline_ConicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, void*); 9780 alias FT_Outline_CubicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, const(FT_Vector)*, void*); 9781 } 9782 struct FT_Outline_Funcs { 9783 FT_Outline_MoveToFunc move_to; 9784 FT_Outline_LineToFunc line_to; 9785 FT_Outline_ConicToFunc conic_to; 9786 FT_Outline_CubicToFunc cubic_to; 9787 int shift; 9788 FT_Pos delta; 9789 } 9790 9791 FT_Error FT_Init_FreeType (FT_Library*); 9792 FT_Error FT_New_Memory_Face (FT_Library, const(FT_Byte)*, FT_Long, FT_Long, FT_Face*); 9793 FT_UInt FT_Get_Char_Index (FT_Face, FT_ULong); 9794 FT_Error FT_Set_Pixel_Sizes (FT_Face, FT_UInt, FT_UInt); 9795 FT_Error FT_Load_Glyph (FT_Face, FT_UInt, FT_Int32); 9796 FT_Error FT_Get_Advance (FT_Face, FT_UInt, FT_Int32, FT_Fixed*); 9797 FT_Error FT_Get_Kerning (FT_Face, FT_UInt, FT_UInt, FT_UInt, FT_Vector*); 9798 void FT_Outline_Get_CBox (const(FT_Outline)*, FT_BBox*); 9799 FT_Error FT_Outline_Decompose (FT_Outline*, const(FT_Outline_Funcs)*, void*); 9800 } 9801 } else version(bindbc) { 9802 import bindbc.freetype; 9803 alias FT_KERNING_DEFAULT = FT_Kerning_Mode.FT_KERNING_DEFAULT; 9804 alias FT_KERNING_UNFITTED = FT_Kerning_Mode.FT_KERNING_UNFITTED; 9805 alias FT_KERNING_UNSCALED = FT_Kerning_Mode.FT_KERNING_UNSCALED; 9806 } else { 9807 import iv.freetype; 9808 } 9809 9810 struct FONSttFontImpl { 9811 FT_Face font; 9812 bool mono; // no aa? 9813 } 9814 9815 __gshared FT_Library ftLibrary; 9816 9817 int fons__tt_init (FONSContext context) nothrow @trusted @nogc { 9818 FT_Error ftError; 9819 //FONS_NOTUSED(context); 9820 ftError = FT_Init_FreeType(&ftLibrary); 9821 return (ftError == 0); 9822 } 9823 9824 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc { 9825 font.mono = v; 9826 } 9827 9828 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc { 9829 return font.mono; 9830 } 9831 9832 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc { 9833 FT_Error ftError; 9834 //font.font.userdata = stash; 9835 ftError = FT_New_Memory_Face(ftLibrary, cast(const(FT_Byte)*)data, dataSize, 0, &font.font); 9836 return ftError == 0; 9837 } 9838 9839 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc { 9840 *ascent = font.font.ascender; 9841 *descent = font.font.descender; 9842 *lineGap = font.font.height-(*ascent - *descent); 9843 } 9844 9845 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc { 9846 return size/(font.font.ascender-font.font.descender); 9847 } 9848 9849 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc { 9850 return FT_Get_Char_Index(font.font, codepoint); 9851 } 9852 9853 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 { 9854 FT_Error ftError; 9855 FT_GlyphSlot ftGlyph; 9856 //version(nanovg_ignore_mono) enum exflags = 0; 9857 //else version(nanovg_ft_mono) enum exflags = FT_LOAD_MONOCHROME; else enum exflags = 0; 9858 uint exflags = (font.mono ? FT_LOAD_MONOCHROME : 0); 9859 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))); 9860 if (ftError) return 0; 9861 ftError = FT_Load_Glyph(font.font, glyph, FT_LOAD_RENDER|/*FT_LOAD_NO_AUTOHINT|*/exflags); 9862 if (ftError) return 0; 9863 ftError = FT_Get_Advance(font.font, glyph, FT_LOAD_NO_SCALE|/*FT_LOAD_NO_AUTOHINT|*/exflags, cast(FT_Fixed*)advance); 9864 if (ftError) return 0; 9865 ftGlyph = font.font.glyph; 9866 *lsb = cast(int)ftGlyph.metrics.horiBearingX; 9867 *x0 = ftGlyph.bitmap_left; 9868 *x1 = *x0+ftGlyph.bitmap.width; 9869 *y0 = -ftGlyph.bitmap_top; 9870 *y1 = *y0+ftGlyph.bitmap.rows; 9871 return 1; 9872 } 9873 9874 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc { 9875 FT_GlyphSlot ftGlyph = font.font.glyph; 9876 //FONS_NOTUSED(glyph); // glyph has already been loaded by fons__tt_buildGlyphBitmap 9877 //version(nanovg_ignore_mono) enum RenderAA = true; 9878 //else version(nanovg_ft_mono) enum RenderAA = false; 9879 //else enum RenderAA = true; 9880 if (font.mono) { 9881 auto src = ftGlyph.bitmap.buffer; 9882 auto dst = output; 9883 auto spt = ftGlyph.bitmap.pitch; 9884 if (spt < 0) spt = -spt; 9885 foreach (int y; 0..ftGlyph.bitmap.rows) { 9886 ubyte count = 0, b = 0; 9887 auto s = src; 9888 auto d = dst; 9889 foreach (int x; 0..ftGlyph.bitmap.width) { 9890 if (count-- == 0) { count = 7; b = *s++; } else b <<= 1; 9891 *d++ = (b&0x80 ? 255 : 0); 9892 } 9893 src += spt; 9894 dst += outStride; 9895 } 9896 } else { 9897 auto src = ftGlyph.bitmap.buffer; 9898 auto dst = output; 9899 auto spt = ftGlyph.bitmap.pitch; 9900 if (spt < 0) spt = -spt; 9901 foreach (int y; 0..ftGlyph.bitmap.rows) { 9902 import core.stdc.string : memcpy; 9903 //dst[0..ftGlyph.bitmap.width] = src[0..ftGlyph.bitmap.width]; 9904 memcpy(dst, src, ftGlyph.bitmap.width); 9905 src += spt; 9906 dst += outStride; 9907 } 9908 } 9909 } 9910 9911 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc { 9912 FT_Vector ftKerning; 9913 version(none) { 9914 // fitted kerning 9915 FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning); 9916 //{ import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d\n", glyph1, glyph2, ftKerning.x, ftKerning.y); } 9917 return cast(int)ftKerning.x; // round up and convert to integer 9918 } else { 9919 // unfitted kerning 9920 //FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNFITTED, &ftKerning); 9921 if (glyph1 <= 0 || glyph2 <= 0 || (font.font.face_flags&FT_FACE_FLAG_KERNING) == 0) return 0; 9922 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; 9923 if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning)) return 0; 9924 version(none) { 9925 if (ftKerning.x) { 9926 //{ import core.stdc.stdio : printf; printf("has kerning: %u\n", cast(uint)(font.font.face_flags&FT_FACE_FLAG_KERNING)); } 9927 { import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d (size=%g)\n", glyph1, glyph2, ftKerning.x, ftKerning.y, cast(double)size); } 9928 } 9929 } 9930 version(none) { 9931 FT_Vector kk; 9932 if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNSCALED, &kk)) assert(0, "wtf?!"); 9933 auto kadvfrac = FT_MulFix(kk.x, font.font.size.metrics.x_scale); // 1/64 of pixel 9934 //return cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6); 9935 //assert(ftKerning.x == kadvfrac); 9936 if (ftKerning.x || kadvfrac) { 9937 { 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); } 9938 } 9939 //return cast(int)(kadvfrac+(kadvfrac < 0 ? -31 : 32)>>6); // round up and convert to integer 9940 return kadvfrac/64.0f; 9941 } 9942 //return cast(int)(ftKerning.x+(ftKerning.x < 0 ? -31 : 32)>>6); // round up and convert to integer 9943 return ftKerning.x/64.0f; 9944 } 9945 } 9946 9947 extern(C) nothrow @trusted @nogc { 9948 static struct OutlinerData { 9949 @disable this (this); 9950 void opAssign() (const scope auto ref OutlinerData a) { static assert(0, "no copies!"); } 9951 NVGContext vg; 9952 NVGPathOutline.DataStore* ol; 9953 FT_BBox outlineBBox; 9954 nothrow @trusted @nogc: 9955 static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; } 9956 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; } 9957 } 9958 9959 int fons__nvg__moveto_cb (const(FT_Vector)* to, void* user) { 9960 auto odata = cast(OutlinerData*)user; 9961 if (odata.vg !is null) odata.vg.moveTo(odata.transx(to.x), odata.transy(to.y)); 9962 if (odata.ol !is null) { 9963 odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo); 9964 odata.ol.putArgs(odata.transx(to.x)); 9965 odata.ol.putArgs(odata.transy(to.y)); 9966 } 9967 return 0; 9968 } 9969 9970 int fons__nvg__lineto_cb (const(FT_Vector)* to, void* user) { 9971 auto odata = cast(OutlinerData*)user; 9972 if (odata.vg !is null) odata.vg.lineTo(odata.transx(to.x), odata.transy(to.y)); 9973 if (odata.ol !is null) { 9974 odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo); 9975 odata.ol.putArgs(odata.transx(to.x)); 9976 odata.ol.putArgs(odata.transy(to.y)); 9977 } 9978 return 0; 9979 } 9980 9981 int fons__nvg__quadto_cb (const(FT_Vector)* c1, const(FT_Vector)* to, void* user) { 9982 auto odata = cast(OutlinerData*)user; 9983 if (odata.vg !is null) odata.vg.quadTo(odata.transx(c1.x), odata.transy(c1.y), odata.transx(to.x), odata.transy(to.y)); 9984 if (odata.ol !is null) { 9985 odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo); 9986 odata.ol.putArgs(odata.transx(c1.x)); 9987 odata.ol.putArgs(odata.transy(c1.y)); 9988 odata.ol.putArgs(odata.transx(to.x)); 9989 odata.ol.putArgs(odata.transy(to.y)); 9990 } 9991 return 0; 9992 } 9993 9994 int fons__nvg__cubicto_cb (const(FT_Vector)* c1, const(FT_Vector)* c2, const(FT_Vector)* to, void* user) { 9995 auto odata = cast(OutlinerData*)user; 9996 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)); 9997 if (odata.ol !is null) { 9998 odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo); 9999 odata.ol.putArgs(odata.transx(c1.x)); 10000 odata.ol.putArgs(odata.transy(c1.y)); 10001 odata.ol.putArgs(odata.transx(c2.x)); 10002 odata.ol.putArgs(odata.transy(c2.y)); 10003 odata.ol.putArgs(odata.transx(to.x)); 10004 odata.ol.putArgs(odata.transy(to.y)); 10005 } 10006 return 0; 10007 } 10008 } 10009 10010 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc { 10011 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 10012 10013 FT_Outline_Funcs funcs; 10014 funcs.move_to = &fons__nvg__moveto_cb; 10015 funcs.line_to = &fons__nvg__lineto_cb; 10016 funcs.conic_to = &fons__nvg__quadto_cb; 10017 funcs.cubic_to = &fons__nvg__cubicto_cb; 10018 10019 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE); 10020 if (err) { bounds[] = 0; return false; } 10021 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; } 10022 10023 FT_Outline outline = font.font.glyph.outline; 10024 10025 OutlinerData odata; 10026 odata.vg = vg; 10027 FT_Outline_Get_CBox(&outline, &odata.outlineBBox); 10028 10029 err = FT_Outline_Decompose(&outline, &funcs, &odata); 10030 if (err) { bounds[] = 0; return false; } 10031 if (bounds.length > 0) bounds.ptr[0] = odata.outlineBBox.xMin; 10032 if (bounds.length > 1) bounds.ptr[1] = -odata.outlineBBox.yMax; 10033 if (bounds.length > 2) bounds.ptr[2] = odata.outlineBBox.xMax; 10034 if (bounds.length > 3) bounds.ptr[3] = -odata.outlineBBox.yMin; 10035 return true; 10036 } 10037 10038 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc { 10039 FT_Outline_Funcs funcs; 10040 funcs.move_to = &fons__nvg__moveto_cb; 10041 funcs.line_to = &fons__nvg__lineto_cb; 10042 funcs.conic_to = &fons__nvg__quadto_cb; 10043 funcs.cubic_to = &fons__nvg__cubicto_cb; 10044 10045 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE); 10046 if (err) return false; 10047 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) return false; 10048 10049 FT_Outline outline = font.font.glyph.outline; 10050 10051 OutlinerData odata; 10052 odata.ol = ol; 10053 FT_Outline_Get_CBox(&outline, &odata.outlineBBox); 10054 10055 err = FT_Outline_Decompose(&outline, &funcs, &odata); 10056 if (err) return false; 10057 ol.bounds.ptr[0] = odata.outlineBBox.xMin; 10058 ol.bounds.ptr[1] = -odata.outlineBBox.yMax; 10059 ol.bounds.ptr[2] = odata.outlineBBox.xMax; 10060 ol.bounds.ptr[3] = -odata.outlineBBox.yMin; 10061 return true; 10062 } 10063 10064 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc { 10065 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 10066 10067 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE); 10068 if (err) return false; 10069 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; } 10070 10071 FT_Outline outline = font.font.glyph.outline; 10072 FT_BBox outlineBBox; 10073 FT_Outline_Get_CBox(&outline, &outlineBBox); 10074 if (bounds.length > 0) bounds.ptr[0] = outlineBBox.xMin; 10075 if (bounds.length > 1) bounds.ptr[1] = -outlineBBox.yMax; 10076 if (bounds.length > 2) bounds.ptr[2] = outlineBBox.xMax; 10077 if (bounds.length > 3) bounds.ptr[3] = -outlineBBox.yMin; 10078 return true; 10079 } 10080 10081 10082 } else { 10083 // ////////////////////////////////////////////////////////////////////////// // 10084 // sorry 10085 import std.traits : isFunctionPointer, isDelegate; 10086 private auto assumeNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) { 10087 import std.traits; 10088 enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_; 10089 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 10090 } 10091 10092 private auto forceNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) { 10093 try { 10094 return assumeNoThrowNoGC(t)(); 10095 } catch (Exception e) { 10096 assert(0, "OOPS!"); 10097 } 10098 } 10099 10100 struct FONSttFontImpl { 10101 stbtt_fontinfo font; 10102 bool mono; // no aa? 10103 } 10104 10105 int fons__tt_init (FONSContext context) nothrow @trusted @nogc { 10106 return 1; 10107 } 10108 10109 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc { 10110 font.mono = v; 10111 } 10112 10113 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc { 10114 return font.mono; 10115 } 10116 10117 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc { 10118 int stbError; 10119 font.font.userdata = context; 10120 forceNoThrowNoGC({ stbError = stbtt_InitFont(&font.font, data, 0); }); 10121 return stbError; 10122 } 10123 10124 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc { 10125 forceNoThrowNoGC({ stbtt_GetFontVMetrics(&font.font, ascent, descent, lineGap); }); 10126 } 10127 10128 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc { 10129 float res = void; 10130 forceNoThrowNoGC({ res = stbtt_ScaleForPixelHeight(&font.font, size); }); 10131 return res; 10132 } 10133 10134 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc { 10135 int res; 10136 forceNoThrowNoGC({ res = stbtt_FindGlyphIndex(&font.font, codepoint); }); 10137 return res; 10138 } 10139 10140 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 { 10141 forceNoThrowNoGC({ stbtt_GetGlyphHMetrics(&font.font, glyph, advance, lsb); }); 10142 forceNoThrowNoGC({ stbtt_GetGlyphBitmapBox(&font.font, glyph, scale, scale, x0, y0, x1, y1); }); 10143 return 1; 10144 } 10145 10146 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc { 10147 forceNoThrowNoGC({ stbtt_MakeGlyphBitmap(&font.font, output, outWidth, outHeight, outStride, scaleX, scaleY, glyph); }); 10148 } 10149 10150 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc { 10151 // FUnits -> pixels: pointSize * resolution / (72 points per inch * units_per_em) 10152 // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#converting 10153 float res = void; 10154 forceNoThrowNoGC({ 10155 res = stbtt_GetGlyphKernAdvance(&font.font, glyph1, glyph2); 10156 res *= stbtt_ScaleForPixelHeight(&font.font, size); 10157 }); 10158 /* 10159 if (res != 0) { 10160 { 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)); } 10161 } 10162 */ 10163 //k8: dunno if this is right; i guess it isn't but... 10164 return res; 10165 } 10166 10167 // old arsd.ttf sux! ;-) 10168 static if (is(typeof(STBTT_vcubic))) { 10169 10170 static struct OutlinerData { 10171 @disable this (this); 10172 void opAssign() (const scope auto ref OutlinerData a) { static assert(0, "no copies!"); } 10173 NVGPathOutline.DataStore* ol; 10174 nothrow @trusted @nogc: 10175 static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; } 10176 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; } 10177 } 10178 10179 10180 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc { 10181 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 10182 10183 bool okflag = false; 10184 10185 forceNoThrowNoGC({ 10186 int x0, y0, x1, y1; 10187 if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) { 10188 bounds[] = 0; 10189 return; 10190 } 10191 10192 if (bounds.length > 0) bounds.ptr[0] = x0; 10193 if (bounds.length > 1) bounds.ptr[1] = -y1; 10194 if (bounds.length > 2) bounds.ptr[2] = x1; 10195 if (bounds.length > 3) bounds.ptr[3] = -y0; 10196 10197 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; } 10198 10199 stbtt_vertex* verts = null; 10200 scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); } 10201 int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts); 10202 if (vcount < 1) return; 10203 10204 foreach (const ref vt; verts[0..vcount]) { 10205 switch (vt.type) { 10206 case STBTT_vmove: vg.moveTo(vt.x, transy(vt.y)); break; 10207 case STBTT_vline: vg.lineTo(vt.x, transy(vt.y)); break; 10208 case STBTT_vcurve: vg.quadTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy)); break; 10209 case STBTT_vcubic: vg.bezierTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy), vt.cx1, transy(vt.cy1)); break; 10210 default: 10211 } 10212 } 10213 10214 okflag = true; 10215 }); 10216 10217 return okflag; 10218 } 10219 10220 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc { 10221 bool okflag = false; 10222 10223 forceNoThrowNoGC({ 10224 int x0, y0, x1, y1; 10225 10226 if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) { 10227 ol.bounds[] = 0; 10228 return; 10229 } 10230 10231 ol.bounds.ptr[0] = x0; 10232 ol.bounds.ptr[1] = -y1; 10233 ol.bounds.ptr[2] = x1; 10234 ol.bounds.ptr[3] = -y0; 10235 10236 stbtt_vertex* verts = null; 10237 scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); } 10238 int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts); 10239 if (vcount < 1) return; 10240 10241 OutlinerData odata; 10242 odata.ol = ol; 10243 10244 foreach (const ref vt; verts[0..vcount]) { 10245 switch (vt.type) { 10246 case STBTT_vmove: 10247 odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo); 10248 odata.ol.putArgs(odata.transx(vt.x)); 10249 odata.ol.putArgs(odata.transy(vt.y)); 10250 break; 10251 case STBTT_vline: 10252 odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo); 10253 odata.ol.putArgs(odata.transx(vt.x)); 10254 odata.ol.putArgs(odata.transy(vt.y)); 10255 break; 10256 case STBTT_vcurve: 10257 odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo); 10258 odata.ol.putArgs(odata.transx(vt.x)); 10259 odata.ol.putArgs(odata.transy(vt.y)); 10260 odata.ol.putArgs(odata.transx(vt.cx)); 10261 odata.ol.putArgs(odata.transy(vt.cy)); 10262 break; 10263 case STBTT_vcubic: 10264 odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo); 10265 odata.ol.putArgs(odata.transx(vt.x)); 10266 odata.ol.putArgs(odata.transy(vt.y)); 10267 odata.ol.putArgs(odata.transx(vt.cx)); 10268 odata.ol.putArgs(odata.transy(vt.cy)); 10269 odata.ol.putArgs(odata.transx(vt.cx1)); 10270 odata.ol.putArgs(odata.transy(vt.cy1)); 10271 break; 10272 default: 10273 } 10274 } 10275 10276 okflag = true; 10277 }); 10278 10279 return okflag; 10280 } 10281 10282 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc { 10283 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 10284 10285 bool okflag = false; 10286 10287 forceNoThrowNoGC({ 10288 int x0, y0, x1, y1; 10289 if (stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) { 10290 if (bounds.length > 0) bounds.ptr[0] = x0; 10291 if (bounds.length > 1) bounds.ptr[1] = -y1; 10292 if (bounds.length > 2) bounds.ptr[2] = x1; 10293 if (bounds.length > 3) bounds.ptr[3] = -y0; 10294 okflag = true; 10295 } else { 10296 bounds[] = 0; 10297 } 10298 }); 10299 10300 return okflag; 10301 } 10302 10303 } // check for old stb_ttf 10304 10305 10306 } // version 10307 10308 10309 // ////////////////////////////////////////////////////////////////////////// // 10310 private: 10311 enum FONS_SCRATCH_BUF_SIZE = 64000; 10312 enum FONS_HASH_LUT_SIZE = 256; 10313 enum FONS_INIT_FONTS = 4; 10314 enum FONS_INIT_GLYPHS = 256; 10315 enum FONS_INIT_ATLAS_NODES = 256; 10316 enum FONS_VERTEX_COUNT = 1024; 10317 enum FONS_MAX_STATES = 20; 10318 enum FONS_MAX_FALLBACKS = 20; 10319 10320 10321 struct FONSglyph { 10322 uint codepoint; 10323 int index; 10324 int next; 10325 short size, blur; 10326 short x0, y0, x1, y1; 10327 short xadv, xoff, yoff; 10328 } 10329 10330 // refcounted 10331 struct FONSfontData { 10332 ubyte* data; 10333 int dataSize; 10334 bool freeData; 10335 int rc; 10336 10337 @disable this (this); // no copies 10338 void opAssign() (const scope auto ref FONSfontData a) { static assert(0, "no copies!"); } 10339 } 10340 10341 // won't set rc to 1 10342 FONSfontData* fons__createFontData (ubyte* adata, int asize, bool afree) nothrow @trusted @nogc { 10343 import core.stdc.stdlib : malloc; 10344 assert(adata !is null); 10345 assert(asize > 0); 10346 auto res = cast(FONSfontData*)malloc(FONSfontData.sizeof); 10347 if (res is null) assert(0, "FONS: out of memory"); 10348 res.data = adata; 10349 res.dataSize = asize; 10350 res.freeData = afree; 10351 res.rc = 0; 10352 return res; 10353 } 10354 10355 void incref (FONSfontData* fd) pure nothrow @trusted @nogc { 10356 pragma(inline, true); 10357 if (fd !is null) ++fd.rc; 10358 } 10359 10360 void decref (ref FONSfontData* fd) nothrow @trusted @nogc { 10361 if (fd !is null) { 10362 if (--fd.rc == 0) { 10363 import core.stdc.stdlib : free; 10364 if (fd.freeData && fd.data !is null) { 10365 free(fd.data); 10366 fd.data = null; 10367 } 10368 free(fd); 10369 fd = null; 10370 } 10371 } 10372 } 10373 10374 // as creating and destroying fonts is a rare operation, malloc some data 10375 struct FONSfont { 10376 FONSttFontImpl font; 10377 char* name; // malloced, strz, always lowercase 10378 uint namelen; 10379 uint namehash; 10380 char* path; // malloced, strz 10381 FONSfontData* fdata; 10382 float ascender; 10383 float descender; 10384 float lineh; 10385 FONSglyph* glyphs; 10386 int cglyphs; 10387 int nglyphs; 10388 int[FONS_HASH_LUT_SIZE] lut; 10389 int[FONS_MAX_FALLBACKS] fallbacks; 10390 int nfallbacks; 10391 10392 @disable this (this); 10393 void opAssign() (const scope auto ref FONSfont a) { static assert(0, "no copies"); } 10394 10395 static uint djbhash (const(void)[] s) pure nothrow @safe @nogc { 10396 uint hash = 5381; 10397 foreach (ubyte b; cast(const(ubyte)[])s) { 10398 if (b >= 'A' && b <= 'Z') b += 32; // poor man's tolower 10399 hash = ((hash<<5)+hash)+b; 10400 } 10401 return hash; 10402 } 10403 10404 // except glyphs 10405 void freeMemory () nothrow @trusted @nogc { 10406 import core.stdc.stdlib : free; 10407 if (name !is null) { free(name); name = null; } 10408 namelen = namehash = 0; 10409 if (path !is null) { free(path); path = null; } 10410 fdata.decref(); 10411 } 10412 10413 // this also calcs name hash 10414 void setName (const(char)[] aname) nothrow @trusted @nogc { 10415 //{ import core.stdc.stdio; printf("setname: [%.*s]\n", cast(uint)aname.length, aname.ptr); } 10416 import core.stdc.stdlib : realloc; 10417 if (aname.length > int.max/32) assert(0, "FONS: invalid font name"); 10418 namelen = cast(uint)aname.length; 10419 name = cast(char*)realloc(name, namelen+1); 10420 if (name is null) assert(0, "FONS: out of memory"); 10421 if (aname.length) name[0..aname.length] = aname[]; 10422 name[namelen] = 0; 10423 // lowercase it 10424 foreach (ref char ch; name[0..namelen]) if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower 10425 namehash = djbhash(name[0..namelen]); 10426 //{ import core.stdc.stdio; printf(" [%s] [%.*s] [0x%08x]\n", name, namelen, name, namehash); } 10427 } 10428 10429 void setPath (const(char)[] apath) nothrow @trusted @nogc { 10430 import core.stdc.stdlib : realloc; 10431 if (apath.length > int.max/32) assert(0, "FONS: invalid font path"); 10432 path = cast(char*)realloc(path, apath.length+1); 10433 if (path is null) assert(0, "FONS: out of memory"); 10434 if (apath.length) path[0..apath.length] = apath[]; 10435 path[apath.length] = 0; 10436 } 10437 10438 // this won't check hash 10439 bool nameEqu (const(char)[] aname) const pure nothrow @trusted @nogc { 10440 //{ import core.stdc.stdio; printf("nameEqu: aname=[%.*s]; namelen=%u; aslen=%u\n", cast(uint)aname.length, aname.ptr, namelen, cast(uint)aname.length); } 10441 if (namelen != aname.length) return false; 10442 const(char)* ns = name; 10443 // name part 10444 foreach (char ch; aname) { 10445 if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower 10446 if (ch != *ns++) return false; 10447 } 10448 // done (length was checked earlier) 10449 return true; 10450 } 10451 10452 void clear () nothrow @trusted @nogc { 10453 import core.stdc.stdlib : free; 10454 import core.stdc.string : memset; 10455 if (glyphs !is null) free(glyphs); 10456 freeMemory(); 10457 memset(&this, 0, this.sizeof); 10458 } 10459 10460 FONSglyph* allocGlyph () nothrow @trusted @nogc { 10461 if (nglyphs+1 > cglyphs) { 10462 import core.stdc.stdlib : realloc; 10463 cglyphs = (cglyphs == 0 ? 8 : cglyphs*2); 10464 glyphs = cast(FONSglyph*)realloc(glyphs, FONSglyph.sizeof*cglyphs); 10465 if (glyphs is null) assert(0, "FontStash: out of memory"); 10466 } 10467 ++nglyphs; 10468 return &glyphs[nglyphs-1]; 10469 } 10470 } 10471 10472 void kill (ref FONSfont* font) nothrow @trusted @nogc { 10473 if (font !is null) { 10474 import core.stdc.stdlib : free; 10475 font.clear(); 10476 free(font); 10477 font = null; 10478 } 10479 } 10480 10481 10482 // ////////////////////////////////////////////////////////////////////////// // 10483 struct FONSstate { 10484 int font; 10485 NVGTextAlign talign; 10486 float size = 0; 10487 float blur = 0; 10488 float spacing = 0; 10489 } 10490 10491 10492 // ////////////////////////////////////////////////////////////////////////// // 10493 // atlas based on Skyline Bin Packer by Jukka Jylänki 10494 alias FONSAtlas = FONSatlasInternal*; 10495 10496 struct FONSatlasInternal { 10497 static struct Node { 10498 short x, y, width; 10499 } 10500 10501 int width, height; 10502 Node* nodes; 10503 int nnodes; 10504 int cnodes; 10505 10506 @disable this (this); 10507 void opAssign() (const scope auto ref FONSatlasInternal a) { static assert(0, "no copies"); } 10508 10509 nothrow @trusted @nogc: 10510 static FONSAtlas create (int w, int h, int nnodes) { 10511 import core.stdc.stdlib : malloc; 10512 import core.stdc.string : memset; 10513 10514 FONSAtlas atlas = cast(FONSAtlas)malloc(FONSatlasInternal.sizeof); 10515 if (atlas is null) assert(0, "FontStash: out of memory"); 10516 memset(atlas, 0, FONSatlasInternal.sizeof); 10517 10518 atlas.width = w; 10519 atlas.height = h; 10520 10521 // allocate space for skyline nodes 10522 atlas.nodes = cast(Node*)malloc(Node.sizeof*nnodes); 10523 if (atlas.nodes is null) assert(0, "FontStash: out of memory"); 10524 memset(atlas.nodes, 0, Node.sizeof*nnodes); 10525 atlas.nnodes = 0; 10526 atlas.cnodes = nnodes; 10527 10528 // init root node 10529 atlas.nodes[0].x = 0; 10530 atlas.nodes[0].y = 0; 10531 atlas.nodes[0].width = cast(short)w; 10532 ++atlas.nnodes; 10533 10534 return atlas; 10535 } 10536 10537 void clear () { 10538 import core.stdc.stdlib : free; 10539 import core.stdc.string : memset; 10540 10541 if (nodes !is null) free(nodes); 10542 memset(&this, 0, this.sizeof); 10543 } 10544 10545 void insertNode (int idx, int x, int y, int w) { 10546 if (nnodes+1 > cnodes) { 10547 import core.stdc.stdlib : realloc; 10548 cnodes = (cnodes == 0 ? 8 : cnodes*2); 10549 nodes = cast(Node*)realloc(nodes, Node.sizeof*cnodes); 10550 if (nodes is null) assert(0, "FontStash: out of memory"); 10551 } 10552 for (int i = nnodes; i > idx; --i) nodes[i] = nodes[i-1]; 10553 nodes[idx].x = cast(short)x; 10554 nodes[idx].y = cast(short)y; 10555 nodes[idx].width = cast(short)w; 10556 ++nnodes; 10557 } 10558 10559 void removeNode (int idx) { 10560 if (nnodes == 0) return; 10561 foreach (immutable int i; idx+1..nnodes) nodes[i-1] = nodes[i]; 10562 --nnodes; 10563 } 10564 10565 // insert node for empty space 10566 void expand (int w, int h) { 10567 if (w > width) insertNode(nnodes, width, 0, w-width); 10568 width = w; 10569 height = h; 10570 } 10571 10572 void reset (int w, int h) { 10573 width = w; 10574 height = h; 10575 nnodes = 0; 10576 // init root node 10577 nodes[0].x = 0; 10578 nodes[0].y = 0; 10579 nodes[0].width = cast(short)w; 10580 ++nnodes; 10581 } 10582 10583 void addSkylineLevel (int idx, int x, int y, int w, int h) { 10584 insertNode(idx, x, y+h, w); 10585 10586 // delete skyline segments that fall under the shadow of the new segment 10587 for (int i = idx+1; i < nnodes; ++i) { 10588 if (nodes[i].x < nodes[i-1].x+nodes[i-1].width) { 10589 int shrink = nodes[i-1].x+nodes[i-1].width-nodes[i].x; 10590 nodes[i].x += cast(short)shrink; 10591 nodes[i].width -= cast(short)shrink; 10592 if (nodes[i].width <= 0) { 10593 removeNode(i); 10594 --i; 10595 } else { 10596 break; 10597 } 10598 } else { 10599 break; 10600 } 10601 } 10602 10603 // Merge same height skyline segments that are next to each other 10604 for (int i = 0; i < nnodes-1; ++i) { 10605 if (nodes[i].y == nodes[i+1].y) { 10606 nodes[i].width += nodes[i+1].width; 10607 removeNode(i+1); 10608 --i; 10609 } 10610 } 10611 } 10612 10613 // checks if there is enough space at the location of skyline span 'i', 10614 // and return the max height of all skyline spans under that at that location, 10615 // (think tetris block being dropped at that position); or -1 if no space found 10616 int rectFits (int i, int w, int h) { 10617 int x = nodes[i].x; 10618 int y = nodes[i].y; 10619 if (x+w > width) return -1; 10620 int spaceLeft = w; 10621 while (spaceLeft > 0) { 10622 if (i == nnodes) return -1; 10623 y = nvg__max(y, nodes[i].y); 10624 if (y+h > height) return -1; 10625 spaceLeft -= nodes[i].width; 10626 ++i; 10627 } 10628 return y; 10629 } 10630 10631 bool addRect (int rw, int rh, int* rx, int* ry) { 10632 int besth = height, bestw = width, besti = -1; 10633 int bestx = -1, besty = -1; 10634 10635 // Bottom left fit heuristic. 10636 for (int i = 0; i < nnodes; ++i) { 10637 int y = rectFits(i, rw, rh); 10638 if (y != -1) { 10639 if (y+rh < besth || (y+rh == besth && nodes[i].width < bestw)) { 10640 besti = i; 10641 bestw = nodes[i].width; 10642 besth = y+rh; 10643 bestx = nodes[i].x; 10644 besty = y; 10645 } 10646 } 10647 } 10648 10649 if (besti == -1) return false; 10650 10651 // perform the actual packing 10652 addSkylineLevel(besti, bestx, besty, rw, rh); 10653 10654 *rx = bestx; 10655 *ry = besty; 10656 10657 return true; 10658 } 10659 } 10660 10661 void kill (ref FONSAtlas atlas) nothrow @trusted @nogc { 10662 if (atlas !is null) { 10663 import core.stdc.stdlib : free; 10664 atlas.clear(); 10665 free(atlas); 10666 atlas = null; 10667 } 10668 } 10669 10670 10671 // ////////////////////////////////////////////////////////////////////////// // 10672 /// FontStash context (internal definition). Don't use it derectly, it was made public only to generate documentation. 10673 /// Group: font_stash 10674 public struct FONScontextInternal { 10675 private: 10676 FONSParams params; 10677 float itw, ith; 10678 ubyte* texData; 10679 int[4] dirtyRect; 10680 FONSfont** fonts; // actually, a simple hash table; can't grow yet 10681 int cfonts; // allocated 10682 int nfonts; // used (so we can track hash table stats) 10683 int* hashidx; // [hsize] items; holds indicies in [fonts] array 10684 int hused, hsize;// used items and total items in [hashidx] 10685 FONSAtlas atlas; 10686 ubyte* scratch; 10687 int nscratch; 10688 FONSstate[FONS_MAX_STATES] states; 10689 int nstates; 10690 10691 void delegate (FONSError error, int val) nothrow @trusted @nogc handleError; 10692 10693 @disable this (this); 10694 void opAssign() (const scope auto ref FONScontextInternal ctx) { static assert(0, "FONS copying is not allowed"); } 10695 10696 private: 10697 static bool strequci (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 10698 if (s0.length != s1.length) return false; 10699 const(char)* sp0 = s0.ptr; 10700 const(char)* sp1 = s1.ptr; 10701 foreach (immutable _; 0..s0.length) { 10702 char c0 = *sp0++; 10703 char c1 = *sp1++; 10704 if (c0 != c1) { 10705 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man tolower 10706 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man tolower 10707 if (c0 != c1) return false; 10708 } 10709 } 10710 return true; 10711 } 10712 10713 inout(FONSstate)* getState () inout pure nothrow @trusted @nogc return { 10714 pragma(inline, true); 10715 return cast(inout)(&states[(nstates > 0 ? nstates-1 : 0)]); 10716 } 10717 10718 // simple linear probing; returns [FONS_INVALID] if not found 10719 int findNameInHash (const(char)[] name) const pure nothrow @trusted @nogc { 10720 if (nfonts == 0) return FONS_INVALID; 10721 auto nhash = FONSfont.djbhash(name); 10722 //{ import core.stdc.stdio; printf("findinhash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); } 10723 auto res = nhash%hsize; 10724 // hash will never be 100% full, so this loop is safe 10725 for (;;) { 10726 int idx = hashidx[res]; 10727 if (idx == -1) break; 10728 auto font = fonts[idx]; 10729 if (font is null) assert(0, "FONS internal error"); 10730 if (font.namehash == nhash && font.nameEqu(name)) return idx; 10731 //{ import core.stdc.stdio; printf("findinhash chained: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); } 10732 res = (res+1)%hsize; 10733 } 10734 return FONS_INVALID; 10735 } 10736 10737 // should be called $(B before) freeing `fonts[fidx]` 10738 void removeIndexFromHash (int fidx) nothrow @trusted @nogc { 10739 if (fidx < 0 || fidx >= nfonts) assert(0, "FONS internal error"); 10740 if (fonts[fidx] is null) assert(0, "FONS internal error"); 10741 if (hused != nfonts) assert(0, "FONS internal error"); 10742 auto nhash = fonts[fidx].namehash; 10743 auto res = nhash%hsize; 10744 // hash will never be 100% full, so this loop is safe 10745 for (;;) { 10746 int idx = hashidx[res]; 10747 if (idx == -1) assert(0, "FONS INTERNAL ERROR"); 10748 if (idx == fidx) { 10749 // i found her! copy rest here 10750 int nidx = (res+1)%hsize; 10751 for (;;) { 10752 if ((hashidx[res] = hashidx[nidx]) == -1) break; // so it will copy `-1` too 10753 res = nidx; 10754 nidx = (nidx+1)%hsize; 10755 } 10756 return; 10757 } 10758 res = (res+1)%hsize; 10759 } 10760 } 10761 10762 // add font with the given index to hash 10763 // prerequisite: font should not exists in hash 10764 void addIndexToHash (int idx) nothrow @trusted @nogc { 10765 if (idx < 0 || idx >= nfonts) assert(0, "FONS internal error"); 10766 if (fonts[idx] is null) assert(0, "FONS internal error"); 10767 import core.stdc.stdlib : realloc; 10768 auto nhash = fonts[idx].namehash; 10769 //{ import core.stdc.stdio; printf("addtohash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); } 10770 // allocate new hash table if there was none 10771 if (hsize == 0) { 10772 enum InitSize = 256; 10773 auto newlist = cast(int*)realloc(null, InitSize*hashidx[0].sizeof); 10774 if (newlist is null) assert(0, "FONS: out of memory"); 10775 newlist[0..InitSize] = -1; 10776 hsize = InitSize; 10777 hused = 0; 10778 hashidx = newlist; 10779 } 10780 int res = cast(int)(nhash%hsize); 10781 // need to rehash? we want our hash table 50% full at max 10782 if (hashidx[res] != -1 && hused >= hsize/2) { 10783 uint nsz = hsize*2; 10784 if (nsz > 1024*1024) assert(0, "FONS: out of memory for fonts"); 10785 auto newlist = cast(int*)realloc(fonts, nsz*hashidx[0].sizeof); 10786 if (newlist is null) assert(0, "FONS: out of memory"); 10787 newlist[0..nsz] = -1; 10788 hused = 0; 10789 // rehash 10790 foreach (immutable fidx, FONSfont* ff; fonts[0..nfonts]) { 10791 if (ff is null) continue; 10792 // find slot for this font (guaranteed to have one) 10793 uint newslot = ff.namehash%nsz; 10794 while (newlist[newslot] != -1) newslot = (newslot+1)%nsz; 10795 newlist[newslot] = cast(int)fidx; 10796 ++hused; 10797 } 10798 hsize = nsz; 10799 hashidx = newlist; 10800 // we added everything, including [idx], so nothing more to do here 10801 } else { 10802 // find slot (guaranteed to have one) 10803 while (hashidx[res] != -1) res = (res+1)%hsize; 10804 // i found her! 10805 hashidx[res] = idx; 10806 ++hused; 10807 } 10808 } 10809 10810 void addWhiteRect (int w, int h) nothrow @trusted @nogc { 10811 int gx, gy; 10812 ubyte* dst; 10813 10814 if (!atlas.addRect(w, h, &gx, &gy)) return; 10815 10816 // Rasterize 10817 dst = &texData[gx+gy*params.width]; 10818 foreach (int y; 0..h) { 10819 foreach (int x; 0..w) { 10820 dst[x] = 0xff; 10821 } 10822 dst += params.width; 10823 } 10824 10825 dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], gx); 10826 dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], gy); 10827 dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], gx+w); 10828 dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], gy+h); 10829 } 10830 10831 // returns fid, not hash slot 10832 int allocFontAt (int atidx) nothrow @trusted @nogc { 10833 if (atidx >= 0 && atidx >= nfonts) assert(0, "internal NanoVega fontstash error"); 10834 10835 if (atidx < 0) { 10836 if (nfonts >= cfonts) { 10837 import core.stdc.stdlib : realloc; 10838 import core.stdc.string : memset; 10839 assert(nfonts == cfonts); 10840 int newsz = cfonts+64; 10841 if (newsz > 65535) assert(0, "FONS: too many fonts"); 10842 auto newlist = cast(FONSfont**)realloc(fonts, newsz*(FONSfont*).sizeof); 10843 if (newlist is null) assert(0, "FONS: out of memory"); 10844 memset(newlist+cfonts, 0, (newsz-cfonts)*(FONSfont*).sizeof); 10845 fonts = newlist; 10846 cfonts = newsz; 10847 } 10848 assert(nfonts < cfonts); 10849 } 10850 10851 FONSfont* font = cast(FONSfont*)malloc(FONSfont.sizeof); 10852 if (font is null) assert(0, "FONS: out of memory"); 10853 memset(font, 0, FONSfont.sizeof); 10854 10855 font.glyphs = cast(FONSglyph*)malloc(FONSglyph.sizeof*FONS_INIT_GLYPHS); 10856 if (font.glyphs is null) assert(0, "FONS: out of memory"); 10857 font.cglyphs = FONS_INIT_GLYPHS; 10858 font.nglyphs = 0; 10859 10860 if (atidx < 0) { 10861 fonts[nfonts] = font; 10862 return nfonts++; 10863 } else { 10864 fonts[atidx] = font; 10865 return atidx; 10866 } 10867 } 10868 10869 // 0: ooops 10870 int findGlyphForCP (FONSfont *font, dchar dch, FONSfont** renderfont) nothrow @trusted @nogc { 10871 if (renderfont !is null) *renderfont = font; 10872 if (font is null || font.fdata is null) return 0; 10873 auto g = fons__tt_getGlyphIndex(&font.font, cast(uint)dch); 10874 // try to find the glyph in fallback fonts 10875 if (g == 0 && renderfont !is null) { 10876 foreach (immutable i; 0..font.nfallbacks) { 10877 FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]]; 10878 if (fallbackFont !is null) { 10879 int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, cast(uint)dch); 10880 if (fallbackIndex != 0) { 10881 *renderfont = fallbackFont; 10882 return fallbackIndex; 10883 } 10884 } 10885 } 10886 // no char, try to find replacement one 10887 if (dch != 0xFFFD) { 10888 g = fons__tt_getGlyphIndex(&font.font, 0xFFFD); 10889 if (g == 0) { 10890 foreach (immutable i; 0..font.nfallbacks) { 10891 FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]]; 10892 if (fallbackFont !is null) { 10893 int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, 0xFFFD); 10894 if (fallbackIndex != 0) { 10895 *renderfont = fallbackFont; 10896 return fallbackIndex; 10897 } 10898 } 10899 } 10900 } 10901 } 10902 } 10903 return g; 10904 } 10905 10906 void clear () nothrow @trusted @nogc { 10907 import core.stdc.stdlib : free; 10908 10909 if (params.renderDelete !is null) params.renderDelete(params.userPtr); 10910 foreach (immutable int i; 0..nfonts) fonts[i].kill(); 10911 10912 if (atlas !is null) atlas.kill(); 10913 if (fonts !is null) free(fonts); 10914 if (texData !is null) free(texData); 10915 if (scratch !is null) free(scratch); 10916 if (hashidx !is null) free(hashidx); 10917 } 10918 10919 // add font from another fontstash 10920 int addCookedFont (FONSfont* font) nothrow @trusted @nogc { 10921 if (font is null || font.fdata is null) return FONS_INVALID; 10922 font.fdata.incref(); 10923 auto res = addFontWithData(font.name[0..font.namelen], font.fdata, !font.font.mono); 10924 if (res == FONS_INVALID) font.fdata.decref(); // oops 10925 return res; 10926 } 10927 10928 // fdata refcount must be already increased; it won't be changed 10929 int addFontWithData (const(char)[] name, FONSfontData* fdata, bool defAA) nothrow @trusted @nogc { 10930 int i, ascent, descent, fh, lineGap; 10931 10932 if (name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID; 10933 if (name.length > 32767) return FONS_INVALID; 10934 if (fdata is null) return FONS_INVALID; 10935 10936 // find a font with the given name 10937 int newidx; 10938 FONSfont* oldfont = null; 10939 int oldidx = findNameInHash(name); 10940 if (oldidx != FONS_INVALID) { 10941 // replacement font 10942 oldfont = fonts[oldidx]; 10943 newidx = oldidx; 10944 } else { 10945 // new font, allocate new bucket 10946 newidx = -1; 10947 } 10948 10949 newidx = allocFontAt(newidx); 10950 FONSfont* font = fonts[newidx]; 10951 font.setName(name); 10952 font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1; // init hash lookup 10953 font.fdata = fdata; // set the font data (don't change reference count) 10954 fons__tt_setMono(&this, &font.font, !defAA); 10955 10956 // init font 10957 nscratch = 0; 10958 if (!fons__tt_loadFont(&this, &font.font, fdata.data, fdata.dataSize)) { 10959 // we promised to not free data on error, so just clear the data store (it will be freed by the caller) 10960 font.fdata = null; 10961 font.kill(); 10962 if (oldidx != FONS_INVALID) { 10963 assert(oldidx == newidx); 10964 fonts[oldidx] = oldfont; 10965 } else { 10966 assert(newidx == nfonts-1); 10967 fonts[newidx] = null; 10968 --nfonts; 10969 } 10970 return FONS_INVALID; 10971 } else { 10972 // free old font data, if any 10973 if (oldfont !is null) oldfont.kill(); 10974 } 10975 10976 // add font to name hash 10977 if (oldidx == FONS_INVALID) addIndexToHash(newidx); 10978 10979 // store normalized line height 10980 // the real line height is got by multiplying the lineh by font size 10981 fons__tt_getFontVMetrics(&font.font, &ascent, &descent, &lineGap); 10982 fh = ascent-descent; 10983 font.ascender = cast(float)ascent/cast(float)fh; 10984 font.descender = cast(float)descent/cast(float)fh; 10985 font.lineh = cast(float)(fh+lineGap)/cast(float)fh; 10986 10987 //{ import core.stdc.stdio; printf("created font [%.*s] (idx=%d)...\n", cast(uint)name.length, name.ptr, idx); } 10988 return newidx; 10989 } 10990 10991 // isize: size*10 10992 float getVertAlign (FONSfont* font, NVGTextAlign talign, short isize) pure nothrow @trusted @nogc { 10993 if (params.isZeroTopLeft) { 10994 final switch (talign.vertical) { 10995 case NVGTextAlign.V.Top: return font.ascender*cast(float)isize/10.0f; 10996 case NVGTextAlign.V.Middle: return (font.ascender+font.descender)/2.0f*cast(float)isize/10.0f; 10997 case NVGTextAlign.V.Baseline: return 0.0f; 10998 case NVGTextAlign.V.Bottom: return font.descender*cast(float)isize/10.0f; 10999 } 11000 } else { 11001 final switch (talign.vertical) { 11002 case NVGTextAlign.V.Top: return -font.ascender*cast(float)isize/10.0f; 11003 case NVGTextAlign.V.Middle: return -(font.ascender+font.descender)/2.0f*cast(float)isize/10.0f; 11004 case NVGTextAlign.V.Baseline: return 0.0f; 11005 case NVGTextAlign.V.Bottom: return -font.descender*cast(float)isize/10.0f; 11006 } 11007 } 11008 assert(0); 11009 } 11010 11011 public: 11012 /** Create new FontStash context. It can be destroyed with `fs.kill()` later. 11013 * 11014 * Note that if you don't plan to rasterize glyphs (i.e. you will use created 11015 * FontStash only to measure text), you can simply pass `FONSParams.init`). 11016 */ 11017 static FONSContext create() (const scope auto ref FONSParams params) nothrow @trusted @nogc { 11018 import core.stdc.string : memcpy; 11019 11020 FONSContext stash = null; 11021 11022 // allocate memory for the font stash 11023 stash = cast(FONSContext)malloc(FONScontextInternal.sizeof); 11024 if (stash is null) goto error; 11025 memset(stash, 0, FONScontextInternal.sizeof); 11026 11027 memcpy(&stash.params, ¶ms, params.sizeof); 11028 if (stash.params.width < 1) stash.params.width = 32; 11029 if (stash.params.height < 1) stash.params.height = 32; 11030 11031 // allocate scratch buffer 11032 stash.scratch = cast(ubyte*)malloc(FONS_SCRATCH_BUF_SIZE); 11033 if (stash.scratch is null) goto error; 11034 11035 // initialize implementation library 11036 if (!fons__tt_init(stash)) goto error; 11037 11038 if (stash.params.renderCreate !is null) { 11039 if (!stash.params.renderCreate(stash.params.userPtr, stash.params.width, stash.params.height)) goto error; 11040 } 11041 11042 stash.atlas = FONSAtlas.create(stash.params.width, stash.params.height, FONS_INIT_ATLAS_NODES); 11043 if (stash.atlas is null) goto error; 11044 11045 // don't allocate space for fonts: hash manager will do that for us later 11046 //stash.cfonts = 0; 11047 //stash.nfonts = 0; 11048 11049 // create texture for the cache 11050 stash.itw = 1.0f/stash.params.width; 11051 stash.ith = 1.0f/stash.params.height; 11052 stash.texData = cast(ubyte*)malloc(stash.params.width*stash.params.height); 11053 if (stash.texData is null) goto error; 11054 memset(stash.texData, 0, stash.params.width*stash.params.height); 11055 11056 stash.dirtyRect.ptr[0] = stash.params.width; 11057 stash.dirtyRect.ptr[1] = stash.params.height; 11058 stash.dirtyRect.ptr[2] = 0; 11059 stash.dirtyRect.ptr[3] = 0; 11060 11061 // add white rect at 0, 0 for debug drawing 11062 stash.addWhiteRect(2, 2); 11063 11064 stash.pushState(); 11065 stash.clearState(); 11066 11067 return stash; 11068 11069 error: 11070 stash.kill(); 11071 return null; 11072 } 11073 11074 public: 11075 /// Add fallback font (FontStash will try to find missing glyph in all fallback fonts before giving up). 11076 bool addFallbackFont (int base, int fallback) nothrow @trusted @nogc { 11077 FONSfont* baseFont = fonts[base]; 11078 if (baseFont !is null && baseFont.nfallbacks < FONS_MAX_FALLBACKS) { 11079 baseFont.fallbacks.ptr[baseFont.nfallbacks++] = fallback; 11080 return true; 11081 } 11082 return false; 11083 } 11084 11085 @property void size (float size) nothrow @trusted @nogc { pragma(inline, true); getState.size = size; } /// Set current font size. 11086 @property float size () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.size; } /// Get current font size. 11087 11088 @property void spacing (float spacing) nothrow @trusted @nogc { pragma(inline, true); getState.spacing = spacing; } /// Set current letter spacing. 11089 @property float spacing () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.spacing; } /// Get current letter spacing. 11090 11091 @property void blur (float blur) nothrow @trusted @nogc { pragma(inline, true); getState.blur = blur; } /// Set current letter blur. 11092 @property float blur () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.blur; } /// Get current letter blur. 11093 11094 @property void textAlign (NVGTextAlign talign) nothrow @trusted @nogc { pragma(inline, true); getState.talign = talign; } /// Set current text align. 11095 @property NVGTextAlign textAlign () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.talign; } /// Get current text align. 11096 11097 @property void fontId (int font) nothrow @trusted @nogc { pragma(inline, true); getState.font = font; } /// Set current font id. 11098 @property int fontId () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.font; } /// Get current font id. 11099 11100 @property void fontId (const(char)[] name) nothrow @trusted @nogc { pragma(inline, true); getState.font = getFontByName(name); } /// Set current font using its name. 11101 11102 /// Check if FontStash has a font with the given name loaded. 11103 bool hasFont (const(char)[] name) const pure nothrow @trusted @nogc { pragma(inline, true); return (getFontByName(name) >= 0); } 11104 11105 /// Get AA for the current font, or for the specified font. 11106 bool getFontAA (int font=-1) nothrow @trusted @nogc { 11107 FONSstate* state = getState; 11108 if (font < 0) font = state.font; 11109 if (font < 0 || font >= nfonts) return false; 11110 FONSfont* f = fonts[font]; 11111 return (f !is null ? !f.font.mono : false); 11112 } 11113 11114 /// Push current state. Returns `false` if state stack overflowed. 11115 bool pushState () nothrow @trusted @nogc { 11116 if (nstates >= FONS_MAX_STATES) { 11117 if (handleError !is null) handleError(FONSError.StatesOverflow, 0); 11118 return false; 11119 } 11120 if (nstates > 0) { 11121 import core.stdc.string : memcpy; 11122 memcpy(&states[nstates], &states[nstates-1], FONSstate.sizeof); 11123 } 11124 ++nstates; 11125 return true; 11126 } 11127 11128 /// Pop current state. Returns `false` if state stack underflowed. 11129 bool popState () nothrow @trusted @nogc { 11130 if (nstates <= 1) { 11131 if (handleError !is null) handleError(FONSError.StatesUnderflow, 0); 11132 return false; 11133 } 11134 --nstates; 11135 return true; 11136 } 11137 11138 /// Clear current state (i.e. set it to some sane defaults). 11139 void clearState () nothrow @trusted @nogc { 11140 FONSstate* state = getState; 11141 state.size = 12.0f; 11142 state.font = 0; 11143 state.blur = 0; 11144 state.spacing = 0; 11145 state.talign.reset; 11146 } 11147 11148 private enum NoAlias = ":noaa"; 11149 11150 /** Add font to FontStash. 11151 * 11152 * Load scalable font from disk, and add it to FontStash. If you will try to load a font 11153 * with same name and path several times, FontStash will load it only once. Also, you can 11154 * load new disk font for any existing logical font. 11155 * 11156 * Params: 11157 * name = logical font name, that will be used to select this font later. 11158 * path = path to disk file with your font. 11159 * defAA = should FontStash use antialiased font rasterizer? 11160 * 11161 * Returns: 11162 * font id or [FONS_INVALID]. 11163 */ 11164 int addFont (const(char)[] name, const(char)[] path, bool defAA=false) nothrow @trusted { 11165 if (path.length == 0 || name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID; 11166 if (path.length > 32768) return FONS_INVALID; // arbitrary limit 11167 11168 // if font path ends with ":noaa", turn off antialiasing 11169 if (path.length >= NoAlias.length && strequci(path[$-NoAlias.length..$], NoAlias)) { 11170 path = path[0..$-NoAlias.length]; 11171 if (path.length == 0) return FONS_INVALID; 11172 defAA = false; 11173 } 11174 11175 // if font name ends with ":noaa", turn off antialiasing 11176 if (name.length > NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) { 11177 name = name[0..$-NoAlias.length]; 11178 defAA = false; 11179 } 11180 11181 // find a font with the given name 11182 int fidx = findNameInHash(name); 11183 //{ import core.stdc.stdio; printf("loading font '%.*s' [%s] (fidx=%d)...\n", cast(uint)path.length, path.ptr, fontnamebuf.ptr, fidx); } 11184 11185 int loadFontFile (const(char)[] path) { 11186 // check if existing font (if any) has the same path 11187 if (fidx >= 0) { 11188 import core.stdc.string : strlen; 11189 auto plen = (fonts[fidx].path !is null ? strlen(fonts[fidx].path) : 0); 11190 version(Posix) { 11191 //{ 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); } 11192 if (plen == path.length && fonts[fidx].path[0..plen] == path) { 11193 //{ import core.stdc.stdio; printf("*** font [%.*s] already loaded from [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)plen, path.ptr); } 11194 // i found her! 11195 return fidx; 11196 } 11197 } else { 11198 if (plen == path.length && strequci(fonts[fidx].path[0..plen], path)) { 11199 // i found her! 11200 return fidx; 11201 } 11202 } 11203 } 11204 version(Windows) { 11205 // special shitdows check: this will reject fontconfig font names (but still allow things like "c:myfont") 11206 foreach (immutable char ch; path[(path.length >= 2 && path[1] == ':' ? 2 : 0)..$]) if (ch == ':') return FONS_INVALID; 11207 } 11208 // either no such font, or different path 11209 //{ import core.stdc.stdio; printf("trying font [%.*s] from file [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)path.length, path.ptr); } 11210 int xres = FONS_INVALID; 11211 try { 11212 import core.stdc.stdlib : free, malloc; 11213 static if (NanoVegaHasIVVFS) { 11214 auto fl = VFile(path); 11215 auto dataSize = fl.size; 11216 if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID; 11217 ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize); 11218 if (data is null) assert(0, "out of memory in NanoVega fontstash"); 11219 scope(failure) free(data); // oops 11220 fl.rawReadExact(data[0..cast(uint)dataSize]); 11221 fl.close(); 11222 } else { 11223 import core.stdc.stdio : FILE, fopen, fclose, fread, ftell, fseek; 11224 import std.internal.cstring : tempCString; 11225 auto fl = fopen(path.tempCString, "rb"); 11226 if (fl is null) return FONS_INVALID; 11227 scope(exit) fclose(fl); 11228 if (fseek(fl, 0, 2/*SEEK_END*/) != 0) return FONS_INVALID; 11229 auto dataSize = ftell(fl); 11230 if (fseek(fl, 0, 0/*SEEK_SET*/) != 0) return FONS_INVALID; 11231 if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID; 11232 ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize); 11233 if (data is null) assert(0, "out of memory in NanoVega fontstash"); 11234 scope(failure) free(data); // oops 11235 ubyte* dptr = data; 11236 auto left = cast(uint)dataSize; 11237 while (left > 0) { 11238 auto rd = fread(dptr, 1, left, fl); 11239 if (rd == 0) { free(data); return FONS_INVALID; } // unexpected EOF or reading error, it doesn't matter 11240 dptr += rd; 11241 left -= rd; 11242 } 11243 } 11244 scope(failure) free(data); // oops 11245 // create font data 11246 FONSfontData* fdata = fons__createFontData(data, cast(int)dataSize, true); // free data 11247 fdata.incref(); 11248 xres = addFontWithData(name, fdata, defAA); 11249 if (xres == FONS_INVALID) { 11250 fdata.decref(); // this will free [data] and [fdata] 11251 } else { 11252 // remember path 11253 fonts[xres].setPath(path); 11254 } 11255 } catch (Exception e) { 11256 // oops; sorry 11257 } 11258 return xres; 11259 } 11260 11261 // first try direct path 11262 auto res = loadFontFile(path); 11263 // if loading failed, try fontconfig (if fontconfig is available) 11264 static if (NanoVegaHasFontConfig) { 11265 if (res == FONS_INVALID && fontconfigAvailable) { 11266 // idiotic fontconfig NEVER fails; let's skip it if `path` looks like a path 11267 bool ok = true; 11268 if (path.length > 4 && (path[$-4..$] == ".ttf" || path[$-4..$] == ".ttc")) ok = false; 11269 if (ok) { foreach (immutable char ch; path) if (ch == '/') { ok = false; break; } } 11270 if (ok) { 11271 import std.internal.cstring : tempCString; 11272 FcPattern* pat = FcNameParse(path.tempCString); 11273 if (pat !is null) { 11274 scope(exit) FcPatternDestroy(pat); 11275 if (FcConfigSubstitute(null, pat, FcMatchPattern)) { 11276 FcDefaultSubstitute(pat); 11277 // find the font 11278 FcResult result; 11279 FcPattern* font = FcFontMatch(null, pat, &result); 11280 if (font !is null) { 11281 scope(exit) FcPatternDestroy(font); 11282 char* file = null; 11283 if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) { 11284 if (file !is null && file[0]) { 11285 import core.stdc.string : strlen; 11286 res = loadFontFile(file[0..strlen(file)]); 11287 } 11288 } 11289 } 11290 } 11291 } 11292 } 11293 } 11294 } 11295 return res; 11296 } 11297 11298 /** Add font to FontStash, using data from memory. 11299 * 11300 * And already loaded font to FontStash. You can replace existing logical fonts. 11301 * But note that you can't remove logical font by passing "empty" data. 11302 * 11303 * $(WARNING If [FONS_INVALID] returned, `data` won't be freed even if `freeData` is `true`.) 11304 * 11305 * Params: 11306 * name = logical font name, that will be used to select this font later. 11307 * data = font data. 11308 * dataSize = font data size. 11309 * freeData = should FontStash take ownership of the font data? 11310 * defAA = should FontStash use antialiased font rasterizer? 11311 * 11312 * Returns: 11313 * font id or [FONS_INVALID]. 11314 */ 11315 int addFontMem (const(char)[] name, ubyte* data, int dataSize, bool freeData, bool defAA=false) nothrow @trusted @nogc { 11316 if (data is null || dataSize < 16) return FONS_INVALID; 11317 FONSfontData* fdata = fons__createFontData(data, dataSize, freeData); 11318 fdata.incref(); 11319 auto res = addFontWithData(name, fdata, defAA); 11320 if (res == FONS_INVALID) { 11321 // we promised to not free data on error 11322 fdata.freeData = false; 11323 fdata.decref(); // this will free [fdata] 11324 } 11325 return res; 11326 } 11327 11328 /** Add fonts from another FontStash. 11329 * 11330 * This is more effective (and faster) than reloading fonts, because internally font data 11331 * is reference counted. 11332 */ 11333 void addFontsFrom (FONSContext source) nothrow @trusted @nogc { 11334 if (source is null) return; 11335 foreach (FONSfont* font; source.fonts[0..source.nfonts]) { 11336 if (font !is null) { 11337 auto newidx = addCookedFont(font); 11338 FONSfont* newfont = fonts[newidx]; 11339 assert(newfont !is null); 11340 assert(newfont.path is null); 11341 // copy path 11342 if (font.path !is null && font.path[0]) { 11343 import core.stdc.stdlib : malloc; 11344 import core.stdc.string : strcpy, strlen; 11345 newfont.path = cast(char*)malloc(strlen(font.path)+1); 11346 if (newfont.path is null) assert(0, "FONS: out of memory"); 11347 strcpy(newfont.path, font.path); 11348 } 11349 } 11350 } 11351 } 11352 11353 /// Returns logical font name corresponding to the given font id, or `null`. 11354 /// $(WARNING Copy returned name, as name buffer can be invalidated by next FontStash API call!) 11355 const(char)[] getNameById (int idx) const pure nothrow @trusted @nogc { 11356 if (idx < 0 || idx >= nfonts || fonts[idx] is null) return null; 11357 return fonts[idx].name[0..fonts[idx].namelen]; 11358 } 11359 11360 /// Returns font id corresponding to the given logical font name, or [FONS_INVALID]. 11361 int getFontByName (const(char)[] name) const pure nothrow @trusted @nogc { 11362 //{ import core.stdc.stdio; printf("fonsGetFontByName: [%.*s]\n", cast(uint)name.length, name.ptr); } 11363 // remove ":noaa" suffix 11364 if (name.length >= NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) { 11365 name = name[0..$-NoAlias.length]; 11366 } 11367 if (name.length == 0) return FONS_INVALID; 11368 return findNameInHash(name); 11369 } 11370 11371 /** Measures the specified text string. Parameter bounds should be a float[4], 11372 * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax] 11373 * Returns the horizontal advance of the measured text (i.e. where the next character should drawn). 11374 */ 11375 float getTextBounds(T) (float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc if (isAnyCharType!T) { 11376 FONSstate* state = getState; 11377 uint codepoint; 11378 uint utf8state = 0; 11379 FONSQuad q; 11380 FONSglyph* glyph = null; 11381 int prevGlyphIndex = -1; 11382 short isize = cast(short)(state.size*10.0f); 11383 short iblur = cast(short)state.blur; 11384 FONSfont* font; 11385 11386 if (state.font < 0 || state.font >= nfonts) return 0; 11387 font = fonts[state.font]; 11388 if (font is null || font.fdata is null) return 0; 11389 11390 float scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f); 11391 11392 // Align vertically. 11393 y += getVertAlign(font, state.talign, isize); 11394 11395 float minx = x, maxx = x; 11396 float miny = y, maxy = y; 11397 float startx = x; 11398 11399 foreach (T ch; str) { 11400 static if (T.sizeof == 1) { 11401 //if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue; 11402 mixin(DecUtfMixin!("utf8state", "codepoint", "(cast(ubyte)ch)")); 11403 if (utf8state) continue; 11404 } else { 11405 static if (T.sizeof == 4) { 11406 if (ch > dchar.max) ch = 0xFFFD; 11407 } 11408 codepoint = cast(uint)ch; 11409 } 11410 glyph = getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional); 11411 if (glyph !is null) { 11412 getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q); 11413 if (q.x0 < minx) minx = q.x0; 11414 if (q.x1 > maxx) maxx = q.x1; 11415 if (params.isZeroTopLeft) { 11416 if (q.y0 < miny) miny = q.y0; 11417 if (q.y1 > maxy) maxy = q.y1; 11418 } else { 11419 if (q.y1 < miny) miny = q.y1; 11420 if (q.y0 > maxy) maxy = q.y0; 11421 } 11422 prevGlyphIndex = glyph.index; 11423 } else { 11424 //{ import core.stdc.stdio; printf("NO GLYPH FOR 0x%04x\n", cast(uint)codepoint); } 11425 prevGlyphIndex = -1; 11426 } 11427 } 11428 11429 float advance = x-startx; 11430 //{ import core.stdc.stdio; printf("***: x=%g; startx=%g; advance=%g\n", cast(double)x, cast(double)startx, cast(double)advance); } 11431 11432 // Align horizontally 11433 if (state.talign.left) { 11434 // empty 11435 } else if (state.talign.right) { 11436 minx -= advance; 11437 maxx -= advance; 11438 } else if (state.talign.center) { 11439 minx -= advance*0.5f; 11440 maxx -= advance*0.5f; 11441 } 11442 11443 if (bounds.length) { 11444 if (bounds.length > 0) bounds.ptr[0] = minx; 11445 if (bounds.length > 1) bounds.ptr[1] = miny; 11446 if (bounds.length > 2) bounds.ptr[2] = maxx; 11447 if (bounds.length > 3) bounds.ptr[3] = maxy; 11448 } 11449 11450 return advance; 11451 } 11452 11453 /// Returns various font metrics. Any argument can be `null` if you aren't interested in its value. 11454 void getVertMetrics (float* ascender, float* descender, float* lineh) nothrow @trusted @nogc { 11455 FONSstate* state = getState; 11456 if (state.font < 0 || state.font >= nfonts) { 11457 if (ascender !is null) *ascender = 0; 11458 if (descender !is null) *descender = 0; 11459 if (lineh !is null) *lineh = 0; 11460 } else { 11461 FONSfont* font = fonts[state.font]; 11462 if (font is null || font.fdata is null) { 11463 if (ascender !is null) *ascender = 0; 11464 if (descender !is null) *descender = 0; 11465 if (lineh !is null) *lineh = 0; 11466 } else { 11467 short isize = cast(short)(state.size*10.0f); 11468 if (ascender !is null) *ascender = font.ascender*isize/10.0f; 11469 if (descender !is null) *descender = font.descender*isize/10.0f; 11470 if (lineh !is null) *lineh = font.lineh*isize/10.0f; 11471 } 11472 } 11473 } 11474 11475 /// Returns line bounds. Any argument can be `null` if you aren't interested in its value. 11476 void getLineBounds (float y, float* minyp, float* maxyp) nothrow @trusted @nogc { 11477 FONSfont* font; 11478 FONSstate* state = getState; 11479 short isize; 11480 11481 if (minyp !is null) *minyp = 0; 11482 if (maxyp !is null) *maxyp = 0; 11483 11484 if (state.font < 0 || state.font >= nfonts) return; 11485 font = fonts[state.font]; 11486 isize = cast(short)(state.size*10.0f); 11487 if (font is null || font.fdata is null) return; 11488 11489 y += getVertAlign(font, state.talign, isize); 11490 11491 if (params.isZeroTopLeft) { 11492 immutable float miny = y-font.ascender*cast(float)isize/10.0f; 11493 immutable float maxy = miny+font.lineh*isize/10.0f; 11494 if (minyp !is null) *minyp = miny; 11495 if (maxyp !is null) *maxyp = maxy; 11496 } else { 11497 immutable float maxy = y+font.descender*cast(float)isize/10.0f; 11498 immutable float miny = maxy-font.lineh*isize/10.0f; 11499 if (minyp !is null) *minyp = miny; 11500 if (maxyp !is null) *maxyp = maxy; 11501 } 11502 } 11503 11504 /// Returns font line height. 11505 float fontHeight () nothrow @trusted @nogc { 11506 float res = void; 11507 getVertMetrics(null, null, &res); 11508 return res; 11509 } 11510 11511 /// Returns font ascender (positive). 11512 float fontAscender () nothrow @trusted @nogc { 11513 float res = void; 11514 getVertMetrics(&res, null, null); 11515 return res; 11516 } 11517 11518 /// Returns font descender (negative). 11519 float fontDescender () nothrow @trusted @nogc { 11520 float res = void; 11521 getVertMetrics(null, &res, null); 11522 return res; 11523 } 11524 11525 //TODO: document this 11526 const(ubyte)* getTextureData (int* width, int* height) nothrow @trusted @nogc { 11527 if (width !is null) *width = params.width; 11528 if (height !is null) *height = params.height; 11529 return texData; 11530 } 11531 11532 //TODO: document this 11533 bool validateTexture (int* dirty) nothrow @trusted @nogc { 11534 if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) { 11535 dirty[0] = dirtyRect.ptr[0]; 11536 dirty[1] = dirtyRect.ptr[1]; 11537 dirty[2] = dirtyRect.ptr[2]; 11538 dirty[3] = dirtyRect.ptr[3]; 11539 // reset dirty rect 11540 dirtyRect.ptr[0] = params.width; 11541 dirtyRect.ptr[1] = params.height; 11542 dirtyRect.ptr[2] = 0; 11543 dirtyRect.ptr[3] = 0; 11544 return true; 11545 } 11546 return false; 11547 } 11548 11549 //TODO: document this 11550 void errorCallback (void delegate (FONSError error, int val) nothrow @trusted @nogc callback) nothrow @trusted @nogc { 11551 handleError = callback; 11552 } 11553 11554 //TODO: document this 11555 void getAtlasSize (int* width, int* height) const pure nothrow @trusted @nogc { 11556 if (width !is null) *width = params.width; 11557 if (height !is null) *height = params.height; 11558 } 11559 11560 //TODO: document this 11561 bool expandAtlas (int width, int height) nothrow @trusted @nogc { 11562 import core.stdc.stdlib : free; 11563 import core.stdc.string : memcpy, memset; 11564 11565 int maxy = 0; 11566 ubyte* data = null; 11567 11568 width = nvg__max(width, params.width); 11569 height = nvg__max(height, params.height); 11570 11571 if (width == params.width && height == params.height) return true; 11572 11573 // Flush pending glyphs. 11574 flush(); 11575 11576 // Create new texture 11577 if (params.renderResize !is null) { 11578 if (params.renderResize(params.userPtr, width, height) == 0) return false; 11579 } 11580 // Copy old texture data over. 11581 data = cast(ubyte*)malloc(width*height); 11582 if (data is null) return 0; 11583 foreach (immutable int i; 0..params.height) { 11584 ubyte* dst = &data[i*width]; 11585 ubyte* src = &texData[i*params.width]; 11586 memcpy(dst, src, params.width); 11587 if (width > params.width) memset(dst+params.width, 0, width-params.width); 11588 } 11589 if (height > params.height) memset(&data[params.height*width], 0, (height-params.height)*width); 11590 11591 free(texData); 11592 texData = data; 11593 11594 // Increase atlas size 11595 atlas.expand(width, height); 11596 11597 // Add existing data as dirty. 11598 foreach (immutable int i; 0..atlas.nnodes) maxy = nvg__max(maxy, atlas.nodes[i].y); 11599 dirtyRect.ptr[0] = 0; 11600 dirtyRect.ptr[1] = 0; 11601 dirtyRect.ptr[2] = params.width; 11602 dirtyRect.ptr[3] = maxy; 11603 11604 params.width = width; 11605 params.height = height; 11606 itw = 1.0f/params.width; 11607 ith = 1.0f/params.height; 11608 11609 return true; 11610 } 11611 11612 //TODO: document this 11613 bool resetAtlas (int width, int height) nothrow @trusted @nogc { 11614 import core.stdc.stdlib : realloc; 11615 import core.stdc.string : memcpy, memset; 11616 11617 // flush pending glyphs 11618 flush(); 11619 11620 // create new texture 11621 if (params.renderResize !is null) { 11622 if (params.renderResize(params.userPtr, width, height) == 0) return false; 11623 } 11624 11625 // reset atlas 11626 atlas.reset(width, height); 11627 11628 // clear texture data 11629 texData = cast(ubyte*)realloc(texData, width*height); 11630 if (texData is null) assert(0, "FONS: out of memory"); 11631 memset(texData, 0, width*height); 11632 11633 // reset dirty rect 11634 dirtyRect.ptr[0] = width; 11635 dirtyRect.ptr[1] = height; 11636 dirtyRect.ptr[2] = 0; 11637 dirtyRect.ptr[3] = 0; 11638 11639 // Reset cached glyphs 11640 foreach (FONSfont* font; fonts[0..nfonts]) { 11641 if (font !is null) { 11642 font.nglyphs = 0; 11643 font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1; 11644 } 11645 } 11646 11647 params.width = width; 11648 params.height = height; 11649 itw = 1.0f/params.width; 11650 ith = 1.0f/params.height; 11651 11652 // Add white rect at 0, 0 for debug drawing. 11653 addWhiteRect(2, 2); 11654 11655 return true; 11656 } 11657 11658 //TODO: document this 11659 bool getPathBounds (dchar dch, float[] bounds) nothrow @trusted @nogc { 11660 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 11661 static if (is(typeof(&fons__nvg__bounds))) { 11662 FONSstate* state = getState; 11663 if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; } 11664 FONSfont* font; 11665 auto g = findGlyphForCP(fonts[state.font], dch, &font); 11666 if (g == 0) { bounds[] = 0; return false; } 11667 assert(font !is null); 11668 return fons__nvg__bounds(&font.font, g, bounds); 11669 } else { 11670 bounds[] = 0; 11671 return false; 11672 } 11673 } 11674 11675 //TODO: document this 11676 bool toPath() (NVGContext vg, dchar dch, float[] bounds=null) nothrow @trusted @nogc { 11677 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 11678 static if (is(typeof(&fons__nvg__toPath))) { 11679 if (vg is null) { bounds[] = 0; return false; } 11680 FONSstate* state = getState; 11681 if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; } 11682 FONSfont* font; 11683 auto g = findGlyphForCP(fonts[state.font], dch, &font); 11684 if (g == 0) { bounds[] = 0; return false; } 11685 assert(font !is null); 11686 return fons__nvg__toPath(vg, &font.font, g, bounds); 11687 } else { 11688 bounds[] = 0; 11689 return false; 11690 } 11691 } 11692 11693 //TODO: document this 11694 bool toOutline (dchar dch, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc { 11695 if (ol is null) return false; 11696 static if (is(typeof(&fons__nvg__toOutline))) { 11697 FONSstate* state = getState; 11698 if (state.font < 0 || state.font >= nfonts) return false; 11699 FONSfont* font; 11700 auto g = findGlyphForCP(fonts[state.font], dch, &font); 11701 if (g == 0) return false; 11702 assert(font !is null); 11703 return fons__nvg__toOutline(&font.font, g, ol); 11704 } else { 11705 return false; 11706 } 11707 } 11708 11709 //TODO: document this 11710 FONSglyph* getGlyph (FONSfont* font, uint codepoint, short isize, short iblur, FONSBitmapFlag bitmapOption) nothrow @trusted @nogc { 11711 static uint fons__hashint() (uint a) pure nothrow @safe @nogc { 11712 pragma(inline, true); 11713 a += ~(a<<15); 11714 a ^= (a>>10); 11715 a += (a<<3); 11716 a ^= (a>>6); 11717 a += ~(a<<11); 11718 a ^= (a>>16); 11719 return a; 11720 } 11721 11722 // based on Exponential blur, Jani Huhtanen, 2006 11723 enum APREC = 16; 11724 enum ZPREC = 7; 11725 11726 static void fons__blurCols (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc { 11727 foreach (immutable int y; 0..h) { 11728 int z = 0; // force zero border 11729 foreach (int x; 1..w) { 11730 z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC; 11731 dst[x] = cast(ubyte)(z>>ZPREC); 11732 } 11733 dst[w-1] = 0; // force zero border 11734 z = 0; 11735 for (int x = w-2; x >= 0; --x) { 11736 z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC; 11737 dst[x] = cast(ubyte)(z>>ZPREC); 11738 } 11739 dst[0] = 0; // force zero border 11740 dst += dstStride; 11741 } 11742 } 11743 11744 static void fons__blurRows (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc { 11745 foreach (immutable int x; 0..w) { 11746 int z = 0; // force zero border 11747 for (int y = dstStride; y < h*dstStride; y += dstStride) { 11748 z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC; 11749 dst[y] = cast(ubyte)(z>>ZPREC); 11750 } 11751 dst[(h-1)*dstStride] = 0; // force zero border 11752 z = 0; 11753 for (int y = (h-2)*dstStride; y >= 0; y -= dstStride) { 11754 z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC; 11755 dst[y] = cast(ubyte)(z>>ZPREC); 11756 } 11757 dst[0] = 0; // force zero border 11758 ++dst; 11759 } 11760 } 11761 11762 static void fons__blur (ubyte* dst, int w, int h, int dstStride, int blur) nothrow @trusted @nogc { 11763 import std.math : expf = exp; 11764 if (blur < 1) return; 11765 // Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity) 11766 immutable float sigma = cast(float)blur*0.57735f; // 1/sqrt(3) 11767 int alpha = cast(int)((1<<APREC)*(1.0f-expf(-2.3f/(sigma+1.0f)))); 11768 fons__blurRows(dst, w, h, dstStride, alpha); 11769 fons__blurCols(dst, w, h, dstStride, alpha); 11770 fons__blurRows(dst, w, h, dstStride, alpha); 11771 fons__blurCols(dst, w, h, dstStride, alpha); 11772 //fons__blurrows(dst, w, h, dstStride, alpha); 11773 //fons__blurcols(dst, w, h, dstStride, alpha); 11774 } 11775 11776 int advance, lsb, x0, y0, x1, y1, gx, gy; 11777 FONSglyph* glyph = null; 11778 float size = isize/10.0f; 11779 FONSfont* renderFont = font; 11780 11781 if (isize < 2) return null; 11782 if (iblur > 20) iblur = 20; 11783 int pad = iblur+2; 11784 11785 // Reset allocator. 11786 nscratch = 0; 11787 11788 // Find code point and size. 11789 uint h = fons__hashint(codepoint)&(FONS_HASH_LUT_SIZE-1); 11790 int i = font.lut.ptr[h]; 11791 while (i != -1) { 11792 //if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) return &font.glyphs[i]; 11793 if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) { 11794 glyph = &font.glyphs[i]; 11795 // Negative coordinate indicates there is no bitmap data created. 11796 if (bitmapOption == FONSBitmapFlag.Optional || (glyph.x0 >= 0 && glyph.y0 >= 0)) return glyph; 11797 // At this point, glyph exists but the bitmap data is not yet created. 11798 break; 11799 } 11800 i = font.glyphs[i].next; 11801 } 11802 11803 // Create a new glyph or rasterize bitmap data for a cached glyph. 11804 //scale = fons__tt_getPixelHeightScale(&font.font, size); 11805 int g = findGlyphForCP(font, cast(dchar)codepoint, &renderFont); 11806 // It is possible that we did not find a fallback glyph. 11807 // In that case the glyph index 'g' is 0, and we'll proceed below and cache empty glyph. 11808 11809 float scale = fons__tt_getPixelHeightScale(&renderFont.font, size); 11810 fons__tt_buildGlyphBitmap(&renderFont.font, g, size, scale, &advance, &lsb, &x0, &y0, &x1, &y1); 11811 int gw = x1-x0+pad*2; 11812 int gh = y1-y0+pad*2; 11813 11814 // Determines the spot to draw glyph in the atlas. 11815 if (bitmapOption == FONSBitmapFlag.Required) { 11816 // Find free spot for the rect in the atlas. 11817 bool added = atlas.addRect(gw, gh, &gx, &gy); 11818 if (!added && handleError !is null) { 11819 // Atlas is full, let the user to resize the atlas (or not), and try again. 11820 handleError(FONSError.AtlasFull, 0); 11821 added = atlas.addRect(gw, gh, &gx, &gy); 11822 } 11823 if (!added) return null; 11824 } else { 11825 // Negative coordinate indicates there is no bitmap data created. 11826 gx = -1; 11827 gy = -1; 11828 } 11829 11830 // Init glyph. 11831 if (glyph is null) { 11832 glyph = font.allocGlyph(); 11833 glyph.codepoint = codepoint; 11834 glyph.size = isize; 11835 glyph.blur = iblur; 11836 glyph.next = 0; 11837 11838 // Insert char to hash lookup. 11839 glyph.next = font.lut.ptr[h]; 11840 font.lut.ptr[h] = font.nglyphs-1; 11841 } 11842 glyph.index = g; 11843 glyph.x0 = cast(short)gx; 11844 glyph.y0 = cast(short)gy; 11845 glyph.x1 = cast(short)(glyph.x0+gw); 11846 glyph.y1 = cast(short)(glyph.y0+gh); 11847 glyph.xadv = cast(short)(scale*advance*10.0f); 11848 glyph.xoff = cast(short)(x0-pad); 11849 glyph.yoff = cast(short)(y0-pad); 11850 11851 if (bitmapOption == FONSBitmapFlag.Optional) return glyph; 11852 11853 // Rasterize 11854 ubyte* dst = &texData[(glyph.x0+pad)+(glyph.y0+pad)*params.width]; 11855 fons__tt_renderGlyphBitmap(&renderFont.font, dst, gw-pad*2, gh-pad*2, params.width, scale, scale, g); 11856 11857 // Make sure there is one pixel empty border. 11858 dst = &texData[glyph.x0+glyph.y0*params.width]; 11859 foreach (immutable int y; 0..gh) { 11860 dst[y*params.width] = 0; 11861 dst[gw-1+y*params.width] = 0; 11862 } 11863 foreach (immutable int x; 0..gw) { 11864 dst[x] = 0; 11865 dst[x+(gh-1)*params.width] = 0; 11866 } 11867 11868 // Debug code to color the glyph background 11869 version(none) { 11870 foreach (immutable yy; 0..gh) { 11871 foreach (immutable xx; 0..gw) { 11872 int a = cast(int)dst[xx+yy*params.width]+42; 11873 if (a > 255) a = 255; 11874 dst[xx+yy*params.width] = cast(ubyte)a; 11875 } 11876 } 11877 } 11878 11879 // Blur 11880 if (iblur > 0) { 11881 nscratch = 0; 11882 ubyte* bdst = &texData[glyph.x0+glyph.y0*params.width]; 11883 fons__blur(bdst, gw, gh, params.width, iblur); 11884 } 11885 11886 dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], glyph.x0); 11887 dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], glyph.y0); 11888 dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], glyph.x1); 11889 dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], glyph.y1); 11890 11891 return glyph; 11892 } 11893 11894 //TODO: document this 11895 void getQuad (FONSfont* font, int prevGlyphIndex, FONSglyph* glyph, float size, float scale, float spacing, float* x, float* y, FONSQuad* q) nothrow @trusted @nogc { 11896 if (prevGlyphIndex >= 0) { 11897 immutable float adv = fons__tt_getGlyphKernAdvance(&font.font, size, prevGlyphIndex, glyph.index)/**scale*/; //k8: do we really need scale here? 11898 //if (adv != 0) { import core.stdc.stdio; printf("adv=%g (scale=%g; spacing=%g)\n", cast(double)adv, cast(double)scale, cast(double)spacing); } 11899 *x += cast(int)(adv+spacing /*+0.5f*/); //k8: for me, it looks better this way (with non-aa fonts) 11900 } 11901 11902 // Each glyph has 2px border to allow good interpolation, 11903 // one pixel to prevent leaking, and one to allow good interpolation for rendering. 11904 // Inset the texture region by one pixel for correct interpolation. 11905 immutable float xoff = cast(short)(glyph.xoff+1); 11906 immutable float yoff = cast(short)(glyph.yoff+1); 11907 immutable float x0 = cast(float)(glyph.x0+1); 11908 immutable float y0 = cast(float)(glyph.y0+1); 11909 immutable float x1 = cast(float)(glyph.x1-1); 11910 immutable float y1 = cast(float)(glyph.y1-1); 11911 11912 if (params.isZeroTopLeft) { 11913 immutable float rx = cast(float)cast(int)(*x+xoff); 11914 immutable float ry = cast(float)cast(int)(*y+yoff); 11915 11916 q.x0 = rx; 11917 q.y0 = ry; 11918 q.x1 = rx+x1-x0; 11919 q.y1 = ry+y1-y0; 11920 11921 q.s0 = x0*itw; 11922 q.t0 = y0*ith; 11923 q.s1 = x1*itw; 11924 q.t1 = y1*ith; 11925 } else { 11926 immutable float rx = cast(float)cast(int)(*x+xoff); 11927 immutable float ry = cast(float)cast(int)(*y-yoff); 11928 11929 q.x0 = rx; 11930 q.y0 = ry; 11931 q.x1 = rx+x1-x0; 11932 q.y1 = ry-y1+y0; 11933 11934 q.s0 = x0*itw; 11935 q.t0 = y0*ith; 11936 q.s1 = x1*itw; 11937 q.t1 = y1*ith; 11938 } 11939 11940 *x += cast(int)(glyph.xadv/10.0f+0.5f); 11941 } 11942 11943 void flush () nothrow @trusted @nogc { 11944 // flush texture 11945 if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) { 11946 if (params.renderUpdate !is null) params.renderUpdate(params.userPtr, dirtyRect.ptr, texData); 11947 // reset dirty rect 11948 dirtyRect.ptr[0] = params.width; 11949 dirtyRect.ptr[1] = params.height; 11950 dirtyRect.ptr[2] = 0; 11951 dirtyRect.ptr[3] = 0; 11952 } 11953 } 11954 } 11955 11956 /// Free all resources used by the `stash`, and `stash` itself. 11957 /// Group: font_stash 11958 public void kill (ref FONSContext stash) nothrow @trusted @nogc { 11959 import core.stdc.stdlib : free; 11960 if (stash is null) return; 11961 stash.clear(); 11962 free(stash); 11963 stash = null; 11964 } 11965 11966 11967 // ////////////////////////////////////////////////////////////////////////// // 11968 void* fons__tmpalloc (usize size, void* up) nothrow @trusted @nogc { 11969 ubyte* ptr; 11970 FONSContext stash = cast(FONSContext)up; 11971 // 16-byte align the returned pointer 11972 size = (size+0xf)&~0xf; 11973 if (stash.nscratch+cast(int)size > FONS_SCRATCH_BUF_SIZE) { 11974 if (stash.handleError !is null) stash.handleError(FONSError.ScratchFull, stash.nscratch+cast(int)size); 11975 return null; 11976 } 11977 ptr = stash.scratch+stash.nscratch; 11978 stash.nscratch += cast(int)size; 11979 return ptr; 11980 } 11981 11982 void fons__tmpfree (void* ptr, void* up) nothrow @trusted @nogc { 11983 // empty 11984 } 11985 11986 11987 // ////////////////////////////////////////////////////////////////////////// // 11988 // Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de> 11989 // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. 11990 11991 enum FONS_UTF8_ACCEPT = 0; 11992 enum FONS_UTF8_REJECT = 12; 11993 11994 static immutable ubyte[364] utf8d = [ 11995 // The first part of the table maps bytes to character classes that 11996 // to reduce the size of the transition table and create bitmasks. 11997 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, 11998 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, 11999 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, 12000 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, 12001 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, 12002 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, 12003 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, 12004 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, 12005 12006 // The second part is a transition table that maps a combination 12007 // of a state of the automaton and a character class to a state. 12008 0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12009 12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12, 12010 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12011 12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12012 12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12013 ]; 12014 12015 private enum DecUtfMixin(string state, string codep, string byte_) = 12016 `{ 12017 uint type_ = utf8d.ptr[`~byte_~`]; 12018 `~codep~` = (`~state~` != FONS_UTF8_ACCEPT ? (`~byte_~`&0x3fu)|(`~codep~`<<6) : (0xff>>type_)&`~byte_~`); 12019 if ((`~state~` = utf8d.ptr[256+`~state~`+type_]) == FONS_UTF8_REJECT) { 12020 `~state~` = FONS_UTF8_ACCEPT; 12021 `~codep~` = 0xFFFD; 12022 } 12023 }`; 12024 12025 /* 12026 uint fons__decutf8 (uint* state, uint* codep, uint byte_) { 12027 pragma(inline, true); 12028 uint type = utf8d.ptr[byte_]; 12029 *codep = (*state != FONS_UTF8_ACCEPT ? (byte_&0x3fu)|(*codep<<6) : (0xff>>type)&byte_); 12030 *state = utf8d.ptr[256 + *state+type]; 12031 return *state; 12032 } 12033 */ 12034 12035 12036 // ////////////////////////////////////////////////////////////////////////// // 12037 /// This iterator can be used to do text measurement. 12038 /// $(WARNING Don't add new fonts to stash while you are iterating, or you WILL get segfault!) 12039 /// Group: font_stash 12040 public struct FONSTextBoundsIterator { 12041 private: 12042 FONSContext stash; 12043 FONSstate state; 12044 uint codepoint = 0xFFFD; 12045 uint utf8state = 0; 12046 int prevGlyphIndex = -1; 12047 short isize, iblur; 12048 float scale = 0; 12049 FONSfont* font; 12050 float startx = 0, x = 0, y = 0; 12051 float minx = 0, miny = 0, maxx = 0, maxy = 0; 12052 12053 private: 12054 void clear () nothrow @trusted @nogc { 12055 import core.stdc.string : memset; 12056 memset(&this, 0, this.sizeof); 12057 this.prevGlyphIndex = -1; 12058 this.codepoint = 0xFFFD; 12059 } 12060 12061 public: 12062 /// Initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator. 12063 this (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc { reset(astash, ax, ay); } 12064 12065 /// (Re)initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator. 12066 void reset (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc { 12067 clear(); 12068 12069 if (astash is null || astash.nstates == 0) return; 12070 12071 stash = astash; 12072 state = *stash.getState; 12073 12074 if (state.font < 0 || state.font >= stash.nfonts) { clear(); return; } 12075 font = stash.fonts[state.font]; 12076 if (font is null || font.fdata is null) { clear(); return; } 12077 12078 x = ax; 12079 y = ay; 12080 isize = cast(short)(state.size*10.0f); 12081 iblur = cast(short)state.blur; 12082 scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f); 12083 12084 // align vertically 12085 y += astash.getVertAlign(font, state.talign, isize); 12086 12087 minx = maxx = x; 12088 miny = maxy = y; 12089 startx = x; 12090 } 12091 12092 /// Can this iterator be used? 12093 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null); } 12094 12095 /// Put some text into iterator, calculate new values. 12096 void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) { 12097 enum DoCodePointMixin = q{ 12098 glyph = stash.getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional); 12099 if (glyph !is null) { 12100 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q); 12101 if (q.x0 < minx) minx = q.x0; 12102 if (q.x1 > maxx) maxx = q.x1; 12103 if (stash.params.isZeroTopLeft) { 12104 if (q.y0 < miny) miny = q.y0; 12105 if (q.y1 > maxy) maxy = q.y1; 12106 } else { 12107 if (q.y1 < miny) miny = q.y1; 12108 if (q.y0 > maxy) maxy = q.y0; 12109 } 12110 prevGlyphIndex = glyph.index; 12111 } else { 12112 prevGlyphIndex = -1; 12113 } 12114 }; 12115 12116 if (stash is null || str.length == 0) return; // alas 12117 12118 FONSQuad q; 12119 FONSglyph* glyph; 12120 12121 static if (is(T == char)) { 12122 foreach (char ch; str) { 12123 mixin(DecUtfMixin!("utf8state", "codepoint", "cast(ubyte)ch")); 12124 if (utf8state) continue; // full char is not collected yet 12125 mixin(DoCodePointMixin); 12126 } 12127 } else { 12128 if (utf8state) { 12129 utf8state = 0; 12130 codepoint = 0xFFFD; 12131 mixin(DoCodePointMixin); 12132 } 12133 foreach (T dch; str) { 12134 static if (is(T == dchar)) { 12135 if (dch > dchar.max) dch = 0xFFFD; 12136 } 12137 codepoint = cast(uint)dch; 12138 mixin(DoCodePointMixin); 12139 } 12140 } 12141 } 12142 12143 /// Returns current advance. 12144 @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null ? x-startx : 0); } 12145 12146 /// Returns current text bounds. 12147 void getBounds (ref float[4] bounds) const pure nothrow @safe @nogc { 12148 if (stash is null) { bounds[] = 0; return; } 12149 float lminx = minx, lmaxx = maxx; 12150 // align horizontally 12151 if (state.talign.left) { 12152 // empty 12153 } else if (state.talign.right) { 12154 float ca = advance; 12155 lminx -= ca; 12156 lmaxx -= ca; 12157 } else if (state.talign.center) { 12158 float ca = advance*0.5f; 12159 lminx -= ca; 12160 lmaxx -= ca; 12161 } 12162 bounds[0] = lminx; 12163 bounds[1] = miny; 12164 bounds[2] = lmaxx; 12165 bounds[3] = maxy; 12166 } 12167 12168 /// Returns current horizontal text bounds. 12169 void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc { 12170 if (stash !is null) { 12171 float lminx = minx, lmaxx = maxx; 12172 // align horizontally 12173 if (state.talign.left) { 12174 // empty 12175 } else if (state.talign.right) { 12176 float ca = advance; 12177 lminx -= ca; 12178 lmaxx -= ca; 12179 } else if (state.talign.center) { 12180 float ca = advance*0.5f; 12181 lminx -= ca; 12182 lmaxx -= ca; 12183 } 12184 xmin = lminx; 12185 xmax = lmaxx; 12186 } else { 12187 xmin = xmax = 0; 12188 } 12189 } 12190 12191 /// Returns current vertical text bounds. 12192 void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc { 12193 pragma(inline, true); 12194 if (stash !is null) { 12195 ymin = miny; 12196 ymax = maxy; 12197 } else { 12198 ymin = ymax = 0; 12199 } 12200 } 12201 12202 /// Returns font line height. 12203 float lineHeight () nothrow @trusted @nogc { 12204 pragma(inline, true); 12205 return (stash !is null ? stash.fonts[state.font].lineh*cast(short)(state.size*10.0f)/10.0f : 0); 12206 } 12207 12208 /// Returns font ascender (positive). 12209 float ascender () nothrow @trusted @nogc { 12210 pragma(inline, true); 12211 return (stash !is null ? stash.fonts[state.font].ascender*cast(short)(state.size*10.0f)/10.0f : 0); 12212 } 12213 12214 /// Returns font descender (negative). 12215 float descender () nothrow @trusted @nogc { 12216 pragma(inline, true); 12217 return (stash !is null ? stash.fonts[state.font].descender*cast(short)(state.size*10.0f)/10.0f : 0); 12218 } 12219 } 12220 12221 12222 // ////////////////////////////////////////////////////////////////////////// // 12223 // backgl 12224 // ////////////////////////////////////////////////////////////////////////// // 12225 import core.stdc.stdlib : malloc, realloc, free; 12226 import core.stdc.string : memcpy, memset; 12227 12228 static if (__VERSION__ < 2076) { 12229 private auto DGNoThrowNoGC(T) (scope T t) /*if (isFunctionPointer!T || isDelegate!T)*/ { 12230 import std.traits; 12231 enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_; 12232 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 12233 } 12234 } 12235 12236 12237 //import arsd.simpledisplay; 12238 version(nanovg_bindbc_opengl_bindings) { 12239 import bindbc.opengl; 12240 } else version(nanovg_builtin_opengl_bindings) { 12241 import arsd.simpledisplay; 12242 12243 /++ 12244 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. 12245 12246 History: 12247 Added January 22, 2021 (version 9.2 release) 12248 +/ 12249 public class NVGWindow : SimpleWindow { 12250 NVGContext nvg; 12251 12252 /++ 12253 12254 +/ 12255 this(int width, int height, string title) { 12256 setOpenGLContextVersion(3, 0); 12257 super(width, height, title, OpenGlOptions.yes, Resizability.allowResizing); 12258 12259 this.onClosing = delegate() { 12260 nvg.kill(); 12261 }; 12262 12263 this.visibleForTheFirstTime = delegate() { 12264 this.setAsCurrentOpenGlContext(); 12265 nvg = nvgCreateContext(); 12266 if(nvg is null) throw new Exception("cannot initialize NanoVega"); 12267 }; 12268 12269 this.redrawOpenGlScene = delegate() { 12270 if(redrawNVGScene is null) 12271 return; 12272 glViewport(0, 0, this.width, this.height); 12273 if(clearOnEachFrame) { 12274 glClearColor(0, 0, 0, 0); 12275 glClear(glNVGClearFlags); 12276 } 12277 12278 nvg.beginFrame(this.width, this.height); 12279 scope(exit) nvg.endFrame(); 12280 12281 redrawNVGScene(nvg); 12282 }; 12283 12284 this.setEventHandlers( 12285 &redrawOpenGlSceneNow, 12286 (KeyEvent ke) { 12287 if(ke.key == Key.Escape || ke.key == Key.Q) 12288 this.close(); 12289 } 12290 ); 12291 } 12292 12293 /++ 12294 12295 +/ 12296 bool clearOnEachFrame = true; 12297 12298 /++ 12299 12300 +/ 12301 void delegate(NVGContext nvg) redrawNVGScene; 12302 12303 /++ 12304 12305 +/ 12306 void redrawNVGSceneNow() { 12307 redrawOpenGlSceneNow(); 12308 } 12309 } 12310 12311 } else { 12312 import iv.glbinds; 12313 } 12314 12315 private: 12316 // sdpy is missing that yet 12317 static if (!is(typeof(GL_STENCIL_BUFFER_BIT))) enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 12318 12319 12320 12321 version(bindbc){ 12322 private extern(System) nothrow @nogc: 12323 // this definition doesn't exist in regular OpenGL (?) 12324 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 12325 private void nanovgInitOpenGL () { 12326 // i'm not aware of calling the load multiple times having negative side effects, so i don't do an initialization check 12327 GLSupport support = loadOpenGL(); 12328 if (support == GLSupport.noLibrary) 12329 assert(0, "OpenGL initialization failed: shared library failed to load"); 12330 else if (support == GLSupport.badLibrary) 12331 assert(0, "OpenGL initialization failed: a context-independent symbol failed to load"); 12332 else if (support == GLSupport.noContext) 12333 assert(0, "OpenGL initialization failed: a context needs to be created prior to initialization"); 12334 } 12335 } else { // OpenGL API missing from simpledisplay 12336 private void nanovgInitOpenGL () @nogc nothrow @system { 12337 __gshared bool initialized = false; 12338 if (initialized) return; 12339 12340 try 12341 gl3.loadDynamicLibrary(); 12342 catch(Exception) 12343 assert(0, "GL 3 failed to load"); 12344 12345 initialized = true; 12346 } 12347 } 12348 12349 12350 12351 /// Context creation flags. 12352 /// Group: context_management 12353 public enum NVGContextFlag : int { 12354 /// Nothing special, i.e. empty flag. 12355 None = 0, 12356 /// Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA). 12357 Antialias = 1U<<0, 12358 /** Flag indicating if strokes should be drawn using stencil buffer. The rendering will be a little 12359 * slower, but path overlaps (i.e. self-intersecting or sharp turns) will be drawn just once. */ 12360 StencilStrokes = 1U<<1, 12361 /// Flag indicating that additional debug checks are done. 12362 Debug = 1U<<2, 12363 /// Filter (antialias) fonts 12364 FontAA = 1U<<7, 12365 /// Don't filter (antialias) fonts 12366 FontNoAA = 1U<<8, 12367 /// You can use this as a substitute for default flags, for cases like this: `nvgCreateContext(NVGContextFlag.Default, NVGContextFlag.Debug);`. 12368 Default = 1U<<31, 12369 } 12370 12371 public enum NANOVG_GL_USE_STATE_FILTER = true; 12372 12373 /// Returns flags for glClear(). 12374 /// Group: context_management 12375 public uint glNVGClearFlags () pure nothrow @safe @nogc { 12376 pragma(inline, true); 12377 return (GL_COLOR_BUFFER_BIT|/*GL_DEPTH_BUFFER_BIT|*/GL_STENCIL_BUFFER_BIT); 12378 } 12379 12380 12381 // ////////////////////////////////////////////////////////////////////////// // 12382 private: 12383 12384 version = nanovega_shared_stencil; 12385 //version = nanovega_debug_clipping; 12386 12387 enum GLNVGuniformLoc { 12388 ViewSize, 12389 Tex, 12390 Frag, 12391 TMat, 12392 TTr, 12393 ClipTex, 12394 } 12395 12396 alias GLNVGshaderType = int; 12397 enum /*GLNVGshaderType*/ { 12398 NSVG_SHADER_FILLCOLOR, 12399 NSVG_SHADER_FILLGRAD, 12400 NSVG_SHADER_FILLIMG, 12401 NSVG_SHADER_SIMPLE, // also used for clipfill 12402 NSVG_SHADER_IMG, 12403 } 12404 12405 struct GLNVGshader { 12406 GLuint prog; 12407 GLuint frag; 12408 GLuint vert; 12409 GLint[GLNVGuniformLoc.max+1] loc; 12410 } 12411 12412 struct GLNVGtexture { 12413 int id; 12414 GLuint tex; 12415 int width, height; 12416 NVGtexture type; 12417 int flags; 12418 shared int rc; // this can be 0 with tex != 0 -- postponed deletion 12419 int nextfree; 12420 } 12421 12422 struct GLNVGblend { 12423 bool simple; 12424 GLenum srcRGB; 12425 GLenum dstRGB; 12426 GLenum srcAlpha; 12427 GLenum dstAlpha; 12428 } 12429 12430 alias GLNVGcallType = int; 12431 enum /*GLNVGcallType*/ { 12432 GLNVG_NONE = 0, 12433 GLNVG_FILL, 12434 GLNVG_CONVEXFILL, 12435 GLNVG_STROKE, 12436 GLNVG_TRIANGLES, 12437 GLNVG_AFFINE, // change affine transformation matrix 12438 GLNVG_PUSHCLIP, 12439 GLNVG_POPCLIP, 12440 GLNVG_RESETCLIP, 12441 GLNVG_CLIP_DDUMP_ON, 12442 GLNVG_CLIP_DDUMP_OFF, 12443 } 12444 12445 struct GLNVGcall { 12446 int type; 12447 int evenOdd; // for fill 12448 int image; 12449 int pathOffset; 12450 int pathCount; 12451 int triangleOffset; 12452 int triangleCount; 12453 int uniformOffset; 12454 NVGMatrix affine; 12455 GLNVGblend blendFunc; 12456 NVGClipMode clipmode; 12457 } 12458 12459 struct GLNVGpath { 12460 int fillOffset; 12461 int fillCount; 12462 int strokeOffset; 12463 int strokeCount; 12464 } 12465 12466 align(1) struct GLNVGfragUniforms { 12467 align(1): 12468 enum UNIFORM_ARRAY_SIZE = 13; 12469 // note: after modifying layout or size of uniform array, 12470 // don't forget to also update the fragment shader source! 12471 align(1) union { 12472 align(1): 12473 align(1) struct { 12474 align(1): 12475 float[12] scissorMat; // matrices are actually 3 vec4s 12476 float[12] paintMat; 12477 NVGColor innerCol; 12478 NVGColor middleCol; 12479 NVGColor outerCol; 12480 float[2] scissorExt; 12481 float[2] scissorScale; 12482 float[2] extent; 12483 float radius; 12484 float feather; 12485 float strokeMult; 12486 float strokeThr; 12487 float texType; 12488 float type; 12489 float doclip; 12490 float midp; // for gradients 12491 float unused2, unused3; 12492 } 12493 float[4][UNIFORM_ARRAY_SIZE] uniformArray; 12494 } 12495 } 12496 12497 enum GLMaskState { 12498 DontMask = -1, 12499 Uninitialized = 0, 12500 Initialized = 1, 12501 JustCleared = 2, 12502 } 12503 12504 import core.sync.mutex; 12505 __gshared Mutex GLNVGTextureLocker; 12506 shared static this() { 12507 GLNVGTextureLocker = new Mutex(); 12508 } 12509 12510 struct GLNVGcontext { 12511 private import core.thread : ThreadID; 12512 12513 GLNVGshader shader; 12514 GLNVGtexture* textures; 12515 float[2] view; 12516 int freetexid; // -1: none 12517 int ntextures; 12518 int ctextures; 12519 GLuint vaoBuf; 12520 GLuint vertBuf; 12521 int fragSize; 12522 int flags; 12523 // FBOs for masks 12524 GLuint[NVG_MAX_STATES] fbo; 12525 GLuint[2][NVG_MAX_STATES] fboTex; // FBO textures: [0] is color, [1] is stencil 12526 int fboWidth, fboHeight; 12527 GLMaskState[NVG_MAX_STATES] maskStack; 12528 int msp; // mask stack pointer; starts from `0`; points to next free item; see below for logic description 12529 int lastClipFBO; // -666: cache invalidated; -1: don't mask 12530 int lastClipUniOfs; 12531 bool doClipUnion; // specal mode 12532 GLNVGshader shaderFillFBO; 12533 GLNVGshader shaderCopyFBO; 12534 12535 bool inFrame; // will be `true` if we can perform OpenGL operations (used in texture deletion) 12536 shared bool mustCleanTextures; // will be `true` if we should delete some textures 12537 ThreadID mainTID; 12538 uint mainFBO; 12539 12540 // Per frame buffers 12541 GLNVGcall* calls; 12542 int ccalls; 12543 int ncalls; 12544 GLNVGpath* paths; 12545 int cpaths; 12546 int npaths; 12547 NVGVertex* verts; 12548 int cverts; 12549 int nverts; 12550 ubyte* uniforms; 12551 int cuniforms; 12552 int nuniforms; 12553 NVGMatrix lastAffine; 12554 12555 // cached state 12556 static if (NANOVG_GL_USE_STATE_FILTER) { 12557 GLuint boundTexture; 12558 GLuint stencilMask; 12559 GLenum stencilFunc; 12560 GLint stencilFuncRef; 12561 GLuint stencilFuncMask; 12562 GLNVGblend blendFunc; 12563 } 12564 } 12565 12566 int glnvg__maxi() (int a, int b) { pragma(inline, true); return (a > b ? a : b); } 12567 12568 void glnvg__bindTexture (GLNVGcontext* gl, GLuint tex) nothrow @trusted @nogc { 12569 static if (NANOVG_GL_USE_STATE_FILTER) { 12570 if (gl.boundTexture != tex) { 12571 gl.boundTexture = tex; 12572 glBindTexture(GL_TEXTURE_2D, tex); 12573 } 12574 } else { 12575 glBindTexture(GL_TEXTURE_2D, tex); 12576 } 12577 } 12578 12579 void glnvg__stencilMask (GLNVGcontext* gl, GLuint mask) nothrow @trusted @nogc { 12580 static if (NANOVG_GL_USE_STATE_FILTER) { 12581 if (gl.stencilMask != mask) { 12582 gl.stencilMask = mask; 12583 glStencilMask(mask); 12584 } 12585 } else { 12586 glStencilMask(mask); 12587 } 12588 } 12589 12590 void glnvg__stencilFunc (GLNVGcontext* gl, GLenum func, GLint ref_, GLuint mask) nothrow @trusted @nogc { 12591 static if (NANOVG_GL_USE_STATE_FILTER) { 12592 if (gl.stencilFunc != func || gl.stencilFuncRef != ref_ || gl.stencilFuncMask != mask) { 12593 gl.stencilFunc = func; 12594 gl.stencilFuncRef = ref_; 12595 gl.stencilFuncMask = mask; 12596 glStencilFunc(func, ref_, mask); 12597 } 12598 } else { 12599 glStencilFunc(func, ref_, mask); 12600 } 12601 } 12602 12603 // texture id is never zero 12604 // sets refcount to one 12605 GLNVGtexture* glnvg__allocTexture (GLNVGcontext* gl) nothrow @trusted @nogc { 12606 GLNVGtexture* tex = null; 12607 12608 int tid = gl.freetexid; 12609 if (tid == -1) { 12610 if (gl.ntextures >= gl.ctextures) { 12611 assert(gl.ntextures == gl.ctextures); 12612 //pragma(msg, GLNVGtexture.sizeof*32); 12613 int ctextures = (gl.ctextures == 0 ? 32 : gl.ctextures+gl.ctextures/2); // 1.5x overallocate 12614 GLNVGtexture* textures = cast(GLNVGtexture*)realloc(gl.textures, GLNVGtexture.sizeof*ctextures); 12615 if (textures is null) assert(0, "NanoVega: out of memory for textures"); 12616 memset(&textures[gl.ctextures], 0, (ctextures-gl.ctextures)*GLNVGtexture.sizeof); 12617 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated more textures (n=%d; c=%d; nc=%d)\n", gl.ntextures, gl.ctextures, ctextures); }} 12618 gl.textures = textures; 12619 gl.ctextures = ctextures; 12620 } 12621 assert(gl.ntextures+1 <= gl.ctextures); 12622 tid = gl.ntextures++; 12623 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf(" got next free texture id %d, ntextures=%d\n", tid+1, gl.ntextures); }} 12624 } else { 12625 gl.freetexid = gl.textures[tid].nextfree; 12626 } 12627 assert(tid <= gl.ntextures); 12628 12629 assert(gl.textures[tid].id == 0); 12630 tex = &gl.textures[tid]; 12631 memset(tex, 0, (*tex).sizeof); 12632 tex.id = tid+1; 12633 tex.rc = 1; 12634 tex.nextfree = -1; 12635 12636 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated texture with id %d (%d)\n", tex.id, tid+1); }} 12637 12638 return tex; 12639 } 12640 12641 GLNVGtexture* glnvg__findTexture (GLNVGcontext* gl, int id) nothrow @trusted @nogc { 12642 if (id <= 0 || id > gl.ntextures) return null; 12643 if (gl.textures[id-1].id == 0) return null; // free one 12644 assert(gl.textures[id-1].id == id); 12645 return &gl.textures[id-1]; 12646 } 12647 12648 bool glnvg__deleteTexture (GLNVGcontext* gl, ref int id) nothrow @trusted @nogc { 12649 if (id <= 0 || id > gl.ntextures) return false; 12650 auto tx = &gl.textures[id-1]; 12651 if (tx.id == 0) { id = 0; return false; } // free one 12652 assert(tx.id == id); 12653 assert(tx.tex != 0); 12654 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("decrefing texture with id %d (%d)\n", tx.id, id); }} 12655 import core.atomic : atomicOp; 12656 if (atomicOp!"-="(tx.rc, 1) == 0) { 12657 import core.thread : ThreadID; 12658 ThreadID mytid; 12659 static if (__VERSION__ < 2076) { 12660 DGNoThrowNoGC(() { 12661 import core.thread; mytid = Thread.getThis.id; 12662 })(); 12663 } else { 12664 try { import core.thread; mytid = Thread.getThis.id; } catch (Exception e) {} 12665 } 12666 if (gl.mainTID == mytid && gl.inFrame) { 12667 // can delete it right now 12668 if ((tx.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tx.tex); 12669 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** deleted texture with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }} 12670 memset(tx, 0, (*tx).sizeof); 12671 //{ import core.stdc.stdio; printf("deleting texture with id %d\n", id); } 12672 tx.nextfree = gl.freetexid; 12673 gl.freetexid = id-1; 12674 } else { 12675 // alas, we aren't doing frame business, so we should postpone deletion 12676 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** POSTPONED texture deletion with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }} 12677 version(aliced) { 12678 { 12679 GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow; 12680 tx.id = 0; // mark it as dead 12681 gl.mustCleanTextures = true; // set "need cleanup" flag 12682 } 12683 } else { 12684 try { 12685 GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow; 12686 tx.id = 0; // mark it as dead 12687 gl.mustCleanTextures = true; // set "need cleanup" flag 12688 } catch (Exception e) {} 12689 } 12690 } 12691 } 12692 id = 0; 12693 return true; 12694 } 12695 12696 void glnvg__dumpShaderError (GLuint shader, const(char)* name, const(char)* type) nothrow @trusted @nogc { 12697 import core.stdc.stdio : fprintf, stderr; 12698 GLchar[512+1] str = 0; 12699 GLsizei len = 0; 12700 glGetShaderInfoLog(shader, 512, &len, str.ptr); 12701 if (len > 512) len = 512; 12702 str[len] = '\0'; 12703 fprintf(stderr, "Shader %s/%s error:\n%s\n", name, type, str.ptr); 12704 } 12705 12706 void glnvg__dumpProgramError (GLuint prog, const(char)* name) nothrow @trusted @nogc { 12707 import core.stdc.stdio : fprintf, stderr; 12708 GLchar[512+1] str = 0; 12709 GLsizei len = 0; 12710 glGetProgramInfoLog(prog, 512, &len, str.ptr); 12711 if (len > 512) len = 512; 12712 str[len] = '\0'; 12713 fprintf(stderr, "Program %s error:\n%s\n", name, str.ptr); 12714 } 12715 12716 void glnvg__resetError(bool force=false) (GLNVGcontext* gl) nothrow @trusted @nogc { 12717 static if (!force) { 12718 if ((gl.flags&NVGContextFlag.Debug) == 0) return; 12719 } 12720 glGetError(); 12721 } 12722 12723 void glnvg__checkError(bool force=false) (GLNVGcontext* gl, const(char)* str) nothrow @trusted @nogc { 12724 GLenum err; 12725 static if (!force) { 12726 if ((gl.flags&NVGContextFlag.Debug) == 0) return; 12727 } 12728 err = glGetError(); 12729 if (err != GL_NO_ERROR) { 12730 import core.stdc.stdio : fprintf, stderr; 12731 fprintf(stderr, "Error %08x after %s\n", err, str); 12732 return; 12733 } 12734 } 12735 12736 bool glnvg__createShader (GLNVGshader* shader, const(char)* name, const(char)* header, const(char)* opts, const(char)* vshader, const(char)* fshader) nothrow @trusted @nogc { 12737 GLint status; 12738 GLuint prog, vert, frag; 12739 const(char)*[3] str; 12740 12741 memset(shader, 0, (*shader).sizeof); 12742 12743 prog = glCreateProgram(); 12744 vert = glCreateShader(GL_VERTEX_SHADER); 12745 frag = glCreateShader(GL_FRAGMENT_SHADER); 12746 str[0] = header; 12747 str[1] = (opts !is null ? opts : ""); 12748 str[2] = vshader; 12749 glShaderSource(vert, 3, cast(const(char)**)str.ptr, null); 12750 12751 glCompileShader(vert); 12752 glGetShaderiv(vert, GL_COMPILE_STATUS, &status); 12753 if (status != GL_TRUE) { 12754 glnvg__dumpShaderError(vert, name, "vert"); 12755 return false; 12756 } 12757 12758 str[0] = header; 12759 str[1] = (opts !is null ? opts : ""); 12760 str[2] = fshader; 12761 glShaderSource(frag, 3, cast(const(char)**)str.ptr, null); 12762 12763 glCompileShader(frag); 12764 glGetShaderiv(frag, GL_COMPILE_STATUS, &status); 12765 if (status != GL_TRUE) { 12766 glnvg__dumpShaderError(frag, name, "frag"); 12767 return false; 12768 } 12769 12770 glAttachShader(prog, vert); 12771 glAttachShader(prog, frag); 12772 12773 glBindAttribLocation(prog, 0, "vertex"); 12774 glBindAttribLocation(prog, 1, "tcoord"); 12775 12776 glLinkProgram(prog); 12777 glGetProgramiv(prog, GL_LINK_STATUS, &status); 12778 if (status != GL_TRUE) { 12779 glnvg__dumpProgramError(prog, name); 12780 return false; 12781 } 12782 12783 shader.prog = prog; 12784 shader.vert = vert; 12785 shader.frag = frag; 12786 12787 return true; 12788 } 12789 12790 void glnvg__deleteShader (GLNVGshader* shader) nothrow @trusted @nogc { 12791 if (shader.prog != 0) glDeleteProgram(shader.prog); 12792 if (shader.vert != 0) glDeleteShader(shader.vert); 12793 if (shader.frag != 0) glDeleteShader(shader.frag); 12794 } 12795 12796 void glnvg__getUniforms (GLNVGshader* shader) nothrow @trusted @nogc { 12797 shader.loc[GLNVGuniformLoc.ViewSize] = glGetUniformLocation(shader.prog, "viewSize"); 12798 shader.loc[GLNVGuniformLoc.Tex] = glGetUniformLocation(shader.prog, "tex"); 12799 shader.loc[GLNVGuniformLoc.Frag] = glGetUniformLocation(shader.prog, "frag"); 12800 shader.loc[GLNVGuniformLoc.TMat] = glGetUniformLocation(shader.prog, "tmat"); 12801 shader.loc[GLNVGuniformLoc.TTr] = glGetUniformLocation(shader.prog, "ttr"); 12802 shader.loc[GLNVGuniformLoc.ClipTex] = glGetUniformLocation(shader.prog, "clipTex"); 12803 } 12804 12805 void glnvg__killFBOs (GLNVGcontext* gl) nothrow @trusted @nogc { 12806 foreach (immutable fidx, ref GLuint fbo; gl.fbo[]) { 12807 if (fbo != 0) { 12808 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); 12809 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); 12810 foreach (ref GLuint tid; gl.fboTex.ptr[fidx][]) if (tid != 0) { glDeleteTextures(1, &tid); tid = 0; } 12811 glDeleteFramebuffers(1, &fbo); 12812 fbo = 0; 12813 } 12814 } 12815 gl.fboWidth = gl.fboHeight = 0; 12816 } 12817 12818 // returns `true` is new FBO was created 12819 // will not unbind buffer, if it was created 12820 bool glnvg__allocFBO (GLNVGcontext* gl, int fidx, bool doclear=true) nothrow @trusted @nogc { 12821 assert(fidx >= 0 && fidx < gl.fbo.length); 12822 assert(gl.fboWidth > 0); 12823 assert(gl.fboHeight > 0); 12824 12825 if (gl.fbo.ptr[fidx] != 0) return false; // nothing to do, this FBO is already initialized 12826 12827 glnvg__resetError(gl); 12828 12829 // allocate FBO object 12830 GLuint fbo = 0; 12831 glGenFramebuffers(1, &fbo); 12832 if (fbo == 0) assert(0, "NanoVega: cannot create FBO"); 12833 glnvg__checkError(gl, "glnvg__allocFBO: glGenFramebuffers"); 12834 glBindFramebuffer(GL_FRAMEBUFFER, fbo); 12835 //scope(exit) glBindFramebuffer(GL_FRAMEBUFFER, 0); 12836 12837 // attach 2D texture to this FBO 12838 GLuint tidColor = 0; 12839 glGenTextures(1, &tidColor); 12840 if (tidColor == 0) assert(0, "NanoVega: cannot create RGBA texture for FBO"); 12841 glBindTexture(GL_TEXTURE_2D, tidColor); 12842 //scope(exit) glBindTexture(GL_TEXTURE_2D, 0); 12843 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 12844 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_S"); 12845 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 12846 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_T"); 12847 //FIXME: linear or nearest? 12848 //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 12849 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 12850 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MIN_FILTER"); 12851 //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 12852 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 12853 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MAG_FILTER"); 12854 // empty texture 12855 //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null); 12856 // create texture with only one color channel 12857 glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, gl.fboWidth, gl.fboHeight, 0, GL_RED, GL_UNSIGNED_BYTE, null); 12858 //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null); 12859 glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (color)"); 12860 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tidColor, 0); 12861 glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (color)"); 12862 12863 // attach stencil texture to this FBO 12864 GLuint tidStencil = 0; 12865 version(nanovega_shared_stencil) { 12866 if (gl.fboTex.ptr[0].ptr[0] == 0) { 12867 glGenTextures(1, &tidStencil); 12868 if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO"); 12869 gl.fboTex.ptr[0].ptr[0] = tidStencil; 12870 } else { 12871 tidStencil = gl.fboTex.ptr[0].ptr[0]; 12872 } 12873 if (fidx != 0) gl.fboTex.ptr[fidx].ptr[1] = 0; // stencil texture is shared among FBOs 12874 } else { 12875 glGenTextures(1, &tidStencil); 12876 if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO"); 12877 gl.fboTex.ptr[0].ptr[0] = tidStencil; 12878 } 12879 glBindTexture(GL_TEXTURE_2D, tidStencil); 12880 glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, gl.fboWidth, gl.fboHeight, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, null); 12881 glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (stencil)"); 12882 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, tidStencil, 0); 12883 glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (stencil)"); 12884 12885 { 12886 GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); 12887 if (status != GL_FRAMEBUFFER_COMPLETE) { 12888 version(all) { 12889 import core.stdc.stdio; 12890 if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) printf("fucked attachement\n"); 12891 if (status == GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS) printf("fucked dimensions\n"); 12892 if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) printf("missing attachement\n"); 12893 if (status == GL_FRAMEBUFFER_UNSUPPORTED) printf("unsupported\n"); 12894 } 12895 assert(0, "NanoVega: framebuffer creation failed"); 12896 } 12897 } 12898 12899 // clear 'em all 12900 if (doclear) { 12901 glClearColor(0, 0, 0, 0); 12902 glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT); 12903 } 12904 12905 // save texture ids 12906 gl.fbo.ptr[fidx] = fbo; 12907 gl.fboTex.ptr[fidx].ptr[0] = tidColor; 12908 version(nanovega_shared_stencil) {} else { 12909 gl.fboTex.ptr[fidx].ptr[1] = tidStencil; 12910 } 12911 12912 static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture); 12913 12914 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): created with index %d\n", gl.msp-1, fidx); } 12915 12916 return true; 12917 } 12918 12919 // will not unbind buffer 12920 void glnvg__clearFBO (GLNVGcontext* gl, int fidx) nothrow @trusted @nogc { 12921 assert(fidx >= 0 && fidx < gl.fbo.length); 12922 assert(gl.fboWidth > 0); 12923 assert(gl.fboHeight > 0); 12924 assert(gl.fbo.ptr[fidx] != 0); 12925 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[fidx]); 12926 glClearColor(0, 0, 0, 0); 12927 glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT); 12928 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cleared with index %d\n", gl.msp-1, fidx); } 12929 } 12930 12931 // will not unbind buffer 12932 void glnvg__copyFBOToFrom (GLNVGcontext* gl, int didx, int sidx) nothrow @trusted @nogc { 12933 import core.stdc.string : memset; 12934 assert(didx >= 0 && didx < gl.fbo.length); 12935 assert(sidx >= 0 && sidx < gl.fbo.length); 12936 assert(gl.fboWidth > 0); 12937 assert(gl.fboHeight > 0); 12938 assert(gl.fbo.ptr[didx] != 0); 12939 assert(gl.fbo.ptr[sidx] != 0); 12940 if (didx == sidx) return; 12941 12942 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copy FBO: %d -> %d\n", gl.msp-1, sidx, didx); } 12943 12944 glUseProgram(gl.shaderCopyFBO.prog); 12945 12946 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[didx]); 12947 glDisable(GL_CULL_FACE); 12948 glDisable(GL_BLEND); 12949 glDisable(GL_SCISSOR_TEST); 12950 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[sidx].ptr[0]); 12951 // copy texture by drawing full quad 12952 enum x = 0; 12953 enum y = 0; 12954 immutable int w = gl.fboWidth; 12955 immutable int h = gl.fboHeight; 12956 immutable(NVGVertex[4]) vertices = 12957 [NVGVertex(x, y), // top-left 12958 NVGVertex(w, y), // top-right 12959 NVGVertex(w, h), // bottom-right 12960 NVGVertex(x, h)]; // bottom-left 12961 12962 glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices); 12963 glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 12964 12965 // restore state (but don't unbind FBO) 12966 static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture); 12967 glEnable(GL_CULL_FACE); 12968 glEnable(GL_BLEND); 12969 glUseProgram(gl.shader.prog); 12970 } 12971 12972 void glnvg__resetFBOClipTextureCache (GLNVGcontext* gl) nothrow @trusted @nogc { 12973 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): texture cache invalidated (%d)\n", gl.msp-1, gl.lastClipFBO); } 12974 /* 12975 if (gl.lastClipFBO >= 0) { 12976 glActiveTexture(GL_TEXTURE1); 12977 glBindTexture(GL_TEXTURE_2D, 0); 12978 glActiveTexture(GL_TEXTURE0); 12979 } 12980 */ 12981 gl.lastClipFBO = -666; 12982 } 12983 12984 void glnvg__setFBOClipTexture (GLNVGcontext* gl, GLNVGfragUniforms* frag) nothrow @trusted @nogc { 12985 //assert(gl.msp > 0 && gl.msp <= gl.maskStack.length); 12986 if (gl.lastClipFBO != -666) { 12987 // cached 12988 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cached (%d)\n", gl.msp-1, gl.lastClipFBO); } 12989 frag.doclip = (gl.lastClipFBO >= 0 ? 1 : 0); 12990 return; 12991 } 12992 12993 // no cache 12994 int fboidx = -1; 12995 mainloop: foreach_reverse (immutable sp, GLMaskState mst; gl.maskStack.ptr[0..gl.msp]/*; reverse*/) { 12996 final switch (mst) { 12997 case GLMaskState.DontMask: fboidx = -1; break mainloop; 12998 case GLMaskState.Uninitialized: break; 12999 case GLMaskState.Initialized: fboidx = cast(int)sp; break mainloop; 13000 case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__setFBOClipTexture()` internal error"); 13001 } 13002 } 13003 13004 if (fboidx < 0) { 13005 // don't mask 13006 gl.lastClipFBO = -1; 13007 frag.doclip = 0; 13008 } else { 13009 // do masking 13010 assert(gl.fbo.ptr[fboidx] != 0); 13011 gl.lastClipFBO = fboidx; 13012 frag.doclip = 1; 13013 } 13014 13015 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): new cache (new:%d)\n", gl.msp-1, gl.lastClipFBO); } 13016 13017 if (gl.lastClipFBO >= 0) { 13018 assert(gl.fboTex.ptr[gl.lastClipFBO].ptr[0]); 13019 glActiveTexture(GL_TEXTURE1); 13020 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]); 13021 glActiveTexture(GL_TEXTURE0); 13022 } 13023 } 13024 13025 // returns index in `gl.fbo`, or -1 for "don't mask" 13026 int glnvg__generateFBOClipTexture (GLNVGcontext* gl) nothrow @trusted @nogc { 13027 assert(gl.msp > 0 && gl.msp <= gl.maskStack.length); 13028 // we need initialized FBO, even for "don't mask" case 13029 // for this, look back in stack, and either copy initialized FBO, 13030 // or stop at first uninitialized one, and clear it 13031 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.Initialized) { 13032 // shortcut 13033 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); } 13034 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[gl.msp-1]); 13035 return gl.msp-1; 13036 } 13037 foreach_reverse (immutable sp; 0..gl.msp/*; reverse*/) { 13038 final switch (gl.maskStack.ptr[sp]) { 13039 case GLMaskState.DontMask: 13040 // clear it 13041 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture\n", gl.msp-1); } 13042 if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1); 13043 gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared; 13044 return gl.msp-1; 13045 case GLMaskState.Uninitialized: break; // do nothing 13046 case GLMaskState.Initialized: 13047 // i found her! copy to TOS 13048 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copying texture from %d\n", gl.msp-1, cast(int)sp); } 13049 glnvg__allocFBO(gl, gl.msp-1, false); 13050 glnvg__copyFBOToFrom(gl, gl.msp-1, sp); 13051 gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized; 13052 return gl.msp-1; 13053 case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__generateFBOClipTexture()` internal error"); 13054 } 13055 } 13056 // nothing was initialized, lol 13057 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture (first one)\n", gl.msp-1); } 13058 if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1); 13059 gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared; 13060 return gl.msp-1; 13061 } 13062 13063 void glnvg__renderPushClip (void* uptr) nothrow @trusted @nogc { 13064 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13065 GLNVGcall* call = glnvg__allocCall(gl); 13066 if (call is null) return; 13067 call.type = GLNVG_PUSHCLIP; 13068 } 13069 13070 void glnvg__renderPopClip (void* uptr) nothrow @trusted @nogc { 13071 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13072 GLNVGcall* call = glnvg__allocCall(gl); 13073 if (call is null) return; 13074 call.type = GLNVG_POPCLIP; 13075 } 13076 13077 void glnvg__renderResetClip (void* uptr) nothrow @trusted @nogc { 13078 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13079 GLNVGcall* call = glnvg__allocCall(gl); 13080 if (call is null) return; 13081 call.type = GLNVG_RESETCLIP; 13082 } 13083 13084 void glnvg__clipDebugDump (void* uptr, bool doit) nothrow @trusted @nogc { 13085 version(nanovega_debug_clipping) { 13086 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13087 GLNVGcall* call = glnvg__allocCall(gl); 13088 call.type = (doit ? GLNVG_CLIP_DDUMP_ON : GLNVG_CLIP_DDUMP_OFF); 13089 } 13090 } 13091 13092 bool glnvg__renderCreate (void* uptr) nothrow @trusted @nogc { 13093 import core.stdc.stdio : snprintf; 13094 13095 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13096 enum align_ = 4; 13097 13098 char[64] shaderHeader = void; 13099 //enum shaderHeader = "#define UNIFORM_ARRAY_SIZE 12\n"; 13100 snprintf(shaderHeader.ptr, shaderHeader.length, "#define UNIFORM_ARRAY_SIZE %u\n", cast(uint)GLNVGfragUniforms.UNIFORM_ARRAY_SIZE); 13101 13102 enum fillVertShader = q{ 13103 uniform vec2 viewSize; 13104 attribute vec2 vertex; 13105 attribute vec2 tcoord; 13106 varying vec2 ftcoord; 13107 varying vec2 fpos; 13108 uniform vec4 tmat; /* abcd of affine matrix: xyzw */ 13109 uniform vec2 ttr; /* tx and ty of affine matrix */ 13110 void main (void) { 13111 /* affine transformation */ 13112 float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x; 13113 float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y; 13114 ftcoord = tcoord; 13115 fpos = vec2(nx, ny); 13116 gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1); 13117 } 13118 }; 13119 13120 enum fillFragShader = ` 13121 uniform vec4 frag[UNIFORM_ARRAY_SIZE]; 13122 uniform sampler2D tex; 13123 uniform sampler2D clipTex; 13124 uniform vec2 viewSize; 13125 varying vec2 ftcoord; 13126 varying vec2 fpos; 13127 #define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz) 13128 #define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz) 13129 #define innerCol frag[6] 13130 #define middleCol frag[7] 13131 #define outerCol frag[7+1] 13132 #define scissorExt frag[8+1].xy 13133 #define scissorScale frag[8+1].zw 13134 #define extent frag[9+1].xy 13135 #define radius frag[9+1].z 13136 #define feather frag[9+1].w 13137 #define strokeMult frag[10+1].x 13138 #define strokeThr frag[10+1].y 13139 #define texType int(frag[10+1].z) 13140 #define type int(frag[10+1].w) 13141 #define doclip int(frag[11+1].x) 13142 #define midp frag[11+1].y 13143 13144 float sdroundrect (in vec2 pt, in vec2 ext, in float rad) { 13145 vec2 ext2 = ext-vec2(rad, rad); 13146 vec2 d = abs(pt)-ext2; 13147 return min(max(d.x, d.y), 0.0)+length(max(d, 0.0))-rad; 13148 } 13149 13150 // Scissoring 13151 float scissorMask (in vec2 p) { 13152 vec2 sc = (abs((scissorMat*vec3(p, 1.0)).xy)-scissorExt); 13153 sc = vec2(0.5, 0.5)-sc*scissorScale; 13154 return clamp(sc.x, 0.0, 1.0)*clamp(sc.y, 0.0, 1.0); 13155 } 13156 13157 #ifdef EDGE_AA 13158 // Stroke - from [0..1] to clipped pyramid, where the slope is 1px. 13159 float strokeMask () { 13160 return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult)*min(1.0, ftcoord.y); 13161 } 13162 #endif 13163 13164 void main (void) { 13165 // clipping 13166 if (doclip != 0) { 13167 /*vec4 clr = texelFetch(clipTex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);*/ 13168 vec4 clr = texture2D(clipTex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y)); 13169 if (clr.r == 0.0) discard; 13170 } 13171 float scissor = scissorMask(fpos); 13172 if (scissor <= 0.0) discard; //k8: is it really faster? 13173 #ifdef EDGE_AA 13174 float strokeAlpha = strokeMask(); 13175 if (strokeAlpha < strokeThr) discard; 13176 #else 13177 float strokeAlpha = 1.0; 13178 #endif 13179 // rendering 13180 vec4 color; 13181 if (type == 0) { /* NSVG_SHADER_FILLCOLOR */ 13182 color = innerCol; 13183 // Combine alpha 13184 color *= strokeAlpha*scissor; 13185 } else if (type == 1) { /* NSVG_SHADER_FILLGRAD */ 13186 // Gradient 13187 // Calculate gradient color using box gradient 13188 vec2 pt = (paintMat*vec3(fpos, 1.0)).xy; 13189 float d = clamp((sdroundrect(pt, extent, radius)+feather*0.5)/feather, 0.0, 1.0); 13190 if (midp <= 0.0) { 13191 color = mix(innerCol, outerCol, d); 13192 } else { 13193 float gdst = min(midp, 1.0); 13194 if (d < gdst) { 13195 color = mix(innerCol, middleCol, d/gdst); 13196 } else { 13197 color = mix(middleCol, outerCol, (d-gdst)/gdst); 13198 } 13199 } 13200 // Combine alpha 13201 color *= strokeAlpha*scissor; 13202 } else if (type == 2) { /* NSVG_SHADER_FILLIMG */ 13203 // Image 13204 // Calculate color from texture 13205 vec2 pt = (paintMat*vec3(fpos, 1.0)).xy/extent; 13206 color = texture2D(tex, pt); 13207 if (texType == 1) color = vec4(color.xyz*color.w, color.w); 13208 if (texType == 2) color = vec4(color.x); 13209 // Apply color tint and alpha 13210 color *= innerCol; 13211 // Combine alpha 13212 color *= strokeAlpha*scissor; 13213 } else if (type == 3) { /* NSVG_SHADER_SIMPLE */ 13214 // Stencil fill 13215 color = vec4(1, 1, 1, 1); 13216 } else if (type == 4) { /* NSVG_SHADER_IMG */ 13217 // Textured tris 13218 color = texture2D(tex, ftcoord); 13219 if (texType == 1) color = vec4(color.xyz*color.w, color.w); 13220 if (texType == 2) color = vec4(color.x); 13221 color *= scissor; 13222 color *= innerCol; // Apply color tint 13223 } 13224 gl_FragColor = color; 13225 } 13226 `; 13227 13228 enum clipVertShaderFill = q{ 13229 uniform vec2 viewSize; 13230 attribute vec2 vertex; 13231 uniform vec4 tmat; /* abcd of affine matrix: xyzw */ 13232 uniform vec2 ttr; /* tx and ty of affine matrix */ 13233 void main (void) { 13234 /* affine transformation */ 13235 float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x; 13236 float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y; 13237 gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1); 13238 } 13239 }; 13240 13241 enum clipFragShaderFill = q{ 13242 uniform vec2 viewSize; 13243 void main (void) { 13244 gl_FragColor = vec4(1, 1, 1, 1); 13245 } 13246 }; 13247 13248 enum clipVertShaderCopy = q{ 13249 uniform vec2 viewSize; 13250 attribute vec2 vertex; 13251 void main (void) { 13252 gl_Position = vec4(2.0*vertex.x/viewSize.x-1.0, 1.0-2.0*vertex.y/viewSize.y, 0, 1); 13253 } 13254 }; 13255 13256 enum clipFragShaderCopy = q{ 13257 uniform sampler2D tex; 13258 uniform vec2 viewSize; 13259 void main (void) { 13260 //gl_FragColor = texelFetch(tex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0); 13261 gl_FragColor = texture2D(tex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y)); 13262 } 13263 }; 13264 13265 glnvg__checkError(gl, "init"); 13266 13267 string defines = (gl.flags&NVGContextFlag.Antialias ? "#define EDGE_AA 1\n" : null); 13268 if (!glnvg__createShader(&gl.shader, "shader", shaderHeader.ptr, defines.ptr, fillVertShader, fillFragShader)) return false; 13269 if (!glnvg__createShader(&gl.shaderFillFBO, "shaderFillFBO", shaderHeader.ptr, defines.ptr, clipVertShaderFill, clipFragShaderFill)) return false; 13270 if (!glnvg__createShader(&gl.shaderCopyFBO, "shaderCopyFBO", shaderHeader.ptr, defines.ptr, clipVertShaderCopy, clipFragShaderCopy)) return false; 13271 13272 glnvg__checkError(gl, "uniform locations"); 13273 glnvg__getUniforms(&gl.shader); 13274 glnvg__getUniforms(&gl.shaderFillFBO); 13275 glnvg__getUniforms(&gl.shaderCopyFBO); 13276 13277 // Create VAO (required for modern OpenGL compatibility) 13278 glGenVertexArrays(1, &gl.vaoBuf); 13279 // Create dynamic vertex array 13280 glGenBuffers(1, &gl.vertBuf); 13281 13282 gl.fragSize = GLNVGfragUniforms.sizeof+align_-GLNVGfragUniforms.sizeof%align_; 13283 13284 glnvg__checkError(gl, "create done"); 13285 13286 glFinish(); 13287 13288 return true; 13289 } 13290 13291 int glnvg__renderCreateTexture (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc { 13292 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13293 GLNVGtexture* tex = glnvg__allocTexture(gl); 13294 13295 if (tex is null) return 0; 13296 13297 glGenTextures(1, &tex.tex); 13298 tex.width = w; 13299 tex.height = h; 13300 tex.type = type; 13301 tex.flags = imageFlags; 13302 glnvg__bindTexture(gl, tex.tex); 13303 13304 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("created texture with id %d; glid=%u\n", tex.id, tex.tex); }} 13305 13306 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 13307 glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width); 13308 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); 13309 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); 13310 13311 13312 13313 immutable ttype = (type == NVGtexture.RGBA ? GL_RGBA : GL_RED); 13314 glTexImage2D(GL_TEXTURE_2D, 0, ttype, w, h, 0, ttype, GL_UNSIGNED_BYTE, data); 13315 // GL 3.0 and later have support for a dedicated function for generating mipmaps 13316 // it needs to be called after the glTexImage2D call 13317 if (imageFlags & NVGImageFlag.GenerateMipmaps) 13318 glGenerateMipmap(GL_TEXTURE_2D); 13319 13320 immutable tfmin = 13321 (imageFlags & NVGImageFlag.GenerateMipmaps ? GL_LINEAR_MIPMAP_LINEAR : 13322 imageFlags & NVGImageFlag.NoFiltering ? GL_NEAREST : 13323 GL_LINEAR); 13324 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.0); 13325 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tfmin); 13326 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (imageFlags&NVGImageFlag.NoFiltering ? GL_NEAREST : GL_LINEAR)); 13327 13328 int flag; 13329 if (imageFlags&NVGImageFlag.RepeatX) 13330 flag = GL_REPEAT; 13331 else if (imageFlags&NVGImageFlag.ClampToBorderX) 13332 flag = GL_CLAMP_TO_BORDER; 13333 else 13334 flag = GL_CLAMP_TO_EDGE; 13335 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, flag); 13336 13337 13338 if (imageFlags&NVGImageFlag.RepeatY) 13339 flag = GL_REPEAT; 13340 else if (imageFlags&NVGImageFlag.ClampToBorderY) 13341 flag = GL_CLAMP_TO_BORDER; 13342 else 13343 flag = GL_CLAMP_TO_EDGE; 13344 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, flag); 13345 13346 glPixelStorei(GL_UNPACK_ALIGNMENT, 4); 13347 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 13348 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); 13349 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); 13350 13351 glnvg__checkError(gl, "create tex"); 13352 glnvg__bindTexture(gl, 0); 13353 13354 return tex.id; 13355 } 13356 13357 bool glnvg__renderDeleteTexture (void* uptr, int image) nothrow @trusted @nogc { 13358 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13359 return glnvg__deleteTexture(gl, image); 13360 } 13361 13362 bool glnvg__renderTextureIncRef (void* uptr, int image) nothrow @trusted @nogc { 13363 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13364 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13365 if (tex is null) { 13366 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT incref texture with id %d\n", image); }} 13367 return false; 13368 } 13369 import core.atomic : atomicOp; 13370 atomicOp!"+="(tex.rc, 1); 13371 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("texture #%d: incref; newref=%d\n", image, tex.rc); }} 13372 return true; 13373 } 13374 13375 bool glnvg__renderUpdateTexture (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc { 13376 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13377 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13378 13379 if (tex is null) { 13380 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT update texture with id %d\n", image); }} 13381 return false; 13382 } 13383 13384 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("updated texture with id %d; glid=%u\n", tex.id, image, tex.tex); }} 13385 13386 glnvg__bindTexture(gl, tex.tex); 13387 13388 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 13389 glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width); 13390 glPixelStorei(GL_UNPACK_SKIP_PIXELS, x); 13391 glPixelStorei(GL_UNPACK_SKIP_ROWS, y); 13392 13393 immutable ttype = (tex.type == NVGtexture.RGBA ? GL_RGBA : GL_RED); 13394 glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, ttype, GL_UNSIGNED_BYTE, data); 13395 13396 glPixelStorei(GL_UNPACK_ALIGNMENT, 4); 13397 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 13398 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); 13399 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); 13400 13401 glnvg__bindTexture(gl, 0); 13402 13403 return true; 13404 } 13405 13406 bool glnvg__renderGetTextureSize (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc { 13407 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13408 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13409 if (tex is null) { 13410 if (w !is null) *w = 0; 13411 if (h !is null) *h = 0; 13412 return false; 13413 } else { 13414 if (w !is null) *w = tex.width; 13415 if (h !is null) *h = tex.height; 13416 return true; 13417 } 13418 } 13419 13420 void glnvg__xformToMat3x4 (float[] m3, const(float)[] t) nothrow @trusted @nogc { 13421 assert(t.length >= 6); 13422 assert(m3.length >= 12); 13423 m3.ptr[0] = t.ptr[0]; 13424 m3.ptr[1] = t.ptr[1]; 13425 m3.ptr[2] = 0.0f; 13426 m3.ptr[3] = 0.0f; 13427 m3.ptr[4] = t.ptr[2]; 13428 m3.ptr[5] = t.ptr[3]; 13429 m3.ptr[6] = 0.0f; 13430 m3.ptr[7] = 0.0f; 13431 m3.ptr[8] = t.ptr[4]; 13432 m3.ptr[9] = t.ptr[5]; 13433 m3.ptr[10] = 1.0f; 13434 m3.ptr[11] = 0.0f; 13435 } 13436 13437 NVGColor glnvg__premulColor() (const scope auto ref NVGColor c) nothrow @trusted @nogc { 13438 //pragma(inline, true); 13439 NVGColor res = void; 13440 res.r = c.r*c.a; 13441 res.g = c.g*c.a; 13442 res.b = c.b*c.a; 13443 res.a = c.a; 13444 return res; 13445 } 13446 13447 bool glnvg__convertPaint (GLNVGcontext* gl, GLNVGfragUniforms* frag, NVGPaint* paint, NVGscissor* scissor, float width, float fringe, float strokeThr) nothrow @trusted @nogc { 13448 import core.stdc.math : sqrtf; 13449 GLNVGtexture* tex = null; 13450 NVGMatrix invxform = void; 13451 13452 memset(frag, 0, (*frag).sizeof); 13453 13454 frag.innerCol = glnvg__premulColor(paint.innerColor); 13455 frag.middleCol = glnvg__premulColor(paint.middleColor); 13456 frag.outerCol = glnvg__premulColor(paint.outerColor); 13457 frag.midp = paint.midp; 13458 13459 if (scissor.extent.ptr[0] < -0.5f || scissor.extent.ptr[1] < -0.5f) { 13460 memset(frag.scissorMat.ptr, 0, frag.scissorMat.sizeof); 13461 frag.scissorExt.ptr[0] = 1.0f; 13462 frag.scissorExt.ptr[1] = 1.0f; 13463 frag.scissorScale.ptr[0] = 1.0f; 13464 frag.scissorScale.ptr[1] = 1.0f; 13465 } else { 13466 //nvgTransformInverse(invxform[], scissor.xform[]); 13467 invxform = scissor.xform.inverted; 13468 glnvg__xformToMat3x4(frag.scissorMat[], invxform.mat[]); 13469 frag.scissorExt.ptr[0] = scissor.extent.ptr[0]; 13470 frag.scissorExt.ptr[1] = scissor.extent.ptr[1]; 13471 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; 13472 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; 13473 } 13474 13475 memcpy(frag.extent.ptr, paint.extent.ptr, frag.extent.sizeof); 13476 frag.strokeMult = (width*0.5f+fringe*0.5f)/fringe; 13477 frag.strokeThr = strokeThr; 13478 13479 if (paint.image.valid) { 13480 tex = glnvg__findTexture(gl, paint.image.id); 13481 if (tex is null) return false; 13482 if ((tex.flags&NVGImageFlag.FlipY) != 0) { 13483 /* 13484 NVGMatrix flipped; 13485 nvgTransformScale(flipped[], 1.0f, -1.0f); 13486 nvgTransformMultiply(flipped[], paint.xform[]); 13487 nvgTransformInverse(invxform[], flipped[]); 13488 */ 13489 /* 13490 NVGMatrix m1 = void, m2 = void; 13491 nvgTransformTranslate(m1[], 0.0f, frag.extent.ptr[1]*0.5f); 13492 nvgTransformMultiply(m1[], paint.xform[]); 13493 nvgTransformScale(m2[], 1.0f, -1.0f); 13494 nvgTransformMultiply(m2[], m1[]); 13495 nvgTransformTranslate(m1[], 0.0f, -frag.extent.ptr[1]*0.5f); 13496 nvgTransformMultiply(m1[], m2[]); 13497 nvgTransformInverse(invxform[], m1[]); 13498 */ 13499 NVGMatrix m1 = NVGMatrix.Translated(0.0f, frag.extent.ptr[1]*0.5f); 13500 m1.mul(paint.xform); 13501 NVGMatrix m2 = NVGMatrix.Scaled(1.0f, -1.0f); 13502 m2.mul(m1); 13503 m1 = NVGMatrix.Translated(0.0f, -frag.extent.ptr[1]*0.5f); 13504 m1.mul(m2); 13505 invxform = m1.inverted; 13506 } else { 13507 //nvgTransformInverse(invxform[], paint.xform[]); 13508 invxform = paint.xform.inverted; 13509 } 13510 frag.type = NSVG_SHADER_FILLIMG; 13511 13512 if (tex.type == NVGtexture.RGBA) { 13513 frag.texType = (tex.flags&NVGImageFlag.Premultiplied ? 0 : 1); 13514 } else { 13515 frag.texType = 2; 13516 } 13517 //printf("frag.texType = %d\n", frag.texType); 13518 } else { 13519 frag.type = (paint.simpleColor ? NSVG_SHADER_FILLCOLOR : NSVG_SHADER_FILLGRAD); 13520 frag.radius = paint.radius; 13521 frag.feather = paint.feather; 13522 //nvgTransformInverse(invxform[], paint.xform[]); 13523 invxform = paint.xform.inverted; 13524 } 13525 13526 glnvg__xformToMat3x4(frag.paintMat[], invxform.mat[]); 13527 13528 return true; 13529 } 13530 13531 void glnvg__setUniforms (GLNVGcontext* gl, int uniformOffset, int image) nothrow @trusted @nogc { 13532 GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset); 13533 glnvg__setFBOClipTexture(gl, frag); 13534 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.Frag], frag.UNIFORM_ARRAY_SIZE, &(frag.uniformArray.ptr[0].ptr[0])); 13535 glnvg__checkError(gl, "glnvg__setUniforms"); 13536 if (image != 0) { 13537 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13538 glnvg__bindTexture(gl, (tex !is null ? tex.tex : 0)); 13539 glnvg__checkError(gl, "tex paint tex"); 13540 } else { 13541 glnvg__bindTexture(gl, 0); 13542 } 13543 } 13544 13545 void glnvg__finishClip (GLNVGcontext* gl, NVGClipMode clipmode) nothrow @trusted @nogc { 13546 assert(clipmode != NVGClipMode.None); 13547 13548 // fill FBO, clear stencil buffer 13549 //TODO: optimize with bounds? 13550 version(all) { 13551 //glnvg__resetAffine(gl); 13552 //glUseProgram(gl.shaderFillFBO.prog); 13553 glDisable(GL_CULL_FACE); 13554 glDisable(GL_BLEND); 13555 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13556 glEnable(GL_STENCIL_TEST); 13557 if (gl.doClipUnion) { 13558 // for "and" we should clear everything that is NOT stencil-masked 13559 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); 13560 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13561 } else { 13562 glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x00, 0xff); 13563 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13564 } 13565 13566 immutable(NVGVertex[4]) vertices = 13567 [NVGVertex(0, 0, 0, 0), 13568 NVGVertex(0, gl.fboHeight, 0, 0), 13569 NVGVertex(gl.fboWidth, gl.fboHeight, 0, 0), 13570 NVGVertex(gl.fboWidth, 0, 0, 0)]; 13571 13572 glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices); 13573 glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 13574 13575 //glnvg__restoreAffine(gl); 13576 } 13577 13578 glBindFramebuffer(GL_FRAMEBUFFER, gl.mainFBO); 13579 glDisable(GL_COLOR_LOGIC_OP); 13580 //glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // done above 13581 glEnable(GL_BLEND); 13582 glDisable(GL_STENCIL_TEST); 13583 glEnable(GL_CULL_FACE); 13584 glUseProgram(gl.shader.prog); 13585 13586 // set current FBO as used one 13587 assert(gl.msp > 0 && gl.fbo.ptr[gl.msp-1] > 0 && gl.fboTex.ptr[gl.msp-1].ptr[0] > 0); 13588 if (gl.lastClipFBO != gl.msp-1) { 13589 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); } 13590 gl.lastClipFBO = gl.msp-1; 13591 glActiveTexture(GL_TEXTURE1); 13592 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]); 13593 glActiveTexture(GL_TEXTURE0); 13594 } 13595 } 13596 13597 void glnvg__setClipUniforms (GLNVGcontext* gl, int uniformOffset, NVGClipMode clipmode) nothrow @trusted @nogc { 13598 assert(clipmode != NVGClipMode.None); 13599 GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset); 13600 // save uniform offset for `glnvg__finishClip()` 13601 gl.lastClipUniOfs = uniformOffset; 13602 // get FBO index, bind this FBO 13603 immutable int clipTexId = glnvg__generateFBOClipTexture(gl); 13604 assert(clipTexId >= 0); 13605 glUseProgram(gl.shaderFillFBO.prog); 13606 glnvg__checkError(gl, "use"); 13607 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[clipTexId]); 13608 // set logic op for clip 13609 gl.doClipUnion = false; 13610 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.JustCleared) { 13611 // it is cleared to zero, we can just draw a path 13612 glDisable(GL_COLOR_LOGIC_OP); 13613 gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized; 13614 } else { 13615 glEnable(GL_COLOR_LOGIC_OP); 13616 final switch (clipmode) { 13617 case NVGClipMode.None: assert(0, "wtf?!"); 13618 case NVGClipMode.Union: glLogicOp(GL_CLEAR); gl.doClipUnion = true; break; // use `GL_CLEAR` to avoid adding another shader mode 13619 case NVGClipMode.Or: glLogicOp(GL_COPY); break; // GL_OR 13620 case NVGClipMode.Xor: glLogicOp(GL_XOR); break; 13621 case NVGClipMode.Sub: glLogicOp(GL_CLEAR); break; 13622 case NVGClipMode.Replace: glLogicOp(GL_COPY); break; 13623 } 13624 } 13625 // set affine matrix 13626 glUniform4fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TMat], 1, gl.lastAffine.mat.ptr); 13627 glnvg__checkError(gl, "affine 0"); 13628 glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TTr], 1, gl.lastAffine.mat.ptr+4); 13629 glnvg__checkError(gl, "affine 1"); 13630 // setup common OpenGL parameters 13631 glDisable(GL_BLEND); 13632 glDisable(GL_CULL_FACE); 13633 glEnable(GL_STENCIL_TEST); 13634 glnvg__stencilMask(gl, 0xff); 13635 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); 13636 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); 13637 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 13638 } 13639 13640 void glnvg__renderViewport (void* uptr, int width, int height) nothrow @trusted @nogc { 13641 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13642 gl.inFrame = true; 13643 gl.view.ptr[0] = cast(float)width; 13644 gl.view.ptr[1] = cast(float)height; 13645 // kill FBOs if we need to create new ones (flushing will recreate 'em if necessary) 13646 if (width != gl.fboWidth || height != gl.fboHeight) { 13647 glnvg__killFBOs(gl); 13648 gl.fboWidth = width; 13649 gl.fboHeight = height; 13650 } 13651 gl.msp = 1; 13652 gl.maskStack.ptr[0] = GLMaskState.DontMask; 13653 // texture cleanup 13654 import core.atomic : atomicLoad; 13655 if (atomicLoad(gl.mustCleanTextures)) { 13656 try { 13657 import core.thread : Thread; 13658 static if (__VERSION__ < 2076) { 13659 DGNoThrowNoGC(() { 13660 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13661 })(); 13662 } else { 13663 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13664 } 13665 { 13666 GLNVGTextureLocker.lock_nothrow; scope(exit) GLNVGTextureLocker.unlock_nothrow; 13667 gl.mustCleanTextures = false; 13668 foreach (immutable tidx, ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) { 13669 // no need to use atomic ops here, as we're locked 13670 if (tex.rc == 0 && tex.tex != 0 && tex.id == 0) { 13671 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** cleaned up texture with glid=%u\n", tex.tex); }} 13672 import core.stdc.string : memset; 13673 if ((tex.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tex.tex); 13674 memset(&tex, 0, tex.sizeof); 13675 tex.nextfree = gl.freetexid; 13676 gl.freetexid = cast(int)tidx; 13677 } 13678 } 13679 } 13680 } catch (Exception e) {} 13681 } 13682 } 13683 13684 void glnvg__fill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13685 GLNVGpath* paths = &gl.paths[call.pathOffset]; 13686 int npaths = call.pathCount; 13687 13688 if (call.clipmode == NVGClipMode.None) { 13689 // Draw shapes 13690 glEnable(GL_STENCIL_TEST); 13691 glnvg__stencilMask(gl, 0xffU); 13692 glnvg__stencilFunc(gl, GL_ALWAYS, 0, 0xffU); 13693 13694 glnvg__setUniforms(gl, call.uniformOffset, 0); 13695 glnvg__checkError(gl, "fill simple"); 13696 13697 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 13698 if (call.evenOdd) { 13699 //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT); 13700 //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT); 13701 glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); 13702 } else { 13703 glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); 13704 glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); 13705 } 13706 glDisable(GL_CULL_FACE); 13707 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13708 glEnable(GL_CULL_FACE); 13709 13710 // Draw anti-aliased pixels 13711 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13712 glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image); 13713 glnvg__checkError(gl, "fill fill"); 13714 13715 if (gl.flags&NVGContextFlag.Antialias) { 13716 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xffU); 13717 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 13718 // Draw fringes 13719 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13720 } 13721 13722 // Draw fill 13723 glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x0, 0xffU); 13724 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13725 if (call.evenOdd) { 13726 glDisable(GL_CULL_FACE); 13727 glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount); 13728 //foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13729 glEnable(GL_CULL_FACE); 13730 } else { 13731 glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount); 13732 } 13733 13734 glDisable(GL_STENCIL_TEST); 13735 } else { 13736 glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode); // this activates our FBO 13737 glnvg__checkError(gl, "fillclip simple"); 13738 glnvg__stencilFunc(gl, GL_ALWAYS, 0x00, 0xffU); 13739 if (call.evenOdd) { 13740 //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT); 13741 //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT); 13742 glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); 13743 } else { 13744 glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); 13745 glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); 13746 } 13747 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13748 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state 13749 } 13750 } 13751 13752 void glnvg__convexFill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13753 GLNVGpath* paths = &gl.paths[call.pathOffset]; 13754 int npaths = call.pathCount; 13755 13756 if (call.clipmode == NVGClipMode.None) { 13757 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13758 glnvg__checkError(gl, "convex fill"); 13759 if (call.evenOdd) glDisable(GL_CULL_FACE); 13760 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13761 if (gl.flags&NVGContextFlag.Antialias) { 13762 // Draw fringes 13763 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13764 } 13765 if (call.evenOdd) glEnable(GL_CULL_FACE); 13766 } else { 13767 glnvg__setClipUniforms(gl, call.uniformOffset, call.clipmode); // this activates our FBO 13768 glnvg__checkError(gl, "clip convex fill"); 13769 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13770 if (gl.flags&NVGContextFlag.Antialias) { 13771 // Draw fringes 13772 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13773 } 13774 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state 13775 } 13776 } 13777 13778 void glnvg__stroke (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13779 GLNVGpath* paths = &gl.paths[call.pathOffset]; 13780 int npaths = call.pathCount; 13781 13782 if (call.clipmode == NVGClipMode.None) { 13783 if (gl.flags&NVGContextFlag.StencilStrokes) { 13784 glEnable(GL_STENCIL_TEST); 13785 glnvg__stencilMask(gl, 0xff); 13786 13787 // Fill the stroke base without overlap 13788 glnvg__stencilFunc(gl, GL_EQUAL, 0x0, 0xff); 13789 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); 13790 glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image); 13791 glnvg__checkError(gl, "stroke fill 0"); 13792 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13793 13794 // Draw anti-aliased pixels. 13795 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13796 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); 13797 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 13798 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13799 13800 // Clear stencil buffer. 13801 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 13802 glnvg__stencilFunc(gl, GL_ALWAYS, 0x0, 0xff); 13803 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13804 glnvg__checkError(gl, "stroke fill 1"); 13805 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13806 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13807 13808 glDisable(GL_STENCIL_TEST); 13809 13810 //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f); 13811 } else { 13812 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13813 glnvg__checkError(gl, "stroke fill"); 13814 // Draw Strokes 13815 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13816 } 13817 } else { 13818 glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode); 13819 glnvg__checkError(gl, "stroke fill 0"); 13820 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13821 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state 13822 } 13823 } 13824 13825 void glnvg__triangles (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13826 if (call.clipmode == NVGClipMode.None) { 13827 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13828 glnvg__checkError(gl, "triangles fill"); 13829 glDrawArrays(GL_TRIANGLES, call.triangleOffset, call.triangleCount); 13830 } else { 13831 //TODO(?): use texture as mask? 13832 } 13833 } 13834 13835 void glnvg__affine (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13836 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, call.affine.mat.ptr); 13837 glnvg__checkError(gl, "affine"); 13838 glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, call.affine.mat.ptr+4); 13839 glnvg__checkError(gl, "affine"); 13840 //glnvg__setUniforms(gl, call.uniformOffset, call.image); 13841 } 13842 13843 void glnvg__renderCancelInternal (GLNVGcontext* gl, bool clearTextures) nothrow @trusted @nogc { 13844 scope(exit) gl.inFrame = false; 13845 if (clearTextures && gl.inFrame) { 13846 try { 13847 import core.thread : Thread; 13848 static if (__VERSION__ < 2076) { 13849 DGNoThrowNoGC(() { 13850 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13851 })(); 13852 } else { 13853 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13854 } 13855 } catch (Exception e) {} 13856 foreach (ref GLNVGcall c; gl.calls[0..gl.ncalls]) if (c.image > 0) glnvg__deleteTexture(gl, c.image); 13857 } 13858 gl.nverts = 0; 13859 gl.npaths = 0; 13860 gl.ncalls = 0; 13861 gl.nuniforms = 0; 13862 gl.msp = 1; 13863 gl.maskStack.ptr[0] = GLMaskState.DontMask; 13864 } 13865 13866 void glnvg__renderCancel (void* uptr) nothrow @trusted @nogc { 13867 glnvg__renderCancelInternal(cast(GLNVGcontext*)uptr, true); 13868 } 13869 13870 GLenum glnvg_convertBlendFuncFactor (NVGBlendFactor factor) pure nothrow @trusted @nogc { 13871 if (factor == NVGBlendFactor.Zero) return GL_ZERO; 13872 if (factor == NVGBlendFactor.One) return GL_ONE; 13873 if (factor == NVGBlendFactor.SrcColor) return GL_SRC_COLOR; 13874 if (factor == NVGBlendFactor.OneMinusSrcColor) return GL_ONE_MINUS_SRC_COLOR; 13875 if (factor == NVGBlendFactor.DstColor) return GL_DST_COLOR; 13876 if (factor == NVGBlendFactor.OneMinusDstColor) return GL_ONE_MINUS_DST_COLOR; 13877 if (factor == NVGBlendFactor.SrcAlpha) return GL_SRC_ALPHA; 13878 if (factor == NVGBlendFactor.OneMinusSrcAlpha) return GL_ONE_MINUS_SRC_ALPHA; 13879 if (factor == NVGBlendFactor.DstAlpha) return GL_DST_ALPHA; 13880 if (factor == NVGBlendFactor.OneMinusDstAlpha) return GL_ONE_MINUS_DST_ALPHA; 13881 if (factor == NVGBlendFactor.SrcAlphaSaturate) return GL_SRC_ALPHA_SATURATE; 13882 return GL_INVALID_ENUM; 13883 } 13884 13885 GLNVGblend glnvg__buildBlendFunc (NVGCompositeOperationState op) pure nothrow @trusted @nogc { 13886 GLNVGblend res; 13887 res.simple = op.simple; 13888 res.srcRGB = glnvg_convertBlendFuncFactor(op.srcRGB); 13889 res.dstRGB = glnvg_convertBlendFuncFactor(op.dstRGB); 13890 res.srcAlpha = glnvg_convertBlendFuncFactor(op.srcAlpha); 13891 res.dstAlpha = glnvg_convertBlendFuncFactor(op.dstAlpha); 13892 if (res.simple) { 13893 if (res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) { 13894 res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM; 13895 } 13896 } else { 13897 if (res.srcRGB == GL_INVALID_ENUM || res.dstRGB == GL_INVALID_ENUM || res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) { 13898 res.simple = true; 13899 res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM; 13900 } 13901 } 13902 return res; 13903 } 13904 13905 void glnvg__blendCompositeOperation() (GLNVGcontext* gl, const scope auto ref GLNVGblend op) nothrow @trusted @nogc { 13906 //glBlendFuncSeparate(glnvg_convertBlendFuncFactor(op.srcRGB), glnvg_convertBlendFuncFactor(op.dstRGB), glnvg_convertBlendFuncFactor(op.srcAlpha), glnvg_convertBlendFuncFactor(op.dstAlpha)); 13907 static if (NANOVG_GL_USE_STATE_FILTER) { 13908 if (gl.blendFunc.simple == op.simple) { 13909 if (op.simple) { 13910 if (gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return; 13911 } else { 13912 if (gl.blendFunc.srcRGB == op.srcRGB && gl.blendFunc.dstRGB == op.dstRGB && gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return; 13913 } 13914 } 13915 gl.blendFunc = op; 13916 } 13917 if (op.simple) { 13918 if (op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) { 13919 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 13920 } else { 13921 glBlendFunc(op.srcAlpha, op.dstAlpha); 13922 } 13923 } else { 13924 if (op.srcRGB == GL_INVALID_ENUM || op.dstRGB == GL_INVALID_ENUM || op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) { 13925 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 13926 } else { 13927 glBlendFuncSeparate(op.srcRGB, op.dstRGB, op.srcAlpha, op.dstAlpha); 13928 } 13929 } 13930 } 13931 13932 void glnvg__renderSetAffine (void* uptr, const scope ref NVGMatrix mat) nothrow @trusted @nogc { 13933 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13934 GLNVGcall* call; 13935 // if last operation was GLNVG_AFFINE, simply replace the matrix 13936 if (gl.ncalls > 0 && gl.calls[gl.ncalls-1].type == GLNVG_AFFINE) { 13937 call = &gl.calls[gl.ncalls-1]; 13938 } else { 13939 call = glnvg__allocCall(gl); 13940 if (call is null) return; 13941 call.type = GLNVG_AFFINE; 13942 } 13943 call.affine.mat.ptr[0..6] = mat.mat.ptr[0..6]; 13944 } 13945 13946 version(nanovega_debug_clipping) public __gshared bool nanovegaClipDebugDump = false; 13947 13948 void glnvg__renderFlush (void* uptr) nothrow @trusted @nogc { 13949 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13950 if (!gl.inFrame) assert(0, "NanoVega: internal driver error"); 13951 try { 13952 import core.thread : Thread; 13953 static if (__VERSION__ < 2076) { 13954 DGNoThrowNoGC(() { 13955 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13956 })(); 13957 } else { 13958 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13959 } 13960 } catch (Exception e) {} 13961 scope(exit) gl.inFrame = false; 13962 13963 glnvg__resetError!true(gl); 13964 { 13965 int vv = 0; 13966 glGetIntegerv(GL_FRAMEBUFFER_BINDING, &vv); 13967 if (glGetError() || vv < 0) vv = 0; 13968 gl.mainFBO = cast(uint)vv; 13969 } 13970 13971 enum ShaderType { None, Fill, Clip } 13972 auto lastShader = ShaderType.None; 13973 if (gl.ncalls > 0) { 13974 gl.msp = 1; 13975 gl.maskStack.ptr[0] = GLMaskState.DontMask; 13976 13977 // Setup require GL state. 13978 glUseProgram(gl.shader.prog); 13979 13980 glActiveTexture(GL_TEXTURE1); 13981 glBindTexture(GL_TEXTURE_2D, 0); 13982 glActiveTexture(GL_TEXTURE0); 13983 glnvg__resetFBOClipTextureCache(gl); 13984 13985 //glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 13986 static if (NANOVG_GL_USE_STATE_FILTER) { 13987 gl.blendFunc.simple = true; 13988 gl.blendFunc.srcRGB = gl.blendFunc.dstRGB = gl.blendFunc.srcAlpha = gl.blendFunc.dstAlpha = GL_INVALID_ENUM; 13989 } 13990 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // just in case 13991 glEnable(GL_CULL_FACE); 13992 glCullFace(GL_BACK); 13993 glFrontFace(GL_CCW); 13994 glEnable(GL_BLEND); 13995 glDisable(GL_DEPTH_TEST); 13996 glDisable(GL_SCISSOR_TEST); 13997 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13998 glStencilMask(0xffffffff); 13999 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 14000 glStencilFunc(GL_ALWAYS, 0, 0xffffffff); 14001 glActiveTexture(GL_TEXTURE0); 14002 glBindTexture(GL_TEXTURE_2D, 0); 14003 static if (NANOVG_GL_USE_STATE_FILTER) { 14004 gl.boundTexture = 0; 14005 gl.stencilMask = 0xffffffff; 14006 gl.stencilFunc = GL_ALWAYS; 14007 gl.stencilFuncRef = 0; 14008 gl.stencilFuncMask = 0xffffffff; 14009 } 14010 glnvg__checkError(gl, "OpenGL setup"); 14011 14012 glBindVertexArray(gl.vaoBuf); 14013 // Upload vertex data 14014 glBindBuffer(GL_ARRAY_BUFFER, gl.vertBuf); 14015 // ensure that there's space for at least 4 vertices in case we need to draw a quad (e. g. framebuffer copy) 14016 glBufferData(GL_ARRAY_BUFFER, (gl.nverts < 4 ? 4 : gl.nverts) * NVGVertex.sizeof, gl.verts, GL_STREAM_DRAW); 14017 glEnableVertexAttribArray(0); 14018 glEnableVertexAttribArray(1); 14019 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)cast(usize)0); 14020 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)(0+2*float.sizeof)); 14021 glnvg__checkError(gl, "vertex data uploading"); 14022 14023 // Set view and texture just once per frame. 14024 glUniform1i(gl.shader.loc[GLNVGuniformLoc.Tex], 0); 14025 if (gl.shader.loc[GLNVGuniformLoc.ClipTex] != -1) { 14026 //{ import core.stdc.stdio; printf("%d\n", gl.shader.loc[GLNVGuniformLoc.ClipTex]); } 14027 glUniform1i(gl.shader.loc[GLNVGuniformLoc.ClipTex], 1); 14028 } 14029 if (gl.shader.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shader.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr); 14030 glnvg__checkError(gl, "render shader setup"); 14031 14032 // Reset affine transformations. 14033 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, NVGMatrix.IdentityMat.ptr); 14034 glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, NVGMatrix.IdentityMat.ptr+4); 14035 glnvg__checkError(gl, "affine setup"); 14036 14037 // set clip shaders params 14038 // fill 14039 glUseProgram(gl.shaderFillFBO.prog); 14040 glnvg__checkError(gl, "clip shaders setup (fill 0)"); 14041 if (gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr); 14042 glnvg__checkError(gl, "clip shaders setup (fill 1)"); 14043 // copy 14044 glUseProgram(gl.shaderCopyFBO.prog); 14045 glnvg__checkError(gl, "clip shaders setup (copy 0)"); 14046 if (gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr); 14047 glnvg__checkError(gl, "clip shaders setup (copy 1)"); 14048 //glUniform1i(gl.shaderFillFBO.loc[GLNVGuniformLoc.Tex], 0); 14049 glUniform1i(gl.shaderCopyFBO.loc[GLNVGuniformLoc.Tex], 0); 14050 glnvg__checkError(gl, "clip shaders setup (copy 2)"); 14051 // restore render shader 14052 glUseProgram(gl.shader.prog); 14053 14054 //{ 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]); } 14055 14056 gl.lastAffine.identity; 14057 14058 foreach (int i; 0..gl.ncalls) { 14059 GLNVGcall* call = &gl.calls[i]; 14060 switch (call.type) { 14061 case GLNVG_FILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__fill(gl, call); break; 14062 case GLNVG_CONVEXFILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__convexFill(gl, call); break; 14063 case GLNVG_STROKE: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__stroke(gl, call); break; 14064 case GLNVG_TRIANGLES: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__triangles(gl, call); break; 14065 case GLNVG_AFFINE: gl.lastAffine = call.affine; glnvg__affine(gl, call); break; 14066 // clip region management 14067 case GLNVG_PUSHCLIP: 14068 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]); } 14069 if (gl.msp >= gl.maskStack.length) assert(0, "NanoVega: mask stack overflow in OpenGL backend"); 14070 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.DontMask) { 14071 gl.maskStack.ptr[gl.msp++] = GLMaskState.DontMask; 14072 } else { 14073 gl.maskStack.ptr[gl.msp++] = GLMaskState.Uninitialized; 14074 } 14075 // no need to reset FBO cache here, as nothing was changed 14076 break; 14077 case GLNVG_POPCLIP: 14078 if (gl.msp <= 1) assert(0, "NanoVega: mask stack underflow in OpenGL backend"); 14079 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]); } 14080 --gl.msp; 14081 assert(gl.msp > 0); 14082 //{ import core.stdc.stdio; printf("popped; new msp is %d; state is %d\n", gl.msp, gl.maskStack.ptr[gl.msp]); } 14083 // check popped item 14084 final switch (gl.maskStack.ptr[gl.msp]) { 14085 case GLMaskState.DontMask: 14086 // if last FBO was "don't mask", reset cache if current is not "don't mask" 14087 if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) { 14088 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); } 14089 glnvg__resetFBOClipTextureCache(gl); 14090 } 14091 break; 14092 case GLMaskState.Uninitialized: 14093 // if last FBO texture was uninitialized, it means that nothing was changed, 14094 // so we can keep using cached FBO 14095 break; 14096 case GLMaskState.Initialized: 14097 // if last FBO was initialized, it means that something was definitely changed 14098 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); } 14099 glnvg__resetFBOClipTextureCache(gl); 14100 break; 14101 case GLMaskState.JustCleared: assert(0, "NanoVega: internal FBO stack error"); 14102 } 14103 break; 14104 case GLNVG_RESETCLIP: 14105 // mark current mask as "don't mask" 14106 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]); } 14107 if (gl.msp > 0) { 14108 if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) { 14109 gl.maskStack.ptr[gl.msp-1] = GLMaskState.DontMask; 14110 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); } 14111 glnvg__resetFBOClipTextureCache(gl); 14112 } 14113 } 14114 break; 14115 case GLNVG_CLIP_DDUMP_ON: 14116 version(nanovega_debug_clipping) nanovegaClipDebugDump = true; 14117 break; 14118 case GLNVG_CLIP_DDUMP_OFF: 14119 version(nanovega_debug_clipping) nanovegaClipDebugDump = false; 14120 break; 14121 case GLNVG_NONE: break; 14122 default: 14123 { 14124 import core.stdc.stdio; stderr.fprintf("NanoVega FATAL: invalid command in OpenGL backend: %d\n", call.type); 14125 } 14126 assert(0, "NanoVega: invalid command in OpenGL backend (fatal internal error)"); 14127 } 14128 // and free texture, why not 14129 glnvg__deleteTexture(gl, call.image); 14130 } 14131 14132 glDisableVertexAttribArray(0); 14133 glDisableVertexAttribArray(1); 14134 glDisable(GL_CULL_FACE); 14135 glBindBuffer(GL_ARRAY_BUFFER, 0); 14136 glBindVertexArray(0); 14137 glUseProgram(0); 14138 glnvg__bindTexture(gl, 0); 14139 } 14140 14141 // this will do all necessary cleanup 14142 glnvg__renderCancelInternal(gl, false); // no need to clear textures 14143 } 14144 14145 int glnvg__maxVertCount (const(NVGpath)* paths, int npaths) nothrow @trusted @nogc { 14146 int count = 0; 14147 foreach (int i; 0..npaths) { 14148 count += paths[i].nfill; 14149 count += paths[i].nstroke; 14150 } 14151 return count; 14152 } 14153 14154 GLNVGcall* glnvg__allocCall (GLNVGcontext* gl) nothrow @trusted @nogc { 14155 GLNVGcall* ret = null; 14156 if (gl.ncalls+1 > gl.ccalls) { 14157 GLNVGcall* calls; 14158 int ccalls = glnvg__maxi(gl.ncalls+1, 128)+gl.ccalls/2; // 1.5x Overallocate 14159 calls = cast(GLNVGcall*)realloc(gl.calls, GLNVGcall.sizeof*ccalls); 14160 if (calls is null) return null; 14161 gl.calls = calls; 14162 gl.ccalls = ccalls; 14163 } 14164 ret = &gl.calls[gl.ncalls++]; 14165 memset(ret, 0, GLNVGcall.sizeof); 14166 return ret; 14167 } 14168 14169 int glnvg__allocPaths (GLNVGcontext* gl, int n) nothrow @trusted @nogc { 14170 int ret = 0; 14171 if (gl.npaths+n > gl.cpaths) { 14172 GLNVGpath* paths; 14173 int cpaths = glnvg__maxi(gl.npaths+n, 128)+gl.cpaths/2; // 1.5x Overallocate 14174 paths = cast(GLNVGpath*)realloc(gl.paths, GLNVGpath.sizeof*cpaths); 14175 if (paths is null) return -1; 14176 gl.paths = paths; 14177 gl.cpaths = cpaths; 14178 } 14179 ret = gl.npaths; 14180 gl.npaths += n; 14181 return ret; 14182 } 14183 14184 int glnvg__allocVerts (GLNVGcontext* gl, int n) nothrow @trusted @nogc { 14185 int ret = 0; 14186 if (gl.nverts+n > gl.cverts) { 14187 NVGVertex* verts; 14188 int cverts = glnvg__maxi(gl.nverts+n, 4096)+gl.cverts/2; // 1.5x Overallocate 14189 verts = cast(NVGVertex*)realloc(gl.verts, NVGVertex.sizeof*cverts); 14190 if (verts is null) return -1; 14191 gl.verts = verts; 14192 gl.cverts = cverts; 14193 } 14194 ret = gl.nverts; 14195 gl.nverts += n; 14196 return ret; 14197 } 14198 14199 int glnvg__allocFragUniforms (GLNVGcontext* gl, int n) nothrow @trusted @nogc { 14200 int ret = 0, structSize = gl.fragSize; 14201 if (gl.nuniforms+n > gl.cuniforms) { 14202 ubyte* uniforms; 14203 int cuniforms = glnvg__maxi(gl.nuniforms+n, 128)+gl.cuniforms/2; // 1.5x Overallocate 14204 uniforms = cast(ubyte*)realloc(gl.uniforms, structSize*cuniforms); 14205 if (uniforms is null) return -1; 14206 gl.uniforms = uniforms; 14207 gl.cuniforms = cuniforms; 14208 } 14209 ret = gl.nuniforms*structSize; 14210 gl.nuniforms += n; 14211 return ret; 14212 } 14213 14214 GLNVGfragUniforms* nvg__fragUniformPtr (GLNVGcontext* gl, int i) nothrow @trusted @nogc { 14215 return cast(GLNVGfragUniforms*)&gl.uniforms[i]; 14216 } 14217 14218 void glnvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc { 14219 vtx.x = x; 14220 vtx.y = y; 14221 vtx.u = u; 14222 vtx.v = v; 14223 } 14224 14225 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 { 14226 if (npaths < 1) return; 14227 14228 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14229 GLNVGcall* call = glnvg__allocCall(gl); 14230 NVGVertex* quad; 14231 GLNVGfragUniforms* frag; 14232 int maxverts, offset; 14233 14234 if (call is null) return; 14235 14236 call.type = GLNVG_FILL; 14237 call.evenOdd = evenOdd; 14238 call.clipmode = clipmode; 14239 //if (clipmode != NVGClipMode.None) { import core.stdc.stdio; printf("CLIP!\n"); } 14240 call.blendFunc = glnvg__buildBlendFunc(compositeOperation); 14241 call.triangleCount = 4; 14242 call.pathOffset = glnvg__allocPaths(gl, npaths); 14243 if (call.pathOffset == -1) goto error; 14244 call.pathCount = npaths; 14245 call.image = paint.image.id; 14246 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); 14247 14248 if (npaths == 1 && paths[0].convex) { 14249 call.type = GLNVG_CONVEXFILL; 14250 call.triangleCount = 0; // Bounding box fill quad not needed for convex fill 14251 } 14252 14253 // Allocate vertices for all the paths. 14254 maxverts = glnvg__maxVertCount(paths, npaths)+call.triangleCount; 14255 offset = glnvg__allocVerts(gl, maxverts); 14256 if (offset == -1) goto error; 14257 14258 foreach (int i; 0..npaths) { 14259 GLNVGpath* copy = &gl.paths[call.pathOffset+i]; 14260 const(NVGpath)* path = &paths[i]; 14261 memset(copy, 0, GLNVGpath.sizeof); 14262 if (path.nfill > 0) { 14263 copy.fillOffset = offset; 14264 copy.fillCount = path.nfill; 14265 memcpy(&gl.verts[offset], path.fill, NVGVertex.sizeof*path.nfill); 14266 offset += path.nfill; 14267 } 14268 if (path.nstroke > 0) { 14269 copy.strokeOffset = offset; 14270 copy.strokeCount = path.nstroke; 14271 memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke); 14272 offset += path.nstroke; 14273 } 14274 } 14275 14276 // Setup uniforms for draw calls 14277 if (call.type == GLNVG_FILL) { 14278 import core.stdc.string : memcpy; 14279 // Quad 14280 call.triangleOffset = offset; 14281 quad = &gl.verts[call.triangleOffset]; 14282 glnvg__vset(&quad[0], bounds[2], bounds[3], 0.5f, 1.0f); 14283 glnvg__vset(&quad[1], bounds[2], bounds[1], 0.5f, 1.0f); 14284 glnvg__vset(&quad[2], bounds[0], bounds[3], 0.5f, 1.0f); 14285 glnvg__vset(&quad[3], bounds[0], bounds[1], 0.5f, 1.0f); 14286 // Get uniform 14287 call.uniformOffset = glnvg__allocFragUniforms(gl, 2); 14288 if (call.uniformOffset == -1) goto error; 14289 // Simple shader for stencil 14290 frag = nvg__fragUniformPtr(gl, call.uniformOffset); 14291 memset(frag, 0, (*frag).sizeof); 14292 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f); 14293 memcpy(nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), frag, (*frag).sizeof); 14294 frag.strokeThr = -1.0f; 14295 frag.type = NSVG_SHADER_SIMPLE; 14296 // Fill shader 14297 //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, fringe, fringe, -1.0f); 14298 } else { 14299 call.uniformOffset = glnvg__allocFragUniforms(gl, 1); 14300 if (call.uniformOffset == -1) goto error; 14301 // Fill shader 14302 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f); 14303 } 14304 14305 return; 14306 14307 error: 14308 // We get here if call alloc was ok, but something else is not. 14309 // Roll back the last call to prevent drawing it. 14310 if (gl.ncalls > 0) --gl.ncalls; 14311 } 14312 14313 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 { 14314 if (npaths < 1) return; 14315 14316 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14317 GLNVGcall* call = glnvg__allocCall(gl); 14318 int maxverts, offset; 14319 14320 if (call is null) return; 14321 14322 call.type = GLNVG_STROKE; 14323 call.clipmode = clipmode; 14324 call.blendFunc = glnvg__buildBlendFunc(compositeOperation); 14325 call.pathOffset = glnvg__allocPaths(gl, npaths); 14326 if (call.pathOffset == -1) goto error; 14327 call.pathCount = npaths; 14328 call.image = paint.image.id; 14329 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); 14330 14331 // Allocate vertices for all the paths. 14332 maxverts = glnvg__maxVertCount(paths, npaths); 14333 offset = glnvg__allocVerts(gl, maxverts); 14334 if (offset == -1) goto error; 14335 14336 foreach (int i; 0..npaths) { 14337 GLNVGpath* copy = &gl.paths[call.pathOffset+i]; 14338 const(NVGpath)* path = &paths[i]; 14339 memset(copy, 0, GLNVGpath.sizeof); 14340 if (path.nstroke) { 14341 copy.strokeOffset = offset; 14342 copy.strokeCount = path.nstroke; 14343 memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke); 14344 offset += path.nstroke; 14345 } 14346 } 14347 14348 if (gl.flags&NVGContextFlag.StencilStrokes) { 14349 // Fill shader 14350 call.uniformOffset = glnvg__allocFragUniforms(gl, 2); 14351 if (call.uniformOffset == -1) goto error; 14352 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f); 14353 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f); 14354 } else { 14355 // Fill shader 14356 call.uniformOffset = glnvg__allocFragUniforms(gl, 1); 14357 if (call.uniformOffset == -1) goto error; 14358 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f); 14359 } 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__renderTriangles (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc { 14370 if (nverts < 1) return; 14371 14372 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14373 GLNVGcall* call = glnvg__allocCall(gl); 14374 GLNVGfragUniforms* frag; 14375 14376 if (call is null) return; 14377 14378 call.type = GLNVG_TRIANGLES; 14379 call.clipmode = clipmode; 14380 call.blendFunc = glnvg__buildBlendFunc(compositeOperation); 14381 call.image = paint.image.id; 14382 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); 14383 14384 // Allocate vertices for all the paths. 14385 call.triangleOffset = glnvg__allocVerts(gl, nverts); 14386 if (call.triangleOffset == -1) goto error; 14387 call.triangleCount = nverts; 14388 14389 memcpy(&gl.verts[call.triangleOffset], verts, NVGVertex.sizeof*nverts); 14390 14391 // Fill shader 14392 call.uniformOffset = glnvg__allocFragUniforms(gl, 1); 14393 if (call.uniformOffset == -1) goto error; 14394 frag = nvg__fragUniformPtr(gl, call.uniformOffset); 14395 glnvg__convertPaint(gl, frag, paint, scissor, 1.0f, 1.0f, -1.0f); 14396 frag.type = NSVG_SHADER_IMG; 14397 14398 return; 14399 14400 error: 14401 // We get here if call alloc was ok, but something else is not. 14402 // Roll back the last call to prevent drawing it. 14403 if (gl.ncalls > 0) --gl.ncalls; 14404 } 14405 14406 void glnvg__renderDelete (void* uptr) nothrow @trusted @nogc { 14407 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14408 if (gl is null) return; 14409 14410 glnvg__killFBOs(gl); 14411 glnvg__deleteShader(&gl.shader); 14412 glnvg__deleteShader(&gl.shaderFillFBO); 14413 glnvg__deleteShader(&gl.shaderCopyFBO); 14414 14415 if (gl.vaoBuf != 0) glDeleteVertexArrays(1, &gl.vaoBuf); 14416 if (gl.vertBuf != 0) glDeleteBuffers(1, &gl.vertBuf); 14417 14418 foreach (ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) { 14419 if (tex.id != 0 && (tex.flags&NVGImageFlag.NoDelete) == 0) { 14420 assert(tex.tex != 0); 14421 glDeleteTextures(1, &tex.tex); 14422 } 14423 } 14424 free(gl.textures); 14425 14426 free(gl.paths); 14427 free(gl.verts); 14428 free(gl.uniforms); 14429 free(gl.calls); 14430 14431 free(gl); 14432 } 14433 14434 14435 /** Creates NanoVega contexts for OpenGL2+. 14436 * 14437 * Specify creation flags as additional arguments, like this: 14438 * `nvgCreateContext(NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes);` 14439 * 14440 * If you won't specify any flags, defaults will be used: 14441 * `[NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes]`. 14442 * 14443 * Group: context_management 14444 */ 14445 public NVGContext nvgCreateContext (const(NVGContextFlag)[] flagList...) nothrow @trusted @nogc { 14446 version(aliced) { 14447 enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes|NVGContextFlag.FontNoAA; 14448 } else { 14449 enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes; 14450 } 14451 uint flags = 0; 14452 if (flagList.length != 0) { 14453 foreach (immutable flg; flagList) flags |= (flg != NVGContextFlag.Default ? flg : DefaultFlags); 14454 } else { 14455 flags = DefaultFlags; 14456 } 14457 NVGparams params = void; 14458 NVGContext ctx = null; 14459 version(nanovg_builtin_opengl_bindings) nanovgInitOpenGL(); // why not? 14460 version(nanovg_bindbc_opengl_bindings) nanovgInitOpenGL(); 14461 GLNVGcontext* gl = cast(GLNVGcontext*)malloc(GLNVGcontext.sizeof); 14462 if (gl is null) goto error; 14463 memset(gl, 0, GLNVGcontext.sizeof); 14464 14465 memset(¶ms, 0, params.sizeof); 14466 params.renderCreate = &glnvg__renderCreate; 14467 params.renderCreateTexture = &glnvg__renderCreateTexture; 14468 params.renderTextureIncRef = &glnvg__renderTextureIncRef; 14469 params.renderDeleteTexture = &glnvg__renderDeleteTexture; 14470 params.renderUpdateTexture = &glnvg__renderUpdateTexture; 14471 params.renderGetTextureSize = &glnvg__renderGetTextureSize; 14472 params.renderViewport = &glnvg__renderViewport; 14473 params.renderCancel = &glnvg__renderCancel; 14474 params.renderFlush = &glnvg__renderFlush; 14475 params.renderPushClip = &glnvg__renderPushClip; 14476 params.renderPopClip = &glnvg__renderPopClip; 14477 params.renderResetClip = &glnvg__renderResetClip; 14478 params.renderFill = &glnvg__renderFill; 14479 params.renderStroke = &glnvg__renderStroke; 14480 params.renderTriangles = &glnvg__renderTriangles; 14481 params.renderSetAffine = &glnvg__renderSetAffine; 14482 params.renderDelete = &glnvg__renderDelete; 14483 params.userPtr = gl; 14484 params.edgeAntiAlias = (flags&NVGContextFlag.Antialias ? true : false); 14485 if (flags&(NVGContextFlag.FontAA|NVGContextFlag.FontNoAA)) { 14486 params.fontAA = (flags&NVGContextFlag.FontNoAA ? NVG_INVERT_FONT_AA : !NVG_INVERT_FONT_AA); 14487 } else { 14488 params.fontAA = NVG_INVERT_FONT_AA; 14489 } 14490 14491 gl.flags = flags; 14492 gl.freetexid = -1; 14493 14494 ctx = createInternal(¶ms); 14495 if (ctx is null) goto error; 14496 14497 static if (__VERSION__ < 2076) { 14498 DGNoThrowNoGC(() { import core.thread; gl.mainTID = Thread.getThis.id; })(); 14499 } else { 14500 try { import core.thread; gl.mainTID = Thread.getThis.id; } catch (Exception e) {} 14501 } 14502 14503 return ctx; 14504 14505 error: 14506 // 'gl' is freed by nvgDeleteInternal. 14507 if (ctx !is null) ctx.deleteInternal(); 14508 return null; 14509 } 14510 14511 /// Using OpenGL texture id creates GLNVGtexture and return its id. 14512 /// Group: images 14513 public int glCreateImageFromHandleGL2 (NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc { 14514 GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr; 14515 GLNVGtexture* tex = glnvg__allocTexture(gl); 14516 14517 if (tex is null) return 0; 14518 14519 tex.type = NVGtexture.RGBA; 14520 tex.tex = textureId; 14521 tex.flags = imageFlags; 14522 tex.width = w; 14523 tex.height = h; 14524 14525 return tex.id; 14526 } 14527 14528 /// Create NVGImage from OpenGL texture id. 14529 /// Group: images 14530 public NVGImage glCreateImageFromOpenGLTexture(NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc { 14531 auto id = glCreateImageFromHandleGL2(ctx, textureId, w, h, imageFlags); 14532 14533 NVGImage res; 14534 if (id > 0) { 14535 res.id = id; 14536 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); } 14537 res.ctx = ctx; 14538 ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount 14539 } 14540 return res; 14541 } 14542 14543 /// Returns OpenGL texture id for NanoVega image. 14544 /// Group: images 14545 public GLuint glImageHandleGL2 (NVGContext ctx, int image) nothrow @trusted @nogc { 14546 GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr; 14547 GLNVGtexture* tex = glnvg__findTexture(gl, image); 14548 return tex.tex; 14549 } 14550 14551 14552 // ////////////////////////////////////////////////////////////////////////// // 14553 private: 14554 14555 static if (NanoVegaHasFontConfig) { 14556 version(nanovg_builtin_fontconfig_bindings) { 14557 pragma(lib, "fontconfig"); 14558 14559 private extern(C) nothrow @trusted @nogc { 14560 enum FC_FILE = "file"; /* String */ 14561 alias FcBool = int; 14562 alias FcChar8 = char; 14563 struct FcConfig; 14564 struct FcPattern; 14565 alias FcMatchKind = int; 14566 enum : FcMatchKind { 14567 FcMatchPattern, 14568 FcMatchFont, 14569 FcMatchScan 14570 } 14571 alias FcResult = int; 14572 enum : FcResult { 14573 FcResultMatch, 14574 FcResultNoMatch, 14575 FcResultTypeMismatch, 14576 FcResultNoId, 14577 FcResultOutOfMemory 14578 } 14579 FcBool FcInit (); 14580 FcBool FcConfigSubstituteWithPat (FcConfig* config, FcPattern* p, FcPattern* p_pat, FcMatchKind kind); 14581 void FcDefaultSubstitute (FcPattern* pattern); 14582 FcBool FcConfigSubstitute (FcConfig* config, FcPattern* p, FcMatchKind kind); 14583 FcPattern* FcFontMatch (FcConfig* config, FcPattern* p, FcResult* result); 14584 FcPattern* FcNameParse (const(FcChar8)* name); 14585 void FcPatternDestroy (FcPattern* p); 14586 FcResult FcPatternGetString (const(FcPattern)* p, const(char)* object, int n, FcChar8** s); 14587 } 14588 } 14589 14590 __gshared bool fontconfigAvailable = false; 14591 // initialize fontconfig 14592 shared static this () { 14593 if (FcInit()) { 14594 fontconfigAvailable = true; 14595 } else { 14596 import core.stdc.stdio : stderr, fprintf; 14597 stderr.fprintf("***NanoVega WARNING: cannot init fontconfig!\n"); 14598 } 14599 } 14600 } 14601 14602 14603 // ////////////////////////////////////////////////////////////////////////// // 14604 public enum BaphometDims = 512.0f; // baphomet icon is 512x512 ([0..511]) 14605 14606 private static immutable ubyte[7641] baphometPath = [ 14607 0x01,0x04,0x06,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x08,0xa0,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43, 14608 0x00,0x80,0xff,0x43,0xa2,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x08,0x00,0x80,0xff, 14609 0x43,0x7a,0x89,0xe5,0x42,0xa0,0x1d,0xc6,0x43,0x00,0x00,0x00,0x00,0x30,0x89,0x7f,0x43,0x00,0x00,0x00, 14610 0x00,0x08,0x7a,0x89,0xe5,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7a,0x89,0xe5,0x42,0x00,0x00, 14611 0x00,0x00,0x30,0x89,0x7f,0x43,0x08,0x00,0x00,0x00,0x00,0xa2,0x1d,0xc6,0x43,0x7a,0x89,0xe5,0x42,0x00, 14612 0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x09,0x06,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd, 14613 0x43,0x08,0x16,0x68,0xb3,0x43,0x72,0x87,0xdd,0x43,0x71,0x87,0xdd,0x43,0x17,0x68,0xb3,0x43,0x71,0x87, 14614 0xdd,0x43,0x30,0x89,0x7f,0x43,0x08,0x71,0x87,0xdd,0x43,0xd2,0x2f,0x18,0x43,0x16,0x68,0xb3,0x43,0x35, 14615 0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x35,0xe2,0x87,0x42,0x08,0xd1,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42, 14616 0x35,0xe2,0x87,0x42,0xd2,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x08,0x35,0xe2,0x87, 14617 0x42,0x17,0x68,0xb3,0x43,0xd1,0x2f,0x18,0x43,0x72,0x87,0xdd,0x43,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd, 14618 0x43,0x09,0x06,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x07,0xa4,0x3f,0x7f,0x43,0x0b,0x86,0xdc,0x43, 14619 0x07,0x6c,0xb9,0xb2,0x43,0xe8,0xd1,0xca,0x42,0x07,0x6e,0x4d,0xa0,0x42,0xa9,0x10,0x9c,0x43,0x07,0xb7, 14620 0x40,0xd7,0x43,0xa9,0x10,0x9c,0x43,0x07,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x09,0x06,0x98,0x42, 14621 0x74,0x43,0xb1,0x8d,0x68,0x43,0x08,0xd7,0x24,0x79,0x43,0xba,0x83,0x6e,0x43,0xa9,0x16,0x7c,0x43,0x56, 14622 0xa1,0x76,0x43,0x74,0x2a,0x7d,0x43,0x44,0x73,0x80,0x43,0x08,0x55,0xd1,0x7e,0x43,0xe3,0xea,0x76,0x43, 14623 0xbc,0x18,0x81,0x43,0x7f,0xa8,0x6e,0x43,0x8f,0x0a,0x84,0x43,0x02,0xfc,0x68,0x43,0x09,0x06,0x92,0x29, 14624 0x8d,0x43,0x73,0xc3,0x67,0x43,0x08,0xa4,0xd9,0x8e,0x43,0xf2,0xa6,0x7a,0x43,0x8f,0x22,0x88,0x43,0x75, 14625 0x2a,0x7d,0x43,0x42,0x7f,0x82,0x43,0x08,0xc8,0x88,0x43,0x09,0x06,0xc1,0x79,0x74,0x43,0x50,0x64,0x89, 14626 0x43,0x08,0x68,0x2d,0x72,0x43,0xee,0x21,0x81,0x43,0xcd,0x97,0x55,0x43,0xe6,0xf1,0x7b,0x43,0x91,0xec, 14627 0x5d,0x43,0xa8,0xc7,0x6a,0x43,0x09,0x06,0xfa,0xa5,0x52,0x43,0x60,0x97,0x7c,0x43,0x08,0x19,0xff,0x50, 14628 0x43,0xe9,0x6e,0x8a,0x43,0xb0,0xbd,0x70,0x43,0x4c,0x51,0x82,0x43,0x04,0xeb,0x69,0x43,0x66,0x0f,0x8e, 14629 0x43,0x09,0x06,0x17,0xbf,0x71,0x43,0x2c,0x58,0x94,0x43,0x08,0x1c,0x96,0x6e,0x43,0x61,0x68,0x99,0x43, 14630 0x2d,0x3a,0x6e,0x43,0xc8,0x81,0x9e,0x43,0xb7,0x9b,0x72,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0x30,0xdb, 14631 0x82,0x43,0xdb,0xe9,0x93,0x43,0x08,0x11,0x82,0x84,0x43,0x61,0x68,0x99,0x43,0xe8,0x4a,0x84,0x43,0x8e, 14632 0xa6,0x9e,0x43,0x42,0x7f,0x82,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0xc4,0x02,0x85,0x43,0xd1,0x0b,0x92, 14633 0x43,0x08,0xd6,0xb2,0x86,0x43,0x34,0x1e,0x92,0x43,0x4f,0x58,0x87,0x43,0xa4,0xf1,0x92,0x43,0x03,0xd9, 14634 0x87,0x43,0x7b,0xc6,0x94,0x43,0x09,0x06,0x87,0x3e,0x64,0x43,0x31,0x3b,0x93,0x43,0x08,0x3b,0xbf,0x64, 14635 0x43,0x6f,0xf9,0x91,0x43,0x96,0x0b,0x67,0x43,0xc5,0x4a,0x91,0x43,0xcf,0xfe,0x6a,0x43,0x31,0x2f,0x91, 14636 0x43,0x09,0x06,0x16,0x74,0xb5,0x43,0x08,0xec,0x8e,0x43,0x08,0x1b,0x4b,0xb2,0x43,0xee,0x5d,0x8b,0x43, 14637 0x48,0x4d,0xad,0x43,0x12,0xa6,0x8a,0x43,0xf3,0xd7,0xa7,0x43,0x74,0xb8,0x8a,0x43,0x08,0x8c,0xb2,0xa0, 14638 0x43,0xcd,0xf8,0x8a,0x43,0x68,0x46,0x9b,0x43,0x79,0x8f,0x87,0x43,0x49,0xc9,0x96,0x43,0xe9,0x3e,0x82, 14639 0x43,0x08,0x60,0x5c,0x97,0x43,0xa1,0xde,0x8b,0x43,0x4e,0xa0,0x93,0x43,0x31,0x3b,0x93,0x43,0x9f,0xea, 14640 0x8d,0x43,0x27,0x8d,0x99,0x43,0x08,0x07,0xe0,0x8c,0x43,0x06,0x34,0x9b,0x43,0x38,0xe9,0x8c,0x43,0x46, 14641 0x0a,0x9e,0x43,0x3d,0xcc,0x8b,0x43,0xb2,0x06,0xa2,0x43,0x08,0xf1,0x40,0x8a,0x43,0xb0,0x12,0xa4,0x43, 14642 0x39,0xd1,0x88,0x43,0x76,0x43,0xa6,0x43,0xfa,0x06,0x88,0x43,0xa4,0x75,0xa9,0x43,0x08,0x19,0x6c,0x88, 14643 0x43,0x9f,0x9e,0xac,0x43,0x66,0xeb,0x87,0x43,0x44,0x76,0xb0,0x43,0x6b,0xce,0x86,0x43,0x3b,0xbc,0xb4, 14644 0x43,0x08,0xa9,0x8c,0x85,0x43,0x06,0xd0,0xb5,0x43,0xfa,0xee,0x83,0x43,0x74,0xa3,0xb6,0x43,0x3d,0x90, 14645 0x81,0x43,0x31,0xf6,0xb6,0x43,0x08,0x9d,0x61,0x7d,0x43,0xee,0x48,0xb7,0x43,0x3b,0x1f,0x75,0x43,0xcf, 14646 0xe3,0xb6,0x43,0xee,0x6f,0x6d,0x43,0x68,0xe2,0xb5,0x43,0x08,0xd4,0xed,0x6b,0x43,0x87,0x2f,0xb2,0x43, 14647 0x0e,0xc9,0x6b,0x43,0xa7,0x7c,0xae,0x43,0x98,0xfa,0x67,0x43,0xab,0x53,0xab,0x43,0x08,0x25,0x2c,0x64, 14648 0x43,0x33,0xa2,0xa8,0x43,0x40,0x96,0x61,0x43,0xc3,0xc2,0xa5,0x43,0x64,0xde,0x60,0x43,0xfa,0xa2,0xa2, 14649 0x43,0x08,0xb0,0x5d,0x60,0x43,0x06,0x4c,0x9f,0x43,0x9a,0xca,0x5f,0x43,0x38,0x3d,0x9b,0x43,0x3b,0x8f, 14650 0x5c,0x43,0x85,0xb0,0x98,0x43,0x08,0x42,0x36,0x51,0x43,0x3d,0xf0,0x91,0x43,0xcd,0x4f,0x49,0x43,0xdb, 14651 0xb9,0x8b,0x43,0xe0,0xdb,0x44,0x43,0x42,0x8b,0x84,0x43,0x08,0x7e,0xc9,0x44,0x43,0x8a,0x57,0x8d,0x43, 14652 0xbc,0x6c,0x0f,0x43,0x23,0x62,0x8e,0x43,0xf5,0x17,0x07,0x43,0xc5,0x3e,0x8f,0x43,0x09,0x06,0xe0,0xea, 14653 0x76,0x43,0xab,0xef,0xc5,0x43,0x08,0x12,0x00,0x79,0x43,0xab,0xcb,0xbf,0x43,0x79,0xb9,0x6d,0x43,0x7e, 14654 0x8d,0xba,0x43,0xee,0x6f,0x6d,0x43,0x98,0xeb,0xb5,0x43,0x08,0xe0,0x02,0x7b,0x43,0x5f,0x1c,0xb8,0x43, 14655 0x85,0x2c,0x82,0x43,0xe9,0x65,0xb8,0x43,0xd6,0xb2,0x86,0x43,0xc6,0x05,0xb5,0x43,0x08,0x03,0xcd,0x85, 14656 0x43,0x5a,0x39,0xb9,0x43,0xe4,0x4f,0x81,0x43,0xdb,0xd4,0xbf,0x43,0xdf,0x6c,0x82,0x43,0xbc,0x93,0xc5, 14657 0x43,0x09,0x06,0xf0,0xd0,0x22,0x43,0x5d,0x19,0x08,0x43,0x08,0xbc,0xab,0x49,0x43,0x4a,0x35,0x29,0x43, 14658 0xcb,0xf7,0x65,0x43,0xce,0x37,0x45,0x43,0x0e,0x99,0x63,0x43,0x67,0xc6,0x5c,0x43,0x09,0x06,0x05,0x94, 14659 0xab,0x43,0xc2,0x13,0x04,0x43,0x08,0x9f,0x26,0x98,0x43,0x11,0x42,0x25,0x43,0x97,0x00,0x8a,0x43,0x32, 14660 0x32,0x41,0x43,0xf5,0x2f,0x8b,0x43,0xc7,0xc0,0x58,0x43,0x09,0x06,0x8f,0x85,0x48,0x43,0xe0,0xa8,0x8c, 14661 0x43,0x08,0x55,0xaa,0x48,0x43,0xe0,0xa8,0x8c,0x43,0x6b,0x3d,0x49,0x43,0xc1,0x43,0x8c,0x43,0x31,0x62, 14662 0x49,0x43,0xc1,0x43,0x8c,0x43,0x08,0x2f,0xe3,0x2f,0x43,0xad,0xe7,0x98,0x43,0xff,0x0d,0x0d,0x43,0xad, 14663 0xf3,0x9a,0x43,0xf0,0xaf,0xcc,0x42,0x74,0x00,0x97,0x43,0x08,0xbb,0xa2,0xf7,0x42,0x93,0x4d,0x93,0x43, 14664 0x5e,0x19,0x08,0x43,0x5a,0x2a,0x87,0x43,0x23,0x6e,0x10,0x43,0x42,0x97,0x86,0x43,0x08,0xca,0xe8,0x33, 14665 0x43,0x1b,0x3c,0x80,0x43,0x80,0xe8,0x4d,0x43,0xda,0xf4,0x70,0x43,0xae,0x0e,0x4f,0x43,0x2b,0x1b,0x65, 14666 0x43,0x08,0x66,0x96,0x54,0x43,0xa3,0xe1,0x3b,0x43,0x4e,0xc4,0x19,0x43,0xa0,0x1a,0x16,0x43,0x10,0xe2, 14667 0x14,0x43,0x26,0x14,0xe0,0x42,0x08,0x5c,0x91,0x1c,0x43,0xcb,0x27,0xee,0x42,0xa9,0x40,0x24,0x43,0x71, 14668 0x3b,0xfc,0x42,0xf3,0xef,0x2b,0x43,0x8b,0x27,0x05,0x43,0x08,0xe2,0x4b,0x2c,0x43,0x48,0x86,0x07,0x43, 14669 0x79,0x62,0x2f,0x43,0x05,0xe5,0x09,0x43,0x55,0x32,0x34,0x43,0xa0,0xd2,0x09,0x43,0x08,0x74,0xa3,0x36, 14670 0x43,0x3a,0xd1,0x08,0x43,0x7e,0x81,0x38,0x43,0x09,0xd4,0x0a,0x43,0x0d,0xba,0x39,0x43,0xa0,0xea,0x0d, 14671 0x43,0x08,0x6f,0xe4,0x3d,0x43,0x43,0xc7,0x0e,0x43,0xd6,0xe5,0x3e,0x43,0xc4,0x4a,0x11,0x43,0x55,0x7a, 14672 0x40,0x43,0x59,0x72,0x13,0x43,0x08,0x55,0x92,0x44,0x43,0xbf,0x73,0x14,0x43,0x23,0x95,0x46,0x43,0xa5, 14673 0x09,0x17,0x43,0xe0,0xf3,0x48,0x43,0xfe,0x55,0x19,0x43,0x08,0xcd,0x4f,0x49,0x43,0xaa,0x10,0x1c,0x43, 14674 0x61,0x77,0x4b,0x43,0xfe,0x6d,0x1d,0x43,0x80,0xe8,0x4d,0x43,0x2b,0x94,0x1e,0x43,0x08,0x58,0xc9,0x51, 14675 0x43,0x41,0x27,0x1f,0x43,0x9b,0x82,0x53,0x43,0x35,0x72,0x20,0x43,0x53,0xf2,0x54,0x43,0x88,0xcf,0x21, 14676 0x43,0x08,0x7b,0x29,0x55,0x43,0xe8,0x0a,0x25,0x43,0xb2,0x2d,0x58,0x43,0xef,0xe8,0x26,0x43,0x9b,0xb2, 14677 0x5b,0x43,0xd0,0x8f,0x28,0x43,0x08,0x5f,0xef,0x5f,0x43,0xeb,0x11,0x2a,0x43,0xfd,0xdc,0x5f,0x43,0x6e, 14678 0x95,0x2c,0x43,0x3b,0xa7,0x60,0x43,0x2b,0xf4,0x2e,0x43,0x08,0x06,0xbb,0x61,0x43,0xfd,0xe5,0x31,0x43, 14679 0xe7,0x61,0x63,0x43,0xef,0x30,0x33,0x43,0x53,0x52,0x65,0x43,0xa3,0xb1,0x33,0x43,0x08,0x12,0xa0,0x68, 14680 0x43,0x7f,0x69,0x34,0x43,0x40,0xc6,0x69,0x43,0x64,0xff,0x36,0x43,0x7e,0x90,0x6a,0x43,0x71,0xcc,0x39, 14681 0x43,0x08,0xbc,0x5a,0x6b,0x43,0x51,0x73,0x3b,0x43,0xc1,0x49,0x6c,0x43,0xa5,0xd0,0x3c,0x43,0xe0,0xba, 14682 0x6e,0x43,0xb8,0x74,0x3c,0x43,0x08,0x6b,0x1c,0x73,0x43,0x13,0xc1,0x3e,0x43,0x40,0xf6,0x71,0x43,0xce, 14683 0x1f,0x41,0x43,0x55,0x89,0x72,0x43,0x8d,0x7e,0x43,0x43,0x08,0x68,0x2d,0x72,0x43,0x89,0xae,0x4b,0x43, 14684 0xc1,0x79,0x74,0x43,0xcb,0x78,0x4c,0x43,0x55,0xa1,0x76,0x43,0x5b,0xb1,0x4d,0x43,0x08,0xa2,0x38,0x7a, 14685 0x43,0xd1,0x56,0x4e,0x43,0x85,0xb6,0x78,0x43,0xb1,0x15,0x54,0x43,0x83,0xc7,0x77,0x43,0x89,0x0e,0x5c, 14686 0x43,0x08,0xcf,0x46,0x77,0x43,0x0f,0x81,0x5f,0x43,0x1a,0xde,0x7a,0x43,0xce,0xc7,0x5d,0x43,0x42,0x73, 14687 0x80,0x43,0x99,0xc3,0x5a,0x43,0x08,0x85,0x2c,0x82,0x43,0xf6,0xe6,0x59,0x43,0x81,0x3d,0x81,0x43,0x16, 14688 0x10,0x50,0x43,0xd6,0x8e,0x80,0x43,0x5b,0x99,0x49,0x43,0x08,0xc4,0xea,0x80,0x43,0x22,0x95,0x46,0x43, 14689 0xfa,0xe2,0x81,0x43,0xda,0xec,0x43,0x43,0x78,0x77,0x83,0x43,0xe4,0xb2,0x41,0x43,0x08,0x8a,0x27,0x85, 14690 0x43,0x86,0x77,0x3e,0x43,0x0c,0x9f,0x85,0x43,0x07,0xf4,0x3b,0x43,0x8f,0x16,0x86,0x43,0xe6,0x82,0x39, 14691 0x43,0x08,0x85,0x44,0x86,0x43,0x37,0xd9,0x35,0x43,0x1e,0x4f,0x87,0x43,0xe1,0x7b,0x34,0x43,0xdf,0x90, 14692 0x88,0x43,0xb6,0x55,0x33,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0xe5,0x31,0x43,0xfa,0x12,0x8a,0x43,0xbf, 14693 0x03,0x2d,0x43,0x19,0x78,0x8a,0x43,0x45,0x5e,0x2c,0x43,0x08,0x03,0xf1,0x8b,0x43,0xac,0x47,0x29,0x43, 14694 0x2f,0x17,0x8d,0x43,0x45,0x46,0x28,0x43,0xc8,0x21,0x8e,0x43,0x30,0xb3,0x27,0x43,0x08,0xa9,0xc8,0x8f, 14695 0x43,0xef,0xe8,0x26,0x43,0xbf,0x5b,0x90,0x43,0x5b,0xc1,0x24,0x43,0x10,0xca,0x90,0x43,0xa0,0x62,0x22, 14696 0x43,0x08,0x26,0x5d,0x91,0x43,0xbb,0xcc,0x1f,0x43,0xf0,0x70,0x92,0x43,0x78,0x13,0x1e,0x43,0x77,0xd7, 14697 0x93,0x43,0x73,0x24,0x1d,0x43,0x08,0x65,0x3f,0x96,0x43,0xce,0x58,0x1b,0x43,0xbe,0x7f,0x96,0x43,0xbf, 14698 0x8b,0x18,0x43,0x60,0x5c,0x97,0x43,0xb6,0xad,0x16,0x43,0x08,0xba,0xa8,0x99,0x43,0x78,0xcb,0x11,0x43, 14699 0x49,0xe1,0x9a,0x43,0x78,0xcb,0x11,0x43,0x01,0x51,0x9c,0x43,0x73,0xdc,0x10,0x43,0x08,0x72,0x24,0x9d, 14700 0x43,0xd2,0xff,0x0f,0x43,0x1c,0xd3,0x9d,0x43,0x07,0xec,0x0e,0x43,0xeb,0xc9,0x9d,0x43,0xe8,0x7a,0x0c, 14701 0x43,0x08,0x60,0x80,0x9d,0x43,0xd7,0xbe,0x08,0x43,0x4d,0xe8,0x9f,0x43,0x86,0x50,0x08,0x43,0x25,0xbd, 14702 0xa1,0x43,0x5b,0x2a,0x07,0x43,0x08,0x99,0x7f,0xa3,0x43,0xc9,0xf1,0x05,0x43,0x48,0x1d,0xa5,0x43,0x86, 14703 0x38,0x04,0x43,0x6c,0x71,0xa6,0x43,0x18,0x59,0x01,0x43,0x08,0x32,0x96,0xa6,0x43,0x6e,0x64,0xff,0x42, 14704 0x48,0x29,0xa7,0x43,0xed,0xcf,0xfd,0x42,0x5f,0xbc,0xa7,0x43,0x71,0x3b,0xfc,0x42,0x08,0xf3,0xe3,0xa9, 14705 0x43,0xf7,0x7d,0xf7,0x42,0xd8,0x6d,0xaa,0x43,0x45,0xe5,0xf2,0x42,0x48,0x41,0xab,0x43,0xcb,0x27,0xee, 14706 0x42,0x08,0x24,0xf9,0xab,0x43,0x52,0x6a,0xe9,0x42,0xee,0x0c,0xad,0x43,0x4c,0x8c,0xe7,0x42,0x1b,0x33, 14707 0xae,0x43,0xcc,0xf7,0xe5,0x42,0x08,0xaa,0x6b,0xaf,0x43,0xe8,0x61,0xe3,0x42,0x90,0xf5,0xaf,0x43,0xc9, 14708 0xf0,0xe0,0x42,0xe0,0x63,0xb0,0x43,0xe5,0x5a,0xde,0x42,0x08,0xaa,0x83,0xb3,0x43,0x29,0x2d,0x09,0x43, 14709 0x6a,0xfe,0x8e,0x43,0xb8,0x74,0x3c,0x43,0xd5,0x06,0x95,0x43,0xe6,0x79,0x67,0x43,0x08,0x2f,0x53,0x97, 14710 0x43,0xe9,0xb0,0x74,0x43,0xa8,0x28,0xa0,0x43,0x43,0xfd,0x76,0x43,0x83,0x28,0xad,0x43,0x17,0x59,0x81, 14711 0x43,0x08,0x3d,0xe7,0xbf,0x43,0x4b,0x8d,0x8c,0x43,0xae,0x96,0xba,0x43,0x66,0x27,0x92,0x43,0x15,0xe0, 14712 0xc7,0x43,0x6f,0x11,0x96,0x43,0x08,0x7e,0x5d,0xb2,0x43,0xdb,0x01,0x98,0x43,0x9e,0x56,0xa0,0x43,0x80, 14713 0xc1,0x97,0x43,0x69,0x2e,0x97,0x43,0x31,0x17,0x8d,0x43,0x09,0x06,0xab,0xa7,0x39,0x43,0x67,0x0f,0x0e, 14714 0x43,0x08,0xdb,0xbc,0x3b,0x43,0xe8,0x92,0x10,0x43,0xb5,0x85,0x3b,0x43,0x97,0x3c,0x14,0x43,0xab,0xa7, 14715 0x39,0x43,0x0c,0x0b,0x18,0x43,0x09,0x06,0xca,0x30,0x40,0x43,0x30,0x3b,0x13,0x43,0x08,0x17,0xc8,0x43, 14716 0x43,0xa5,0x09,0x17,0x43,0x7e,0xc9,0x44,0x43,0x1a,0xd8,0x1a,0x43,0x9d,0x22,0x43,0x43,0x8d,0xa6,0x1e, 14717 0x43,0x09,0x06,0xc8,0x78,0x4c,0x43,0xed,0xc9,0x1d,0x43,0x08,0x0b,0x32,0x4e,0x43,0x22,0xce,0x20,0x43, 14718 0x23,0xc5,0x4e,0x43,0x58,0xd2,0x23,0x43,0x0b,0x32,0x4e,0x43,0x2b,0xc4,0x26,0x43,0x09,0x06,0xec,0x08, 14719 0x58,0x43,0xc7,0xb1,0x26,0x43,0x08,0x02,0x9c,0x58,0x43,0xef,0x00,0x2b,0x43,0xd9,0x64,0x58,0x43,0x02, 14720 0xbd,0x2e,0x43,0x10,0x51,0x57,0x43,0x37,0xc1,0x31,0x43,0x09,0x06,0xcb,0xdf,0x61,0x43,0x4a,0x65,0x31, 14721 0x43,0x08,0xbe,0x2a,0x63,0x43,0xbd,0x33,0x35,0x43,0x32,0xe1,0x62,0x43,0x56,0x4a,0x38,0x43,0xde,0x83, 14722 0x61,0x43,0x3c,0xe0,0x3a,0x43,0x09,0x06,0x1c,0x7e,0x6a,0x43,0x5b,0x39,0x39,0x43,0x08,0x31,0x11,0x6b, 14723 0x43,0x0c,0xd2,0x3d,0x43,0x1c,0x7e,0x6a,0x43,0x13,0xd9,0x42,0x43,0xd9,0xc4,0x68,0x43,0xcb,0x60,0x48, 14724 0x43,0x09,0x06,0xe5,0xc1,0x73,0x43,0x16,0xf8,0x4b,0x43,0x08,0xa6,0xf7,0x72,0x43,0xb1,0xfd,0x4f,0x43, 14725 0x3b,0x07,0x71,0x43,0x4a,0x14,0x53,0x43,0xa2,0xf0,0x6d,0x43,0x7c,0x29,0x55,0x43,0x09,0x06,0x00,0x8d, 14726 0xa6,0x43,0xef,0x21,0x01,0x43,0x08,0x52,0xfb,0xa6,0x43,0xce,0xc8,0x02,0x43,0xe6,0x16,0xa7,0x43,0x51, 14727 0x4c,0x05,0x43,0x3b,0x68,0xa6,0x43,0x4c,0x75,0x08,0x43,0x09,0x06,0xde,0x20,0xa1,0x43,0x86,0x50,0x08, 14728 0x43,0x08,0xd4,0x4e,0xa1,0x43,0xd3,0xe7,0x0b,0x43,0xb5,0xe9,0xa0,0x43,0x59,0x5a,0x0f,0x43,0xba,0xcc, 14729 0x9f,0x43,0x54,0x83,0x12,0x43,0x09,0x06,0x77,0xfb,0x99,0x43,0x6c,0x16,0x13,0x43,0x08,0xde,0xfc,0x9a, 14730 0x43,0x4a,0xbd,0x14,0x43,0x06,0x34,0x9b,0x43,0xfe,0x55,0x19,0x43,0x13,0xe9,0x99,0x43,0x41,0x27,0x1f, 14731 0x43,0x09,0x06,0x46,0xce,0x93,0x43,0x26,0xa5,0x1d,0x43,0x08,0xe7,0xaa,0x94,0x43,0xbb,0xcc,0x1f,0x43, 14732 0x18,0xb4,0x94,0x43,0xa8,0x40,0x24,0x43,0xe2,0xbb,0x93,0x43,0x21,0xfe,0x28,0x43,0x09,0x06,0xb1,0x8e, 14733 0x8d,0x43,0xa8,0x58,0x28,0x43,0x08,0x19,0x90,0x8e,0x43,0x54,0x13,0x2b,0x43,0xa4,0xd9,0x8e,0x43,0x84, 14734 0x40,0x31,0x43,0x46,0xaa,0x8d,0x43,0x29,0x24,0x37,0x43,0x09,0x06,0xd6,0xbe,0x88,0x43,0xef,0x30,0x33, 14735 0x43,0x08,0x0c,0xb7,0x89,0x43,0x0e,0xa2,0x35,0x43,0xc0,0x37,0x8a,0x43,0x7a,0xaa,0x3b,0x43,0xbb,0x48, 14736 0x89,0x43,0xbb,0x7b,0x41,0x43,0x09,0x06,0x3a,0xad,0x82,0x43,0xc4,0x59,0x43,0x43,0x08,0xd2,0xb7,0x83, 14737 0x43,0x2b,0x5b,0x44,0x43,0x35,0xd6,0x85,0x43,0x48,0xf5,0x49,0x43,0x42,0x97,0x86,0x43,0xc4,0xa1,0x4f, 14738 0x43,0x09,0x06,0x9c,0xb3,0x80,0x43,0x48,0x55,0x5a,0x43,0x08,0xff,0xc5,0x80,0x43,0x09,0x73,0x55,0x43, 14739 0x93,0xe1,0x80,0x43,0x0f,0x39,0x53,0x43,0xf1,0xbe,0x7e,0x43,0x18,0xe7,0x4c,0x43,0x09,0x06,0xe0,0x02, 14740 0x7b,0x43,0x92,0xec,0x5d,0x43,0x08,0x09,0x3a,0x7b,0x43,0xf0,0xf7,0x58,0x43,0x09,0x3a,0x7b,0x43,0xe6, 14741 0x31,0x5b,0x43,0xe0,0x02,0x7b,0x43,0xa8,0x4f,0x56,0x43,0x09,0x06,0x39,0x4f,0x7d,0x43,0x3e,0x8f,0x5c, 14742 0x43,0x08,0xe9,0xe0,0x7c,0x43,0x03,0x9c,0x58,0x43,0x1e,0x2b,0x81,0x43,0x7f,0x30,0x5a,0x43,0xff,0x73, 14743 0x7d,0x43,0xf6,0xb6,0x51,0x43,0x09,0x06,0x5c,0xb8,0x52,0x43,0x28,0x21,0x87,0x43,0x08,0xae,0x3e,0x57, 14744 0x43,0x12,0x9a,0x88,0x43,0x23,0xf5,0x56,0x43,0x04,0xf1,0x8b,0x43,0x25,0xfc,0x5b,0x43,0x85,0x74,0x8e, 14745 0x43,0x08,0x2f,0xf2,0x61,0x43,0x8e,0x52,0x90,0x43,0xd9,0xdc,0x6c,0x43,0x85,0x74,0x8e,0x43,0xc6,0x20, 14746 0x69,0x43,0x3d,0xd8,0x8d,0x43,0x08,0x6d,0x8c,0x5a,0x43,0xf5,0x3b,0x8d,0x43,0x3d,0x77,0x58,0x43,0xa1, 14747 0xc6,0x87,0x43,0xf8,0xed,0x5e,0x43,0x5e,0x0d,0x86,0x43,0x09,0x06,0xde,0xcc,0x92,0x43,0xf7,0x17,0x87, 14748 0x43,0x08,0xb6,0x89,0x90,0x43,0xae,0x87,0x88,0x43,0x4a,0xa5,0x90,0x43,0xa1,0xde,0x8b,0x43,0xf9,0x2a, 14749 0x8e,0x43,0x23,0x62,0x8e,0x43,0x08,0xf5,0x2f,0x8b,0x43,0x5c,0x49,0x90,0x43,0x35,0xd6,0x85,0x43,0x8e, 14750 0x46,0x8e,0x43,0x3d,0xb4,0x87,0x43,0x47,0xaa,0x8d,0x43,0x08,0x6a,0xfe,0x8e,0x43,0xff,0x0d,0x8d,0x43, 14751 0xbb,0x6c,0x8f,0x43,0xf7,0x17,0x87,0x43,0x5c,0x31,0x8c,0x43,0xb2,0x5e,0x85,0x43,0x09,0x06,0x60,0x38, 14752 0x91,0x43,0x69,0x5d,0x7a,0x43,0x08,0x34,0x1e,0x92,0x43,0x1e,0x5b,0x89,0x43,0x04,0x63,0x7e,0x43,0x5e, 14753 0x01,0x84,0x43,0x59,0x2a,0x87,0x43,0x0d,0xcf,0x8d,0x43,0x09,0x03,0x04,0x06,0x5a,0x18,0x63,0x43,0x82, 14754 0x79,0x8b,0x43,0x08,0x25,0x2c,0x64,0x43,0x82,0x79,0x8b,0x43,0x2a,0x1b,0x65,0x43,0x9d,0xef,0x8a,0x43, 14755 0x2a,0x1b,0x65,0x43,0xc1,0x37,0x8a,0x43,0x08,0x2a,0x1b,0x65,0x43,0x17,0x89,0x89,0x43,0x25,0x2c,0x64, 14756 0x43,0x31,0xff,0x88,0x43,0x5a,0x18,0x63,0x43,0x31,0xff,0x88,0x43,0x08,0xf3,0x16,0x62,0x43,0x31,0xff, 14757 0x88,0x43,0xee,0x27,0x61,0x43,0x17,0x89,0x89,0x43,0xee,0x27,0x61,0x43,0xc1,0x37,0x8a,0x43,0x08,0xee, 14758 0x27,0x61,0x43,0x9d,0xef,0x8a,0x43,0xf3,0x16,0x62,0x43,0x82,0x79,0x8b,0x43,0x5a,0x18,0x63,0x43,0x82, 14759 0x79,0x8b,0x43,0x09,0x06,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x08,0x34,0xee,0x89,0x43,0x82,0x79, 14760 0x8b,0x43,0x85,0x5c,0x8a,0x43,0x9d,0xef,0x8a,0x43,0x85,0x5c,0x8a,0x43,0xc1,0x37,0x8a,0x43,0x08,0x85, 14761 0x5c,0x8a,0x43,0x17,0x89,0x89,0x43,0x34,0xee,0x89,0x43,0x31,0xff,0x88,0x43,0x4f,0x64,0x89,0x43,0x31, 14762 0xff,0x88,0x43,0x08,0x9c,0xe3,0x88,0x43,0x31,0xff,0x88,0x43,0x19,0x6c,0x88,0x43,0x17,0x89,0x89,0x43, 14763 0x19,0x6c,0x88,0x43,0xc1,0x37,0x8a,0x43,0x08,0x19,0x6c,0x88,0x43,0x9d,0xef,0x8a,0x43,0x9c,0xe3,0x88, 14764 0x43,0x82,0x79,0x8b,0x43,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x09,0x02,0x04,0x06,0x19,0x60,0x86, 14765 0x43,0xec,0xed,0xa3,0x43,0x08,0x35,0xd6,0x85,0x43,0x76,0x43,0xa6,0x43,0x93,0xe1,0x80,0x43,0x57,0x02, 14766 0xac,0x43,0x61,0xd8,0x80,0x43,0x87,0x17,0xae,0x43,0x08,0xa5,0x85,0x80,0x43,0xc3,0xfe,0xaf,0x43,0xce, 14767 0xbc,0x80,0x43,0x83,0x40,0xb1,0x43,0xa5,0x91,0x82,0x43,0x79,0x6e,0xb1,0x43,0x08,0x23,0x26,0x84,0x43, 14768 0x40,0x93,0xb1,0x43,0x30,0xe7,0x84,0x43,0xbe,0x1b,0xb1,0x43,0x11,0x82,0x84,0x43,0xab,0x6b,0xaf,0x43, 14769 0x08,0xb7,0x41,0x84,0x43,0x3b,0x98,0xae,0x43,0xb7,0x41,0x84,0x43,0xc3,0xf2,0xad,0x43,0xa1,0xae,0x83, 14770 0x43,0x83,0x28,0xad,0x43,0x08,0xb2,0x52,0x83,0x43,0x80,0x39,0xac,0x43,0x81,0x49,0x83,0x43,0xf0,0x00, 14771 0xab,0x43,0xe4,0x67,0x85,0x43,0x76,0x4f,0xa8,0x43,0x08,0x9c,0xd7,0x86,0x43,0xd1,0x83,0xa6,0x43,0xec, 14772 0x45,0x87,0x43,0x01,0x75,0xa2,0x43,0x19,0x60,0x86,0x43,0xec,0xed,0xa3,0x43,0x09,0x06,0xd9,0xdc,0x6c, 14773 0x43,0x14,0x25,0xa4,0x43,0x08,0xa2,0xf0,0x6d,0x43,0x9f,0x7a,0xa6,0x43,0x47,0xec,0x77,0x43,0x80,0x39, 14774 0xac,0x43,0xa9,0xfe,0x77,0x43,0xb0,0x4e,0xae,0x43,0x08,0x23,0xa4,0x78,0x43,0xea,0x35,0xb0,0x43,0xd2, 14775 0x35,0x78,0x43,0xab,0x77,0xb1,0x43,0xc1,0x79,0x74,0x43,0xa2,0xa5,0xb1,0x43,0x08,0xc6,0x50,0x71,0x43, 14776 0x68,0xca,0xb1,0x43,0xab,0xce,0x6f,0x43,0xe7,0x52,0xb1,0x43,0xea,0x98,0x70,0x43,0xd4,0xa2,0xaf,0x43, 14777 0x08,0x9d,0x19,0x71,0x43,0x96,0xd8,0xae,0x43,0x9d,0x19,0x71,0x43,0xec,0x29,0xae,0x43,0xca,0x3f,0x72, 14778 0x43,0xab,0x5f,0xad,0x43,0x08,0xa6,0xf7,0x72,0x43,0xa7,0x70,0xac,0x43,0x09,0x0a,0x73,0x43,0x17,0x38, 14779 0xab,0x43,0x44,0xcd,0x6e,0x43,0x9f,0x86,0xa8,0x43,0x08,0xd4,0xed,0x6b,0x43,0xf8,0xba,0xa6,0x43,0x31, 14780 0x11,0x6b,0x43,0x2a,0xac,0xa2,0x43,0xd9,0xdc,0x6c,0x43,0x14,0x25,0xa4,0x43,0x09,0x01,0x05,0x06,0x66, 14781 0x5d,0x7a,0x43,0x74,0xeb,0xc2,0x43,0x08,0x09,0x22,0x77,0x43,0x50,0xbb,0xc7,0x43,0xe9,0xe0,0x7c,0x43, 14782 0xf5,0x86,0xc9,0x43,0x8f,0x94,0x7a,0x43,0xc5,0x95,0xcd,0x43,0x09,0x06,0x08,0x98,0x80,0x43,0x6b,0x19, 14783 0xc3,0x43,0x08,0xb7,0x35,0x82,0x43,0x79,0xf2,0xc7,0x43,0xf1,0xbe,0x7e,0x43,0x1e,0xbe,0xc9,0x43,0x73, 14784 0x7c,0x80,0x43,0xec,0xcc,0xcd,0x43,0x09,0x06,0x28,0xab,0x7d,0x43,0xae,0xde,0xc6,0x43,0x08,0x1e,0xcd, 14785 0x7b,0x43,0x8a,0xa2,0xc9,0x43,0x30,0x89,0x7f,0x43,0x5c,0x94,0xcc,0x43,0x28,0xab,0x7d,0x43,0x42,0x2a, 14786 0xcf,0x43,0x09,0x01,0x05,0x06,0x24,0x14,0xe0,0x42,0xf5,0x77,0x97,0x43,0x08,0xf7,0x1d,0xe7,0x42,0x74, 14787 0x00,0x97,0x43,0x4d,0x93,0xec,0x42,0xdb,0xf5,0x95,0x43,0x29,0x4b,0xed,0x42,0xcd,0x34,0x95,0x43,0x09, 14788 0x06,0x29,0x7b,0xf5,0x42,0x6f,0x1d,0x98,0x43,0x08,0xe4,0xf1,0xfb,0x42,0x61,0x5c,0x97,0x43,0xdb,0x7d, 14789 0x01,0x43,0xb2,0xbe,0x95,0x43,0x55,0x23,0x02,0x43,0xe7,0xaa,0x94,0x43,0x09,0x06,0x98,0xdc,0x03,0x43, 14790 0xbe,0x8b,0x98,0x43,0x08,0x66,0xdf,0x05,0x43,0x47,0xe6,0x97,0x43,0xae,0x87,0x08,0x43,0x98,0x48,0x96, 14791 0x43,0x61,0x08,0x09,0x43,0xd6,0x06,0x95,0x43,0x09,0x06,0x31,0x0b,0x0b,0x43,0x8e,0x82,0x98,0x43,0x08, 14792 0xdb,0xc5,0x0d,0x43,0x80,0xc1,0x97,0x43,0xd6,0xee,0x10,0x43,0xa9,0xec,0x95,0x43,0x79,0xcb,0x11,0x43, 14793 0x55,0x8f,0x94,0x43,0x09,0x06,0xd1,0x2f,0x18,0x43,0xdb,0x01,0x98,0x43,0x08,0xad,0xe7,0x18,0x43,0x38, 14794 0x25,0x97,0x43,0x8a,0x9f,0x19,0x43,0x80,0xb5,0x95,0x43,0xd6,0x1e,0x19,0x43,0xe0,0xd8,0x94,0x43,0x09, 14795 0x06,0x9a,0x5b,0x1d,0x43,0x58,0x8a,0x97,0x43,0x08,0x01,0x5d,0x1e,0x43,0xf1,0x88,0x96,0x43,0x2f,0x83, 14796 0x1f,0x43,0x19,0xb4,0x94,0x43,0x19,0xf0,0x1e,0x43,0x6f,0x05,0x94,0x43,0x09,0x06,0x0b,0x53,0x24,0x43, 14797 0xae,0xdb,0x96,0x43,0x08,0x25,0xd5,0x25,0x43,0x50,0xac,0x95,0x43,0x53,0xfb,0x26,0x43,0x8a,0x7b,0x93, 14798 0x43,0x76,0x43,0x26,0x43,0xb7,0x95,0x92,0x43,0x09,0x06,0x76,0x5b,0x2a,0x43,0x47,0xda,0x95,0x43,0x08, 14799 0xf3,0xef,0x2b,0x43,0x10,0xe2,0x94,0x43,0x6d,0x95,0x2c,0x43,0xae,0xc3,0x92,0x43,0x68,0xa6,0x2b,0x43, 14800 0x47,0xc2,0x91,0x43,0x09,0x06,0x36,0xc1,0x31,0x43,0x2c,0x58,0x94,0x43,0x08,0x8c,0x1e,0x33,0x43,0x31, 14801 0x3b,0x93,0x43,0x79,0x7a,0x33,0x43,0xff,0x25,0x91,0x43,0xd9,0x9d,0x32,0x43,0xc1,0x5b,0x90,0x43,0x09, 14802 0x06,0x25,0x35,0x36,0x43,0x31,0x3b,0x93,0x43,0x08,0x3f,0xb7,0x37,0x43,0xc1,0x67,0x92,0x43,0xe0,0x93, 14803 0x38,0x43,0xae,0xb7,0x90,0x43,0x7e,0x81,0x38,0x43,0x0d,0xdb,0x8f,0x43,0x09,0x06,0xb5,0x85,0x3b,0x43, 14804 0xe4,0xaf,0x91,0x43,0x08,0xcf,0x07,0x3d,0x43,0x9d,0x13,0x91,0x43,0xbc,0x63,0x3d,0x43,0x47,0xb6,0x8f, 14805 0x43,0xe5,0x9a,0x3d,0x43,0x74,0xd0,0x8e,0x43,0x09,0x06,0xae,0xc6,0x42,0x43,0xa4,0xd9,0x8e,0x43,0x08, 14806 0xca,0x48,0x44,0x43,0xfa,0x2a,0x8e,0x43,0xa2,0x11,0x44,0x43,0x9d,0xfb,0x8c,0x43,0x55,0x92,0x44,0x43, 14807 0x0d,0xc3,0x8b,0x43,0x09,0x06,0x39,0x10,0xc3,0x43,0x34,0x36,0x96,0x43,0x08,0x92,0x44,0xc1,0x43,0xe4, 14808 0xc7,0x95,0x43,0x6f,0xf0,0xbf,0x43,0x4b,0xbd,0x94,0x43,0x47,0xb9,0xbf,0x43,0x0b,0xf3,0x93,0x43,0x09, 14809 0x06,0x8f,0x49,0xbe,0x43,0xb7,0xad,0x96,0x43,0x08,0x11,0xb5,0xbc,0x43,0x77,0xe3,0x95,0x43,0x9c,0xf2, 14810 0xba,0x43,0xfa,0x4e,0x94,0x43,0xae,0x96,0xba,0x43,0x31,0x3b,0x93,0x43,0x09,0x06,0xdb,0xb0,0xb9,0x43, 14811 0x10,0xee,0x96,0x43,0x08,0x42,0xa6,0xb8,0x43,0xc8,0x51,0x96,0x43,0x50,0x5b,0xb7,0x43,0x19,0xb4,0x94, 14812 0x43,0xf7,0x1a,0xb7,0x43,0x58,0x72,0x93,0x43,0x09,0x06,0xf2,0x2b,0xb6,0x43,0x10,0xee,0x96,0x43,0x08, 14813 0x9d,0xce,0xb4,0x43,0x04,0x2d,0x96,0x43,0xed,0x30,0xb3,0x43,0x2c,0x58,0x94,0x43,0xce,0xcb,0xb2,0x43, 14814 0xd6,0xfa,0x92,0x43,0x09,0x06,0x5a,0x09,0xb1,0x43,0x19,0xc0,0x96,0x43,0x08,0x6c,0xad,0xb0,0x43,0x77, 14815 0xe3,0x95,0x43,0x7e,0x51,0xb0,0x43,0xc0,0x73,0x94,0x43,0xd8,0x91,0xb0,0x43,0x1e,0x97,0x93,0x43,0x09, 14816 0x06,0x48,0x4d,0xad,0x43,0xbe,0x7f,0x96,0x43,0x08,0x95,0xcc,0xac,0x43,0x58,0x7e,0x95,0x43,0x4d,0x30, 14817 0xac,0x43,0x80,0xa9,0x93,0x43,0xd8,0x79,0xac,0x43,0xd6,0xfa,0x92,0x43,0x09,0x06,0x90,0xd1,0xa9,0x43, 14818 0x14,0xd1,0x95,0x43,0x08,0x83,0x10,0xa9,0x43,0xb7,0xa1,0x94,0x43,0x3b,0x74,0xa8,0x43,0xf1,0x70,0x92, 14819 0x43,0x29,0xd0,0xa8,0x43,0x1e,0x8b,0x91,0x43,0x09,0x06,0x5a,0xcd,0xa6,0x43,0x8a,0x87,0x95,0x43,0x08, 14820 0x1c,0x03,0xa6,0x43,0x23,0x86,0x94,0x43,0x5f,0xb0,0xa5,0x43,0xc1,0x67,0x92,0x43,0xe1,0x27,0xa6,0x43, 14821 0x8a,0x6f,0x91,0x43,0x09,0x06,0xd4,0x5a,0xa3,0x43,0x2c,0x58,0x94,0x43,0x08,0x29,0xac,0xa2,0x43,0x31, 14822 0x3b,0x93,0x43,0x32,0x7e,0xa2,0x43,0xff,0x25,0x91,0x43,0x83,0xec,0xa2,0x43,0x8e,0x52,0x90,0x43,0x09, 14823 0x06,0xf8,0x96,0xa0,0x43,0x1e,0x97,0x93,0x43,0x08,0xeb,0xd5,0x9f,0x43,0x7b,0xba,0x92,0x43,0x99,0x67, 14824 0x9f,0x43,0x9d,0x13,0x91,0x43,0x99,0x67,0x9f,0x43,0xfa,0x36,0x90,0x43,0x09,0x06,0xeb,0xc9,0x9d,0x43, 14825 0xc8,0x39,0x92,0x43,0x08,0xde,0x08,0x9d,0x43,0xb2,0xa6,0x91,0x43,0xe6,0xda,0x9c,0x43,0x2c,0x40,0x90, 14826 0x43,0x52,0xbf,0x9c,0x43,0x5a,0x5a,0x8f,0x43,0x09,0x06,0x37,0x3d,0x9b,0x43,0x85,0x80,0x90,0x43,0x08, 14827 0x2a,0x7c,0x9a,0x43,0xdb,0xd1,0x8f,0x43,0xf0,0xa0,0x9a,0x43,0x7d,0xa2,0x8e,0x43,0x65,0x57,0x9a,0x43, 14828 0xee,0x69,0x8d,0x43,0x09,0x02,0x04,0x06,0x2a,0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x08,0x0d,0x8a,0x31, 14829 0x42,0x9f,0x0e,0x94,0x43,0xf3,0x1f,0x34,0x42,0x3d,0xfc,0x93,0x43,0x63,0xff,0x36,0x42,0xa9,0xe0,0x93, 14830 0x43,0x08,0xb5,0x34,0x5d,0x42,0x0b,0xf3,0x93,0x43,0x6d,0xa4,0x5e,0x42,0x03,0x39,0x98,0x43,0xe7,0x31, 14831 0x5b,0x42,0x93,0x89,0x9d,0x43,0x08,0x02,0x9c,0x58,0x42,0xd4,0x5a,0xa3,0x43,0x38,0x70,0x53,0x42,0x14, 14832 0x49,0xaa,0x43,0xf8,0xed,0x5e,0x42,0x83,0x28,0xad,0x43,0x08,0xea,0x68,0x68,0x42,0x20,0x22,0xaf,0x43, 14833 0x12,0xb8,0x6c,0x42,0xb5,0x49,0xb1,0x43,0x2a,0x4b,0x6d,0x42,0x0d,0x96,0xb3,0x43,0x07,0x2a,0x4b,0x6d, 14834 0x42,0xc6,0x05,0xb5,0x43,0x08,0x87,0x6e,0x6c,0x42,0x68,0xee,0xb7,0x43,0x1c,0x66,0x66,0x42,0x31,0x0e, 14835 0xbb,0x43,0x57,0x11,0x5e,0x42,0x8f,0x49,0xbe,0x43,0x08,0x66,0x96,0x54,0x42,0xb9,0x5c,0xb8,0x43,0x2c, 14836 0x2b,0x3c,0x42,0x68,0xd6,0xb3,0x43,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x07,0x2a,0xf4,0x2e,0x42, 14837 0x61,0xa4,0xa3,0x43,0x08,0x55,0x1a,0x30,0x42,0xf0,0xd0,0xa2,0x43,0xf8,0xf6,0x30,0x42,0xb2,0x06,0xa2, 14838 0x43,0x98,0xd3,0x31,0x42,0xd6,0x4e,0xa1,0x43,0x08,0x1c,0x6f,0x38,0x42,0x2a,0x94,0x9e,0x43,0xc1,0x22, 14839 0x36,0x42,0xf5,0x9b,0x9d,0x43,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x57, 14840 0xa2,0x9b,0x43,0x08,0xab,0x8f,0x35,0x42,0x8a,0xab,0x9b,0x43,0xe9,0x71,0x3a,0x42,0xb2,0xe2,0x9b,0x43, 14841 0xb7,0x74,0x3c,0x42,0x34,0x5a,0x9c,0x43,0x08,0x23,0x7d,0x42,0x42,0x0b,0x2f,0x9e,0x43,0xe5,0x9a,0x3d, 14842 0x42,0x38,0x6d,0xa3,0x43,0x36,0xd9,0x35,0x42,0xf3,0xd7,0xa7,0x43,0x08,0x12,0x61,0x2e,0x42,0xb0,0x42, 14843 0xac,0x43,0x63,0xff,0x36,0x42,0xdd,0x74,0xaf,0x43,0x1e,0xa6,0x45,0x42,0x44,0x82,0xb2,0x43,0x08,0x74, 14844 0x1b,0x4b,0x42,0x79,0x7a,0xb3,0x43,0x10,0x21,0x4f,0x42,0x2a,0x18,0xb5,0x43,0xdb,0x4c,0x54,0x42,0x91, 14845 0x19,0xb6,0x43,0x08,0xee,0x3f,0x65,0x42,0x5f,0x28,0xba,0x43,0xa7,0xaf,0x66,0x42,0xb9,0x50,0xb6,0x43, 14846 0x14,0x58,0x5c,0x42,0xca,0xdc,0xb1,0x43,0x08,0x2c,0x8b,0x4c,0x42,0x4e,0x30,0xac,0x43,0x19,0xcf,0x48, 14847 0x42,0x2a,0xd0,0xa8,0x43,0xbc,0xab,0x49,0x42,0xa9,0x4c,0xa6,0x43,0x08,0x61,0x5f,0x47,0x42,0xfa,0xa2, 14848 0xa2,0x43,0xa7,0xaf,0x66,0x42,0x85,0x98,0x94,0x43,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x07,0x2a, 14849 0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x09,0x06,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x08,0xdc,0xe3, 14850 0xf1,0x41,0xe9,0x9e,0x92,0x43,0xd2,0xe7,0x0b,0x42,0xd6,0x06,0x95,0x43,0x2a,0xf4,0x2e,0x42,0x04,0x21, 14851 0x94,0x43,0x07,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x08,0x87,0x17,0x2e,0x42,0xc3,0x62,0x95,0x43, 14852 0xe7,0x3a,0x2d,0x42,0xf5,0x6b,0x95,0x43,0x44,0x5e,0x2c,0x42,0xf5,0x6b,0x95,0x43,0x08,0xd1,0x47,0x1c, 14853 0x42,0x19,0xc0,0x96,0x43,0x66,0xdf,0x05,0x42,0x38,0x19,0x95,0x43,0x12,0x6a,0x00,0x42,0xb2,0xbe,0x95, 14854 0x43,0x08,0xbb,0x6b,0xea,0x41,0xd6,0x12,0x97,0x43,0x2d,0x82,0xfa,0x41,0x61,0x74,0x9b,0x43,0x7e,0x72, 14855 0x06,0x42,0x8a,0xab,0x9b,0x43,0x08,0xc8,0x39,0x12,0x42,0x4e,0xd0,0x9b,0x43,0x53,0xe3,0x22,0x42,0xc3, 14856 0x86,0x9b,0x43,0x2a,0xf4,0x2e,0x42,0x57,0xa2,0x9b,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43, 14857 0x08,0x01,0xa5,0x2a,0x42,0xa4,0x2d,0x9d,0x43,0x96,0x9c,0x24,0x42,0x06,0x40,0x9d,0x43,0x8a,0xb7,0x1d, 14858 0x42,0x9a,0x5b,0x9d,0x43,0x08,0x6b,0x16,0x13,0x42,0xcd,0x64,0x9d,0x43,0x42,0xc7,0x0e,0x42,0x9a,0x5b, 14859 0x9d,0x43,0x23,0x26,0x04,0x42,0xcd,0x64,0x9d,0x43,0x08,0xe6,0x91,0xeb,0x41,0x38,0x49,0x9d,0x43,0x73, 14860 0x7b,0xdb,0x41,0xf5,0x83,0x99,0x43,0x7f,0x60,0xe2,0x41,0x0b,0x0b,0x98,0x43,0x08,0x7f,0x60,0xe2,0x41, 14861 0xec,0x99,0x95,0x43,0xe3,0x5a,0xde,0x41,0xbe,0x7f,0x96,0x43,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43, 14862 0x07,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x09,0x06,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x08, 14863 0xd4,0x7e,0x29,0x42,0xab,0x6b,0xaf,0x43,0x4e,0x0c,0x26,0x42,0x44,0x6a,0xae,0x43,0x38,0x79,0x25,0x42, 14864 0xd4,0x96,0xad,0x43,0x08,0x25,0xbd,0x21,0x42,0xe2,0x4b,0xac,0x43,0x49,0x35,0x29,0x42,0x9a,0x97,0xa7, 14865 0x43,0x2a,0xf4,0x2e,0x42,0x61,0xa4,0xa3,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x09,0x06, 14866 0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x08,0x86,0x20,0x80,0x43,0x57,0x41,0xe6,0x43,0x7d,0x4e,0x80, 14867 0x43,0x25,0x38,0xe6,0x43,0xa5,0x85,0x80,0x43,0xf3,0x2e,0xe6,0x43,0x08,0x35,0xca,0x83,0x43,0xd4,0xc9, 14868 0xe5,0x43,0x9c,0xd7,0x86,0x43,0x44,0x91,0xe4,0x43,0xd5,0xca,0x8a,0x43,0x91,0x1c,0xe6,0x43,0x08,0x53, 14869 0x5f,0x8c,0x43,0xf8,0x1d,0xe7,0x43,0x2f,0x17,0x8d,0x43,0x4e,0x7b,0xe8,0x43,0x92,0x29,0x8d,0x43,0x2f, 14870 0x22,0xea,0x43,0x07,0x92,0x29,0x8d,0x43,0x44,0xb5,0xea,0x43,0x08,0xfe,0x0d,0x8d,0x43,0x2a,0x4b,0xed, 14871 0x43,0xe3,0x8b,0x8b,0x43,0x55,0x7d,0xf0,0x43,0xec,0x51,0x89,0x43,0x72,0x0b,0xf4,0x43,0x08,0xcd,0xd4, 14872 0x84,0x43,0x9d,0x55,0xfb,0x43,0xc9,0xe5,0x83,0x43,0x74,0x1e,0xfb,0x43,0x73,0x94,0x84,0x43,0x5a,0x90, 14873 0xf7,0x43,0x08,0xe8,0x62,0x88,0x43,0xfd,0x30,0xee,0x43,0x39,0xc5,0x86,0x43,0xdd,0xbf,0xeb,0x43,0x35, 14874 0xbe,0x81,0x43,0x40,0xde,0xed,0x43,0x08,0x4f,0x34,0x81,0x43,0x36,0x0c,0xee,0x43,0x08,0x98,0x80,0x43, 14875 0xfd,0x30,0xee,0x43,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x40,0xec, 14876 0x43,0x08,0x35,0xbe,0x81,0x43,0x06,0xf7,0xeb,0x43,0x15,0x65,0x83,0x43,0x49,0xa4,0xeb,0x43,0x1e,0x43, 14877 0x85,0x43,0xbe,0x5a,0xeb,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0x18,0xea,0x43,0x42,0x97,0x86,0x43,0x5f, 14878 0x67,0xf4,0x43,0xa9,0x98,0x87,0x43,0xd4,0x1d,0xf4,0x43,0x08,0x5c,0x25,0x8a,0x43,0xcf,0x16,0xef,0x43, 14879 0x46,0xaa,0x8d,0x43,0x5a,0x3c,0xe9,0x43,0x19,0x6c,0x88,0x43,0x53,0x5e,0xe7,0x43,0x08,0xc4,0x02,0x85, 14880 0x43,0x96,0x0b,0xe7,0x43,0x85,0x2c,0x82,0x43,0x83,0x67,0xe7,0x43,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7, 14881 0x43,0x07,0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x09,0x06,0xfd,0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43, 14882 0x08,0xfa,0x6c,0x78,0x43,0xd1,0xc2,0xe0,0x43,0x25,0x5c,0x6c,0x43,0x25,0x44,0xe8,0x43,0x1d,0xe5,0x7f, 14883 0x43,0x87,0x4a,0xe6,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7,0x43,0x08,0xa6,0x27,0x7b,0x43,0x91, 14884 0x28,0xe8,0x43,0xbc,0xa2,0x77,0x43,0xb0,0x8d,0xe8,0x43,0xc6,0x68,0x75,0x43,0x57,0x4d,0xe8,0x43,0x08, 14885 0xe0,0xd2,0x72,0x43,0xab,0x9e,0xe7,0x43,0x50,0x9a,0x71,0x43,0x2a,0x27,0xe7,0x43,0xea,0x98,0x70,0x43, 14886 0x57,0x35,0xe4,0x43,0x08,0x94,0x3b,0x6f,0x43,0x14,0x7c,0xe2,0x43,0xff,0x13,0x6d,0x43,0x06,0xbb,0xe1, 14887 0x43,0xcf,0xfe,0x6a,0x43,0x06,0xbb,0xe1,0x43,0x08,0x44,0x9d,0x66,0x43,0x77,0x8e,0xe2,0x43,0x3b,0xef, 14888 0x6c,0x43,0x91,0x10,0xe4,0x43,0xfd,0x24,0x6c,0x43,0xb0,0x81,0xe6,0x43,0x08,0x96,0x23,0x6b,0x43,0xee, 14889 0x57,0xe9,0x43,0xca,0x0f,0x6a,0x43,0x5f,0x37,0xec,0x43,0x55,0x71,0x6e,0x43,0x9f,0x01,0xed,0x43,0x08, 14890 0xdb,0xfb,0x75,0x43,0x3b,0xef,0xec,0x43,0x09,0x3a,0x7b,0x43,0xb0,0xa5,0xec,0x43,0x1d,0xe5,0x7f,0x43, 14891 0x91,0x40,0xec,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x08,0xa9,0x16,0x7c,0x43,0xb0,0xb1, 14892 0xee,0x43,0x47,0xec,0x77,0x43,0xd9,0xe8,0xee,0x43,0x1e,0x9d,0x73,0x43,0xcf,0x16,0xef,0x43,0x08,0x0e, 14893 0xc9,0x6b,0x43,0xee,0x7b,0xef,0x43,0x7e,0x90,0x6a,0x43,0xfd,0x30,0xee,0x43,0x01,0xfc,0x68,0x43,0x4e, 14894 0x93,0xec,0x43,0x08,0x31,0xf9,0x66,0x43,0x4e,0x87,0xea,0x43,0x31,0x11,0x6b,0x43,0xd4,0xd5,0xe7,0x43, 14895 0xd9,0xc4,0x68,0x43,0xd4,0xc9,0xe5,0x43,0x08,0xe5,0x79,0x67,0x43,0x77,0x9a,0xe4,0x43,0x44,0x9d,0x66, 14896 0x43,0xab,0x86,0xe3,0x43,0x7e,0x78,0x66,0x43,0x0b,0xaa,0xe2,0x43,0x07,0x7e,0x78,0x66,0x43,0x57,0x29, 14897 0xe2,0x43,0x08,0xa7,0xaf,0x66,0x43,0xbe,0x1e,0xe1,0x43,0x87,0x56,0x68,0x43,0x77,0x82,0xe0,0x43,0xfd, 14898 0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43,0x09,0x06,0xc4,0x41,0xbf,0x43,0x85,0xc0,0x72,0x42,0x08,0x73,0xdf, 14899 0xc0,0x43,0xf4,0x76,0x72,0x42,0x97,0x33,0xc2,0x43,0x85,0xc0,0x72,0x42,0xb2,0xb5,0xc3,0x43,0x64,0x56, 14900 0x75,0x42,0x08,0x03,0x24,0xc4,0x43,0x5e,0x7f,0x78,0x42,0xfa,0x51,0xc4,0x43,0x01,0x85,0x7c,0x42,0x5c, 14901 0x64,0xc4,0x43,0xa0,0xb3,0x80,0x42,0x07,0x5c,0x64,0xc4,0x43,0x10,0x93,0x83,0x42,0x08,0xc8,0x48,0xc4, 14902 0x43,0x1c,0x78,0x8a,0x42,0x27,0x6c,0xc3,0x43,0xaf,0xcf,0x94,0x42,0x23,0x7d,0xc2,0x43,0x99,0x9c,0xa4, 14903 0x42,0x08,0x3d,0xe7,0xbf,0x43,0xfb,0xfd,0xb5,0x42,0xb3,0x9d,0xbf,0x43,0x88,0x17,0xae,0x42,0xc4,0x41, 14904 0xbf,0x43,0x69,0x76,0xa3,0x42,0x07,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x08,0x4f,0x8b,0xbf,0x43, 14905 0xed,0x81,0x91,0x42,0xe4,0xa6,0xbf,0x43,0x5d,0x61,0x94,0x42,0xfa,0x39,0xc0,0x43,0x3b,0x49,0x9d,0x42, 14906 0x08,0x2b,0x43,0xc0,0x43,0x28,0xed,0xa9,0x42,0x61,0x3b,0xc1,0x43,0x00,0x9e,0xa5,0x42,0xe4,0xb2,0xc1, 14907 0x43,0x5d,0x91,0x9c,0x42,0x08,0x78,0xce,0xc1,0x43,0xfd,0x36,0x90,0x42,0x22,0x89,0xc4,0x43,0x81,0x72, 14908 0x86,0x42,0xae,0xc6,0xc2,0x43,0xa0,0xb3,0x80,0x42,0x08,0x54,0x86,0xc2,0x43,0x58,0xd1,0x7e,0x42,0x30, 14909 0x32,0xc1,0x43,0xce,0x5e,0x7b,0x42,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x07,0xc4,0x41,0xbf,0x43, 14910 0x85,0xc0,0x72,0x42,0x09,0x06,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x08,0x35,0xfd,0xbb,0x43,0xa4, 14911 0xa1,0x5c,0x42,0x5e,0x34,0xbc,0x43,0x9d,0x2a,0x70,0x42,0x5e,0x40,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08, 14912 0x4c,0x9c,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08,0xef,0xbe,0x43,0x0e,0x0a,0x73,0x42,0xc4,0x41,0xbf,0x43, 14913 0x85,0xc0,0x72,0x42,0x07,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x08,0xcd,0x13,0xbf,0x43,0xe8,0xf1, 14914 0x7b,0x42,0xd6,0xe5,0xbe,0x43,0x71,0x3b,0x7c,0x42,0xdf,0xb7,0xbe,0x43,0x71,0x3b,0x7c,0x42,0x08,0x08, 14915 0xe3,0xbc,0x43,0xa4,0x61,0x7d,0x42,0x28,0x3c,0xbb,0x43,0x91,0x45,0x69,0x42,0x28,0x3c,0xbb,0x43,0x58, 14916 0x71,0x6e,0x42,0x08,0xce,0xfb,0xba,0x43,0xd5,0x35,0x78,0x42,0x59,0x45,0xbb,0x43,0x58,0x23,0x82,0x42, 14917 0xa1,0xe1,0xbb,0x43,0xd7,0xbe,0x88,0x42,0x08,0xc9,0x18,0xbc,0x43,0xaf,0x9f,0x8c,0x42,0x1e,0x76,0xbd, 14918 0x43,0x51,0x7c,0x8d,0x42,0xd6,0xe5,0xbe,0x43,0xf4,0x58,0x8e,0x42,0x08,0x9c,0x0a,0xbf,0x43,0x45,0xc7, 14919 0x8e,0x42,0x30,0x26,0xbf,0x43,0x96,0x35,0x8f,0x42,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x07,0xc4, 14920 0x41,0xbf,0x43,0x69,0x76,0xa3,0x42,0x08,0x08,0xef,0xbe,0x43,0xb1,0xd6,0x99,0x42,0xe8,0x89,0xbe,0x43, 14921 0xde,0xc5,0x8d,0x42,0xc0,0x46,0xbc,0x43,0xc2,0x5b,0x90,0x42,0x08,0x9c,0xf2,0xba,0x43,0x86,0x80,0x90, 14922 0x42,0xf2,0x43,0xba,0x43,0xe8,0x73,0x87,0x42,0x8f,0x31,0xba,0x43,0xb6,0xf4,0x7d,0x42,0x07,0x8f,0x31, 14923 0xba,0x43,0x21,0xc6,0x76,0x42,0x08,0xc0,0x3a,0xba,0x43,0x5f,0x48,0x6b,0x42,0xae,0x96,0xba,0x43,0xe3, 14924 0x83,0x61,0x42,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x09,0x06,0xea,0x74,0xea,0x43,0x61,0x44,0x93, 14925 0x43,0x08,0x24,0x5c,0xec,0x43,0x31,0x3b,0x93,0x43,0xfb,0x30,0xee,0x43,0x93,0x4d,0x93,0x43,0x0d,0xe1, 14926 0xef,0x43,0x80,0xa9,0x93,0x43,0x08,0x8f,0x58,0xf0,0x43,0xd1,0x17,0x94,0x43,0xb7,0x8f,0xf0,0x43,0x10, 14927 0xe2,0x94,0x43,0xea,0x98,0xf0,0x43,0xa9,0xec,0x95,0x43,0x07,0xea,0x98,0xf0,0x43,0x38,0x25,0x97,0x43, 14928 0x08,0x23,0x74,0xf0,0x43,0x9f,0x32,0x9a,0x43,0x5a,0x60,0xef,0x43,0x53,0xcb,0x9e,0x43,0x2d,0x3a,0xee, 14929 0x43,0xfd,0x91,0xa3,0x43,0x08,0xa2,0xf0,0xed,0x43,0xdd,0x38,0xa5,0x43,0x17,0xa7,0xed,0x43,0xbe,0xdf, 14930 0xa6,0x43,0x5a,0x54,0xed,0x43,0x9f,0x86,0xa8,0x43,0x08,0xfc,0x24,0xec,0x43,0xca,0xc4,0xad,0x43,0x48, 14931 0xa4,0xeb,0x43,0x40,0x6f,0xab,0x43,0x28,0x3f,0xeb,0x43,0x1c,0x0f,0xa8,0x43,0x08,0x1f,0x6d,0xeb,0x43, 14932 0x72,0x48,0xa3,0x43,0x67,0x09,0xec,0x43,0xd1,0x53,0x9e,0x43,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b,0x43, 14933 0x07,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x08,0x7e,0x90,0xea,0x43,0x8a,0x9f,0x99,0x43,0x12,0xac, 14934 0xea,0x43,0xbc,0xa8,0x99,0x43,0xa7,0xc7,0xea,0x43,0xbc,0xa8,0x99,0x43,0x08,0x51,0x76,0xeb,0x43,0x9f, 14935 0x32,0x9a,0x43,0x5e,0x37,0xec,0x43,0x49,0xed,0x9c,0x43,0xb0,0xa5,0xec,0x43,0x2a,0xa0,0xa0,0x43,0x08, 14936 0x09,0xe6,0xec,0x43,0xd1,0x77,0xa4,0x43,0x28,0x4b,0xed,0x43,0x61,0xa4,0xa3,0x43,0xab,0xc2,0xed,0x43, 14937 0x8e,0xb2,0xa0,0x43,0x08,0x70,0xe7,0xed,0x43,0xde,0x08,0x9d,0x43,0x87,0x86,0xf0,0x43,0x2f,0x53,0x97, 14938 0x43,0x87,0x7a,0xee,0x43,0xec,0x99,0x95,0x43,0x08,0xca,0x27,0xee,0x43,0xff,0x3d,0x95,0x43,0x74,0xca, 14939 0xec,0x43,0x55,0x8f,0x94,0x43,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x07,0xea,0x74,0xea,0x43,0x61, 14940 0x44,0x93,0x43,0x09,0x06,0x05,0xd3,0xe5,0x43,0x19,0x9c,0x90,0x43,0x08,0x09,0xc2,0xe6,0x43,0xd1,0xff, 14941 0x8f,0x43,0x4d,0x6f,0xe6,0x43,0x74,0xe8,0x92,0x43,0x3b,0xd7,0xe8,0x43,0xc3,0x56,0x93,0x43,0x08,0x1f, 14942 0x61,0xe9,0x43,0x93,0x4d,0x93,0x43,0x05,0xeb,0xe9,0x43,0x93,0x4d,0x93,0x43,0xea,0x74,0xea,0x43,0x61, 14943 0x44,0x93,0x43,0x07,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x08,0x24,0x50,0xea,0x43,0xe7,0xaa,0x94, 14944 0x43,0x2d,0x22,0xea,0x43,0xe7,0xaa,0x94,0x43,0x36,0xf4,0xe9,0x43,0xe7,0xaa,0x94,0x43,0x08,0xa2,0xcc, 14945 0xe7,0x43,0xe0,0xd8,0x94,0x43,0xd4,0xc9,0xe5,0x43,0x19,0xa8,0x92,0x43,0xd4,0xc9,0xe5,0x43,0x27,0x69, 14946 0x93,0x43,0x08,0x17,0x77,0xe5,0x43,0xe0,0xd8,0x94,0x43,0x67,0xe5,0xe5,0x43,0x47,0xda,0x95,0x43,0x43, 14947 0x9d,0xe6,0x43,0xe2,0xd3,0x97,0x43,0x08,0x9d,0xdd,0xe6,0x43,0xad,0xe7,0x98,0x43,0x09,0xce,0xe8,0x43, 14948 0xff,0x55,0x99,0x43,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x07,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b, 14949 0x43,0x08,0x71,0xcf,0xe9,0x43,0x53,0xb3,0x9a,0x43,0xa7,0xbb,0xe8,0x43,0xdb,0x0d,0x9a,0x43,0xc6,0x14, 14950 0xe7,0x43,0xdb,0x0d,0x9a,0x43,0x08,0x48,0x80,0xe5,0x43,0xdb,0x0d,0x9a,0x43,0x0a,0xb6,0xe4,0x43,0xc3, 14951 0x6e,0x97,0x43,0x76,0x9a,0xe4,0x43,0x74,0xf4,0x94,0x43,0x07,0x76,0x9a,0xe4,0x43,0x79,0xd7,0x93,0x43, 14952 0x08,0xd8,0xac,0xe4,0x43,0x66,0x27,0x92,0x43,0x29,0x1b,0xe5,0x43,0xe0,0xc0,0x90,0x43,0x05,0xd3,0xe5, 14953 0x43,0x19,0x9c,0x90,0x43,0x09,0x06,0x1b,0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x08,0x71,0x0b,0xf4,0x42, 14954 0x00,0x0e,0x8d,0x42,0x8c,0x0f,0x01,0x43,0x3e,0xc0,0x89,0x42,0xf3,0x28,0x06,0x43,0x48,0x9e,0x8b,0x42, 14955 0x08,0x15,0x89,0x09,0x43,0x00,0x0e,0x8d,0x42,0xe0,0x9c,0x0a,0x43,0xc1,0x8b,0x98,0x42,0xa6,0xc1,0x0a, 14956 0x43,0x02,0xa5,0xaa,0x42,0x07,0xa6,0xc1,0x0a,0x43,0xf9,0xf6,0xb0,0x42,0x08,0xa6,0xc1,0x0a,0x43,0x47, 14957 0x8e,0xb4,0x42,0x42,0xaf,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0xe0,0x9c,0x0a,0x43,0xba,0x74,0xbc,0x42,0x08, 14958 0xa1,0xd2,0x09,0x43,0x40,0x47,0xd0,0x42,0x0d,0xab,0x07,0x43,0x91,0xb5,0xd0,0x42,0x3b,0xb9,0x04,0x43, 14959 0xec,0x71,0xba,0x42,0x08,0xe5,0x5b,0x03,0x43,0xe3,0x33,0xa8,0x42,0x63,0xd8,0x00,0x43,0xce,0x70,0x9f, 14960 0x42,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x07,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e,0x42,0x08,0xed, 14961 0x6f,0xed,0x42,0x73,0x24,0x9d,0x42,0xd8,0x0c,0xf5,0x42,0x99,0x6c,0x9c,0x42,0x27,0xab,0xfd,0x42,0xea, 14962 0xda,0x9c,0x42,0x08,0x36,0xca,0x03,0x43,0x2b,0x94,0x9e,0x42,0x68,0xc7,0x01,0x43,0x8f,0xbe,0xa2,0x42, 14963 0xfa,0x06,0x08,0x43,0x73,0xb4,0xb5,0x42,0x08,0x8e,0x2e,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0x9d,0xe3,0x08, 14964 0x43,0xd7,0x1e,0x99,0x42,0x28,0x15,0x05,0x43,0x32,0x3b,0x93,0x42,0x08,0x63,0xf0,0x04,0x43,0x70,0xed, 14965 0x8f,0x42,0x71,0x0b,0xf4,0x42,0x32,0x3b,0x93,0x42,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x07,0x1b, 14966 0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x09,0x06,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42,0x08,0x8e,0x55, 14967 0xc0,0x42,0xb8,0x4d,0x86,0x42,0x60,0xbf,0xd7,0x42,0x3e,0xf0,0x91,0x42,0x63,0xf6,0xe4,0x42,0x70,0xed, 14968 0x8f,0x42,0x08,0x7a,0x89,0xe5,0x42,0xac,0xc8,0x8f,0x42,0xcc,0xf7,0xe5,0x42,0xac,0xc8,0x8f,0x42,0x1b, 14969 0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x07,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x08,0x63,0xf6,0xe4, 14970 0x42,0x3b,0x19,0x95,0x42,0xe6,0x61,0xe3,0x42,0x00,0x3e,0x95,0x42,0xf4,0x16,0xe2,0x42,0xc4,0x62,0x95, 14971 0x42,0x08,0x6e,0x74,0xd6,0x42,0x15,0xd1,0x95,0x42,0x97,0x63,0xca,0x42,0xaf,0xcf,0x94,0x42,0xfb,0x2d, 14972 0xbe,0x42,0x86,0x80,0x90,0x42,0x08,0x97,0x03,0xba,0x42,0xce,0x10,0x8f,0x42,0x5e,0x28,0xba,0x42,0x3e, 14973 0xf0,0x91,0x42,0xf2,0x4f,0xbc,0x42,0x45,0xf7,0x96,0x42,0x08,0x27,0x54,0xbf,0x42,0x73,0x24,0x9d,0x42, 14974 0xa5,0xe8,0xc0,0x42,0x86,0xe0,0xa0,0x42,0xe4,0xca,0xc5,0x42,0xed,0x11,0xaa,0x42,0x08,0x54,0xaa,0xc8, 14975 0x42,0x86,0x40,0xb1,0x42,0x59,0x81,0xc5,0x42,0xa1,0x11,0xc4,0x42,0x3e,0xe7,0xbf,0x42,0xfb,0x8d,0xce, 14976 0x42,0x08,0xb4,0x6d,0xb7,0x42,0x30,0xc2,0xd9,0x42,0x46,0xf5,0xc9,0x42,0xdf,0x53,0xd9,0x42,0x38,0x40, 14977 0xcb,0x42,0x62,0x8f,0xcf,0x42,0x08,0x7d,0xf9,0xcc,0x42,0xec,0xa1,0xc2,0x42,0x07,0x43,0xcd,0x42,0x6c, 14978 0xdd,0xb8,0x42,0x2b,0x8b,0xcc,0x42,0x92,0xf5,0xaf,0x42,0x08,0xf9,0x8d,0xce,0x42,0x41,0x57,0xa7,0x42, 14979 0x5b,0xb8,0xd2,0x42,0xae,0x2f,0xa5,0x42,0x18,0x2f,0xd9,0x42,0x13,0x2a,0xa1,0x42,0x08,0x41,0x7e,0xdd, 14980 0x42,0xe3,0x03,0xa0,0x42,0x2e,0xf2,0xe1,0x42,0x7c,0x02,0x9f,0x42,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e, 14981 0x42,0x07,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x08,0x4d,0x63,0xe4,0x42,0x00,0x9e,0xa5,0x42,0xf4, 14982 0x16,0xe2,0x42,0x15,0x31,0xa6,0x42,0x99,0xca,0xdf,0x42,0x2b,0xc4,0xa6,0x42,0x08,0xc0,0x82,0xc6,0x42, 14983 0xc4,0xc2,0xa5,0x42,0x57,0xe1,0xd5,0x42,0x91,0xb5,0xd0,0x42,0x54,0xda,0xd0,0x42,0x97,0x93,0xd2,0x42, 14984 0x08,0x9c,0x3a,0xc7,0x42,0x17,0x58,0xdc,0x42,0x9c,0x0a,0xbf,0x42,0x6e,0xa4,0xde,0x42,0x90,0x25,0xb8, 14985 0x42,0xdf,0x53,0xd9,0x42,0x08,0x59,0x21,0xb5,0x42,0xf2,0xdf,0xd4,0x42,0x51,0x43,0xb3,0x42,0x91,0xb5, 14986 0xd0,0x42,0xc5,0x29,0xbb,0x42,0x0e,0x1a,0xca,0x42,0x08,0x65,0x36,0xc4,0x42,0xd0,0x07,0xbd,0x42,0x3e, 14987 0xe7,0xbf,0x42,0x37,0x09,0xbe,0x42,0x0c,0xea,0xc1,0x42,0xcd,0xd0,0xaf,0x42,0x08,0x2b,0x5b,0xc4,0x42, 14988 0x18,0x08,0xa3,0x42,0x67,0xa6,0xab,0x42,0x99,0x3c,0x94,0x42,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42, 14989 0x09,]; 14990 14991 private struct ThePath { 14992 public: 14993 enum Command { 14994 Bounds, // always first, has 4 args (x0, y0, x1, y1) 14995 StrokeMode, 14996 FillMode, 14997 StrokeFillMode, 14998 NormalStroke, 14999 ThinStroke, 15000 MoveTo, 15001 LineTo, 15002 CubicTo, // cubic bezier 15003 EndPath, 15004 } 15005 15006 public: 15007 const(ubyte)[] path; 15008 uint ppos; 15009 15010 public: 15011 this (const(void)[] apath) pure nothrow @trusted @nogc { 15012 path = cast(const(ubyte)[])apath; 15013 } 15014 15015 @property bool empty () const pure nothrow @safe @nogc { pragma(inline, true); return (ppos >= path.length); } 15016 15017 Command getCommand () nothrow @trusted @nogc { 15018 pragma(inline, true); 15019 if (ppos >= cast(uint)path.length) assert(0, "invalid path"); 15020 return cast(Command)(path.ptr[ppos++]); 15021 } 15022 15023 // number of (x,y) pairs for this command 15024 static int argCount (in Command cmd) nothrow @safe @nogc { 15025 version(aliced) pragma(inline, true); 15026 if (cmd == Command.Bounds) return 2; 15027 else if (cmd == Command.MoveTo || cmd == Command.LineTo) return 1; 15028 else if (cmd == Command.CubicTo) return 3; 15029 else return 0; 15030 } 15031 15032 void skipArgs (int argc) nothrow @trusted @nogc { 15033 pragma(inline, true); 15034 ppos += cast(uint)(float.sizeof*2*argc); 15035 } 15036 15037 float getFloat () nothrow @trusted @nogc { 15038 pragma(inline, true); 15039 if (ppos >= cast(uint)path.length || cast(uint)path.length-ppos < float.sizeof) assert(0, "invalid path"); 15040 version(LittleEndian) { 15041 float res = *cast(const(float)*)(&path.ptr[ppos]); 15042 ppos += cast(uint)float.sizeof; 15043 return res; 15044 } else { 15045 static assert(float.sizeof == 4); 15046 uint xp = path.ptr[ppos]|(path.ptr[ppos+1]<<8)|(path.ptr[ppos+2]<<16)|(path.ptr[ppos+3]<<24); 15047 ppos += cast(uint)float.sizeof; 15048 return *cast(const(float)*)(&xp); 15049 } 15050 } 15051 } 15052 15053 // this will add baphomet's background path to the current NanoVega path, so you can fill it. 15054 public void addBaphometBack (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc { 15055 if (nvg is null) return; 15056 15057 auto path = ThePath(baphometPath); 15058 15059 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); } 15060 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); } 15061 15062 bool inPath = false; 15063 while (!path.empty) { 15064 auto cmd = path.getCommand(); 15065 switch (cmd) { 15066 case ThePath.Command.MoveTo: 15067 inPath = true; 15068 immutable float ex = getScaledX(); 15069 immutable float ey = getScaledY(); 15070 nvg.moveTo(ex, ey); 15071 break; 15072 case ThePath.Command.LineTo: 15073 inPath = true; 15074 immutable float ex = getScaledX(); 15075 immutable float ey = getScaledY(); 15076 nvg.lineTo(ex, ey); 15077 break; 15078 case ThePath.Command.CubicTo: // cubic bezier 15079 inPath = true; 15080 immutable float x1 = getScaledX(); 15081 immutable float y1 = getScaledY(); 15082 immutable float x2 = getScaledX(); 15083 immutable float y2 = getScaledY(); 15084 immutable float ex = getScaledX(); 15085 immutable float ey = getScaledY(); 15086 nvg.bezierTo(x1, y1, x2, y2, ex, ey); 15087 break; 15088 case ThePath.Command.EndPath: 15089 if (inPath) return; 15090 break; 15091 default: 15092 path.skipArgs(path.argCount(cmd)); 15093 break; 15094 } 15095 } 15096 } 15097 15098 // this will add baphomet's pupil paths to the current NanoVega path, so you can fill it. 15099 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 { 15100 // pupils starts with "fill-and-stroke" mode 15101 if (nvg is null) return; 15102 15103 auto path = ThePath(baphometPath); 15104 15105 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); } 15106 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); } 15107 15108 bool inPath = false; 15109 bool pupLeft = true; 15110 while (!path.empty) { 15111 auto cmd = path.getCommand(); 15112 switch (cmd) { 15113 case ThePath.Command.StrokeFillMode: inPath = true; break; 15114 case ThePath.Command.MoveTo: 15115 if (!inPath) goto default; 15116 static if (!left) { if (pupLeft) goto default; } 15117 static if (!right) { if (!pupLeft) goto default; } 15118 immutable float ex = getScaledX(); 15119 immutable float ey = getScaledY(); 15120 nvg.moveTo(ex, ey); 15121 break; 15122 case ThePath.Command.LineTo: 15123 if (!inPath) goto default; 15124 static if (!left) { if (pupLeft) goto default; } 15125 static if (!right) { if (!pupLeft) goto default; } 15126 immutable float ex = getScaledX(); 15127 immutable float ey = getScaledY(); 15128 nvg.lineTo(ex, ey); 15129 break; 15130 case ThePath.Command.CubicTo: // cubic bezier 15131 if (!inPath) goto default; 15132 static if (!left) { if (pupLeft) goto default; } 15133 static if (!right) { if (!pupLeft) goto default; } 15134 immutable float x1 = getScaledX(); 15135 immutable float y1 = getScaledY(); 15136 immutable float x2 = getScaledX(); 15137 immutable float y2 = getScaledY(); 15138 immutable float ex = getScaledX(); 15139 immutable float ey = getScaledY(); 15140 nvg.bezierTo(x1, y1, x2, y2, ex, ey); 15141 break; 15142 case ThePath.Command.EndPath: 15143 if (inPath) { 15144 if (pupLeft) pupLeft = false; else return; 15145 } 15146 break; 15147 default: 15148 path.skipArgs(path.argCount(cmd)); 15149 break; 15150 } 15151 } 15152 } 15153 15154 // mode: 'f' to allow fills; 's' to allow strokes; 'w' to allow stroke widths; 'c' to replace fills with strokes 15155 public void renderBaphomet(string mode="fs") (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc { 15156 template hasChar(char ch, string s) { 15157 static if (s.length == 0) enum hasChar = false; 15158 else static if (s[0] == ch) enum hasChar = true; 15159 else enum hasChar = hasChar!(ch, s[1..$]); 15160 } 15161 enum AllowStroke = hasChar!('s', mode); 15162 enum AllowFill = hasChar!('f', mode); 15163 enum AllowWidth = hasChar!('w', mode); 15164 enum Contour = hasChar!('c', mode); 15165 //static assert(AllowWidth || AllowFill); 15166 15167 if (nvg is null) return; 15168 15169 auto path = ThePath(baphometPath); 15170 15171 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); } 15172 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); } 15173 15174 int mode = 0; 15175 int sw = ThePath.Command.NormalStroke; 15176 nvg.beginPath(); 15177 while (!path.empty) { 15178 auto cmd = path.getCommand(); 15179 switch (cmd) { 15180 case ThePath.Command.StrokeMode: mode = ThePath.Command.StrokeMode; break; 15181 case ThePath.Command.FillMode: mode = ThePath.Command.FillMode; break; 15182 case ThePath.Command.StrokeFillMode: mode = ThePath.Command.StrokeFillMode; break; 15183 case ThePath.Command.NormalStroke: sw = ThePath.Command.NormalStroke; break; 15184 case ThePath.Command.ThinStroke: sw = ThePath.Command.ThinStroke; break; 15185 case ThePath.Command.MoveTo: 15186 immutable float ex = getScaledX(); 15187 immutable float ey = getScaledY(); 15188 nvg.moveTo(ex, ey); 15189 break; 15190 case ThePath.Command.LineTo: 15191 immutable float ex = getScaledX(); 15192 immutable float ey = getScaledY(); 15193 nvg.lineTo(ex, ey); 15194 break; 15195 case ThePath.Command.CubicTo: // cubic bezier 15196 immutable float x1 = getScaledX(); 15197 immutable float y1 = getScaledY(); 15198 immutable float x2 = getScaledX(); 15199 immutable float y2 = getScaledY(); 15200 immutable float ex = getScaledX(); 15201 immutable float ey = getScaledY(); 15202 nvg.bezierTo(x1, y1, x2, y2, ex, ey); 15203 break; 15204 case ThePath.Command.EndPath: 15205 if (mode == ThePath.Command.FillMode || mode == ThePath.Command.StrokeFillMode) { 15206 static if (AllowFill || Contour) { 15207 static if (Contour) { 15208 if (mode == ThePath.Command.FillMode) { nvg.strokeWidth = 1; nvg.stroke(); } 15209 } else { 15210 nvg.fill(); 15211 } 15212 } 15213 } 15214 if (mode == ThePath.Command.StrokeMode || mode == ThePath.Command.StrokeFillMode) { 15215 static if (AllowStroke || Contour) { 15216 static if (AllowWidth) { 15217 if (sw == ThePath.Command.NormalStroke) nvg.strokeWidth = 1; 15218 else if (sw == ThePath.Command.ThinStroke) nvg.strokeWidth = 0.5; 15219 else assert(0, "wtf?!"); 15220 } 15221 nvg.stroke(); 15222 } 15223 } 15224 nvg.newPath(); 15225 break; 15226 default: 15227 path.skipArgs(path.argCount(cmd)); 15228 break; 15229 } 15230 } 15231 nvg.newPath(); 15232 }