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 32 D code with nanovega: 33 34 --- 35 import arsd.simpledisplay; 36 37 import arsd.nanovega; 38 39 void main () { 40 NVGContext nvg; // our NanoVega context 41 42 // we need at least OpenGL3 with GLSL to use NanoVega, 43 // so let's tell simpledisplay about that 44 setOpenGLContextVersion(3, 0); 45 46 // now create OpenGL window 47 auto sdmain = new SimpleWindow(800, 600, "NanoVega Simple Sample", OpenGlOptions.yes, Resizability.allowResizing); 48 49 // we need to destroy NanoVega context on window close 50 // stricly speaking, it is not necessary, as nothing fatal 51 // will happen if you'll forget it, but let's be polite. 52 // note that we cannot do that *after* our window was closed, 53 // as we need alive OpenGL context to do proper cleanup. 54 sdmain.onClosing = delegate () { 55 nvg.kill(); 56 }; 57 58 // this is called just before our window will be shown for the first time. 59 // we must create NanoVega context here, as it needs to initialize 60 // internal OpenGL subsystem with valid OpenGL context. 61 sdmain.visibleForTheFirstTime = delegate () { 62 // yes, that's all 63 nvg = nvgCreateContext(); 64 if (nvg is null) assert(0, "cannot initialize NanoVega"); 65 }; 66 67 // this callback will be called when we will need to repaint our window 68 sdmain.redrawOpenGlScene = delegate () { 69 // fix viewport (we can do this in resize event, or here, it doesn't matter) 70 glViewport(0, 0, sdmain.width, sdmain.height); 71 72 // clear window 73 glClearColor(0, 0, 0, 0); 74 glClear(glNVGClearFlags); // use NanoVega API to get flags for OpenGL call 75 76 { 77 nvg.beginFrame(sdmain.width, sdmain.height); // begin rendering 78 scope(exit) nvg.endFrame(); // and flush render queue on exit 79 80 nvg.beginPath(); // start new path 81 nvg.roundedRect(20.5, 30.5, sdmain.width-40, sdmain.height-60, 8); // .5 to draw at pixel center (see NanoVega documentation) 82 // now set filling mode for our rectangle 83 // you can create colors using HTML syntax, or with convenient constants 84 nvg.fillPaint = nvg.linearGradient(20.5, 30.5, sdmain.width-40, sdmain.height-60, NVGColor("#f70"), NVGColor.green); 85 // now fill our rect 86 nvg.fill(); 87 // and draw a nice outline 88 nvg.strokeColor = NVGColor.white; 89 nvg.strokeWidth = 2; 90 nvg.stroke(); 91 // that's all, folks! 92 } 93 }; 94 95 sdmain.eventLoop(0, // no pulse timer required 96 delegate (KeyEvent event) { 97 if (event == "*-Q" || event == "Escape") { sdmain.close(); return; } // quit on Q, Ctrl+Q, and so on 98 }, 99 ); 100 101 flushGui(); // let OS do it's cleanup 102 } 103 --- 104 ) 105 106 $(COLUMN 107 Javascript code with HTML5 Canvas 108 109 ```html 110 <!DOCTYPE html> 111 <html> 112 <head> 113 <title>NanoVega Simple Sample (HTML5 Translation)</title> 114 <style> 115 body { background-color: black; } 116 </style> 117 </head> 118 <body> 119 <canvas id="my-canvas" width="800" height="600"></canvas> 120 <script> 121 var canvas = document.getElementById("my-canvas"); 122 var context = canvas.getContext("2d"); 123 124 context.beginPath(); 125 126 context.rect(20.5, 30.5, canvas.width - 40, canvas.height - 60); 127 128 var gradient = context.createLinearGradient(20.5, 30.5, canvas.width - 40, canvas.height - 60); 129 gradient.addColorStop(0, "#f70"); 130 gradient.addColorStop(1, "green"); 131 132 context.fillStyle = gradient; 133 context.fill(); 134 context.closePath(); 135 context.strokeStyle = "white"; 136 context.lineWidth = 2; 137 context.stroke(); 138 </script> 139 </body> 140 </html> 141 ``` 142 ) 143 ) 144 145 $(TIP 146 This library can use either inbuilt or BindBC (external dependency) provided bindings for OpenGL and FreeType. 147 Former are used by default, latter can be activated by passing the `bindbc` version specifier to the compiler. 148 ) 149 150 Creating drawing context 151 ======================== 152 153 The drawing context is created using platform specific constructor function. 154 155 --- 156 NVGContext vg = nvgCreateContext(); 157 --- 158 159 $(WARNING You must use created context ONLY in that thread where you created it. 160 There is no way to "transfer" context between threads. Trying to do so 161 will lead to UB.) 162 163 $(WARNING Never issue any commands outside of [beginFrame]/[endFrame]. Trying to 164 do so will lead to UB.) 165 166 167 Drawing shapes with NanoVega 168 ============================ 169 170 Drawing a simple shape using NanoVega consists of four steps: 171 $(LIST 172 * begin a new shape, 173 * define the path to draw, 174 * set fill or stroke, 175 * and finally fill or stroke the path. 176 ) 177 178 --- 179 vg.beginPath(); 180 vg.rect(100, 100, 120, 30); 181 vg.fillColor(nvgRGBA(255, 192, 0, 255)); 182 vg.fill(); 183 --- 184 185 Calling [beginPath] will clear any existing paths and start drawing from blank slate. 186 There are number of number of functions to define the path to draw, such as rectangle, 187 rounded rectangle and ellipse, or you can use the common moveTo, lineTo, bezierTo and 188 arcTo API to compose the paths step by step. 189 190 191 Understanding Composite Paths 192 ============================= 193 194 Because of the way the rendering backend is built in NanoVega, drawing a composite path, 195 that is path consisting from multiple paths defining holes and fills, is a bit more 196 involved. NanoVega uses non-zero filling rule and by default, and paths are wound in counter 197 clockwise order. Keep that in mind when drawing using the low level draw API. In order to 198 wind one of the predefined shapes as a hole, you should call `pathWinding(NVGSolidity.Hole)`, 199 or `pathWinding(NVGSolidity.Solid)` $(B after) defining the path. 200 201 --- 202 vg.beginPath(); 203 vg.rect(100, 100, 120, 30); 204 vg.circle(120, 120, 5); 205 vg.pathWinding(NVGSolidity.Hole); // mark circle as a hole 206 vg.fillColor(nvgRGBA(255, 192, 0, 255)); 207 vg.fill(); 208 --- 209 210 211 Rendering is wrong, what to do? 212 =============================== 213 214 $(LIST 215 * make sure you have created NanoVega context using [nvgCreateContext] call 216 * make sure you have initialised OpenGL with $(B stencil buffer) 217 * make sure you have cleared stencil buffer 218 * make sure all rendering calls happen between [beginFrame] and [endFrame] 219 * to enable more checks for OpenGL errors, add `NVGContextFlag.Debug` flag to [nvgCreateContext] 220 ) 221 222 223 OpenGL state touched by the backend 224 =================================== 225 226 The OpenGL back-end touches following states: 227 228 When textures are uploaded or updated, the following pixel store is set to defaults: 229 `GL_UNPACK_ALIGNMENT`, `GL_UNPACK_ROW_LENGTH`, `GL_UNPACK_SKIP_PIXELS`, `GL_UNPACK_SKIP_ROWS`. 230 Texture binding is also affected. Texture updates can happen when the user loads images, 231 or when new font glyphs are added. Glyphs are added as needed between calls to [beginFrame] 232 and [endFrame]. 233 234 The data for the whole frame is buffered and flushed in [endFrame]. 235 The following code illustrates the OpenGL state touched by the rendering code: 236 237 --- 238 glUseProgram(prog); 239 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 240 glEnable(GL_CULL_FACE); 241 glCullFace(GL_BACK); 242 glFrontFace(GL_CCW); 243 glEnable(GL_BLEND); 244 glDisable(GL_DEPTH_TEST); 245 glDisable(GL_SCISSOR_TEST); 246 glDisable(GL_COLOR_LOGIC_OP); 247 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 248 glStencilMask(0xffffffff); 249 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 250 glStencilFunc(GL_ALWAYS, 0, 0xffffffff); 251 glActiveTexture(GL_TEXTURE1); 252 glActiveTexture(GL_TEXTURE0); 253 glBindBuffer(GL_UNIFORM_BUFFER, buf); 254 glBindVertexArray(arr); 255 glBindBuffer(GL_ARRAY_BUFFER, buf); 256 glBindTexture(GL_TEXTURE_2D, tex); 257 glUniformBlockBinding(... , GLNVG_FRAG_BINDING); 258 --- 259 260 Symbol_groups: 261 262 context_management = 263 ## Context Management 264 265 Functions to create and destory NanoVega context. 266 267 frame_management = 268 ## Frame Management 269 270 To start drawing with NanoVega context, you have to "begin frame", and then 271 "end frame" to flush your rendering commands to GPU. 272 273 composite_operation = 274 ## Composite Operation 275 276 The composite operations in NanoVega are modeled after HTML Canvas API, and 277 the blend func is based on OpenGL (see corresponding manuals for more info). 278 The colors in the blending state have premultiplied alpha. 279 280 color_utils = 281 ## Color Utils 282 283 Colors in NanoVega are stored as ARGB. Zero alpha means "transparent color". 284 285 matrices = 286 ## Matrices and Transformations 287 288 The paths, gradients, patterns and scissor region are transformed by an transformation 289 matrix at the time when they are passed to the API. 290 The current transformation matrix is an affine matrix: 291 292 ---------------------- 293 [sx kx tx] 294 [ky sy ty] 295 [ 0 0 1] 296 ---------------------- 297 298 Where: (sx, sy) define scaling, (kx, ky) skewing, and (tx, ty) translation. 299 The last row is assumed to be (0, 0, 1) and is not stored. 300 301 Apart from [resetTransform], each transformation function first creates 302 specific transformation matrix and pre-multiplies the current transformation by it. 303 304 Current coordinate system (transformation) can be saved and restored using [save] and [restore]. 305 306 The following functions can be used to make calculations on 2x3 transformation matrices. 307 A 2x3 matrix is represented as float[6]. 308 309 state_handling = 310 ## State Handling 311 312 NanoVega contains state which represents how paths will be rendered. 313 The state contains transform, fill and stroke styles, text and font styles, 314 and scissor clipping. 315 316 render_styles = 317 ## Render Styles 318 319 Fill and stroke render style can be either a solid color or a paint which is a gradient or a pattern. 320 Solid color is simply defined as a color value, different kinds of paints can be created 321 using [linearGradient], [boxGradient], [radialGradient] and [imagePattern]. 322 323 Current render style can be saved and restored using [save] and [restore]. 324 325 Note that if you want "almost perfect" pixel rendering, you should set aspect ratio to 1, 326 and use `integerCoord+0.5f` as pixel coordinates. 327 328 render_transformations = 329 ## Render Transformations 330 331 Transformation matrix management for the current rendering style. Transformations are applied in 332 backwards order. I.e. if you first translate, and then rotate, your path will be rotated around 333 it's origin, and then translated to the destination point. 334 335 scissoring = 336 ## Scissoring 337 338 Scissoring allows you to clip the rendering into a rectangle. This is useful for various 339 user interface cases like rendering a text edit or a timeline. 340 341 images = 342 ## Images 343 344 NanoVega allows you to load image files in various formats (if arsd loaders are in place) to be used for rendering. 345 In addition you can upload your own image. 346 The parameter imageFlagsList is a list of flags defined in [NVGImageFlag]. 347 348 If you will use your image as fill pattern, it will be scaled by default. To make it repeat, pass 349 [NVGImageFlag.RepeatX] and [NVGImageFlag.RepeatY] flags to image creation function respectively. 350 351 paints = 352 ## Paints 353 354 NanoVega supports four types of paints: linear gradient, box gradient, radial gradient and image pattern. 355 These can be used as paints for strokes and fills. 356 357 gpu_affine = 358 ## Render-Time Affine Transformations 359 360 It is possible to set affine transformation matrix for GPU. That matrix will 361 be applied by the shader code. This can be used to quickly translate and rotate 362 saved paths. Call this $(B only) between [beginFrame] and [endFrame]. 363 364 Note that [beginFrame] resets this matrix to identity one. 365 366 $(WARNING Don't use this for scaling or skewing, or your image will be heavily distorted!) 367 368 paths = 369 ## Paths 370 371 Drawing a new shape starts with [beginPath], it clears all the currently defined paths. 372 Then you define one or more paths and sub-paths which describe the shape. The are functions 373 to draw common shapes like rectangles and circles, and lower level step-by-step functions, 374 which allow to define a path curve by curve. 375 376 NanoVega uses even-odd fill rule to draw the shapes. Solid shapes should have counter clockwise 377 winding and holes should have counter clockwise order. To specify winding of a path you can 378 call [pathWinding]. This is useful especially for the common shapes, which are drawn CCW. 379 380 Finally you can fill the path using current fill style by calling [fill], and stroke it 381 with current stroke style by calling [stroke]. 382 383 The curve segments and sub-paths are transformed by the current transform. 384 385 picking_api = 386 ## Picking API 387 388 This is picking API that works directly on paths, without rasterizing them first. 389 390 [beginFrame] resets picking state. Then you can create paths as usual, but 391 there is a possibility to perform hit checks $(B before) rasterizing a path. 392 Call either id assigning functions ([currFillHitId]/[currStrokeHitId]), or 393 immediate hit test functions ([hitTestCurrFill]/[hitTestCurrStroke]) 394 before rasterizing (i.e. calling [fill] or [stroke]) to perform hover 395 effects, for example. 396 397 Also note that picking API is ignoring GPU affine transformation matrix. 398 You can "untransform" picking coordinates before checking with [gpuUntransformPoint]. 399 400 $(WARNING Picking API completely ignores clipping. If you want to check for 401 clip regions, you have to manually register them as fill/stroke paths, 402 and perform the necessary logic. See [hitTestForId] function.) 403 404 clipping = 405 ## Clipping with paths 406 407 If scissoring is not enough for you, you can clip rendering with arbitrary path, 408 or with combination of paths. Clip region is saved by [save] and restored by 409 [restore] NanoVega functions. You can combine clip paths with various logic 410 operations, see [NVGClipMode]. 411 412 Note that both [clip] and [clipStroke] are ignoring scissoring (i.e. clip mask 413 is created as if there was no scissor set). Actual rendering is affected by 414 scissors, though. 415 416 text_api = 417 ## Text 418 419 NanoVega allows you to load .ttf files and use the font to render text. 420 You have to load some font, and set proper font size before doing anything 421 with text, as there is no "default" font provided by NanoVega. Also, don't 422 forget to check return value of `createFont()`, 'cause NanoVega won't fail 423 if it cannot load font, it will silently try to render nothing. 424 425 The appearance of the text can be defined by setting the current text style 426 and by specifying the fill color. Common text and font settings such as 427 font size, letter spacing and text align are supported. Font blur allows you 428 to create simple text effects such as drop shadows. 429 430 At render time the font face can be set based on the font handles or name. 431 432 Font measure functions return values in local space, the calculations are 433 carried in the same resolution as the final rendering. This is done because 434 the text glyph positions are snapped to the nearest pixels sharp rendering. 435 436 The local space means that values are not rotated or scale as per the current 437 transformation. For example if you set font size to 12, which would mean that 438 line height is 16, then regardless of the current scaling and rotation, the 439 returned line height is always 16. Some measures may vary because of the scaling 440 since aforementioned pixel snapping. 441 442 While this may sound a little odd, the setup allows you to always render the 443 same way regardless of scaling. I.e. following works regardless of scaling: 444 445 ---------------------- 446 string txt = "Text me up."; 447 vg.textBounds(x, y, txt, bounds); 448 vg.beginPath(); 449 vg.roundedRect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1], 6); 450 vg.fill(); 451 ---------------------- 452 453 Note: currently only solid color fill is supported for text. 454 455 font_stash = 456 ## Low-Level Font Engine (FontStash) 457 458 FontStash is used to load fonts, to manage font atlases, and to get various text metrics. 459 You don't need any graphics context to use FontStash, so you can do things like text 460 layouting outside of your rendering code. Loaded fonts are refcounted, so it is cheap 461 to create new FontStash, copy fonts from NanoVega context into it, and use that new 462 FontStash to do some UI layouting, for example. Also note that you can get text metrics 463 without creating glyph bitmaps (by using [FONSTextBoundsIterator], for example); this way 464 you don't need to waste CPU and memory resources to render unneeded images into font atlas, 465 and you can layout alot of text very fast. 466 467 Note that "FontStash" is abbrevated as "FONS". So when you see some API that contains 468 word "fons" in it, this is not a typo, and it should not read "font" intead. 469 470 TODO for Ketmar: write some nice example code here, and finish documenting FontStash API. 471 */ 472 module arsd.nanovega; 473 private: 474 475 version(aliced) { 476 import iv.meta; 477 import iv.vfs; 478 } else { 479 private alias usize = size_t; 480 // i fear phobos! 481 private template Unqual(T) { 482 static if (is(T U == immutable U)) alias Unqual = U; 483 else static if (is(T U == shared inout const U)) alias Unqual = U; 484 else static if (is(T U == shared inout U)) alias Unqual = U; 485 else static if (is(T U == shared const U)) alias Unqual = U; 486 else static if (is(T U == shared U)) alias Unqual = U; 487 else static if (is(T U == inout const U)) alias Unqual = U; 488 else static if (is(T U == inout U)) alias Unqual = U; 489 else static if (is(T U == const U)) alias Unqual = U; 490 else alias Unqual = T; 491 } 492 private template isAnyCharType(T, bool unqual=false) { 493 static if (unqual) private alias UT = Unqual!T; else private alias UT = T; 494 enum isAnyCharType = is(UT == char) || is(UT == wchar) || is(UT == dchar); 495 } 496 private template isWideCharType(T, bool unqual=false) { 497 static if (unqual) private alias UT = Unqual!T; else private alias UT = T; 498 enum isWideCharType = is(UT == wchar) || is(UT == dchar); 499 } 500 } 501 version(nanovg_disable_vfs) { 502 enum NanoVegaHasIVVFS = false; 503 } else { 504 static if (is(typeof((){import iv.vfs;}))) { 505 enum NanoVegaHasIVVFS = true; 506 import iv.vfs; 507 } else { 508 enum NanoVegaHasIVVFS = false; 509 } 510 } 511 512 // ////////////////////////////////////////////////////////////////////////// // 513 // engine 514 // ////////////////////////////////////////////////////////////////////////// // 515 import core.stdc.stdlib : malloc, realloc, free; 516 import core.stdc..string : memset, memcpy, strlen; 517 import std.math : PI; 518 519 //version = nanovg_force_stb_ttf; 520 521 version(Posix) { 522 version = nanovg_use_freetype; 523 } else { 524 version = nanovg_disable_fontconfig; 525 } 526 527 version (bindbc) { 528 version = nanovg_builtin_fontconfig_bindings; 529 version = nanovg_bindbc_opengl_bindings; 530 version = nanovg_bindbc_freetype_bindings; 531 version(BindFT_Dynamic) 532 static assert(0, "AsumFace was too lazy to write the code for the dynamic bindbc freetype bindings"); 533 else { 534 version(BindFT_Static) {} 535 else 536 static assert(0, "well, duh. you got to pass the BindFT_Static version identifier to the compiler"); 537 } 538 } else version(aliced) { 539 version = nanovg_default_no_font_aa; 540 version = nanovg_builtin_fontconfig_bindings; 541 version = nanovg_builtin_freetype_bindings; 542 version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings 543 } else { 544 version = nanovg_builtin_fontconfig_bindings; 545 version = nanovg_builtin_freetype_bindings; 546 version = nanovg_builtin_opengl_bindings; // use `arsd.simpledisplay` to get basic bindings 547 } 548 549 version(nanovg_disable_fontconfig) { 550 public enum NanoVegaHasFontConfig = false; 551 } else { 552 public enum NanoVegaHasFontConfig = true; 553 version(nanovg_builtin_fontconfig_bindings) {} else import iv.fontconfig; 554 } 555 556 //version = nanovg_bench_flatten; 557 558 /++ 559 Annotation to indicate the marked function is compatible with [arsd.script]. 560 561 562 Any function that takes a [Color] argument will be passed a string instead. 563 564 Scriptable Functions 565 ==================== 566 567 $(UDA_USES) 568 569 $(ALWAYS_DOCUMENT) 570 +/ 571 private enum scriptable = "arsd_jsvar_compatible"; 572 573 public: 574 alias NVG_PI = PI; 575 576 enum NanoVegaHasArsdColor = (is(typeof((){ import arsd.color; }))); 577 enum NanoVegaHasArsdImage = (is(typeof((){ import arsd.color; import arsd.image; }))); 578 579 static if (NanoVegaHasArsdColor) private import arsd.color; 580 static if (NanoVegaHasArsdImage) { 581 private import arsd.image; 582 } else { 583 void stbi_set_unpremultiply_on_load (int flag_true_if_should_unpremultiply) {} 584 void stbi_convert_iphone_png_to_rgb (int flag_true_if_should_convert) {} 585 ubyte* stbi_load (const(char)* filename, int* x, int* y, int* comp, int req_comp) { return null; } 586 ubyte* stbi_load_from_memory (const(void)* buffer, int len, int* x, int* y, int* comp, int req_comp) { return null; } 587 void stbi_image_free (void* retval_from_stbi_load) {} 588 } 589 590 version(nanovg_default_no_font_aa) { 591 __gshared bool NVG_INVERT_FONT_AA = false; 592 } else { 593 __gshared bool NVG_INVERT_FONT_AA = true; 594 } 595 596 597 /// this is branchless for ints on x86, and even for longs on x86_64 598 public ubyte nvgClampToByte(T) (T n) pure nothrow @safe @nogc if (__traits(isIntegral, T)) { 599 static if (__VERSION__ > 2067) pragma(inline, true); 600 static if (T.sizeof == 2 || T.sizeof == 4) { 601 static if (__traits(isUnsigned, T)) { 602 return cast(ubyte)(n&0xff|(255-((-cast(int)(n < 256))>>24))); 603 } else { 604 n &= -cast(int)(n >= 0); 605 return cast(ubyte)(n|((255-cast(int)n)>>31)); 606 } 607 } else static if (T.sizeof == 1) { 608 static assert(__traits(isUnsigned, T), "clampToByte: signed byte? no, really?"); 609 return cast(ubyte)n; 610 } else static if (T.sizeof == 8) { 611 static if (__traits(isUnsigned, T)) { 612 return cast(ubyte)(n&0xff|(255-((-cast(long)(n < 256))>>56))); 613 } else { 614 n &= -cast(long)(n >= 0); 615 return cast(ubyte)(n|((255-cast(long)n)>>63)); 616 } 617 } else { 618 static assert(false, "clampToByte: integer too big"); 619 } 620 } 621 622 623 /// NanoVega RGBA color 624 /// Group: color_utils 625 public align(1) struct NVGColor { 626 align(1): 627 public: 628 float[4] rgba = 0; /// default color is transparent (a=1 is opaque) 629 630 public: 631 @property string toString () const @safe { import std..string : format; return "NVGColor(%s,%s,%s,%s)".format(r, g, b, a); } 632 633 public: 634 enum transparent = NVGColor(0.0f, 0.0f, 0.0f, 0.0f); 635 enum k8orange = NVGColor(1.0f, 0.5f, 0.0f, 1.0f); 636 637 enum aliceblue = NVGColor(240, 248, 255); 638 enum antiquewhite = NVGColor(250, 235, 215); 639 enum aqua = NVGColor(0, 255, 255); 640 enum aquamarine = NVGColor(127, 255, 212); 641 enum azure = NVGColor(240, 255, 255); 642 enum beige = NVGColor(245, 245, 220); 643 enum bisque = NVGColor(255, 228, 196); 644 enum black = NVGColor(0, 0, 0); // basic color 645 enum blanchedalmond = NVGColor(255, 235, 205); 646 enum blue = NVGColor(0, 0, 255); // basic color 647 enum blueviolet = NVGColor(138, 43, 226); 648 enum brown = NVGColor(165, 42, 42); 649 enum burlywood = NVGColor(222, 184, 135); 650 enum cadetblue = NVGColor(95, 158, 160); 651 enum chartreuse = NVGColor(127, 255, 0); 652 enum chocolate = NVGColor(210, 105, 30); 653 enum coral = NVGColor(255, 127, 80); 654 enum cornflowerblue = NVGColor(100, 149, 237); 655 enum cornsilk = NVGColor(255, 248, 220); 656 enum crimson = NVGColor(220, 20, 60); 657 enum cyan = NVGColor(0, 255, 255); // basic color 658 enum darkblue = NVGColor(0, 0, 139); 659 enum darkcyan = NVGColor(0, 139, 139); 660 enum darkgoldenrod = NVGColor(184, 134, 11); 661 enum darkgray = NVGColor(169, 169, 169); 662 enum darkgreen = NVGColor(0, 100, 0); 663 enum darkgrey = NVGColor(169, 169, 169); 664 enum darkkhaki = NVGColor(189, 183, 107); 665 enum darkmagenta = NVGColor(139, 0, 139); 666 enum darkolivegreen = NVGColor(85, 107, 47); 667 enum darkorange = NVGColor(255, 140, 0); 668 enum darkorchid = NVGColor(153, 50, 204); 669 enum darkred = NVGColor(139, 0, 0); 670 enum darksalmon = NVGColor(233, 150, 122); 671 enum darkseagreen = NVGColor(143, 188, 143); 672 enum darkslateblue = NVGColor(72, 61, 139); 673 enum darkslategray = NVGColor(47, 79, 79); 674 enum darkslategrey = NVGColor(47, 79, 79); 675 enum darkturquoise = NVGColor(0, 206, 209); 676 enum darkviolet = NVGColor(148, 0, 211); 677 enum deeppink = NVGColor(255, 20, 147); 678 enum deepskyblue = NVGColor(0, 191, 255); 679 enum dimgray = NVGColor(105, 105, 105); 680 enum dimgrey = NVGColor(105, 105, 105); 681 enum dodgerblue = NVGColor(30, 144, 255); 682 enum firebrick = NVGColor(178, 34, 34); 683 enum floralwhite = NVGColor(255, 250, 240); 684 enum forestgreen = NVGColor(34, 139, 34); 685 enum fuchsia = NVGColor(255, 0, 255); 686 enum gainsboro = NVGColor(220, 220, 220); 687 enum ghostwhite = NVGColor(248, 248, 255); 688 enum gold = NVGColor(255, 215, 0); 689 enum goldenrod = NVGColor(218, 165, 32); 690 enum gray = NVGColor(128, 128, 128); // basic color 691 enum green = NVGColor(0, 128, 0); // basic color 692 enum greenyellow = NVGColor(173, 255, 47); 693 enum grey = NVGColor(128, 128, 128); // basic color 694 enum honeydew = NVGColor(240, 255, 240); 695 enum hotpink = NVGColor(255, 105, 180); 696 enum indianred = NVGColor(205, 92, 92); 697 enum indigo = NVGColor(75, 0, 130); 698 enum ivory = NVGColor(255, 255, 240); 699 enum khaki = NVGColor(240, 230, 140); 700 enum lavender = NVGColor(230, 230, 250); 701 enum lavenderblush = NVGColor(255, 240, 245); 702 enum lawngreen = NVGColor(124, 252, 0); 703 enum lemonchiffon = NVGColor(255, 250, 205); 704 enum lightblue = NVGColor(173, 216, 230); 705 enum lightcoral = NVGColor(240, 128, 128); 706 enum lightcyan = NVGColor(224, 255, 255); 707 enum lightgoldenrodyellow = NVGColor(250, 250, 210); 708 enum lightgray = NVGColor(211, 211, 211); 709 enum lightgreen = NVGColor(144, 238, 144); 710 enum lightgrey = NVGColor(211, 211, 211); 711 enum lightpink = NVGColor(255, 182, 193); 712 enum lightsalmon = NVGColor(255, 160, 122); 713 enum lightseagreen = NVGColor(32, 178, 170); 714 enum lightskyblue = NVGColor(135, 206, 250); 715 enum lightslategray = NVGColor(119, 136, 153); 716 enum lightslategrey = NVGColor(119, 136, 153); 717 enum lightsteelblue = NVGColor(176, 196, 222); 718 enum lightyellow = NVGColor(255, 255, 224); 719 enum lime = NVGColor(0, 255, 0); 720 enum limegreen = NVGColor(50, 205, 50); 721 enum linen = NVGColor(250, 240, 230); 722 enum magenta = NVGColor(255, 0, 255); // basic color 723 enum maroon = NVGColor(128, 0, 0); 724 enum mediumaquamarine = NVGColor(102, 205, 170); 725 enum mediumblue = NVGColor(0, 0, 205); 726 enum mediumorchid = NVGColor(186, 85, 211); 727 enum mediumpurple = NVGColor(147, 112, 219); 728 enum mediumseagreen = NVGColor(60, 179, 113); 729 enum mediumslateblue = NVGColor(123, 104, 238); 730 enum mediumspringgreen = NVGColor(0, 250, 154); 731 enum mediumturquoise = NVGColor(72, 209, 204); 732 enum mediumvioletred = NVGColor(199, 21, 133); 733 enum midnightblue = NVGColor(25, 25, 112); 734 enum mintcream = NVGColor(245, 255, 250); 735 enum mistyrose = NVGColor(255, 228, 225); 736 enum moccasin = NVGColor(255, 228, 181); 737 enum navajowhite = NVGColor(255, 222, 173); 738 enum navy = NVGColor(0, 0, 128); 739 enum oldlace = NVGColor(253, 245, 230); 740 enum olive = NVGColor(128, 128, 0); 741 enum olivedrab = NVGColor(107, 142, 35); 742 enum orange = NVGColor(255, 165, 0); 743 enum orangered = NVGColor(255, 69, 0); 744 enum orchid = NVGColor(218, 112, 214); 745 enum palegoldenrod = NVGColor(238, 232, 170); 746 enum palegreen = NVGColor(152, 251, 152); 747 enum paleturquoise = NVGColor(175, 238, 238); 748 enum palevioletred = NVGColor(219, 112, 147); 749 enum papayawhip = NVGColor(255, 239, 213); 750 enum peachpuff = NVGColor(255, 218, 185); 751 enum peru = NVGColor(205, 133, 63); 752 enum pink = NVGColor(255, 192, 203); 753 enum plum = NVGColor(221, 160, 221); 754 enum powderblue = NVGColor(176, 224, 230); 755 enum purple = NVGColor(128, 0, 128); 756 enum red = NVGColor(255, 0, 0); // basic color 757 enum rosybrown = NVGColor(188, 143, 143); 758 enum royalblue = NVGColor(65, 105, 225); 759 enum saddlebrown = NVGColor(139, 69, 19); 760 enum salmon = NVGColor(250, 128, 114); 761 enum sandybrown = NVGColor(244, 164, 96); 762 enum seagreen = NVGColor(46, 139, 87); 763 enum seashell = NVGColor(255, 245, 238); 764 enum sienna = NVGColor(160, 82, 45); 765 enum silver = NVGColor(192, 192, 192); 766 enum skyblue = NVGColor(135, 206, 235); 767 enum slateblue = NVGColor(106, 90, 205); 768 enum slategray = NVGColor(112, 128, 144); 769 enum slategrey = NVGColor(112, 128, 144); 770 enum snow = NVGColor(255, 250, 250); 771 enum springgreen = NVGColor(0, 255, 127); 772 enum steelblue = NVGColor(70, 130, 180); 773 enum tan = NVGColor(210, 180, 140); 774 enum teal = NVGColor(0, 128, 128); 775 enum thistle = NVGColor(216, 191, 216); 776 enum tomato = NVGColor(255, 99, 71); 777 enum turquoise = NVGColor(64, 224, 208); 778 enum violet = NVGColor(238, 130, 238); 779 enum wheat = NVGColor(245, 222, 179); 780 enum white = NVGColor(255, 255, 255); // basic color 781 enum whitesmoke = NVGColor(245, 245, 245); 782 enum yellow = NVGColor(255, 255, 0); // basic color 783 enum yellowgreen = NVGColor(154, 205, 50); 784 785 nothrow @safe @nogc: 786 public: 787 /// 788 this (ubyte ar, ubyte ag, ubyte ab, ubyte aa=255) pure { 789 pragma(inline, true); 790 r = ar/255.0f; 791 g = ag/255.0f; 792 b = ab/255.0f; 793 a = aa/255.0f; 794 } 795 796 /// 797 this (float ar, float ag, float ab, float aa=1.0f) pure { 798 pragma(inline, true); 799 r = ar; 800 g = ag; 801 b = ab; 802 a = aa; 803 } 804 805 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color) 806 this (uint c) pure { 807 pragma(inline, true); 808 r = (c&0xff)/255.0f; 809 g = ((c>>8)&0xff)/255.0f; 810 b = ((c>>16)&0xff)/255.0f; 811 a = ((c>>24)&0xff)/255.0f; 812 } 813 814 /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb" 815 this (const(char)[] srgb) { 816 static int c2d (char ch) pure nothrow @safe @nogc { 817 pragma(inline, true); 818 return 819 ch >= '0' && ch <= '9' ? ch-'0' : 820 ch >= 'A' && ch <= 'F' ? ch-'A'+10 : 821 ch >= 'a' && ch <= 'f' ? ch-'a'+10 : 822 -1; 823 } 824 int[8] digs; 825 int dc = -1; 826 foreach (immutable char ch; srgb) { 827 if (ch <= ' ') continue; 828 if (ch == '#') { 829 if (dc != -1) { dc = -1; break; } 830 dc = 0; 831 } else { 832 if (dc >= digs.length) { dc = -1; break; } 833 if ((digs[dc++] = c2d(ch)) < 0) { dc = -1; break; } 834 } 835 } 836 switch (dc) { 837 case 3: // rgb 838 a = 1.0f; 839 r = digs[0]/15.0f; 840 g = digs[1]/15.0f; 841 b = digs[2]/15.0f; 842 break; 843 case 4: // argb 844 a = digs[0]/15.0f; 845 r = digs[1]/15.0f; 846 g = digs[2]/15.0f; 847 b = digs[3]/15.0f; 848 break; 849 case 6: // rrggbb 850 a = 1.0f; 851 r = (digs[0]*16+digs[1])/255.0f; 852 g = (digs[2]*16+digs[3])/255.0f; 853 b = (digs[4]*16+digs[5])/255.0f; 854 break; 855 case 8: // aarrggbb 856 a = (digs[0]*16+digs[1])/255.0f; 857 r = (digs[2]*16+digs[3])/255.0f; 858 g = (digs[4]*16+digs[5])/255.0f; 859 b = (digs[6]*16+digs[7])/255.0f; 860 break; 861 default: 862 break; 863 } 864 } 865 866 /// Is this color completely opaque? 867 @property bool isOpaque () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] >= 1.0f); } 868 /// Is this color completely transparent? 869 @property bool isTransparent () const pure nothrow @trusted @nogc { pragma(inline, true); return (rgba.ptr[3] <= 0.0f); } 870 871 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color) 872 @property uint asUint () const pure { 873 pragma(inline, true); 874 return 875 cast(uint)(r*255)| 876 (cast(uint)(g*255)<<8)| 877 (cast(uint)(b*255)<<16)| 878 (cast(uint)(a*255)<<24); 879 } 880 881 alias asUintABGR = asUint; /// Ditto. 882 883 /// AABBGGRR (same format as little-endian RGBA image, coincidentally, the same as arsd.color) 884 static NVGColor fromUint (uint c) pure { pragma(inline, true); return NVGColor(c); } 885 886 alias fromUintABGR = fromUint; /// Ditto. 887 888 /// AARRGGBB 889 @property uint asUintARGB () const pure { 890 pragma(inline, true); 891 return 892 cast(uint)(b*255)| 893 (cast(uint)(g*255)<<8)| 894 (cast(uint)(r*255)<<16)| 895 (cast(uint)(a*255)<<24); 896 } 897 898 /// AARRGGBB 899 static NVGColor fromUintARGB (uint c) pure { pragma(inline, true); return NVGColor((c>>16)&0xff, (c>>8)&0xff, c&0xff, (c>>24)&0xff); } 900 901 @property ref inout(float) r () inout pure @trusted { pragma(inline, true); return rgba.ptr[0]; } /// 902 @property ref inout(float) g () inout pure @trusted { pragma(inline, true); return rgba.ptr[1]; } /// 903 @property ref inout(float) b () inout pure @trusted { pragma(inline, true); return rgba.ptr[2]; } /// 904 @property ref inout(float) a () inout pure @trusted { pragma(inline, true); return rgba.ptr[3]; } /// 905 906 ref NVGColor applyTint() (in auto ref NVGColor tint) nothrow @trusted @nogc { 907 if (tint.a == 0) return this; 908 foreach (immutable idx, ref float v; rgba[0..4]) { 909 v = nvg__clamp(v*tint.rgba.ptr[idx], 0.0f, 1.0f); 910 } 911 return this; 912 } 913 914 NVGHSL asHSL() (bool useWeightedLightness=false) const { pragma(inline, true); return NVGHSL.fromColor(this, useWeightedLightness); } /// 915 static fromHSL() (in auto ref NVGHSL hsl) { pragma(inline, true); return hsl.asColor; } /// 916 917 static if (NanoVegaHasArsdColor) { 918 Color toArsd () const { pragma(inline, true); return Color(cast(int)(r*255), cast(int)(g*255), cast(int)(b*255), cast(int)(a*255)); } /// 919 static NVGColor fromArsd (in Color c) { pragma(inline, true); return NVGColor(c.r, c.g, c.b, c.a); } /// 920 /// 921 this (in Color c) { 922 version(aliced) pragma(inline, true); 923 r = c.r/255.0f; 924 g = c.g/255.0f; 925 b = c.b/255.0f; 926 a = c.a/255.0f; 927 } 928 } 929 } 930 931 932 /// NanoVega A-HSL color 933 /// Group: color_utils 934 public align(1) struct NVGHSL { 935 align(1): 936 float h=0, s=0, l=1, a=1; /// 937 938 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)); } 939 940 nothrow @safe @nogc: 941 public: 942 /// 943 this (float ah, float as, float al, float aa=1) pure { pragma(inline, true); h = ah; s = as; l = al; a = aa; } 944 945 NVGColor asColor () const { pragma(inline, true); return nvgHSLA(h, s, l, a); } /// 946 947 // taken from Adam's arsd.color 948 /** Converts an RGB color into an HSL triplet. 949 * [useWeightedLightness] will try to get a better value for luminosity for the human eye, 950 * which is more sensitive to green than red and more to red than blue. 951 * If it is false, it just does average of the rgb. */ 952 static NVGHSL fromColor() (in auto ref NVGColor c, bool useWeightedLightness=false) pure { 953 NVGHSL res; 954 res.a = c.a; 955 float r1 = c.r; 956 float g1 = c.g; 957 float b1 = c.b; 958 959 float maxColor = r1; 960 if (g1 > maxColor) maxColor = g1; 961 if (b1 > maxColor) maxColor = b1; 962 float minColor = r1; 963 if (g1 < minColor) minColor = g1; 964 if (b1 < minColor) minColor = b1; 965 966 res.l = (maxColor+minColor)/2; 967 if (useWeightedLightness) { 968 // the colors don't affect the eye equally 969 // this is a little more accurate than plain HSL numbers 970 res.l = 0.2126*r1+0.7152*g1+0.0722*b1; 971 } 972 if (maxColor != minColor) { 973 if (res.l < 0.5) { 974 res.s = (maxColor-minColor)/(maxColor+minColor); 975 } else { 976 res.s = (maxColor-minColor)/(2.0-maxColor-minColor); 977 } 978 if (r1 == maxColor) { 979 res.h = (g1-b1)/(maxColor-minColor); 980 } else if(g1 == maxColor) { 981 res.h = 2.0+(b1-r1)/(maxColor-minColor); 982 } else { 983 res.h = 4.0+(r1-g1)/(maxColor-minColor); 984 } 985 } 986 987 res.h = res.h*60; 988 if (res.h < 0) res.h += 360; 989 res.h /= 360; 990 991 return res; 992 } 993 } 994 995 996 //version = nanovega_debug_image_manager; 997 //version = nanovega_debug_image_manager_rc; 998 999 /** NanoVega image handle. 1000 * 1001 * This is refcounted struct, so you don't need to do anything special to free it once it is allocated. 1002 * 1003 * Group: images 1004 */ 1005 struct NVGImage { 1006 enum isOpaqueStruct = true; 1007 private: 1008 NVGContext ctx; 1009 int id; // backend image id 1010 1011 public: 1012 /// 1013 this() (in auto ref NVGImage src) nothrow @trusted @nogc { 1014 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); } 1015 if (src.id > 0 && src.ctx !is null) { 1016 ctx = cast(NVGContext)src.ctx; 1017 id = src.id; 1018 ctx.nvg__imageIncRef(id); 1019 } 1020 } 1021 1022 /// 1023 ~this () nothrow @trusted @nogc { version(aliced) pragma(inline, true); clear(); } 1024 1025 /// 1026 this (this) nothrow @trusted @nogc { 1027 version(aliced) pragma(inline, true); 1028 if (id > 0 && ctx !is null) { 1029 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p postblit (imgid=%d)\n", &this, id); } 1030 ctx.nvg__imageIncRef(id); 1031 } 1032 } 1033 1034 /// 1035 void opAssign() (in auto ref NVGImage src) nothrow @trusted @nogc { 1036 if (src.id <= 0 || src.ctx is null) { 1037 clear(); 1038 } else { 1039 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); } 1040 if (src.id > 0 && src.ctx !is null) (cast(NVGContext)src.ctx).nvg__imageIncRef(src.id); 1041 if (id > 0 && ctx !is null) ctx.nvg__imageDecRef(id); 1042 ctx = cast(NVGContext)src.ctx; 1043 id = src.id; 1044 } 1045 } 1046 1047 /// Free this image. 1048 void clear () nothrow @trusted @nogc { 1049 if (id > 0 && ctx !is null) { 1050 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("NVGImage %p cleared (imgid=%d)\n", &this, id); } 1051 ctx.nvg__imageDecRef(id); 1052 } 1053 id = 0; 1054 ctx = null; 1055 } 1056 1057 /// Is this image valid? 1058 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (id > 0 && ctx.valid); } 1059 1060 /// Is the given image valid and comes from the same context? 1061 @property bool isSameContext (const(NVGContext) actx) const pure nothrow @safe @nogc { pragma(inline, true); return (actx !is null && ctx is actx); } 1062 1063 /// Returns image width, or zero for invalid image. 1064 int width () const nothrow @trusted @nogc { 1065 int w = 0; 1066 if (valid) { 1067 int h = void; 1068 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h); 1069 } 1070 return w; 1071 } 1072 1073 /// Returns image height, or zero for invalid image. 1074 int height () const nothrow @trusted @nogc { 1075 int h = 0; 1076 if (valid) { 1077 int w = void; 1078 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, id, &w, &h); 1079 } 1080 return h; 1081 } 1082 } 1083 1084 1085 /// Paint parameters for various fills. Don't change anything here! 1086 /// Group: render_styles 1087 public struct NVGPaint { 1088 enum isOpaqueStruct = true; 1089 1090 NVGMatrix xform; 1091 float[2] extent = 0.0f; 1092 float radius = 0.0f; 1093 float feather = 0.0f; 1094 NVGColor innerColor; /// this can be used to modulate images (fill/font) 1095 NVGColor middleColor; 1096 NVGColor outerColor; 1097 float midp = -1; // middle stop for 3-color gradient 1098 NVGImage image; 1099 bool simpleColor; /// if `true`, only innerColor is used, and this is solid-color paint 1100 1101 this() (in auto ref NVGPaint p) nothrow @trusted @nogc { 1102 xform = p.xform; 1103 extent[] = p.extent[]; 1104 radius = p.radius; 1105 feather = p.feather; 1106 innerColor = p.innerColor; 1107 middleColor = p.middleColor; 1108 midp = p.midp; 1109 outerColor = p.outerColor; 1110 image = p.image; 1111 simpleColor = p.simpleColor; 1112 } 1113 1114 void opAssign() (in auto ref NVGPaint p) nothrow @trusted @nogc { 1115 xform = p.xform; 1116 extent[] = p.extent[]; 1117 radius = p.radius; 1118 feather = p.feather; 1119 innerColor = p.innerColor; 1120 middleColor = p.middleColor; 1121 midp = p.midp; 1122 outerColor = p.outerColor; 1123 image = p.image; 1124 simpleColor = p.simpleColor; 1125 } 1126 1127 void clear () nothrow @trusted @nogc { 1128 version(aliced) pragma(inline, true); 1129 import core.stdc..string : memset; 1130 image.clear(); 1131 memset(&this, 0, this.sizeof); 1132 simpleColor = true; 1133 } 1134 } 1135 1136 /// Path winding. 1137 /// Group: paths 1138 public enum NVGWinding { 1139 CCW = 1, /// Winding for solid shapes 1140 CW = 2, /// Winding for holes 1141 } 1142 1143 /// Path solidity. 1144 /// Group: paths 1145 public enum NVGSolidity { 1146 Solid = 1, /// Solid shape (CCW winding). 1147 Hole = 2, /// Hole (CW winding). 1148 } 1149 1150 /// Line cap style. 1151 /// Group: render_styles 1152 public enum NVGLineCap { 1153 Butt, /// 1154 Round, /// 1155 Square, /// 1156 Bevel, /// 1157 Miter, /// 1158 } 1159 1160 /// Text align. 1161 /// Group: text_api 1162 public align(1) struct NVGTextAlign { 1163 align(1): 1164 /// Horizontal align. 1165 enum H : ubyte { 1166 Left = 0, /// Default, align text horizontally to left. 1167 Center = 1, /// Align text horizontally to center. 1168 Right = 2, /// Align text horizontally to right. 1169 } 1170 1171 /// Vertical align. 1172 enum V : ubyte { 1173 Baseline = 0, /// Default, align text vertically to baseline. 1174 Top = 1, /// Align text vertically to top. 1175 Middle = 2, /// Align text vertically to middle. 1176 Bottom = 3, /// Align text vertically to bottom. 1177 } 1178 1179 pure nothrow @safe @nogc: 1180 public: 1181 this (H h) { pragma(inline, true); value = h; } /// 1182 this (V v) { pragma(inline, true); value = cast(ubyte)(v<<4); } /// 1183 this (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1184 this (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1185 void reset () { pragma(inline, true); value = 0; } /// 1186 void reset (H h, V v) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1187 void reset (V v, H h) { pragma(inline, true); value = cast(ubyte)(h|(v<<4)); } /// 1188 @property: 1189 bool left () const { pragma(inline, true); return ((value&0x0f) == H.Left); } /// 1190 void left (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Left : 0)); } /// 1191 bool center () const { pragma(inline, true); return ((value&0x0f) == H.Center); } /// 1192 void center (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Center : 0)); } /// 1193 bool right () const { pragma(inline, true); return ((value&0x0f) == H.Right); } /// 1194 void right (bool v) { pragma(inline, true); value = cast(ubyte)((value&0xf0)|(v ? H.Right : 0)); } /// 1195 // 1196 bool baseline () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Baseline); } /// 1197 void baseline (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Baseline<<4 : 0)); } /// 1198 bool top () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Top); } /// 1199 void top (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Top<<4 : 0)); } /// 1200 bool middle () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Middle); } /// 1201 void middle (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Middle<<4 : 0)); } /// 1202 bool bottom () const { pragma(inline, true); return (((value>>4)&0x0f) == V.Bottom); } /// 1203 void bottom (bool v) { pragma(inline, true); value = cast(ubyte)((value&0x0f)|(v ? V.Bottom<<4 : 0)); } /// 1204 // 1205 H horizontal () const { pragma(inline, true); return cast(H)(value&0x0f); } /// 1206 void horizontal (H v) { pragma(inline, true); value = (value&0xf0)|v; } /// 1207 // 1208 V vertical () const { pragma(inline, true); return cast(V)((value>>4)&0x0f); } /// 1209 void vertical (V v) { pragma(inline, true); value = (value&0x0f)|cast(ubyte)(v<<4); } /// 1210 // 1211 private: 1212 ubyte value = 0; // low nibble: horizontal; high nibble: vertical 1213 } 1214 1215 /// Blending type. 1216 /// Group: composite_operation 1217 public enum NVGBlendFactor { 1218 Zero = 1<<0, /// 1219 One = 1<<1, /// 1220 SrcColor = 1<<2, /// 1221 OneMinusSrcColor = 1<<3, /// 1222 DstColor = 1<<4, /// 1223 OneMinusDstColor = 1<<5, /// 1224 SrcAlpha = 1<<6, /// 1225 OneMinusSrcAlpha = 1<<7, /// 1226 DstAlpha = 1<<8, /// 1227 OneMinusDstAlpha = 1<<9, /// 1228 SrcAlphaSaturate = 1<<10, /// 1229 } 1230 1231 /// Composite operation (HTML5-alike). 1232 /// Group: composite_operation 1233 public enum NVGCompositeOperation { 1234 SourceOver, /// 1235 SourceIn, /// 1236 SourceOut, /// 1237 SourceAtop, /// 1238 DestinationOver, /// 1239 DestinationIn, /// 1240 DestinationOut, /// 1241 DestinationAtop, /// 1242 Lighter, /// 1243 Copy, /// 1244 Xor, /// 1245 } 1246 1247 /// Composite operation state. 1248 /// Group: composite_operation 1249 public struct NVGCompositeOperationState { 1250 bool simple; /// `true`: use `glBlendFunc()` instead of `glBlendFuncSeparate()` 1251 NVGBlendFactor srcRGB; /// 1252 NVGBlendFactor dstRGB; /// 1253 NVGBlendFactor srcAlpha; /// 1254 NVGBlendFactor dstAlpha; /// 1255 } 1256 1257 /// Mask combining more 1258 /// Group: clipping 1259 public enum NVGClipMode { 1260 None, /// normal rendering (i.e. render path instead of modifying clip region) 1261 Union, /// old mask will be masked with the current one; this is the default mode for [clip] 1262 Or, /// new mask will be added to the current one (logical `OR` operation); 1263 Xor, /// new mask will be logically `XOR`ed with the current one 1264 Sub, /// "subtract" current path from mask 1265 Replace, /// replace current mask 1266 Add = Or, /// Synonym 1267 } 1268 1269 /// Glyph position info. 1270 /// Group: text_api 1271 public struct NVGGlyphPosition { 1272 usize strpos; /// Position of the glyph in the input string. 1273 float x; /// The x-coordinate of the logical glyph position. 1274 float minx, maxx; /// The bounds of the glyph shape. 1275 } 1276 1277 /// Text row storage. 1278 /// Group: text_api 1279 public struct NVGTextRow(CT) if (isAnyCharType!CT) { 1280 alias CharType = CT; 1281 const(CT)[] s; 1282 int start; /// Index in the input text where the row starts. 1283 int end; /// Index in the input text where the row ends (one past the last character). 1284 float width; /// Logical width of the row. 1285 float minx, maxx; /// Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending. 1286 /// Get rest of the string. 1287 @property const(CT)[] rest () const pure nothrow @trusted @nogc { pragma(inline, true); return (end <= s.length ? s[end..$] : null); } 1288 /// Get current row. 1289 @property const(CT)[] row () const pure nothrow @trusted @nogc { pragma(inline, true); return s[start..end]; } 1290 @property const(CT)[] string () const pure nothrow @trusted @nogc { pragma(inline, true); return s; } 1291 @property void string(CT) (const(CT)[] v) pure nothrow @trusted @nogc { pragma(inline, true); s = v; } 1292 } 1293 1294 /// Image creation flags. 1295 /// Group: images 1296 public enum NVGImageFlag : uint { 1297 None = 0, /// Nothing special. 1298 GenerateMipmaps = 1<<0, /// Generate mipmaps during creation of the image. 1299 RepeatX = 1<<1, /// Repeat image in X direction. 1300 RepeatY = 1<<2, /// Repeat image in Y direction. 1301 FlipY = 1<<3, /// Flips (inverses) image in Y direction when rendered. 1302 Premultiplied = 1<<4, /// Image data has premultiplied alpha. 1303 ClampToBorderX = 1<<5, /// Clamp image to border (instead of clamping to edge by default) 1304 ClampToBorderY = 1<<6, /// Clamp image to border (instead of clamping to edge by default) 1305 NoFiltering = 1<<8, /// use GL_NEAREST instead of GL_LINEAR. Only affects upscaling if GenerateMipmaps is active. 1306 Nearest = NoFiltering, /// compatibility with original NanoVG 1307 NoDelete = 1<<16,/// Do not delete GL texture handle. 1308 } 1309 1310 alias NVGImageFlags = NVGImageFlag; /// Backwards compatibility for [NVGImageFlag]. 1311 1312 1313 // ////////////////////////////////////////////////////////////////////////// // 1314 private: 1315 1316 static T* xdup(T) (const(T)* ptr, int count) nothrow @trusted @nogc { 1317 import core.stdc.stdlib : malloc; 1318 import core.stdc..string : memcpy; 1319 if (count == 0) return null; 1320 T* res = cast(T*)malloc(T.sizeof*count); 1321 if (res is null) assert(0, "NanoVega: out of memory"); 1322 memcpy(res, ptr, T.sizeof*count); 1323 return res; 1324 } 1325 1326 // Internal Render API 1327 enum NVGtexture { 1328 Alpha = 0x01, 1329 RGBA = 0x02, 1330 } 1331 1332 struct NVGscissor { 1333 NVGMatrix xform; 1334 float[2] extent = -1.0f; 1335 } 1336 1337 /// General NanoVega vertex struct. Contains geometry coordinates, and (sometimes unused) texture coordinates. 1338 public struct NVGVertex { 1339 float x, y, u, v; 1340 } 1341 1342 struct NVGpath { 1343 int first; 1344 int count; 1345 bool closed; 1346 int nbevel; 1347 NVGVertex* fill; 1348 int nfill; 1349 NVGVertex* stroke; 1350 int nstroke; 1351 NVGWinding mWinding; 1352 bool convex; 1353 bool cloned; 1354 1355 @disable this (this); // no copies 1356 void opAssign() (in auto ref NVGpath a) { static assert(0, "no copies!"); } 1357 1358 void clear () nothrow @trusted @nogc { 1359 import core.stdc.stdlib : free; 1360 import core.stdc..string : memset; 1361 if (cloned) { 1362 if (stroke !is null && stroke !is fill) free(stroke); 1363 if (fill !is null) free(fill); 1364 } 1365 memset(&this, 0, this.sizeof); 1366 } 1367 1368 // won't clear current path 1369 void copyFrom (const NVGpath* src) nothrow @trusted @nogc { 1370 import core.stdc..string : memcpy; 1371 assert(src !is null); 1372 memcpy(&this, src, NVGpath.sizeof); 1373 this.fill = xdup(src.fill, src.nfill); 1374 if (src.stroke is src.fill) { 1375 this.stroke = this.fill; 1376 } else { 1377 this.stroke = xdup(src.stroke, src.nstroke); 1378 } 1379 this.cloned = true; 1380 } 1381 1382 public @property const(NVGVertex)[] fillVertices () const pure nothrow @trusted @nogc { 1383 pragma(inline, true); 1384 return (nfill > 0 ? fill[0..nfill] : null); 1385 } 1386 1387 public @property const(NVGVertex)[] strokeVertices () const pure nothrow @trusted @nogc { 1388 pragma(inline, true); 1389 return (nstroke > 0 ? stroke[0..nstroke] : null); 1390 } 1391 1392 public @property NVGWinding winding () const pure nothrow @trusted @nogc { pragma(inline, true); return mWinding; } 1393 public @property bool complex () const pure nothrow @trusted @nogc { pragma(inline, true); return !convex; } 1394 } 1395 1396 1397 struct NVGparams { 1398 void* userPtr; 1399 bool edgeAntiAlias; 1400 bool fontAA; 1401 bool function (void* uptr) nothrow @trusted @nogc renderCreate; 1402 int function (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc renderCreateTexture; 1403 bool function (void* uptr, int image) nothrow @trusted @nogc renderTextureIncRef; 1404 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 1405 bool function (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc renderUpdateTexture; 1406 bool function (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc renderGetTextureSize; 1407 void function (void* uptr, int width, int height) nothrow @trusted @nogc renderViewport; // called in [beginFrame] 1408 void function (void* uptr) nothrow @trusted @nogc renderCancel; 1409 void function (void* uptr) nothrow @trusted @nogc renderFlush; 1410 void function (void* uptr) nothrow @trusted @nogc renderPushClip; // backend should support stack of at least [NVG_MAX_STATES] elements 1411 void function (void* uptr) nothrow @trusted @nogc renderPopClip; // backend should support stack of at least [NVG_MAX_STATES] elements 1412 void function (void* uptr) nothrow @trusted @nogc renderResetClip; // reset current clip region to `non-clipped` 1413 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; 1414 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; 1415 void function (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc renderTriangles; 1416 void function (void* uptr, in ref NVGMatrix mat) nothrow @trusted @nogc renderSetAffine; 1417 void function (void* uptr) nothrow @trusted @nogc renderDelete; 1418 } 1419 1420 // ////////////////////////////////////////////////////////////////////////// // 1421 private: 1422 1423 enum NVG_INIT_FONTIMAGE_SIZE = 512; 1424 enum NVG_MAX_FONTIMAGE_SIZE = 2048; 1425 enum NVG_MAX_FONTIMAGES = 4; 1426 1427 enum NVG_INIT_COMMANDS_SIZE = 256; 1428 enum NVG_INIT_POINTS_SIZE = 128; 1429 enum NVG_INIT_PATHS_SIZE = 16; 1430 enum NVG_INIT_VERTS_SIZE = 256; 1431 enum NVG_MAX_STATES = 32; 1432 1433 public enum NVG_KAPPA90 = 0.5522847493f; /// Length proportional to radius of a cubic bezier handle for 90deg arcs. 1434 enum NVG_MIN_FEATHER = 0.001f; // it should be greater than zero, 'cause it is used in shader for divisions 1435 1436 enum Command { 1437 MoveTo = 0, 1438 LineTo = 1, 1439 BezierTo = 2, 1440 Close = 3, 1441 Winding = 4, 1442 } 1443 1444 enum PointFlag : int { 1445 Corner = 0x01, 1446 Left = 0x02, 1447 Bevel = 0x04, 1448 InnerBevelPR = 0x08, 1449 } 1450 1451 struct NVGstate { 1452 NVGCompositeOperationState compositeOperation; 1453 bool shapeAntiAlias = true; 1454 NVGPaint fill; 1455 NVGPaint stroke; 1456 float strokeWidth = 1.0f; 1457 float miterLimit = 10.0f; 1458 NVGLineCap lineJoin = NVGLineCap.Miter; 1459 NVGLineCap lineCap = NVGLineCap.Butt; 1460 float alpha = 1.0f; 1461 NVGMatrix xform; 1462 NVGscissor scissor; 1463 float fontSize = 16.0f; 1464 float letterSpacing = 0.0f; 1465 float lineHeight = 1.0f; 1466 float fontBlur = 0.0f; 1467 NVGTextAlign textAlign; 1468 int fontId = 0; 1469 bool evenOddMode = false; // use even-odd filling rule (required for some svgs); otherwise use non-zero fill 1470 // dashing 1471 enum MaxDashes = 32; // max 16 dashes 1472 float[MaxDashes] dashes; 1473 uint dashCount = 0; 1474 uint lastFlattenDashCount = 0; 1475 float dashStart = 0; 1476 float totalDashLen; 1477 bool firstDashIsGap = false; 1478 // dasher state for flattener 1479 bool dasherActive = false; 1480 1481 void clearPaint () nothrow @trusted @nogc { 1482 fill.clear(); 1483 stroke.clear(); 1484 } 1485 } 1486 1487 struct NVGpoint { 1488 float x, y; 1489 float dx, dy; 1490 float len; 1491 float dmx, dmy; 1492 ubyte flags; 1493 } 1494 1495 struct NVGpathCache { 1496 NVGpoint* points; 1497 int npoints; 1498 int cpoints; 1499 NVGpath* paths; 1500 int npaths; 1501 int cpaths; 1502 NVGVertex* verts; 1503 int nverts; 1504 int cverts; 1505 float[4] bounds; 1506 // this is required for saved paths 1507 bool strokeReady; 1508 bool fillReady; 1509 float strokeAlphaMul; 1510 float strokeWidth; 1511 float fringeWidth; 1512 bool evenOddMode; 1513 NVGClipMode clipmode; 1514 // non-saved path will not have this 1515 float* commands; 1516 int ncommands; 1517 1518 @disable this (this); // no copies 1519 void opAssign() (in auto ref NVGpathCache a) { static assert(0, "no copies!"); } 1520 1521 // won't clear current path 1522 void copyFrom (const NVGpathCache* src) nothrow @trusted @nogc { 1523 import core.stdc.stdlib : malloc; 1524 import core.stdc..string : memcpy, memset; 1525 assert(src !is null); 1526 memcpy(&this, src, NVGpathCache.sizeof); 1527 this.points = xdup(src.points, src.npoints); 1528 this.cpoints = src.npoints; 1529 this.verts = xdup(src.verts, src.nverts); 1530 this.cverts = src.nverts; 1531 this.commands = xdup(src.commands, src.ncommands); 1532 if (src.npaths > 0) { 1533 this.paths = cast(NVGpath*)malloc(src.npaths*NVGpath.sizeof); 1534 memset(this.paths, 0, npaths*NVGpath.sizeof); 1535 foreach (immutable pidx; 0..npaths) this.paths[pidx].copyFrom(&src.paths[pidx]); 1536 this.cpaths = src.npaths; 1537 } else { 1538 this.npaths = this.cpaths = 0; 1539 } 1540 } 1541 1542 void clear () nothrow @trusted @nogc { 1543 import core.stdc.stdlib : free; 1544 import core.stdc..string : memset; 1545 if (paths !is null) { 1546 foreach (ref p; paths[0..npaths]) p.clear(); 1547 free(paths); 1548 } 1549 if (points !is null) free(points); 1550 if (verts !is null) free(verts); 1551 if (commands !is null) free(commands); 1552 memset(&this, 0, this.sizeof); 1553 } 1554 } 1555 1556 /// Pointer to opaque NanoVega context structure. 1557 /// Group: context_management 1558 public alias NVGContext = NVGcontextinternal*; 1559 1560 /// FontStash context 1561 /// Group: font_stash 1562 public alias FONSContext = FONScontextInternal*; 1563 1564 /// Returns FontStash context of the given NanoVega context. 1565 /// Group: font_stash 1566 public FONSContext fonsContext (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.fs : null); } 1567 1568 /// Returns scale that should be applied to FontStash parameters due to matrix transformations on the context (or 1) 1569 /// Group: font_stash 1570 public float fonsScale (NVGContext ctx) /*pure*/ nothrow @trusted @nogc { 1571 pragma(inline, true); 1572 return (ctx !is null && ctx.contextAlive && ctx.nstates > 0 ? nvg__getFontScale(&ctx.states.ptr[ctx.nstates-1])*ctx.devicePxRatio : 1); 1573 } 1574 1575 /// Setup FontStash from the given NanoVega context font parameters. Note that this will apply transformation scale too. 1576 /// Returns `false` if FontStash or NanoVega context is not active. 1577 /// Group: font_stash 1578 public bool setupFonsFrom (FONSContext stash, NVGContext ctx) nothrow @trusted @nogc { 1579 if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false; 1580 NVGstate* state = nvg__getState(ctx); 1581 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 1582 stash.size = state.fontSize*scale; 1583 stash.spacing = state.letterSpacing*scale; 1584 stash.blur = state.fontBlur*scale; 1585 stash.textAlign = state.textAlign; 1586 stash.fontId = state.fontId; 1587 return true; 1588 } 1589 1590 /// Setup NanoVega context font parameters from the given FontStash. Note that NanoVega can apply transformation scale later. 1591 /// Returns `false` if FontStash or NanoVega context is not active. 1592 /// Group: font_stash 1593 public bool setupCtxFrom (NVGContext ctx, FONSContext stash) nothrow @trusted @nogc { 1594 if (stash is null || ctx is null || !ctx.contextAlive || ctx.nstates == 0) return false; 1595 NVGstate* state = nvg__getState(ctx); 1596 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 1597 state.fontSize = stash.size; 1598 state.letterSpacing = stash.spacing; 1599 state.fontBlur = stash.blur; 1600 state.textAlign = stash.textAlign; 1601 state.fontId = stash.fontId; 1602 return true; 1603 } 1604 1605 /** Bezier curve rasterizer. 1606 * 1607 * De Casteljau Bezier rasterizer is faster, but currently rasterizing curves with cusps sligtly wrong. 1608 * It doesn't really matter in practice. 1609 * 1610 * AFD tesselator is somewhat slower, but does cusps better. 1611 * 1612 * McSeem rasterizer should have the best quality, bit it is the slowest method. Basically, you will 1613 * never notice any visial difference (and this code is not really debugged), so you probably should 1614 * not use it. It is there for further experiments. 1615 */ 1616 public enum NVGTesselation { 1617 DeCasteljau, /// default: standard well-known tesselation algorithm 1618 AFD, /// adaptive forward differencing 1619 DeCasteljauMcSeem, /// standard well-known tesselation algorithm, with improvements from Maxim Shemanarev; slowest one, but should give best results 1620 } 1621 1622 /// Default tesselator for Bezier curves. 1623 public __gshared NVGTesselation NVG_DEFAULT_TESSELATOR = NVGTesselation.DeCasteljau; 1624 1625 1626 // some public info 1627 1628 /// valid only inside [beginFrame]/[endFrame] 1629 /// Group: context_management 1630 public int width (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mWidth : 0); } 1631 1632 /// valid only inside [beginFrame]/[endFrame] 1633 /// Group: context_management 1634 public int height (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.mHeight : 0); } 1635 1636 /// valid only inside [beginFrame]/[endFrame] 1637 /// Group: context_management 1638 public float devicePixelRatio (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.devicePxRatio : float.nan); } 1639 1640 /// Returns `true` if [beginFrame] was called, and neither [endFrame], nor [cancelFrame] were. 1641 /// Group: context_management 1642 public bool inFrame (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.nstates > 0 : false); } 1643 1644 // path autoregistration 1645 1646 /// [pickid] to stop autoregistration. 1647 /// Group: context_management 1648 public enum NVGNoPick = -1; 1649 1650 /// >=0: this pickid will be assigned to all filled/stroked paths 1651 /// Group: context_management 1652 public int pickid (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickId : NVGNoPick); } 1653 1654 /// >=0: this pickid will be assigned to all filled/stroked paths 1655 /// Group: context_management 1656 public void pickid (NVGContext ctx, int v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.pathPickId = v; } 1657 1658 /// pick autoregistration mode; see [NVGPickKind] 1659 /// Group: context_management 1660 public uint pickmode (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.pathPickRegistered&NVGPickKind.All : 0); } 1661 1662 /// pick autoregistration mode; see [NVGPickKind] 1663 /// Group: context_management 1664 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); } 1665 1666 // tesselator options 1667 1668 /// Get current Bezier tesselation mode. See [NVGTesselation]. 1669 /// Group: context_management 1670 public NVGTesselation tesselation (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null ? ctx.tesselatortype : NVGTesselation.DeCasteljau); } 1671 1672 /// Set current Bezier tesselation mode. See [NVGTesselation]. 1673 /// Group: context_management 1674 public void tesselation (NVGContext ctx, NVGTesselation v) nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null) ctx.tesselatortype = v; } 1675 1676 1677 private struct NVGcontextinternal { 1678 private: 1679 NVGparams params; 1680 float* commands; 1681 int ccommands; 1682 int ncommands; 1683 float commandx, commandy; 1684 NVGstate[NVG_MAX_STATES] states; 1685 int nstates; 1686 NVGpathCache* cache; 1687 public float tessTol; 1688 public float angleTol; // 0.0f -- angle tolerance for McSeem Bezier rasterizer 1689 public float cuspLimit; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps) 1690 float distTol; 1691 public float fringeWidth; 1692 float devicePxRatio; 1693 FONSContext fs; 1694 NVGImage[NVG_MAX_FONTIMAGES] fontImages; 1695 int fontImageIdx; 1696 int drawCallCount; 1697 int fillTriCount; 1698 int strokeTriCount; 1699 int textTriCount; 1700 NVGTesselation tesselatortype; 1701 // picking API 1702 NVGpickScene* pickScene; 1703 int pathPickId; // >=0: register all paths for picking using this id 1704 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 1705 // path recording 1706 NVGPathSet recset; 1707 int recstart; // used to cancel recording 1708 bool recblockdraw; 1709 // internals 1710 NVGMatrix gpuAffine; 1711 int mWidth, mHeight; 1712 // image manager 1713 shared int imageCount; // number of alive images in this context 1714 bool contextAlive; // context can be dead, but still contain some images 1715 1716 @disable this (this); // no copies 1717 void opAssign() (in auto ref NVGcontextinternal a) { static assert(0, "no copies!"); } 1718 1719 // debug feature 1720 public @property int getImageCount () nothrow @trusted @nogc { 1721 import core.atomic; 1722 return atomicLoad(imageCount); 1723 } 1724 } 1725 1726 /** Returns number of tesselated pathes in context. 1727 * 1728 * Tesselated pathes are either triangle strips (for strokes), or 1729 * triangle fans (for fills). Note that NanoVega can generate some 1730 * surprising pathes (like fringe stroke for antialiasing, for example). 1731 * 1732 * One render path can contain vertices both for fill, and for stroke triangles. 1733 */ 1734 public int renderPathCount (NVGContext ctx) pure nothrow @trusted @nogc { 1735 pragma(inline, true); 1736 return (ctx !is null && ctx.contextAlive ? ctx.cache.npaths : 0); 1737 } 1738 1739 /** Get vertices of "fill" triangle fan for the given render path. Can return empty slice. 1740 * 1741 * $(WARNING Returned slice can be invalidated by any other NanoVega API call 1742 * (except the calls to render path accessors), and using it in such 1743 * case is UB. So copy vertices to freshly allocated array if you want 1744 * to keep them for further processing.) 1745 */ 1746 public const(NVGVertex)[] renderPathFillVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1747 pragma(inline, true); 1748 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].fillVertices : null); 1749 } 1750 1751 /** Get vertices of "stroke" triangle strip for the given render path. Can return empty slice. 1752 * 1753 * $(WARNING Returned slice can be invalidated by any other NanoVega API call 1754 * (except the calls to render path accessors), and using it in such 1755 * case is UB. So copy vertices to freshly allocated array if you want 1756 * to keep them for further processing.) 1757 */ 1758 public const(NVGVertex)[] renderPathStrokeVertices (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1759 pragma(inline, true); 1760 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].strokeVertices : null); 1761 1762 } 1763 1764 /// Returns winding for the given render path. 1765 public NVGWinding renderPathWinding (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1766 pragma(inline, true); 1767 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].winding : NVGWinding.CCW); 1768 1769 } 1770 1771 /// Returns "complex path" flag for the given render path. 1772 public bool renderPathComplex (NVGContext ctx, int pathidx) pure nothrow @trusted @nogc { 1773 pragma(inline, true); 1774 return (ctx !is null && ctx.contextAlive && pathidx >= 0 && pathidx < ctx.cache.npaths ? ctx.cache.paths[pathidx].complex : false); 1775 1776 } 1777 1778 void nvg__imageIncRef (NVGContext ctx, int imgid, bool increfInGL=true) nothrow @trusted @nogc { 1779 if (ctx !is null && imgid > 0) { 1780 import core.atomic : atomicOp; 1781 atomicOp!"+="(ctx.imageCount, 1); 1782 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[++]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); } 1783 if (ctx.contextAlive && increfInGL) ctx.params.renderTextureIncRef(ctx.params.userPtr, imgid); 1784 } 1785 } 1786 1787 void nvg__imageDecRef (NVGContext ctx, int imgid) nothrow @trusted @nogc { 1788 if (ctx !is null && imgid > 0) { 1789 import core.atomic : atomicOp; 1790 int icnt = atomicOp!"-="(ctx.imageCount, 1); 1791 if (icnt < 0) assert(0, "NanoVega: internal image refcounting error"); 1792 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("image[--]ref: context %p: %d image refs (%d)\n", ctx, ctx.imageCount, imgid); } 1793 if (ctx.contextAlive) ctx.params.renderDeleteTexture(ctx.params.userPtr, imgid); 1794 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); } 1795 if (!ctx.contextAlive && icnt == 0) { 1796 // it is finally safe to free context memory 1797 import core.stdc.stdlib : free; 1798 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("killed zombie context %p\n", ctx); } 1799 free(ctx); 1800 } 1801 } 1802 } 1803 1804 1805 public import core.stdc.math : 1806 nvg__sqrtf = sqrtf, 1807 nvg__modf = fmodf, 1808 nvg__sinf = sinf, 1809 nvg__cosf = cosf, 1810 nvg__tanf = tanf, 1811 nvg__atan2f = atan2f, 1812 nvg__acosf = acosf, 1813 nvg__ceilf = ceilf; 1814 1815 version(Windows) { 1816 public int nvg__lrintf (float f) nothrow @trusted @nogc { pragma(inline, true); return cast(int)(f+0.5); } 1817 } else { 1818 public import core.stdc.math : nvg__lrintf = lrintf; 1819 } 1820 1821 public auto nvg__min(T) (T a, T b) { pragma(inline, true); return (a < b ? a : b); } 1822 public auto nvg__max(T) (T a, T b) { pragma(inline, true); return (a > b ? a : b); } 1823 public auto nvg__clamp(T) (T a, T mn, T mx) { pragma(inline, true); return (a < mn ? mn : (a > mx ? mx : a)); } 1824 //float nvg__absf() (float a) { pragma(inline, true); return (a >= 0.0f ? a : -a); } 1825 public auto nvg__sign(T) (T a) { pragma(inline, true); return (a >= cast(T)0 ? cast(T)1 : cast(T)(-1)); } 1826 public float nvg__cross() (float dx0, float dy0, float dx1, float dy1) { pragma(inline, true); return (dx1*dy0-dx0*dy1); } 1827 1828 //public import core.stdc.math : nvg__absf = fabsf; 1829 public import core.math : nvg__absf = fabs; 1830 1831 1832 float nvg__normalize (float* x, float* y) nothrow @safe @nogc { 1833 float d = nvg__sqrtf((*x)*(*x)+(*y)*(*y)); 1834 if (d > 1e-6f) { 1835 immutable float id = 1.0f/d; 1836 *x *= id; 1837 *y *= id; 1838 } 1839 return d; 1840 } 1841 1842 void nvg__deletePathCache (ref NVGpathCache* c) nothrow @trusted @nogc { 1843 if (c !is null) { 1844 c.clear(); 1845 free(c); 1846 } 1847 } 1848 1849 NVGpathCache* nvg__allocPathCache () nothrow @trusted @nogc { 1850 NVGpathCache* c = cast(NVGpathCache*)malloc(NVGpathCache.sizeof); 1851 if (c is null) goto error; 1852 memset(c, 0, NVGpathCache.sizeof); 1853 1854 c.points = cast(NVGpoint*)malloc(NVGpoint.sizeof*NVG_INIT_POINTS_SIZE); 1855 if (c.points is null) goto error; 1856 assert(c.npoints == 0); 1857 c.cpoints = NVG_INIT_POINTS_SIZE; 1858 1859 c.paths = cast(NVGpath*)malloc(NVGpath.sizeof*NVG_INIT_PATHS_SIZE); 1860 if (c.paths is null) goto error; 1861 assert(c.npaths == 0); 1862 c.cpaths = NVG_INIT_PATHS_SIZE; 1863 1864 c.verts = cast(NVGVertex*)malloc(NVGVertex.sizeof*NVG_INIT_VERTS_SIZE); 1865 if (c.verts is null) goto error; 1866 assert(c.nverts == 0); 1867 c.cverts = NVG_INIT_VERTS_SIZE; 1868 1869 return c; 1870 1871 error: 1872 nvg__deletePathCache(c); 1873 return null; 1874 } 1875 1876 void nvg__setDevicePixelRatio (NVGContext ctx, float ratio) pure nothrow @safe @nogc { 1877 ctx.tessTol = 0.25f/ratio; 1878 ctx.distTol = 0.01f/ratio; 1879 ctx.fringeWidth = 1.0f/ratio; 1880 ctx.devicePxRatio = ratio; 1881 } 1882 1883 NVGCompositeOperationState nvg__compositeOperationState (NVGCompositeOperation op) pure nothrow @safe @nogc { 1884 NVGCompositeOperationState state; 1885 NVGBlendFactor sfactor, dfactor; 1886 1887 if (op == NVGCompositeOperation.SourceOver) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha;} 1888 else if (op == NVGCompositeOperation.SourceIn) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.Zero; } 1889 else if (op == NVGCompositeOperation.SourceOut) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.Zero; } 1890 else if (op == NVGCompositeOperation.SourceAtop) { sfactor = NVGBlendFactor.DstAlpha; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } 1891 else if (op == NVGCompositeOperation.DestinationOver) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.One; } 1892 else if (op == NVGCompositeOperation.DestinationIn) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.SrcAlpha; } 1893 else if (op == NVGCompositeOperation.DestinationOut) { sfactor = NVGBlendFactor.Zero; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } 1894 else if (op == NVGCompositeOperation.DestinationAtop) { sfactor = NVGBlendFactor.OneMinusDstAlpha; dfactor = NVGBlendFactor.SrcAlpha; } 1895 else if (op == NVGCompositeOperation.Lighter) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.One; } 1896 else if (op == NVGCompositeOperation.Copy) { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.Zero; } 1897 else if (op == NVGCompositeOperation.Xor) { 1898 state.simple = false; 1899 state.srcRGB = NVGBlendFactor.OneMinusDstColor; 1900 state.srcAlpha = NVGBlendFactor.OneMinusDstAlpha; 1901 state.dstRGB = NVGBlendFactor.OneMinusSrcColor; 1902 state.dstAlpha = NVGBlendFactor.OneMinusSrcAlpha; 1903 return state; 1904 } 1905 else { sfactor = NVGBlendFactor.One; dfactor = NVGBlendFactor.OneMinusSrcAlpha; } // default value for invalid op: SourceOver 1906 1907 state.simple = true; 1908 state.srcAlpha = sfactor; 1909 state.dstAlpha = dfactor; 1910 return state; 1911 } 1912 1913 NVGstate* nvg__getState (NVGContext ctx) pure nothrow @trusted @nogc { 1914 pragma(inline, true); 1915 if (ctx is null || !ctx.contextAlive || ctx.nstates == 0) assert(0, "NanoVega: cannot perform commands on inactive context"); 1916 return &ctx.states.ptr[ctx.nstates-1]; 1917 } 1918 1919 // Constructor called by the render back-end. 1920 NVGContext createInternal (NVGparams* params) nothrow @trusted @nogc { 1921 FONSParams fontParams; 1922 NVGContext ctx = cast(NVGContext)malloc(NVGcontextinternal.sizeof); 1923 if (ctx is null) goto error; 1924 memset(ctx, 0, NVGcontextinternal.sizeof); 1925 1926 ctx.angleTol = 0; // angle tolerance for McSeem Bezier rasterizer 1927 ctx.cuspLimit = 0; // cusp limit for McSeem Bezier rasterizer (0: real cusps) 1928 1929 ctx.contextAlive = true; 1930 1931 ctx.params = *params; 1932 //ctx.fontImages[0..NVG_MAX_FONTIMAGES] = 0; 1933 1934 ctx.commands = cast(float*)malloc(float.sizeof*NVG_INIT_COMMANDS_SIZE); 1935 if (ctx.commands is null) goto error; 1936 ctx.ncommands = 0; 1937 ctx.ccommands = NVG_INIT_COMMANDS_SIZE; 1938 1939 ctx.cache = nvg__allocPathCache(); 1940 if (ctx.cache is null) goto error; 1941 1942 ctx.save(); 1943 ctx.reset(); 1944 1945 nvg__setDevicePixelRatio(ctx, 1.0f); 1946 ctx.mWidth = ctx.mHeight = 0; 1947 1948 if (!ctx.params.renderCreate(ctx.params.userPtr)) goto error; 1949 1950 // init font rendering 1951 memset(&fontParams, 0, fontParams.sizeof); 1952 fontParams.width = NVG_INIT_FONTIMAGE_SIZE; 1953 fontParams.height = NVG_INIT_FONTIMAGE_SIZE; 1954 fontParams.flags = FONSParams.Flag.ZeroTopLeft; 1955 fontParams.renderCreate = null; 1956 fontParams.renderUpdate = null; 1957 fontParams.renderDelete = null; 1958 fontParams.userPtr = null; 1959 ctx.fs = FONSContext.create(fontParams); 1960 if (ctx.fs is null) goto error; 1961 1962 // create font texture 1963 ctx.fontImages[0].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, fontParams.width, fontParams.height, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null); 1964 if (ctx.fontImages[0].id == 0) goto error; 1965 ctx.fontImages[0].ctx = ctx; 1966 ctx.nvg__imageIncRef(ctx.fontImages[0].id, false); // don't increment driver refcount 1967 ctx.fontImageIdx = 0; 1968 1969 ctx.pathPickId = -1; 1970 ctx.tesselatortype = NVG_DEFAULT_TESSELATOR; 1971 1972 return ctx; 1973 1974 error: 1975 ctx.deleteInternal(); 1976 return null; 1977 } 1978 1979 // Called by render backend. 1980 NVGparams* internalParams (NVGContext ctx) nothrow @trusted @nogc { 1981 return &ctx.params; 1982 } 1983 1984 // Destructor called by the render back-end. 1985 void deleteInternal (ref NVGContext ctx) nothrow @trusted @nogc { 1986 if (ctx is null) return; 1987 if (ctx.contextAlive) { 1988 if (ctx.commands !is null) free(ctx.commands); 1989 nvg__deletePathCache(ctx.cache); 1990 1991 if (ctx.fs) ctx.fs.kill(); 1992 1993 foreach (uint i; 0..NVG_MAX_FONTIMAGES) ctx.fontImages[i].clear(); 1994 1995 if (ctx.params.renderDelete !is null) ctx.params.renderDelete(ctx.params.userPtr); 1996 1997 if (ctx.pickScene !is null) nvg__deletePickScene(ctx.pickScene); 1998 1999 ctx.contextAlive = false; 2000 2001 import core.atomic : atomicLoad; 2002 if (atomicLoad(ctx.imageCount) == 0) { 2003 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("destroyed context %p\n", ctx); } 2004 free(ctx); 2005 } else { 2006 version(nanovega_debug_image_manager) { import core.stdc.stdio; printf("context %p is zombie now (%d image refs)\n", ctx, ctx.imageCount); } 2007 } 2008 } 2009 } 2010 2011 /// Delete NanoVega context. 2012 /// Group: context_management 2013 public void kill (ref NVGContext ctx) nothrow @trusted @nogc { 2014 if (ctx !is null) { 2015 ctx.deleteInternal(); 2016 ctx = null; 2017 } 2018 } 2019 2020 /// Returns `true` if the given context is not `null` and can be used for painting. 2021 /// Group: context_management 2022 public bool valid (in NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive); } 2023 2024 2025 // ////////////////////////////////////////////////////////////////////////// // 2026 // Frame Management 2027 2028 /** Begin drawing a new frame. 2029 * 2030 * Calls to NanoVega drawing API should be wrapped in [beginFrame] and [endFrame] 2031 * 2032 * [beginFrame] defines the size of the window to render to in relation currently 2033 * set viewport (i.e. glViewport on GL backends). Device pixel ration allows to 2034 * control the rendering on Hi-DPI devices. 2035 * 2036 * For example, GLFW returns two dimension for an opened window: window size and 2037 * frame buffer size. In that case you would set windowWidth/windowHeight to the window size, 2038 * devicePixelRatio to: `windowWidth/windowHeight`. 2039 * 2040 * Default ratio is `1`. 2041 * 2042 * Note that fractional ratio can (and will) distort your fonts and images. 2043 * 2044 * This call also resets pick marks (see picking API for non-rasterized paths), 2045 * path recording, and GPU affine transformatin matrix. 2046 * 2047 * see also [glNVGClearFlags], which returns necessary flags for [glClear]. 2048 * 2049 * Group: frame_management 2050 */ 2051 public void beginFrame (NVGContext ctx, int windowWidth, int windowHeight, float devicePixelRatio=1.0f) nothrow @trusted @nogc { 2052 import std.math : isNaN; 2053 /* 2054 printf("Tris: draws:%d fill:%d stroke:%d text:%d TOT:%d\n", 2055 ctx.drawCallCount, ctx.fillTriCount, ctx.strokeTriCount, ctx.textTriCount, 2056 ctx.fillTriCount+ctx.strokeTriCount+ctx.textTriCount); 2057 */ 2058 if (ctx.nstates > 0) ctx.cancelFrame(); 2059 2060 if (windowWidth < 1) windowWidth = 1; 2061 if (windowHeight < 1) windowHeight = 1; 2062 2063 if (isNaN(devicePixelRatio)) devicePixelRatio = (windowHeight > 0 ? cast(float)windowWidth/cast(float)windowHeight : 1024.0/768.0); 2064 2065 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint(); 2066 ctx.nstates = 0; 2067 ctx.save(); 2068 ctx.reset(); 2069 2070 nvg__setDevicePixelRatio(ctx, devicePixelRatio); 2071 2072 ctx.params.renderViewport(ctx.params.userPtr, windowWidth, windowHeight); 2073 ctx.mWidth = windowWidth; 2074 ctx.mHeight = windowHeight; 2075 2076 ctx.recset = null; 2077 ctx.recstart = -1; 2078 2079 ctx.pathPickId = NVGNoPick; 2080 ctx.pathPickRegistered = 0; 2081 2082 ctx.drawCallCount = 0; 2083 ctx.fillTriCount = 0; 2084 ctx.strokeTriCount = 0; 2085 ctx.textTriCount = 0; 2086 2087 ctx.ncommands = 0; 2088 ctx.pathPickRegistered = 0; 2089 nvg__clearPathCache(ctx); 2090 2091 ctx.gpuAffine = NVGMatrix.Identity; 2092 2093 nvg__pickBeginFrame(ctx, windowWidth, windowHeight); 2094 } 2095 2096 /// Cancels drawing the current frame. Cancels path recording. 2097 /// Group: frame_management 2098 public void cancelFrame (NVGContext ctx) nothrow @trusted @nogc { 2099 ctx.cancelRecording(); 2100 //ctx.mWidth = 0; 2101 //ctx.mHeight = 0; 2102 // cancel render queue 2103 ctx.params.renderCancel(ctx.params.userPtr); 2104 // clear saved states (this may free some textures) 2105 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint(); 2106 ctx.nstates = 0; 2107 } 2108 2109 /// Ends drawing the current frame (flushing remaining render state). Commits recorded paths. 2110 /// Group: frame_management 2111 public void endFrame (NVGContext ctx) nothrow @trusted @nogc { 2112 if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx); 2113 ctx.stopRecording(); 2114 //ctx.mWidth = 0; 2115 //ctx.mHeight = 0; 2116 // flush render queue 2117 NVGstate* state = nvg__getState(ctx); 2118 ctx.params.renderFlush(ctx.params.userPtr); 2119 if (ctx.fontImageIdx != 0) { 2120 auto fontImage = ctx.fontImages[ctx.fontImageIdx]; 2121 int j = 0, iw, ih; 2122 // delete images that smaller than current one 2123 if (!fontImage.valid) return; 2124 ctx.imageSize(fontImage, iw, ih); 2125 foreach (int i; 0..ctx.fontImageIdx) { 2126 if (ctx.fontImages[i].valid) { 2127 int nw, nh; 2128 ctx.imageSize(ctx.fontImages[i], nw, nh); 2129 if (nw < iw || nh < ih) { 2130 ctx.deleteImage(ctx.fontImages[i]); 2131 } else { 2132 ctx.fontImages[j++] = ctx.fontImages[i]; 2133 } 2134 } 2135 } 2136 // make current font image to first 2137 ctx.fontImages[j++] = ctx.fontImages[0]; 2138 ctx.fontImages[0] = fontImage; 2139 ctx.fontImageIdx = 0; 2140 // clear all images after j 2141 ctx.fontImages[j..NVG_MAX_FONTIMAGES] = NVGImage.init; 2142 } 2143 // clear saved states (this may free some textures) 2144 foreach (ref NVGstate st; ctx.states[0..ctx.nstates]) st.clearPaint(); 2145 ctx.nstates = 0; 2146 } 2147 2148 2149 // ////////////////////////////////////////////////////////////////////////// // 2150 // Recording and Replaying Pathes 2151 2152 // Saved path set. 2153 // Group: path_recording 2154 public alias NVGPathSet = NVGPathSetS*; 2155 2156 2157 //TODO: save scissor info? 2158 struct NVGPathSetS { 2159 private: 2160 // either path cache, or text item 2161 static struct Node { 2162 NVGPaint paint; 2163 NVGpathCache* path; 2164 } 2165 2166 private: 2167 Node* nodes; 2168 int nnodes, cnodes; 2169 NVGpickScene* pickscene; 2170 //int npickscenes, cpickscenes; 2171 NVGContext svctx; // used to do some sanity checks, and to free resources 2172 2173 private: 2174 Node* allocNode () nothrow @trusted @nogc { 2175 import core.stdc..string : memset; 2176 // grow buffer if necessary 2177 if (nnodes+1 > cnodes) { 2178 import core.stdc.stdlib : realloc; 2179 int newsz = (cnodes == 0 ? 8 : cnodes <= 1024 ? cnodes*2 : cnodes+1024); 2180 nodes = cast(Node*)realloc(nodes, newsz*Node.sizeof); 2181 if (nodes is null) assert(0, "NanoVega: out of memory"); 2182 //memset(svp.caches+svp.ccaches, 0, (newsz-svp.ccaches)*NVGpathCache.sizeof); 2183 cnodes = newsz; 2184 } 2185 assert(nnodes < cnodes); 2186 memset(nodes+nnodes, 0, Node.sizeof); 2187 return &nodes[nnodes++]; 2188 } 2189 2190 Node* allocPathNode () nothrow @trusted @nogc { 2191 import core.stdc.stdlib : malloc; 2192 import core.stdc..string : memset; 2193 auto node = allocNode(); 2194 // allocate path cache 2195 auto pc = cast(NVGpathCache*)malloc(NVGpathCache.sizeof); 2196 if (pc is null) assert(0, "NanoVega: out of memory"); 2197 node.path = pc; 2198 return node; 2199 } 2200 2201 void clearNode (int idx) nothrow @trusted @nogc { 2202 if (idx < 0 || idx >= nnodes) return; 2203 Node* node = &nodes[idx]; 2204 if (svctx !is null && node.paint.image.valid) node.paint.image.clear(); 2205 if (node.path !is null) node.path.clear(); 2206 } 2207 2208 private: 2209 void takeCurrentPickScene (NVGContext ctx) nothrow @trusted @nogc { 2210 NVGpickScene* ps = ctx.pickScene; 2211 if (ps is null) return; // nothing to do 2212 if (ps.npaths == 0) return; // pick scene is empty 2213 ctx.pickScene = null; 2214 pickscene = ps; 2215 } 2216 2217 void replay (NVGContext ctx, in ref NVGColor fillTint, in ref NVGColor strokeTint) nothrow @trusted @nogc { 2218 NVGstate* state = nvg__getState(ctx); 2219 foreach (ref node; nodes[0..nnodes]) { 2220 if (auto cc = node.path) { 2221 if (cc.npaths <= 0) continue; 2222 2223 if (cc.fillReady) { 2224 NVGPaint fillPaint = node.paint; 2225 2226 // apply global alpha 2227 fillPaint.innerColor.a *= state.alpha; 2228 fillPaint.middleColor.a *= state.alpha; 2229 fillPaint.outerColor.a *= state.alpha; 2230 2231 fillPaint.innerColor.applyTint(fillTint); 2232 fillPaint.middleColor.applyTint(fillTint); 2233 fillPaint.outerColor.applyTint(fillTint); 2234 2235 ctx.params.renderFill(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &fillPaint, &state.scissor, cc.fringeWidth, cc.bounds.ptr, cc.paths, cc.npaths, cc.evenOddMode); 2236 2237 // count triangles 2238 foreach (int i; 0..cc.npaths) { 2239 NVGpath* path = &cc.paths[i]; 2240 ctx.fillTriCount += path.nfill-2; 2241 ctx.fillTriCount += path.nstroke-2; 2242 ctx.drawCallCount += 2; 2243 } 2244 } 2245 2246 if (cc.strokeReady) { 2247 NVGPaint strokePaint = node.paint; 2248 2249 strokePaint.innerColor.a *= cc.strokeAlphaMul; 2250 strokePaint.middleColor.a *= cc.strokeAlphaMul; 2251 strokePaint.outerColor.a *= cc.strokeAlphaMul; 2252 2253 // apply global alpha 2254 strokePaint.innerColor.a *= state.alpha; 2255 strokePaint.middleColor.a *= state.alpha; 2256 strokePaint.outerColor.a *= state.alpha; 2257 2258 strokePaint.innerColor.applyTint(strokeTint); 2259 strokePaint.middleColor.applyTint(strokeTint); 2260 strokePaint.outerColor.applyTint(strokeTint); 2261 2262 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, cc.clipmode, &strokePaint, &state.scissor, cc.fringeWidth, cc.strokeWidth, cc.paths, cc.npaths); 2263 2264 // count triangles 2265 foreach (int i; 0..cc.npaths) { 2266 NVGpath* path = &cc.paths[i]; 2267 ctx.strokeTriCount += path.nstroke-2; 2268 ++ctx.drawCallCount; 2269 } 2270 } 2271 } 2272 } 2273 } 2274 2275 public: 2276 @disable this (this); // no copies 2277 void opAssign() (in auto ref NVGPathSetS a) { static assert(0, "no copies!"); } 2278 2279 // pick test 2280 // Call delegate [dg] for each path under the specified position (in no particular order). 2281 // Returns the id of the path for which delegate [dg] returned true or -1. 2282 // dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending). 2283 int hitTestDG(bool bestOrder=false, DG) (in float x, in float y, NVGPickKind kind, scope DG dg) if (IsGoodHitTestDG!DG || IsGoodHitTestInternalDG!DG) { 2284 if (pickscene is null) return -1; 2285 2286 NVGpickScene* ps = pickscene; 2287 int levelwidth = 1<<(ps.nlevels-1); 2288 int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth); 2289 int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth); 2290 int npicked = 0; 2291 2292 for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) { 2293 NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx]; 2294 while (pp !is null) { 2295 if (nvg__pickPathTestBounds(svctx, ps, pp, x, y)) { 2296 int hit = 0; 2297 if ((kind&NVGPickKind.Stroke) && (pp.flags&NVGPathFlags.Stroke)) hit = nvg__pickPathStroke(ps, pp, x, y); 2298 if (!hit && (kind&NVGPickKind.Fill) && (pp.flags&NVGPathFlags.Fill)) hit = nvg__pickPath(ps, pp, x, y); 2299 if (hit) { 2300 static if (IsGoodHitTestDG!DG) { 2301 static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) { 2302 if (dg(pp.id, cast(int)pp.order)) return pp.id; 2303 } else { 2304 dg(pp.id, cast(int)pp.order); 2305 } 2306 } else { 2307 static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) { 2308 if (dg(pp)) return pp.id; 2309 } else { 2310 dg(pp); 2311 } 2312 } 2313 } 2314 } 2315 pp = pp.next; 2316 } 2317 cellx >>= 1; 2318 celly >>= 1; 2319 levelwidth >>= 1; 2320 } 2321 2322 return -1; 2323 } 2324 2325 // Fills ids with a list of the top most hit ids under the specified position. 2326 // Returns the slice of [ids]. 2327 int[] hitTestAll (in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc { 2328 if (pickscene is null || ids.length == 0) return ids[0..0]; 2329 2330 int npicked = 0; 2331 NVGpickScene* ps = pickscene; 2332 2333 hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc { 2334 if (npicked == ps.cpicked) { 2335 int cpicked = ps.cpicked+ps.cpicked; 2336 NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked); 2337 if (picked is null) return true; // abort 2338 ps.cpicked = cpicked; 2339 ps.picked = picked; 2340 } 2341 ps.picked[npicked] = pp; 2342 ++npicked; 2343 return false; // go on 2344 }); 2345 2346 qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths); 2347 2348 assert(npicked >= 0); 2349 if (npicked > ids.length) npicked = cast(int)ids.length; 2350 foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id; 2351 2352 return ids[0..npicked]; 2353 } 2354 2355 // Returns the id of the pickable shape containing x,y or -1 if no shape was found. 2356 int hitTest (in float x, in float y, NVGPickKind kind) nothrow @trusted @nogc { 2357 if (pickscene is null) return -1; 2358 2359 int bestOrder = -1; 2360 int bestID = -1; 2361 2362 hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc { 2363 if (pp.order > bestOrder) { 2364 bestOrder = pp.order; 2365 bestID = pp.id; 2366 } 2367 }); 2368 2369 return bestID; 2370 } 2371 } 2372 2373 // Append current path to existing path set. Is is safe to call this with `null` [svp]. 2374 void appendCurrentPathToCache (NVGContext ctx, NVGPathSet svp, in ref NVGPaint paint) nothrow @trusted @nogc { 2375 if (ctx is null || svp is null) return; 2376 if (ctx !is svp.svctx) assert(0, "NanoVega: cannot save paths from different contexts"); 2377 if (ctx.ncommands == 0) { 2378 assert(ctx.cache.npaths == 0); 2379 return; 2380 } 2381 if (!ctx.cache.fillReady && !ctx.cache.strokeReady) return; 2382 2383 // tesselate current path 2384 //if (!ctx.cache.fillReady) nvg__prepareFill(ctx); 2385 //if (!ctx.cache.strokeReady) nvg__prepareStroke(ctx); 2386 2387 auto node = svp.allocPathNode(); 2388 NVGpathCache* cc = node.path; 2389 cc.copyFrom(ctx.cache); 2390 node.paint = paint; 2391 // copy path commands (we may need 'em for picking) 2392 version(all) { 2393 cc.ncommands = ctx.ncommands; 2394 if (cc.ncommands) { 2395 import core.stdc.stdlib : malloc; 2396 import core.stdc..string : memcpy; 2397 cc.commands = cast(float*)malloc(ctx.ncommands*float.sizeof); 2398 if (cc.commands is null) assert(0, "NanoVega: out of memory"); 2399 memcpy(cc.commands, ctx.commands, ctx.ncommands*float.sizeof); 2400 } else { 2401 cc.commands = null; 2402 } 2403 } 2404 } 2405 2406 // Create new empty path set. 2407 // Group: path_recording 2408 public NVGPathSet newPathSet (NVGContext ctx) nothrow @trusted @nogc { 2409 import core.stdc.stdlib : malloc; 2410 import core.stdc..string : memset; 2411 if (ctx is null) return null; 2412 NVGPathSet res = cast(NVGPathSet)malloc(NVGPathSetS.sizeof); 2413 if (res is null) assert(0, "NanoVega: out of memory"); 2414 memset(res, 0, NVGPathSetS.sizeof); 2415 res.svctx = ctx; 2416 return res; 2417 } 2418 2419 // Is the given path set empty? Empty path set can be `null`. 2420 // Group: path_recording 2421 public bool empty (NVGPathSet svp) pure nothrow @safe @nogc { pragma(inline, true); return (svp is null || svp.nnodes == 0); } 2422 2423 // Clear path set contents. Will release $(B some) allocated memory (this function is meant to clear something that will be reused). 2424 // Group: path_recording 2425 public void clear (NVGPathSet svp) nothrow @trusted @nogc { 2426 if (svp !is null) { 2427 import core.stdc.stdlib : free; 2428 foreach (immutable idx; 0.. svp.nnodes) svp.clearNode(idx); 2429 svp.nnodes = 0; 2430 } 2431 } 2432 2433 // Destroy path set (frees all allocated memory). 2434 // Group: path_recording 2435 public void kill (ref NVGPathSet svp) nothrow @trusted @nogc { 2436 if (svp !is null) { 2437 import core.stdc.stdlib : free; 2438 svp.clear(); 2439 if (svp.nodes !is null) free(svp.nodes); 2440 free(svp); 2441 if (svp.pickscene !is null) nvg__deletePickScene(svp.pickscene); 2442 svp = null; 2443 } 2444 } 2445 2446 // Start path recording. [svp] should be alive until recording is cancelled or stopped. 2447 // Group: path_recording 2448 public void startRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { 2449 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2450 ctx.stopRecording(); 2451 ctx.recset = svp; 2452 ctx.recstart = (svp !is null ? svp.nnodes : -1); 2453 ctx.recblockdraw = false; 2454 } 2455 2456 /* Start path recording. [svp] should be alive until recording is cancelled or stopped. 2457 * 2458 * This will block all rendering, so you can call your rendering functions to record paths without actual drawing. 2459 * Commiting or cancelling will re-enable rendering. 2460 * You can call this with `null` svp to block rendering without recording any paths. 2461 * 2462 * Group: path_recording 2463 */ 2464 public void startBlockingRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { 2465 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2466 ctx.stopRecording(); 2467 ctx.recset = svp; 2468 ctx.recstart = (svp !is null ? svp.nnodes : -1); 2469 ctx.recblockdraw = true; 2470 } 2471 2472 // Commit recorded paths. It is safe to call this when recording is not started. 2473 // Group: path_recording 2474 public void stopRecording (NVGContext ctx) nothrow @trusted @nogc { 2475 if (ctx.recset !is null && ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2476 if (ctx.recset !is null) ctx.recset.takeCurrentPickScene(ctx); 2477 ctx.recset = null; 2478 ctx.recstart = -1; 2479 ctx.recblockdraw = false; 2480 } 2481 2482 // Cancel path recording. 2483 // Group: path_recording 2484 public void cancelRecording (NVGContext ctx) nothrow @trusted @nogc { 2485 if (ctx.recset !is null) { 2486 if (ctx.recset.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2487 assert(ctx.recstart >= 0 && ctx.recstart <= ctx.recset.nnodes); 2488 foreach (immutable idx; ctx.recstart..ctx.recset.nnodes) ctx.recset.clearNode(idx); 2489 ctx.recset.nnodes = ctx.recstart; 2490 ctx.recset = null; 2491 ctx.recstart = -1; 2492 } 2493 ctx.recblockdraw = false; 2494 } 2495 2496 /* Replay saved path set. 2497 * 2498 * Replaying record while you're recording another one is undefined behavior. 2499 * 2500 * Group: path_recording 2501 */ 2502 public void replayRecording() (NVGContext ctx, NVGPathSet svp, in auto ref NVGColor fillTint, in auto ref NVGColor strokeTint) nothrow @trusted @nogc { 2503 if (svp !is null && svp.svctx !is ctx) assert(0, "NanoVega: cannot share path set between contexts"); 2504 svp.replay(ctx, fillTint, strokeTint); 2505 } 2506 2507 /// Ditto. 2508 public void replayRecording() (NVGContext ctx, NVGPathSet svp, in auto ref NVGColor fillTint) nothrow @trusted @nogc { ctx.replayRecording(svp, fillTint, NVGColor.transparent); } 2509 2510 /// Ditto. 2511 public void replayRecording (NVGContext ctx, NVGPathSet svp) nothrow @trusted @nogc { ctx.replayRecording(svp, NVGColor.transparent, NVGColor.transparent); } 2512 2513 2514 // ////////////////////////////////////////////////////////////////////////// // 2515 // Composite operation 2516 2517 /// Sets the composite operation. 2518 /// Group: composite_operation 2519 public void globalCompositeOperation (NVGContext ctx, NVGCompositeOperation op) nothrow @trusted @nogc { 2520 NVGstate* state = nvg__getState(ctx); 2521 state.compositeOperation = nvg__compositeOperationState(op); 2522 } 2523 2524 /// Sets the composite operation with custom pixel arithmetic. 2525 /// Group: composite_operation 2526 public void globalCompositeBlendFunc (NVGContext ctx, NVGBlendFactor sfactor, NVGBlendFactor dfactor) nothrow @trusted @nogc { 2527 ctx.globalCompositeBlendFuncSeparate(sfactor, dfactor, sfactor, dfactor); 2528 } 2529 2530 /// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately. 2531 /// Group: composite_operation 2532 public void globalCompositeBlendFuncSeparate (NVGContext ctx, NVGBlendFactor srcRGB, NVGBlendFactor dstRGB, NVGBlendFactor srcAlpha, NVGBlendFactor dstAlpha) nothrow @trusted @nogc { 2533 NVGCompositeOperationState op; 2534 op.simple = false; 2535 op.srcRGB = srcRGB; 2536 op.dstRGB = dstRGB; 2537 op.srcAlpha = srcAlpha; 2538 op.dstAlpha = dstAlpha; 2539 NVGstate* state = nvg__getState(ctx); 2540 state.compositeOperation = op; 2541 } 2542 2543 2544 // ////////////////////////////////////////////////////////////////////////// // 2545 // Color utils 2546 2547 /// Returns a color value from string form. 2548 /// Supports: "#rgb", "#rrggbb", "#argb", "#aarrggbb" 2549 /// Group: color_utils 2550 public NVGColor nvgRGB (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); } 2551 2552 /// Ditto. 2553 public NVGColor nvgRGBA (const(char)[] srgb) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(srgb); } 2554 2555 /// Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f). 2556 /// Group: color_utils 2557 public NVGColor nvgRGB (int r, int g, int b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(nvgClampToByte(r), nvgClampToByte(g), nvgClampToByte(b), 255); } 2558 2559 /// Returns a color value from red, green, blue values. Alpha will be set to 1.0f. 2560 /// Group: color_utils 2561 public NVGColor nvgRGBf (float r, float g, float b) nothrow @trusted @nogc { pragma(inline, true); return NVGColor(r, g, b, 1.0f); } 2562 2563 /// Returns a color value from red, green, blue and alpha values. 2564 /// Group: color_utils 2565 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)); } 2566 2567 /// Returns a color value from red, green, blue and alpha values. 2568 /// Group: color_utils 2569 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); } 2570 2571 /// Returns new color with transparency (alpha) set to [a]. 2572 /// Group: color_utils 2573 public NVGColor nvgTransRGBA (NVGColor c, ubyte a) nothrow @trusted @nogc { 2574 pragma(inline, true); 2575 c.a = a/255.0f; 2576 return c; 2577 } 2578 2579 /// Ditto. 2580 public NVGColor nvgTransRGBAf (NVGColor c, float a) nothrow @trusted @nogc { 2581 pragma(inline, true); 2582 c.a = a; 2583 return c; 2584 } 2585 2586 /// Linearly interpolates from color c0 to c1, and returns resulting color value. 2587 /// Group: color_utils 2588 public NVGColor nvgLerpRGBA() (in auto ref NVGColor c0, in auto ref NVGColor c1, float u) nothrow @trusted @nogc { 2589 NVGColor cint = void; 2590 u = nvg__clamp(u, 0.0f, 1.0f); 2591 float oneminu = 1.0f-u; 2592 foreach (uint i; 0..4) cint.rgba.ptr[i] = c0.rgba.ptr[i]*oneminu+c1.rgba.ptr[i]*u; 2593 return cint; 2594 } 2595 2596 /* see below 2597 public NVGColor nvgHSL() (float h, float s, float l) { 2598 //pragma(inline, true); // alas 2599 return nvgHSLA(h, s, l, 255); 2600 } 2601 */ 2602 2603 float nvg__hue (float h, float m1, float m2) pure nothrow @safe @nogc { 2604 if (h < 0) h += 1; 2605 if (h > 1) h -= 1; 2606 if (h < 1.0f/6.0f) return m1+(m2-m1)*h*6.0f; 2607 if (h < 3.0f/6.0f) return m2; 2608 if (h < 4.0f/6.0f) return m1+(m2-m1)*(2.0f/3.0f-h)*6.0f; 2609 return m1; 2610 } 2611 2612 /// Returns color value specified by hue, saturation and lightness. 2613 /// HSL values are all in range [0..1], alpha will be set to 255. 2614 /// Group: color_utils 2615 public alias nvgHSL = nvgHSLA; // trick to allow inlining 2616 2617 /// Returns color value specified by hue, saturation and lightness and alpha. 2618 /// HSL values are all in range [0..1], alpha in range [0..255]. 2619 /// Group: color_utils 2620 public NVGColor nvgHSLA (float h, float s, float l, ubyte a=255) nothrow @trusted @nogc { 2621 pragma(inline, true); 2622 NVGColor col = void; 2623 h = nvg__modf(h, 1.0f); 2624 if (h < 0.0f) h += 1.0f; 2625 s = nvg__clamp(s, 0.0f, 1.0f); 2626 l = nvg__clamp(l, 0.0f, 1.0f); 2627 immutable float m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s); 2628 immutable float m1 = 2*l-m2; 2629 col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2630 col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f); 2631 col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2632 col.a = a/255.0f; 2633 return col; 2634 } 2635 2636 /// Returns color value specified by hue, saturation and lightness and alpha. 2637 /// HSL values and alpha are all in range [0..1]. 2638 /// Group: color_utils 2639 public NVGColor nvgHSLA (float h, float s, float l, float a) nothrow @trusted @nogc { 2640 // sorry for copypasta, it is for inliner 2641 static if (__VERSION__ >= 2072) pragma(inline, true); 2642 NVGColor col = void; 2643 h = nvg__modf(h, 1.0f); 2644 if (h < 0.0f) h += 1.0f; 2645 s = nvg__clamp(s, 0.0f, 1.0f); 2646 l = nvg__clamp(l, 0.0f, 1.0f); 2647 immutable m2 = (l <= 0.5f ? l*(1+s) : l+s-l*s); 2648 immutable m1 = 2*l-m2; 2649 col.r = nvg__clamp(nvg__hue(h+1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2650 col.g = nvg__clamp(nvg__hue(h, m1, m2), 0.0f, 1.0f); 2651 col.b = nvg__clamp(nvg__hue(h-1.0f/3.0f, m1, m2), 0.0f, 1.0f); 2652 col.a = a; 2653 return col; 2654 } 2655 2656 2657 // ////////////////////////////////////////////////////////////////////////// // 2658 // Matrices and Transformations 2659 2660 /** Matrix class. 2661 * 2662 * Group: matrices 2663 */ 2664 public align(1) struct NVGMatrix { 2665 align(1): 2666 private: 2667 static immutable float[6] IdentityMat = [ 2668 1.0f, 0.0f, 2669 0.0f, 1.0f, 2670 0.0f, 0.0f, 2671 ]; 2672 2673 public: 2674 /// Matrix values. Initial value is identity matrix. 2675 float[6] mat = [ 2676 1.0f, 0.0f, 2677 0.0f, 1.0f, 2678 0.0f, 0.0f, 2679 ]; 2680 2681 public nothrow @trusted @nogc: 2682 /// Create Matrix with the given values. 2683 this (const(float)[] amat...) { 2684 pragma(inline, true); 2685 if (amat.length >= 6) { 2686 mat.ptr[0..6] = amat.ptr[0..6]; 2687 } else { 2688 mat.ptr[0..6] = 0; 2689 mat.ptr[0..amat.length] = amat[]; 2690 } 2691 } 2692 2693 /// Can be used to check validity of [inverted] result 2694 @property bool valid () const { import core.stdc.math : isfinite; return (isfinite(mat.ptr[0]) != 0); } 2695 2696 /// Returns `true` if this matrix is identity matrix. 2697 @property bool isIdentity () const { version(aliced) pragma(inline, true); return (mat[] == IdentityMat[]); } 2698 2699 /// Returns new inverse matrix. 2700 /// If inverted matrix cannot be calculated, `res.valid` fill be `false`. 2701 NVGMatrix inverted () const { 2702 NVGMatrix res = this; 2703 res.invert; 2704 return res; 2705 } 2706 2707 /// Inverts this matrix. 2708 /// If inverted matrix cannot be calculated, `this.valid` fill be `false`. 2709 ref NVGMatrix invert () return { 2710 float[6] inv = void; 2711 immutable double det = cast(double)mat.ptr[0]*mat.ptr[3]-cast(double)mat.ptr[2]*mat.ptr[1]; 2712 if (det > -1e-6 && det < 1e-6) { 2713 inv[] = float.nan; 2714 } else { 2715 immutable double invdet = 1.0/det; 2716 inv.ptr[0] = cast(float)(mat.ptr[3]*invdet); 2717 inv.ptr[2] = cast(float)(-mat.ptr[2]*invdet); 2718 inv.ptr[4] = cast(float)((cast(double)mat.ptr[2]*mat.ptr[5]-cast(double)mat.ptr[3]*mat.ptr[4])*invdet); 2719 inv.ptr[1] = cast(float)(-mat.ptr[1]*invdet); 2720 inv.ptr[3] = cast(float)(mat.ptr[0]*invdet); 2721 inv.ptr[5] = cast(float)((cast(double)mat.ptr[1]*mat.ptr[4]-cast(double)mat.ptr[0]*mat.ptr[5])*invdet); 2722 } 2723 mat.ptr[0..6] = inv.ptr[0..6]; 2724 return this; 2725 } 2726 2727 /// Sets this matrix to identity matrix. 2728 ref NVGMatrix identity () return { version(aliced) pragma(inline, true); mat[] = IdentityMat[]; return this; } 2729 2730 /// Translate this matrix. 2731 ref NVGMatrix translate (in float tx, in float ty) return { 2732 version(aliced) pragma(inline, true); 2733 return this.mul(Translated(tx, ty)); 2734 } 2735 2736 /// Scale this matrix. 2737 ref NVGMatrix scale (in float sx, in float sy) return { 2738 version(aliced) pragma(inline, true); 2739 return this.mul(Scaled(sx, sy)); 2740 } 2741 2742 /// Rotate this matrix. 2743 ref NVGMatrix rotate (in float a) return { 2744 version(aliced) pragma(inline, true); 2745 return this.mul(Rotated(a)); 2746 } 2747 2748 /// Skew this matrix by X axis. 2749 ref NVGMatrix skewX (in float a) return { 2750 version(aliced) pragma(inline, true); 2751 return this.mul(SkewedX(a)); 2752 } 2753 2754 /// Skew this matrix by Y axis. 2755 ref NVGMatrix skewY (in float a) return { 2756 version(aliced) pragma(inline, true); 2757 return this.mul(SkewedY(a)); 2758 } 2759 2760 /// Skew this matrix by both axes. 2761 ref NVGMatrix skewY (in float ax, in float ay) return { 2762 version(aliced) pragma(inline, true); 2763 return this.mul(SkewedXY(ax, ay)); 2764 } 2765 2766 /// Transform point with this matrix. `null` destinations are allowed. 2767 /// [sx] and [sy] is the source point. [dx] and [dy] may point to the same variables. 2768 void point (float* dx, float* dy, float sx, float sy) nothrow @trusted @nogc { 2769 version(aliced) pragma(inline, true); 2770 if (dx !is null) *dx = sx*mat.ptr[0]+sy*mat.ptr[2]+mat.ptr[4]; 2771 if (dy !is null) *dy = sx*mat.ptr[1]+sy*mat.ptr[3]+mat.ptr[5]; 2772 } 2773 2774 /// Transform point with this matrix. 2775 void point (ref float x, ref float y) nothrow @trusted @nogc { 2776 version(aliced) pragma(inline, true); 2777 immutable float nx = x*mat.ptr[0]+y*mat.ptr[2]+mat.ptr[4]; 2778 immutable float ny = x*mat.ptr[1]+y*mat.ptr[3]+mat.ptr[5]; 2779 x = nx; 2780 y = ny; 2781 } 2782 2783 /// Sets this matrix to the result of multiplication of `this` and [s] (this * S). 2784 ref NVGMatrix mul() (in auto ref NVGMatrix s) { 2785 immutable float t0 = mat.ptr[0]*s.mat.ptr[0]+mat.ptr[1]*s.mat.ptr[2]; 2786 immutable float t2 = mat.ptr[2]*s.mat.ptr[0]+mat.ptr[3]*s.mat.ptr[2]; 2787 immutable float t4 = mat.ptr[4]*s.mat.ptr[0]+mat.ptr[5]*s.mat.ptr[2]+s.mat.ptr[4]; 2788 mat.ptr[1] = mat.ptr[0]*s.mat.ptr[1]+mat.ptr[1]*s.mat.ptr[3]; 2789 mat.ptr[3] = mat.ptr[2]*s.mat.ptr[1]+mat.ptr[3]*s.mat.ptr[3]; 2790 mat.ptr[5] = mat.ptr[4]*s.mat.ptr[1]+mat.ptr[5]*s.mat.ptr[3]+s.mat.ptr[5]; 2791 mat.ptr[0] = t0; 2792 mat.ptr[2] = t2; 2793 mat.ptr[4] = t4; 2794 return this; 2795 } 2796 2797 /// Sets this matrix to the result of multiplication of [s] and `this` (S * this). 2798 /// Sets the transform to the result of multiplication of two transforms, of A = B*A. 2799 /// Group: matrices 2800 ref NVGMatrix premul() (in auto ref NVGMatrix s) { 2801 NVGMatrix s2 = s; 2802 s2.mul(this); 2803 mat[] = s2.mat[]; 2804 return this; 2805 } 2806 2807 /// Multiply this matrix by [s], return result as new matrix. 2808 /// Performs operations in this left-to-right order. 2809 NVGMatrix opBinary(string op="*") (in auto ref NVGMatrix s) const { 2810 version(aliced) pragma(inline, true); 2811 NVGMatrix res = this; 2812 res.mul(s); 2813 return res; 2814 } 2815 2816 /// Multiply this matrix by [s]. 2817 /// Performs operations in this left-to-right order. 2818 ref NVGMatrix opOpAssign(string op="*") (in auto ref NVGMatrix s) { 2819 version(aliced) pragma(inline, true); 2820 return this.mul(s); 2821 } 2822 2823 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. 2824 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. 2825 float rotation () const { pragma(inline, true); return nvg__atan2f(mat.ptr[1], mat.ptr[0]); } /// Returns rotation of this matrix. 2826 float tx () const { pragma(inline, true); return mat.ptr[4]; } /// Returns x translation of this matrix. 2827 float ty () const { pragma(inline, true); return mat.ptr[5]; } /// Returns y translation of this matrix. 2828 2829 ref NVGMatrix scaleX (in float v) return { pragma(inline, true); return scaleRotateTransform(v, scaleY, rotation, tx, ty); } /// Sets x scaling of this matrix. 2830 ref NVGMatrix scaleY (in float v) return { pragma(inline, true); return scaleRotateTransform(scaleX, v, rotation, tx, ty); } /// Sets y scaling of this matrix. 2831 ref NVGMatrix rotation (in float v) return { pragma(inline, true); return scaleRotateTransform(scaleX, scaleY, v, tx, ty); } /// Sets rotation of this matrix. 2832 ref NVGMatrix tx (in float v) return { pragma(inline, true); mat.ptr[4] = v; return this; } /// Sets x translation of this matrix. 2833 ref NVGMatrix ty (in float v) return { pragma(inline, true); mat.ptr[5] = v; return this; } /// Sets y translation of this matrix. 2834 2835 /// Utility function to be used in `setXXX()`. 2836 /// This is the same as doing: `mat.identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster 2837 ref NVGMatrix scaleRotateTransform (in float xscale, in float yscale, in float a, in float tx, in float ty) return { 2838 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a); 2839 mat.ptr[0] = xscale*cs; mat.ptr[1] = yscale*sn; 2840 mat.ptr[2] = xscale*-sn; mat.ptr[3] = yscale*cs; 2841 mat.ptr[4] = tx; mat.ptr[5] = ty; 2842 return this; 2843 } 2844 2845 /// This is the same as doing: `mat.identity.rotate(a).translate(tx, ty)`, only faster 2846 ref NVGMatrix rotateTransform (in float a, in float tx, in float ty) return { 2847 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a); 2848 mat.ptr[0] = cs; mat.ptr[1] = sn; 2849 mat.ptr[2] = -sn; mat.ptr[3] = cs; 2850 mat.ptr[4] = tx; mat.ptr[5] = ty; 2851 return this; 2852 } 2853 2854 /// Returns new identity matrix. 2855 static NVGMatrix Identity () { pragma(inline, true); return NVGMatrix.init; } 2856 2857 /// Returns new translation matrix. 2858 static NVGMatrix Translated (in float tx, in float ty) { 2859 version(aliced) pragma(inline, true); 2860 NVGMatrix res = void; 2861 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f; 2862 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f; 2863 res.mat.ptr[4] = tx; res.mat.ptr[5] = ty; 2864 return res; 2865 } 2866 2867 /// Returns new scaling matrix. 2868 static NVGMatrix Scaled (in float sx, in float sy) { 2869 version(aliced) pragma(inline, true); 2870 NVGMatrix res = void; 2871 res.mat.ptr[0] = sx; res.mat.ptr[1] = 0.0f; 2872 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = sy; 2873 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2874 return res; 2875 } 2876 2877 /// Returns new rotation matrix. Angle is specified in radians. 2878 static NVGMatrix Rotated (in float a) { 2879 version(aliced) pragma(inline, true); 2880 immutable float cs = nvg__cosf(a), sn = nvg__sinf(a); 2881 NVGMatrix res = void; 2882 res.mat.ptr[0] = cs; res.mat.ptr[1] = sn; 2883 res.mat.ptr[2] = -sn; res.mat.ptr[3] = cs; 2884 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2885 return res; 2886 } 2887 2888 /// Returns new x-skewing matrix. Angle is specified in radians. 2889 static NVGMatrix SkewedX (in float a) { 2890 version(aliced) pragma(inline, true); 2891 NVGMatrix res = void; 2892 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = 0.0f; 2893 res.mat.ptr[2] = nvg__tanf(a); res.mat.ptr[3] = 1.0f; 2894 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2895 return res; 2896 } 2897 2898 /// Returns new y-skewing matrix. Angle is specified in radians. 2899 static NVGMatrix SkewedY (in float a) { 2900 version(aliced) pragma(inline, true); 2901 NVGMatrix res = void; 2902 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(a); 2903 res.mat.ptr[2] = 0.0f; res.mat.ptr[3] = 1.0f; 2904 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2905 return res; 2906 } 2907 2908 /// Returns new xy-skewing matrix. Angles are specified in radians. 2909 static NVGMatrix SkewedXY (in float ax, in float ay) { 2910 version(aliced) pragma(inline, true); 2911 NVGMatrix res = void; 2912 res.mat.ptr[0] = 1.0f; res.mat.ptr[1] = nvg__tanf(ay); 2913 res.mat.ptr[2] = nvg__tanf(ax); res.mat.ptr[3] = 1.0f; 2914 res.mat.ptr[4] = 0.0f; res.mat.ptr[5] = 0.0f; 2915 return res; 2916 } 2917 2918 /// Utility function to be used in `setXXX()`. 2919 /// This is the same as doing: `NVGMatrix.Identity.rotate(a).scale(xs, ys).translate(tx, ty)`, only faster 2920 static NVGMatrix ScaledRotatedTransformed (in float xscale, in float yscale, in float a, in float tx, in float ty) { 2921 NVGMatrix res = void; 2922 res.scaleRotateTransform(xscale, yscale, a, tx, ty); 2923 return res; 2924 } 2925 2926 /// This is the same as doing: `NVGMatrix.Identity.rotate(a).translate(tx, ty)`, only faster 2927 static NVGMatrix RotatedTransformed (in float a, in float tx, in float ty) { 2928 NVGMatrix res = void; 2929 res.rotateTransform(a, tx, ty); 2930 return res; 2931 } 2932 } 2933 2934 2935 /// Converts degrees to radians. 2936 /// Group: matrices 2937 public float nvgDegToRad() (in float deg) pure nothrow @safe @nogc { pragma(inline, true); return deg/180.0f*NVG_PI; } 2938 2939 /// Converts radians to degrees. 2940 /// Group: matrices 2941 public float nvgRadToDeg() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad/NVG_PI*180.0f; } 2942 2943 public alias nvgDegrees = nvgDegToRad; /// Use this like `42.nvgDegrees` 2944 public float nvgRadians() (in float rad) pure nothrow @safe @nogc { pragma(inline, true); return rad; } /// Use this like `0.1.nvgRadians` 2945 2946 2947 // ////////////////////////////////////////////////////////////////////////// // 2948 void nvg__setPaintColor() (ref NVGPaint p, in auto ref NVGColor color) nothrow @trusted @nogc { 2949 p.clear(); 2950 p.xform.identity; 2951 p.radius = 0.0f; 2952 p.feather = 1.0f; 2953 p.innerColor = p.middleColor = p.outerColor = color; 2954 p.midp = -1; 2955 p.simpleColor = true; 2956 } 2957 2958 2959 // ////////////////////////////////////////////////////////////////////////// // 2960 // State handling 2961 2962 version(nanovega_debug_clipping) { 2963 public void nvgClipDumpOn (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, true); } 2964 public void nvgClipDumpOff (NVGContext ctx) { glnvg__clipDebugDump(ctx.params.userPtr, false); } 2965 } 2966 2967 /** Pushes and saves the current render state into a state stack. 2968 * A matching [restore] must be used to restore the state. 2969 * Returns `false` if state stack overflowed. 2970 * 2971 * Group: state_handling 2972 */ 2973 public bool save (NVGContext ctx) nothrow @trusted @nogc { 2974 if (ctx.nstates >= NVG_MAX_STATES) return false; 2975 if (ctx.nstates > 0) { 2976 //memcpy(&ctx.states[ctx.nstates], &ctx.states[ctx.nstates-1], NVGstate.sizeof); 2977 ctx.states[ctx.nstates] = ctx.states[ctx.nstates-1]; 2978 ctx.params.renderPushClip(ctx.params.userPtr); 2979 } 2980 ++ctx.nstates; 2981 return true; 2982 } 2983 2984 /// Pops and restores current render state. 2985 /// Group: state_handling 2986 public bool restore (NVGContext ctx) nothrow @trusted @nogc { 2987 if (ctx.nstates <= 1) return false; 2988 ctx.states[ctx.nstates-1].clearPaint(); 2989 ctx.params.renderPopClip(ctx.params.userPtr); 2990 --ctx.nstates; 2991 return true; 2992 } 2993 2994 /// Resets current render state to default values. Does not affect the render state stack. 2995 /// Group: state_handling 2996 public void reset (NVGContext ctx) nothrow @trusted @nogc { 2997 NVGstate* state = nvg__getState(ctx); 2998 state.clearPaint(); 2999 3000 nvg__setPaintColor(state.fill, nvgRGBA(255, 255, 255, 255)); 3001 nvg__setPaintColor(state.stroke, nvgRGBA(0, 0, 0, 255)); 3002 state.compositeOperation = nvg__compositeOperationState(NVGCompositeOperation.SourceOver); 3003 state.shapeAntiAlias = true; 3004 state.strokeWidth = 1.0f; 3005 state.miterLimit = 10.0f; 3006 state.lineCap = NVGLineCap.Butt; 3007 state.lineJoin = NVGLineCap.Miter; 3008 state.alpha = 1.0f; 3009 state.xform.identity; 3010 3011 state.scissor.extent[] = -1.0f; 3012 3013 state.fontSize = 16.0f; 3014 state.letterSpacing = 0.0f; 3015 state.lineHeight = 1.0f; 3016 state.fontBlur = 0.0f; 3017 state.textAlign.reset; 3018 state.fontId = 0; 3019 state.evenOddMode = false; 3020 state.dashCount = 0; 3021 state.lastFlattenDashCount = 0; 3022 state.dashStart = 0; 3023 state.firstDashIsGap = false; 3024 state.dasherActive = false; 3025 3026 ctx.params.renderResetClip(ctx.params.userPtr); 3027 } 3028 3029 /** Returns `true` if we have any room in state stack. 3030 * It is guaranteed to have at least 32 stack slots. 3031 * 3032 * Group: state_handling 3033 */ 3034 public bool canSave (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates < NVG_MAX_STATES); } 3035 3036 /** Returns `true` if we have any saved state. 3037 * 3038 * Group: state_handling 3039 */ 3040 public bool canRestore (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx.nstates > 1); } 3041 3042 /// Returns `true` if rendering is currently blocked. 3043 /// Group: state_handling 3044 public bool renderBlocked (NVGContext ctx) pure nothrow @trusted @nogc { pragma(inline, true); return (ctx !is null && ctx.contextAlive ? ctx.recblockdraw : false); } 3045 3046 /// Blocks/unblocks rendering 3047 /// Group: state_handling 3048 public void renderBlocked (NVGContext ctx, bool v) pure nothrow @trusted @nogc { pragma(inline, true); if (ctx !is null && ctx.contextAlive) ctx.recblockdraw = v; } 3049 3050 /// Blocks/unblocks rendering; returns previous state. 3051 /// Group: state_handling 3052 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; } 3053 3054 3055 // ////////////////////////////////////////////////////////////////////////// // 3056 // Render styles 3057 3058 /// Sets filling mode to "even-odd". 3059 /// Group: render_styles 3060 public void evenOddFill (NVGContext ctx) nothrow @trusted @nogc { 3061 NVGstate* state = nvg__getState(ctx); 3062 state.evenOddMode = true; 3063 } 3064 3065 /// Sets filling mode to "non-zero" (this is default mode). 3066 /// Group: render_styles 3067 public void nonZeroFill (NVGContext ctx) nothrow @trusted @nogc { 3068 NVGstate* state = nvg__getState(ctx); 3069 state.evenOddMode = false; 3070 } 3071 3072 /// Sets whether to draw antialias for [stroke] and [fill]. It's enabled by default. 3073 /// Group: render_styles 3074 public void shapeAntiAlias (NVGContext ctx, bool enabled) { 3075 NVGstate* state = nvg__getState(ctx); 3076 state.shapeAntiAlias = enabled; 3077 } 3078 3079 /// Sets the stroke width of the stroke style. 3080 /// Group: render_styles 3081 @scriptable 3082 public void strokeWidth (NVGContext ctx, float width) nothrow @trusted @nogc { 3083 NVGstate* state = nvg__getState(ctx); 3084 state.strokeWidth = width; 3085 } 3086 3087 /// Sets the miter limit of the stroke style. Miter limit controls when a sharp corner is beveled. 3088 /// Group: render_styles 3089 public void miterLimit (NVGContext ctx, float limit) nothrow @trusted @nogc { 3090 NVGstate* state = nvg__getState(ctx); 3091 state.miterLimit = limit; 3092 } 3093 3094 /// Sets how the end of the line (cap) is drawn, 3095 /// Can be one of: NVGLineCap.Butt (default), NVGLineCap.Round, NVGLineCap.Square. 3096 /// Group: render_styles 3097 public void lineCap (NVGContext ctx, NVGLineCap cap) nothrow @trusted @nogc { 3098 NVGstate* state = nvg__getState(ctx); 3099 state.lineCap = cap; 3100 } 3101 3102 /// Sets how sharp path corners are drawn. 3103 /// Can be one of NVGLineCap.Miter (default), NVGLineCap.Round, NVGLineCap.Bevel. 3104 /// Group: render_styles 3105 public void lineJoin (NVGContext ctx, NVGLineCap join) nothrow @trusted @nogc { 3106 NVGstate* state = nvg__getState(ctx); 3107 state.lineJoin = join; 3108 } 3109 3110 /// Sets stroke dashing, using (dash_length, gap_length) pairs. 3111 /// Current limit is 16 pairs. 3112 /// Resets dash start to zero. 3113 /// Group: render_styles 3114 public void setLineDash (NVGContext ctx, const(float)[] dashdata) nothrow @trusted @nogc { 3115 NVGstate* state = nvg__getState(ctx); 3116 state.dashCount = 0; 3117 state.dashStart = 0; 3118 state.firstDashIsGap = false; 3119 if (dashdata.length >= 2) { 3120 bool curFIsGap = true; // trick 3121 foreach (immutable idx, float f; dashdata) { 3122 curFIsGap = !curFIsGap; 3123 if (f < 0.01f) continue; // skip it 3124 if (idx == 0) { 3125 // register first dash 3126 state.firstDashIsGap = curFIsGap; 3127 state.dashes.ptr[state.dashCount++] = f; 3128 } else { 3129 if ((idx&1) != ((state.dashCount&1)^cast(uint)state.firstDashIsGap)) { 3130 // oops, continuation 3131 state.dashes[state.dashCount-1] += f; 3132 } else { 3133 if (state.dashCount == state.dashes.length) break; 3134 state.dashes[state.dashCount++] = f; 3135 } 3136 } 3137 } 3138 if (state.dashCount&1) { 3139 if (state.dashCount == 1) { 3140 state.dashCount = 0; 3141 } else { 3142 assert(state.dashCount < state.dashes.length); 3143 state.dashes[state.dashCount++] = 0; 3144 } 3145 } 3146 // calculate total dash path length 3147 state.totalDashLen = 0; 3148 foreach (float f; state.dashes.ptr[0..state.dashCount]) state.totalDashLen += f; 3149 if (state.totalDashLen < 0.01f) { 3150 state.dashCount = 0; // nothing to do 3151 } else { 3152 if (state.lastFlattenDashCount != 0) state.lastFlattenDashCount = uint.max; // force re-flattening 3153 } 3154 } 3155 } 3156 3157 public alias lineDash = setLineDash; /// Ditto. 3158 3159 /// Sets stroke dashing, using (dash_length, gap_length) pairs. 3160 /// Current limit is 16 pairs. 3161 /// Group: render_styles 3162 public void setLineDashStart (NVGContext ctx, in float dashStart) nothrow @trusted @nogc { 3163 NVGstate* state = nvg__getState(ctx); 3164 if (state.lastFlattenDashCount != 0 && state.dashStart != dashStart) { 3165 state.lastFlattenDashCount = uint.max; // force re-flattening 3166 } 3167 state.dashStart = dashStart; 3168 } 3169 3170 public alias lineDashStart = setLineDashStart; /// Ditto. 3171 3172 /// Sets the transparency applied to all rendered shapes. 3173 /// Already transparent paths will get proportionally more transparent as well. 3174 /// Group: render_styles 3175 public void globalAlpha (NVGContext ctx, float alpha) nothrow @trusted @nogc { 3176 NVGstate* state = nvg__getState(ctx); 3177 state.alpha = alpha; 3178 } 3179 3180 private void strokeColor() {} 3181 3182 static if (NanoVegaHasArsdColor) { 3183 /// Sets current stroke style to a solid color. 3184 /// Group: render_styles 3185 @scriptable 3186 public void strokeColor (NVGContext ctx, Color color) nothrow @trusted @nogc { 3187 NVGstate* state = nvg__getState(ctx); 3188 nvg__setPaintColor(state.stroke, NVGColor(color)); 3189 } 3190 } 3191 3192 /// Sets current stroke style to a solid color. 3193 /// Group: render_styles 3194 public void strokeColor() (NVGContext ctx, in auto ref NVGColor color) nothrow @trusted @nogc { 3195 NVGstate* state = nvg__getState(ctx); 3196 nvg__setPaintColor(state.stroke, color); 3197 } 3198 3199 @scriptable 3200 public void strokePaint(NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc { 3201 strokePaint(ctx, *paint); 3202 } 3203 3204 /// Sets current stroke style to a paint, which can be a one of the gradients or a pattern. 3205 /// Group: render_styles 3206 @scriptable 3207 public void strokePaint() (NVGContext ctx, in auto ref NVGPaint paint) nothrow @trusted @nogc { 3208 NVGstate* state = nvg__getState(ctx); 3209 state.stroke = paint; 3210 //nvgTransformMultiply(state.stroke.xform[], state.xform[]); 3211 state.stroke.xform.mul(state.xform); 3212 } 3213 3214 // this is a hack to work around https://issues.dlang.org/show_bug.cgi?id=16206 3215 // for scriptable reflection. it just needs to be declared first among the overloads 3216 private void fillColor (NVGContext ctx) nothrow @trusted @nogc { } 3217 3218 static if (NanoVegaHasArsdColor) { 3219 /// Sets current fill style to a solid color. 3220 /// Group: render_styles 3221 @scriptable 3222 public void fillColor (NVGContext ctx, Color color) nothrow @trusted @nogc { 3223 NVGstate* state = nvg__getState(ctx); 3224 nvg__setPaintColor(state.fill, NVGColor(color)); 3225 } 3226 } 3227 3228 /// Sets current fill style to a solid color. 3229 /// Group: render_styles 3230 public void fillColor() (NVGContext ctx, in auto ref NVGColor color) nothrow @trusted @nogc { 3231 NVGstate* state = nvg__getState(ctx); 3232 nvg__setPaintColor(state.fill, color); 3233 3234 } 3235 3236 @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) 3237 public void fillPaint (NVGContext ctx, in NVGPaint* paint) nothrow @trusted @nogc { 3238 fillPaint(ctx, *paint); 3239 } 3240 3241 /// Sets current fill style to a paint, which can be a one of the gradients or a pattern. 3242 /// Group: render_styles 3243 @scriptable 3244 public void fillPaint() (NVGContext ctx, in auto ref NVGPaint paint) nothrow @trusted @nogc { 3245 NVGstate* state = nvg__getState(ctx); 3246 state.fill = paint; 3247 //nvgTransformMultiply(state.fill.xform[], state.xform[]); 3248 state.fill.xform.mul(state.xform); 3249 } 3250 3251 /// Sets current fill style to a multistop linear gradient. 3252 /// Group: render_styles 3253 public void fillPaint() (NVGContext ctx, in auto ref NVGLGS lgs) nothrow @trusted @nogc { 3254 if (!lgs.valid) { 3255 NVGPaint p = void; 3256 memset(&p, 0, p.sizeof); 3257 nvg__setPaintColor(p, NVGColor.red); 3258 ctx.fillPaint = p; 3259 } else if (lgs.midp >= -1) { 3260 //{ import core.stdc.stdio; printf("SIMPLE! midp=%f\n", cast(double)lgs.midp); } 3261 ctx.fillPaint = ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc); 3262 } else { 3263 ctx.fillPaint = ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid); 3264 } 3265 } 3266 3267 /// Returns current transformation matrix. 3268 /// Group: render_transformations 3269 public NVGMatrix currTransform (NVGContext ctx) pure nothrow @trusted @nogc { 3270 NVGstate* state = nvg__getState(ctx); 3271 return state.xform; 3272 } 3273 3274 /// Sets current transformation matrix. 3275 /// Group: render_transformations 3276 public void currTransform() (NVGContext ctx, in auto ref NVGMatrix m) nothrow @trusted @nogc { 3277 NVGstate* state = nvg__getState(ctx); 3278 state.xform = m; 3279 } 3280 3281 /// Resets current transform to an identity matrix. 3282 /// Group: render_transformations 3283 @scriptable 3284 public void resetTransform (NVGContext ctx) nothrow @trusted @nogc { 3285 NVGstate* state = nvg__getState(ctx); 3286 state.xform.identity; 3287 } 3288 3289 /// Premultiplies current coordinate system by specified matrix. 3290 /// Group: render_transformations 3291 public void transform() (NVGContext ctx, in auto ref NVGMatrix mt) nothrow @trusted @nogc { 3292 NVGstate* state = nvg__getState(ctx); 3293 //nvgTransformPremultiply(state.xform[], t[]); 3294 state.xform *= mt; 3295 } 3296 3297 /// Translates current coordinate system. 3298 /// Group: render_transformations 3299 @scriptable 3300 public void translate (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 3301 NVGstate* state = nvg__getState(ctx); 3302 //NVGMatrix t = void; 3303 //nvgTransformTranslate(t[], x, y); 3304 //nvgTransformPremultiply(state.xform[], t[]); 3305 state.xform.premul(NVGMatrix.Translated(x, y)); 3306 } 3307 3308 /// Rotates current coordinate system. Angle is specified in radians. 3309 /// Group: render_transformations 3310 @scriptable 3311 public void rotate (NVGContext ctx, in float angle) nothrow @trusted @nogc { 3312 NVGstate* state = nvg__getState(ctx); 3313 //NVGMatrix t = void; 3314 //nvgTransformRotate(t[], angle); 3315 //nvgTransformPremultiply(state.xform[], t[]); 3316 state.xform.premul(NVGMatrix.Rotated(angle)); 3317 } 3318 3319 /// Skews the current coordinate system along X axis. Angle is specified in radians. 3320 /// Group: render_transformations 3321 @scriptable 3322 public void skewX (NVGContext ctx, in float angle) nothrow @trusted @nogc { 3323 NVGstate* state = nvg__getState(ctx); 3324 //NVGMatrix t = void; 3325 //nvgTransformSkewX(t[], angle); 3326 //nvgTransformPremultiply(state.xform[], t[]); 3327 state.xform.premul(NVGMatrix.SkewedX(angle)); 3328 } 3329 3330 /// Skews the current coordinate system along Y axis. Angle is specified in radians. 3331 /// Group: render_transformations 3332 @scriptable 3333 public void skewY (NVGContext ctx, in float angle) nothrow @trusted @nogc { 3334 NVGstate* state = nvg__getState(ctx); 3335 //NVGMatrix t = void; 3336 //nvgTransformSkewY(t[], angle); 3337 //nvgTransformPremultiply(state.xform[], t[]); 3338 state.xform.premul(NVGMatrix.SkewedY(angle)); 3339 } 3340 3341 /// Scales the current coordinate system. 3342 /// Group: render_transformations 3343 @scriptable 3344 public void scale (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 3345 NVGstate* state = nvg__getState(ctx); 3346 //NVGMatrix t = void; 3347 //nvgTransformScale(t[], x, y); 3348 //nvgTransformPremultiply(state.xform[], t[]); 3349 state.xform.premul(NVGMatrix.Scaled(x, y)); 3350 } 3351 3352 3353 // ////////////////////////////////////////////////////////////////////////// // 3354 // Images 3355 3356 /// Creates image by loading it from the disk from specified file name. 3357 /// Returns handle to the image or 0 on error. 3358 /// Group: images 3359 public NVGImage createImage() (NVGContext ctx, const(char)[] filename, const(NVGImageFlag)[] imageFlagsList...) { 3360 static if (NanoVegaHasArsdImage) { 3361 import arsd.image; 3362 // do we have new arsd API to load images? 3363 static if (!is(typeof(MemoryImage.fromImageFile)) || !is(typeof(MemoryImage.clearInternal))) { 3364 static assert(0, "Sorry, your ARSD is too old. Please, update it."); 3365 } 3366 try { 3367 auto oimg = MemoryImage.fromImageFile(filename); 3368 if (auto img = cast(TrueColorImage)oimg) { 3369 scope(exit) oimg.clearInternal(); 3370 return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList); 3371 } else { 3372 TrueColorImage img = oimg.getAsTrueColorImage; 3373 scope(exit) img.clearInternal(); 3374 oimg.clearInternal(); // drop original image, as `getAsTrueColorImage()` MUST create a new one here 3375 oimg = null; 3376 return ctx.createImageRGBA(img.width, img.height, img.imageData.bytes[], imageFlagsList); 3377 } 3378 } catch (Exception) {} 3379 return NVGImage.init; 3380 } else { 3381 import std.internal.cstring; 3382 ubyte* img; 3383 int w, h, n; 3384 stbi_set_unpremultiply_on_load(1); 3385 stbi_convert_iphone_png_to_rgb(1); 3386 img = stbi_load(filename.tempCString, &w, &h, &n, 4); 3387 if (img is null) { 3388 //printf("Failed to load %s - %s\n", filename, stbi_failure_reason()); 3389 return NVGImage.init; 3390 } 3391 auto image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList); 3392 stbi_image_free(img); 3393 return image; 3394 } 3395 } 3396 3397 static if (NanoVegaHasArsdImage) { 3398 /// Creates image by loading it from the specified memory image. 3399 /// Returns handle to the image or 0 on error. 3400 /// Group: images 3401 public NVGImage createImageFromMemoryImage() (NVGContext ctx, MemoryImage img, const(NVGImageFlag)[] imageFlagsList...) { 3402 if (img is null) return NVGImage.init; 3403 if (auto tc = cast(TrueColorImage)img) { 3404 return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList); 3405 } else { 3406 auto tc = img.getAsTrueColorImage; 3407 scope(exit) tc.clearInternal(); // here, it is guaranteed that `tc` is newly allocated image, so it is safe to kill it 3408 return ctx.createImageRGBA(tc.width, tc.height, tc.imageData.bytes[], imageFlagsList); 3409 } 3410 } 3411 } else { 3412 /// Creates image by loading it from the specified chunk of memory. 3413 /// Returns handle to the image or 0 on error. 3414 /// Group: images 3415 public NVGImage createImageMem() (NVGContext ctx, const(ubyte)* data, int ndata, const(NVGImageFlag)[] imageFlagsList...) { 3416 int w, h, n, image; 3417 ubyte* img = stbi_load_from_memory(data, ndata, &w, &h, &n, 4); 3418 if (img is null) { 3419 //printf("Failed to load %s - %s\n", filename, stbi_failure_reason()); 3420 return NVGImage.init; 3421 } 3422 image = ctx.createImageRGBA(w, h, img[0..w*h*4], imageFlagsList); 3423 stbi_image_free(img); 3424 return image; 3425 } 3426 } 3427 3428 /// Creates image from specified image data. 3429 /// Returns handle to the image or 0 on error. 3430 /// Group: images 3431 public NVGImage createImageRGBA (NVGContext ctx, int w, int h, const(void)[] data, const(NVGImageFlag)[] imageFlagsList...) nothrow @trusted @nogc { 3432 if (w < 1 || h < 1 || data.length < w*h*4) return NVGImage.init; 3433 uint imageFlags = 0; 3434 foreach (immutable uint flag; imageFlagsList) imageFlags |= flag; 3435 NVGImage res; 3436 res.id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.RGBA, w, h, imageFlags, cast(const(ubyte)*)data.ptr); 3437 if (res.id > 0) { 3438 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); } 3439 res.ctx = ctx; 3440 ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount 3441 } 3442 return res; 3443 } 3444 3445 /// Updates image data specified by image handle. 3446 /// Group: images 3447 public void updateImage() (NVGContext ctx, auto ref NVGImage image, const(void)[] data) nothrow @trusted @nogc { 3448 if (image.valid) { 3449 int w, h; 3450 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context"); 3451 ctx.params.renderGetTextureSize(ctx.params.userPtr, image.id, &w, &h); 3452 ctx.params.renderUpdateTexture(ctx.params.userPtr, image.id, 0, 0, w, h, cast(const(ubyte)*)data.ptr); 3453 } 3454 } 3455 3456 /// Returns the dimensions of a created image. 3457 /// Group: images 3458 public void imageSize() (NVGContext ctx, in auto ref NVGImage image, out int w, out int h) nothrow @trusted @nogc { 3459 if (image.valid) { 3460 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context"); 3461 ctx.params.renderGetTextureSize(cast(void*)ctx.params.userPtr, image.id, &w, &h); 3462 } 3463 } 3464 3465 /// Deletes created image. 3466 /// Group: images 3467 public void deleteImage() (NVGContext ctx, ref NVGImage image) nothrow @trusted @nogc { 3468 if (ctx is null || !image.valid) return; 3469 if (image.ctx !is ctx) assert(0, "NanoVega: you cannot use image from one context in another context"); 3470 image.clear(); 3471 } 3472 3473 3474 // ////////////////////////////////////////////////////////////////////////// // 3475 // Paints 3476 3477 private void linearGradient() {} // hack for dmd bug 3478 3479 static if (NanoVegaHasArsdColor) { 3480 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates 3481 * of the linear gradient, icol specifies the start color and ocol the end color. 3482 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3483 * 3484 * Group: paints 3485 */ 3486 @scriptable 3487 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 { 3488 return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), NVGColor(ocol)); 3489 } 3490 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start 3491 * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in 3492 * range `(0..1)`, and ocol the end color. 3493 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3494 * 3495 * Group: paints 3496 */ 3497 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 { 3498 return ctx.linearGradient(sx, sy, ex, ey, NVGColor(icol), midp, NVGColor(mcol), NVGColor(ocol)); 3499 } 3500 } 3501 3502 /** Creates and returns a linear gradient. Parameters `(sx, sy) (ex, ey)` specify the start and end coordinates 3503 * of the linear gradient, icol specifies the start color and ocol the end color. 3504 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3505 * 3506 * Group: paints 3507 */ 3508 public NVGPaint linearGradient() (NVGContext ctx, float sx, float sy, float ex, float ey, in auto ref NVGColor icol, in auto ref NVGColor ocol) nothrow @trusted @nogc { 3509 enum large = 1e5f; 3510 3511 NVGPaint p = void; 3512 memset(&p, 0, p.sizeof); 3513 p.simpleColor = false; 3514 3515 // Calculate transform aligned to the line 3516 float dx = ex-sx; 3517 float dy = ey-sy; 3518 immutable float d = nvg__sqrtf(dx*dx+dy*dy); 3519 if (d > 0.0001f) { 3520 dx /= d; 3521 dy /= d; 3522 } else { 3523 dx = 0; 3524 dy = 1; 3525 } 3526 3527 p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx; 3528 p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy; 3529 p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large; 3530 3531 p.extent.ptr[0] = large; 3532 p.extent.ptr[1] = large+d*0.5f; 3533 3534 p.radius = 0.0f; 3535 3536 p.feather = nvg__max(NVG_MIN_FEATHER, d); 3537 3538 p.innerColor = p.middleColor = icol; 3539 p.outerColor = ocol; 3540 p.midp = -1; 3541 3542 return p; 3543 } 3544 3545 /** Creates and returns a linear gradient with middle stop. Parameters `(sx, sy) (ex, ey)` specify the start 3546 * and end coordinates of the linear gradient, icol specifies the start color, midp specifies stop point in 3547 * range `(0..1)`, and ocol the end color. 3548 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3549 * 3550 * Group: paints 3551 */ 3552 public NVGPaint linearGradient() (NVGContext ctx, float sx, float sy, float ex, float ey, in auto ref NVGColor icol, in float midp, in auto ref NVGColor mcol, in auto ref NVGColor ocol) nothrow @trusted @nogc { 3553 enum large = 1e5f; 3554 3555 NVGPaint p = void; 3556 memset(&p, 0, p.sizeof); 3557 p.simpleColor = false; 3558 3559 // Calculate transform aligned to the line 3560 float dx = ex-sx; 3561 float dy = ey-sy; 3562 immutable float d = nvg__sqrtf(dx*dx+dy*dy); 3563 if (d > 0.0001f) { 3564 dx /= d; 3565 dy /= d; 3566 } else { 3567 dx = 0; 3568 dy = 1; 3569 } 3570 3571 p.xform.mat.ptr[0] = dy; p.xform.mat.ptr[1] = -dx; 3572 p.xform.mat.ptr[2] = dx; p.xform.mat.ptr[3] = dy; 3573 p.xform.mat.ptr[4] = sx-dx*large; p.xform.mat.ptr[5] = sy-dy*large; 3574 3575 p.extent.ptr[0] = large; 3576 p.extent.ptr[1] = large+d*0.5f; 3577 3578 p.radius = 0.0f; 3579 3580 p.feather = nvg__max(NVG_MIN_FEATHER, d); 3581 3582 if (midp <= 0) { 3583 p.innerColor = p.middleColor = mcol; 3584 p.midp = -1; 3585 } else if (midp > 1) { 3586 p.innerColor = p.middleColor = icol; 3587 p.midp = -1; 3588 } else { 3589 p.innerColor = icol; 3590 p.middleColor = mcol; 3591 p.midp = midp; 3592 } 3593 p.outerColor = ocol; 3594 3595 return p; 3596 } 3597 3598 static if (NanoVegaHasArsdColor) { 3599 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify 3600 * the inner and outer radius of the gradient, icol specifies the start color and ocol the end color. 3601 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3602 * 3603 * Group: paints 3604 */ 3605 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 { 3606 return ctx.radialGradient(cx, cy, inr, outr, NVGColor(icol), NVGColor(ocol)); 3607 } 3608 } 3609 3610 /** Creates and returns a radial gradient. Parameters (cx, cy) specify the center, inr and outr specify 3611 * the inner and outer radius of the gradient, icol specifies the start color 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 radialGradient() (NVGContext ctx, float cx, float cy, float inr, float outr, in auto ref NVGColor icol, in auto ref NVGColor ocol) nothrow @trusted @nogc { 3617 immutable float r = (inr+outr)*0.5f; 3618 immutable float f = (outr-inr); 3619 3620 NVGPaint p = void; 3621 memset(&p, 0, p.sizeof); 3622 p.simpleColor = false; 3623 3624 p.xform.identity; 3625 p.xform.mat.ptr[4] = cx; 3626 p.xform.mat.ptr[5] = cy; 3627 3628 p.extent.ptr[0] = r; 3629 p.extent.ptr[1] = r; 3630 3631 p.radius = r; 3632 3633 p.feather = nvg__max(NVG_MIN_FEATHER, f); 3634 3635 p.innerColor = p.middleColor = icol; 3636 p.outerColor = ocol; 3637 p.midp = -1; 3638 3639 return p; 3640 } 3641 3642 static if (NanoVegaHasArsdColor) { 3643 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering 3644 * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle, 3645 * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry 3646 * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient. 3647 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3648 * 3649 * Group: paints 3650 */ 3651 public NVGPaint 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 { 3652 return ctx.boxGradient(x, y, w, h, r, f, NVGColor(icol), NVGColor(ocol)); 3653 } 3654 } 3655 3656 /** Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering 3657 * drop shadows or highlights for boxes. Parameters (x, y) define the top-left corner of the rectangle, 3658 * (w, h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry 3659 * the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient. 3660 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3661 * 3662 * Group: paints 3663 */ 3664 public NVGPaint boxGradient() (NVGContext ctx, float x, float y, float w, float h, float r, float f, in auto ref NVGColor icol, in auto ref NVGColor ocol) nothrow @trusted @nogc { 3665 NVGPaint p = void; 3666 memset(&p, 0, p.sizeof); 3667 p.simpleColor = false; 3668 3669 p.xform.identity; 3670 p.xform.mat.ptr[4] = x+w*0.5f; 3671 p.xform.mat.ptr[5] = y+h*0.5f; 3672 3673 p.extent.ptr[0] = w*0.5f; 3674 p.extent.ptr[1] = h*0.5f; 3675 3676 p.radius = r; 3677 3678 p.feather = nvg__max(NVG_MIN_FEATHER, f); 3679 3680 p.innerColor = p.middleColor = icol; 3681 p.outerColor = ocol; 3682 p.midp = -1; 3683 3684 return p; 3685 } 3686 3687 /** Creates and returns an image pattern. Parameters `(cx, cy)` specify the left-top location of the image pattern, 3688 * `(w, h)` the size of one image, [angle] rotation around the top-left corner, [image] is handle to the image to render. 3689 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3690 * 3691 * Group: paints 3692 */ 3693 public NVGPaint imagePattern() (NVGContext ctx, float cx, float cy, float w, float h, float angle, in auto ref NVGImage image, float alpha=1) nothrow @trusted @nogc { 3694 NVGPaint p = void; 3695 memset(&p, 0, p.sizeof); 3696 p.simpleColor = false; 3697 3698 p.xform.identity.rotate(angle); 3699 p.xform.mat.ptr[4] = cx; 3700 p.xform.mat.ptr[5] = cy; 3701 3702 p.extent.ptr[0] = w; 3703 p.extent.ptr[1] = h; 3704 3705 p.image = image; 3706 3707 p.innerColor = p.middleColor = p.outerColor = nvgRGBAf(1, 1, 1, alpha); 3708 p.midp = -1; 3709 3710 return p; 3711 } 3712 3713 /// Linear gradient with multiple stops. 3714 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3715 /// Group: paints 3716 public struct NVGLGS { 3717 private: 3718 NVGColor ic, mc, oc; // inner, middle, out 3719 float midp; 3720 NVGImage imgid; 3721 // [imagePattern] arguments 3722 float cx, cy, dimx, dimy; // dimx and dimy are ex and ey for simple gradients 3723 public float angle; /// 3724 3725 public: 3726 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (imgid.valid || midp >= -1); } /// 3727 void clear () nothrow @safe @nogc { pragma(inline, true); imgid.clear(); midp = float.nan; } /// 3728 } 3729 3730 /** Returns [NVGPaint] for linear gradient with stops, created with [createLinearGradientWithStops]. 3731 * The gradient is transformed by the current transform when it is passed to [fillPaint] or [strokePaint]. 3732 * 3733 * $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3734 * Group: paints 3735 */ 3736 public NVGPaint asPaint() (NVGContext ctx, in auto ref NVGLGS lgs) nothrow @trusted @nogc { 3737 if (!lgs.valid) { 3738 NVGPaint p = void; 3739 memset(&p, 0, p.sizeof); 3740 nvg__setPaintColor(p, NVGColor.red); 3741 return p; 3742 } else if (lgs.midp >= -1) { 3743 return ctx.linearGradient(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.ic, lgs.midp, lgs.mc, lgs.oc); 3744 } else { 3745 return ctx.imagePattern(lgs.cx, lgs.cy, lgs.dimx, lgs.dimy, lgs.angle, lgs.imgid); 3746 } 3747 } 3748 3749 /// Gradient Stop Point. 3750 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3751 /// Group: paints 3752 public struct NVGGradientStop { 3753 float offset = 0; /// [0..1] 3754 NVGColor color; /// 3755 3756 this() (in float aofs, in auto ref NVGColor aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = aclr; } /// 3757 static if (NanoVegaHasArsdColor) { 3758 this() (in float aofs, in Color aclr) nothrow @trusted @nogc { pragma(inline, true); offset = aofs; color = NVGColor(aclr); } /// 3759 } 3760 } 3761 3762 /// Create linear gradient data suitable to use with `linearGradient(res)`. 3763 /// Don't forget to destroy the result when you don't need it anymore with `ctx.kill(res);`. 3764 /// $(WARNING THIS IS EXPERIMENTAL API AND MAY BE CHANGED/BROKEN IN NEXT RELEASES!) 3765 /// Group: paints 3766 public NVGLGS createLinearGradientWithStops (NVGContext ctx, in float sx, in float sy, in float ex, in float ey, const(NVGGradientStop)[] stops...) nothrow @trusted @nogc { 3767 // based on the code by Jorge Acereda <jacereda@gmail.com> 3768 enum NVG_GRADIENT_SAMPLES = 1024; 3769 static void gradientSpan (uint* dst, const(NVGGradientStop)* s0, const(NVGGradientStop)* s1) nothrow @trusted @nogc { 3770 immutable float s0o = nvg__clamp(s0.offset, 0.0f, 1.0f); 3771 immutable float s1o = nvg__clamp(s1.offset, 0.0f, 1.0f); 3772 uint s = cast(uint)(s0o*NVG_GRADIENT_SAMPLES); 3773 uint e = cast(uint)(s1o*NVG_GRADIENT_SAMPLES); 3774 uint sc = 0xffffffffU; 3775 uint sh = 24; 3776 uint r = cast(uint)(s0.color.rgba[0]*sc); 3777 uint g = cast(uint)(s0.color.rgba[1]*sc); 3778 uint b = cast(uint)(s0.color.rgba[2]*sc); 3779 uint a = cast(uint)(s0.color.rgba[3]*sc); 3780 uint dr = cast(uint)((s1.color.rgba[0]*sc-r)/(e-s)); 3781 uint dg = cast(uint)((s1.color.rgba[1]*sc-g)/(e-s)); 3782 uint db = cast(uint)((s1.color.rgba[2]*sc-b)/(e-s)); 3783 uint da = cast(uint)((s1.color.rgba[3]*sc-a)/(e-s)); 3784 dst += s; 3785 foreach (immutable _; s..e) { 3786 version(BigEndian) { 3787 *dst++ = ((r>>sh)<<24)+((g>>sh)<<16)+((b>>sh)<<8)+((a>>sh)<<0); 3788 } else { 3789 *dst++ = ((a>>sh)<<24)+((b>>sh)<<16)+((g>>sh)<<8)+((r>>sh)<<0); 3790 } 3791 r += dr; 3792 g += dg; 3793 b += db; 3794 a += da; 3795 } 3796 } 3797 3798 NVGLGS res; 3799 res.cx = sx; 3800 res.cy = sy; 3801 3802 if (stops.length == 2 && stops.ptr[0].offset <= 0 && stops.ptr[1].offset >= 1) { 3803 // create simple linear gradient 3804 res.ic = res.mc = stops.ptr[0].color; 3805 res.oc = stops.ptr[1].color; 3806 res.midp = -1; 3807 res.dimx = ex; 3808 res.dimy = ey; 3809 } else if (stops.length == 3 && stops.ptr[0].offset <= 0 && stops.ptr[2].offset >= 1) { 3810 // create simple linear gradient with middle stop 3811 res.ic = stops.ptr[0].color; 3812 res.mc = stops.ptr[1].color; 3813 res.oc = stops.ptr[2].color; 3814 res.midp = stops.ptr[1].offset; 3815 res.dimx = ex; 3816 res.dimy = ey; 3817 } else { 3818 // create image gradient 3819 uint[NVG_GRADIENT_SAMPLES] data = void; 3820 immutable float w = ex-sx; 3821 immutable float h = ey-sy; 3822 res.dimx = nvg__sqrtf(w*w+h*h); 3823 res.dimy = 1; //??? 3824 3825 res.angle = 3826 (/*nvg__absf(h) < 0.0001 ? 0 : 3827 nvg__absf(w) < 0.0001 ? 90.nvgDegrees :*/ 3828 nvg__atan2f(h/*ey-sy*/, w/*ex-sx*/)); 3829 3830 if (stops.length > 0) { 3831 auto s0 = NVGGradientStop(0, nvgRGBAf(0, 0, 0, 1)); 3832 auto s1 = NVGGradientStop(1, nvgRGBAf(1, 1, 1, 1)); 3833 if (stops.length > 64) stops = stops[0..64]; 3834 if (stops.length) { 3835 s0.color = stops[0].color; 3836 s1.color = stops[$-1].color; 3837 } 3838 gradientSpan(data.ptr, &s0, (stops.length ? stops.ptr : &s1)); 3839 foreach (immutable i; 0..stops.length-1) gradientSpan(data.ptr, stops.ptr+i, stops.ptr+i+1); 3840 gradientSpan(data.ptr, (stops.length ? stops.ptr+stops.length-1 : &s0), &s1); 3841 res.imgid = ctx.createImageRGBA(NVG_GRADIENT_SAMPLES, 1, data[]/*, NVGImageFlag.RepeatX, NVGImageFlag.RepeatY*/); 3842 } 3843 } 3844 return res; 3845 } 3846 3847 3848 // ////////////////////////////////////////////////////////////////////////// // 3849 // Scissoring 3850 3851 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform. 3852 /// Group: scissoring 3853 public void scissor (NVGContext ctx, in float x, in float y, float w, float h) nothrow @trusted @nogc { 3854 NVGstate* state = nvg__getState(ctx); 3855 3856 w = nvg__max(0.0f, w); 3857 h = nvg__max(0.0f, h); 3858 3859 state.scissor.xform.identity; 3860 state.scissor.xform.mat.ptr[4] = x+w*0.5f; 3861 state.scissor.xform.mat.ptr[5] = y+h*0.5f; 3862 //nvgTransformMultiply(state.scissor.xform[], state.xform[]); 3863 state.scissor.xform.mul(state.xform); 3864 3865 state.scissor.extent.ptr[0] = w*0.5f; 3866 state.scissor.extent.ptr[1] = h*0.5f; 3867 } 3868 3869 /// Sets the current scissor rectangle. The scissor rectangle is transformed by the current transform. 3870 /// Arguments: [x, y, w, h]* 3871 /// Group: scissoring 3872 public void scissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 3873 enum ArgC = 4; 3874 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [scissor] call"); 3875 if (args.length < ArgC) return; 3876 NVGstate* state = nvg__getState(ctx); 3877 const(float)* aptr = args.ptr; 3878 foreach (immutable idx; 0..args.length/ArgC) { 3879 immutable x = *aptr++; 3880 immutable y = *aptr++; 3881 immutable w = nvg__max(0.0f, *aptr++); 3882 immutable h = nvg__max(0.0f, *aptr++); 3883 3884 state.scissor.xform.identity; 3885 state.scissor.xform.mat.ptr[4] = x+w*0.5f; 3886 state.scissor.xform.mat.ptr[5] = y+h*0.5f; 3887 //nvgTransformMultiply(state.scissor.xform[], state.xform[]); 3888 state.scissor.xform.mul(state.xform); 3889 3890 state.scissor.extent.ptr[0] = w*0.5f; 3891 state.scissor.extent.ptr[1] = h*0.5f; 3892 } 3893 } 3894 3895 void nvg__isectRects (float* dst, float ax, float ay, float aw, float ah, float bx, float by, float bw, float bh) nothrow @trusted @nogc { 3896 immutable float minx = nvg__max(ax, bx); 3897 immutable float miny = nvg__max(ay, by); 3898 immutable float maxx = nvg__min(ax+aw, bx+bw); 3899 immutable float maxy = nvg__min(ay+ah, by+bh); 3900 dst[0] = minx; 3901 dst[1] = miny; 3902 dst[2] = nvg__max(0.0f, maxx-minx); 3903 dst[3] = nvg__max(0.0f, maxy-miny); 3904 } 3905 3906 /** Intersects current scissor rectangle with the specified rectangle. 3907 * The scissor rectangle is transformed by the current transform. 3908 * Note: in case the rotation of previous scissor rect differs from 3909 * the current one, the intersection will be done between the specified 3910 * rectangle and the previous scissor rectangle transformed in the current 3911 * transform space. The resulting shape is always rectangle. 3912 * 3913 * Group: scissoring 3914 */ 3915 public void intersectScissor (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc { 3916 NVGstate* state = nvg__getState(ctx); 3917 3918 // If no previous scissor has been set, set the scissor as current scissor. 3919 if (state.scissor.extent.ptr[0] < 0) { 3920 ctx.scissor(x, y, w, h); 3921 return; 3922 } 3923 3924 NVGMatrix pxform = void; 3925 NVGMatrix invxorm = void; 3926 float[4] rect = void; 3927 3928 // Transform the current scissor rect into current transform space. 3929 // If there is difference in rotation, this will be approximation. 3930 //memcpy(pxform.mat.ptr, state.scissor.xform.ptr, float.sizeof*6); 3931 pxform = state.scissor.xform; 3932 immutable float ex = state.scissor.extent.ptr[0]; 3933 immutable float ey = state.scissor.extent.ptr[1]; 3934 //nvgTransformInverse(invxorm[], state.xform[]); 3935 invxorm = state.xform.inverted; 3936 //nvgTransformMultiply(pxform[], invxorm[]); 3937 pxform.mul(invxorm); 3938 immutable float tex = ex*nvg__absf(pxform.mat.ptr[0])+ey*nvg__absf(pxform.mat.ptr[2]); 3939 immutable float tey = ex*nvg__absf(pxform.mat.ptr[1])+ey*nvg__absf(pxform.mat.ptr[3]); 3940 3941 // Intersect rects. 3942 nvg__isectRects(rect.ptr, pxform.mat.ptr[4]-tex, pxform.mat.ptr[5]-tey, tex*2, tey*2, x, y, w, h); 3943 3944 //ctx.scissor(rect.ptr[0], rect.ptr[1], rect.ptr[2], rect.ptr[3]); 3945 ctx.scissor(rect.ptr[0..4]); 3946 } 3947 3948 /** Intersects current scissor rectangle with the specified rectangle. 3949 * The scissor rectangle is transformed by the current transform. 3950 * Note: in case the rotation of previous scissor rect differs from 3951 * the current one, the intersection will be done between the specified 3952 * rectangle and the previous scissor rectangle transformed in the current 3953 * transform space. The resulting shape is always rectangle. 3954 * 3955 * Arguments: [x, y, w, h]* 3956 * 3957 * Group: scissoring 3958 */ 3959 public void intersectScissor (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 3960 enum ArgC = 4; 3961 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [intersectScissor] call"); 3962 if (args.length < ArgC) return; 3963 const(float)* aptr = args.ptr; 3964 foreach (immutable idx; 0..args.length/ArgC) { 3965 immutable x = *aptr++; 3966 immutable y = *aptr++; 3967 immutable w = *aptr++; 3968 immutable h = *aptr++; 3969 ctx.intersectScissor(x, y, w, h); 3970 } 3971 } 3972 3973 /// Reset and disables scissoring. 3974 /// Group: scissoring 3975 public void resetScissor (NVGContext ctx) nothrow @trusted @nogc { 3976 NVGstate* state = nvg__getState(ctx); 3977 state.scissor.xform.mat[] = 0.0f; 3978 state.scissor.extent[] = -1.0f; 3979 } 3980 3981 3982 // ////////////////////////////////////////////////////////////////////////// // 3983 // Render-Time Affine Transformations 3984 3985 /// Sets GPU affine transformatin matrix. Don't do scaling or skewing here. 3986 /// This matrix won't be saved/restored with context state save/restore operations, as it is not a part of that state. 3987 /// Group: gpu_affine 3988 public void affineGPU() (NVGContext ctx, in auto ref NVGMatrix mat) nothrow @trusted @nogc { 3989 ctx.gpuAffine = mat; 3990 ctx.params.renderSetAffine(ctx.params.userPtr, ctx.gpuAffine); 3991 } 3992 3993 /// Get current GPU affine transformatin matrix. 3994 /// Group: gpu_affine 3995 public NVGMatrix affineGPU (NVGContext ctx) nothrow @safe @nogc { 3996 pragma(inline, true); 3997 return ctx.gpuAffine; 3998 } 3999 4000 /// "Untransform" point using current GPU affine matrix. 4001 /// Group: gpu_affine 4002 public void gpuUntransformPoint (NVGContext ctx, float *dx, float *dy, in float x, in float y) nothrow @safe @nogc { 4003 if (ctx.gpuAffine.isIdentity) { 4004 if (dx !is null) *dx = x; 4005 if (dy !is null) *dy = y; 4006 } else { 4007 // inverse GPU transformation 4008 NVGMatrix igpu = ctx.gpuAffine.inverted; 4009 igpu.point(dx, dy, x, y); 4010 } 4011 } 4012 4013 4014 // ////////////////////////////////////////////////////////////////////////// // 4015 // rasterization (tesselation) code 4016 4017 int nvg__ptEquals (float x1, float y1, float x2, float y2, float tol) pure nothrow @safe @nogc { 4018 //pragma(inline, true); 4019 immutable float dx = x2-x1; 4020 immutable float dy = y2-y1; 4021 return dx*dx+dy*dy < tol*tol; 4022 } 4023 4024 float nvg__distPtSeg (float x, float y, float px, float py, float qx, float qy) pure nothrow @safe @nogc { 4025 immutable float pqx = qx-px; 4026 immutable float pqy = qy-py; 4027 float dx = x-px; 4028 float dy = y-py; 4029 immutable float d = pqx*pqx+pqy*pqy; 4030 float t = pqx*dx+pqy*dy; 4031 if (d > 0) t /= d; 4032 if (t < 0) t = 0; else if (t > 1) t = 1; 4033 dx = px+t*pqx-x; 4034 dy = py+t*pqy-y; 4035 return dx*dx+dy*dy; 4036 } 4037 4038 void nvg__appendCommands(bool useCommand=true) (NVGContext ctx, Command acmd, const(float)[] vals...) nothrow @trusted @nogc { 4039 int nvals = cast(int)vals.length; 4040 static if (useCommand) { 4041 enum addon = 1; 4042 } else { 4043 enum addon = 0; 4044 if (nvals == 0) return; // nothing to do 4045 } 4046 4047 NVGstate* state = nvg__getState(ctx); 4048 4049 if (ctx.ncommands+nvals+addon > ctx.ccommands) { 4050 //int ccommands = ctx.ncommands+nvals+ctx.ccommands/2; 4051 int ccommands = ((ctx.ncommands+(nvals+addon))|0xfff)+1; 4052 float* commands = cast(float*)realloc(ctx.commands, float.sizeof*ccommands); 4053 if (commands is null) assert(0, "NanoVega: out of memory"); 4054 ctx.commands = commands; 4055 ctx.ccommands = ccommands; 4056 assert(ctx.ncommands+(nvals+addon) <= ctx.ccommands); 4057 } 4058 4059 static if (!useCommand) acmd = cast(Command)vals.ptr[0]; 4060 4061 if (acmd != Command.Close && acmd != Command.Winding) { 4062 //assert(nvals+addon >= 3); 4063 ctx.commandx = vals.ptr[nvals-2]; 4064 ctx.commandy = vals.ptr[nvals-1]; 4065 } 4066 4067 // copy commands 4068 float* vp = ctx.commands+ctx.ncommands; 4069 static if (useCommand) { 4070 vp[0] = cast(float)acmd; 4071 if (nvals > 0) memcpy(vp+1, vals.ptr, nvals*float.sizeof); 4072 } else { 4073 memcpy(vp, vals.ptr, nvals*float.sizeof); 4074 } 4075 ctx.ncommands += nvals+addon; 4076 4077 // transform commands 4078 int i = nvals+addon; 4079 while (i > 0) { 4080 int nlen = 1; 4081 final switch (cast(Command)(*vp)) { 4082 case Command.MoveTo: 4083 case Command.LineTo: 4084 assert(i >= 3); 4085 state.xform.point(vp+1, vp+2, vp[1], vp[2]); 4086 nlen = 3; 4087 break; 4088 case Command.BezierTo: 4089 assert(i >= 7); 4090 state.xform.point(vp+1, vp+2, vp[1], vp[2]); 4091 state.xform.point(vp+3, vp+4, vp[3], vp[4]); 4092 state.xform.point(vp+5, vp+6, vp[5], vp[6]); 4093 nlen = 7; 4094 break; 4095 case Command.Close: 4096 nlen = 1; 4097 break; 4098 case Command.Winding: 4099 nlen = 2; 4100 break; 4101 } 4102 assert(nlen > 0 && nlen <= i); 4103 i -= nlen; 4104 vp += nlen; 4105 } 4106 } 4107 4108 void nvg__clearPathCache (NVGContext ctx) nothrow @trusted @nogc { 4109 // no need to clear paths, as data is not copied there 4110 //foreach (ref p; ctx.cache.paths[0..ctx.cache.npaths]) p.clear(); 4111 ctx.cache.npoints = 0; 4112 ctx.cache.npaths = 0; 4113 ctx.cache.fillReady = ctx.cache.strokeReady = false; 4114 ctx.cache.clipmode = NVGClipMode.None; 4115 } 4116 4117 NVGpath* nvg__lastPath (NVGContext ctx) nothrow @trusted @nogc { 4118 return (ctx.cache.npaths > 0 ? &ctx.cache.paths[ctx.cache.npaths-1] : null); 4119 } 4120 4121 void nvg__addPath (NVGContext ctx) nothrow @trusted @nogc { 4122 import core.stdc.stdlib : realloc; 4123 import core.stdc..string : memset; 4124 4125 if (ctx.cache.npaths+1 > ctx.cache.cpaths) { 4126 int cpaths = ctx.cache.npaths+1+ctx.cache.cpaths/2; 4127 NVGpath* paths = cast(NVGpath*)realloc(ctx.cache.paths, NVGpath.sizeof*cpaths); 4128 if (paths is null) assert(0, "NanoVega: out of memory"); 4129 ctx.cache.paths = paths; 4130 ctx.cache.cpaths = cpaths; 4131 } 4132 4133 NVGpath* path = &ctx.cache.paths[ctx.cache.npaths++]; 4134 memset(path, 0, NVGpath.sizeof); 4135 path.first = ctx.cache.npoints; 4136 path.mWinding = NVGWinding.CCW; 4137 } 4138 4139 NVGpoint* nvg__lastPoint (NVGContext ctx) nothrow @trusted @nogc { 4140 return (ctx.cache.npoints > 0 ? &ctx.cache.points[ctx.cache.npoints-1] : null); 4141 } 4142 4143 void nvg__addPoint (NVGContext ctx, float x, float y, int flags) nothrow @trusted @nogc { 4144 NVGpath* path = nvg__lastPath(ctx); 4145 if (path is null) return; 4146 4147 if (path.count > 0 && ctx.cache.npoints > 0) { 4148 NVGpoint* pt = nvg__lastPoint(ctx); 4149 if (nvg__ptEquals(pt.x, pt.y, x, y, ctx.distTol)) { 4150 pt.flags |= flags; 4151 return; 4152 } 4153 } 4154 4155 if (ctx.cache.npoints+1 > ctx.cache.cpoints) { 4156 int cpoints = ctx.cache.npoints+1+ctx.cache.cpoints/2; 4157 NVGpoint* points = cast(NVGpoint*)realloc(ctx.cache.points, NVGpoint.sizeof*cpoints); 4158 if (points is null) return; 4159 ctx.cache.points = points; 4160 ctx.cache.cpoints = cpoints; 4161 } 4162 4163 NVGpoint* pt = &ctx.cache.points[ctx.cache.npoints]; 4164 memset(pt, 0, (*pt).sizeof); 4165 pt.x = x; 4166 pt.y = y; 4167 pt.flags = cast(ubyte)flags; 4168 4169 ++ctx.cache.npoints; 4170 ++path.count; 4171 } 4172 4173 void nvg__closePath (NVGContext ctx) nothrow @trusted @nogc { 4174 NVGpath* path = nvg__lastPath(ctx); 4175 if (path is null) return; 4176 path.closed = true; 4177 } 4178 4179 void nvg__pathWinding (NVGContext ctx, NVGWinding winding) nothrow @trusted @nogc { 4180 NVGpath* path = nvg__lastPath(ctx); 4181 if (path is null) return; 4182 path.mWinding = winding; 4183 } 4184 4185 float nvg__getAverageScale() (in auto ref NVGMatrix t) /*pure*/ nothrow @trusted @nogc { 4186 immutable float sx = nvg__sqrtf(t.mat.ptr[0]*t.mat.ptr[0]+t.mat.ptr[2]*t.mat.ptr[2]); 4187 immutable float sy = nvg__sqrtf(t.mat.ptr[1]*t.mat.ptr[1]+t.mat.ptr[3]*t.mat.ptr[3]); 4188 return (sx+sy)*0.5f; 4189 } 4190 4191 NVGVertex* nvg__allocTempVerts (NVGContext ctx, int nverts) nothrow @trusted @nogc { 4192 if (nverts > ctx.cache.cverts) { 4193 int cverts = (nverts+0xff)&~0xff; // Round up to prevent allocations when things change just slightly. 4194 NVGVertex* verts = cast(NVGVertex*)realloc(ctx.cache.verts, NVGVertex.sizeof*cverts); 4195 if (verts is null) return null; 4196 ctx.cache.verts = verts; 4197 ctx.cache.cverts = cverts; 4198 } 4199 4200 return ctx.cache.verts; 4201 } 4202 4203 float nvg__triarea2 (float ax, float ay, float bx, float by, float cx, float cy) pure nothrow @safe @nogc { 4204 immutable float abx = bx-ax; 4205 immutable float aby = by-ay; 4206 immutable float acx = cx-ax; 4207 immutable float acy = cy-ay; 4208 return acx*aby-abx*acy; 4209 } 4210 4211 float nvg__polyArea (NVGpoint* pts, int npts) nothrow @trusted @nogc { 4212 float area = 0; 4213 foreach (int i; 2..npts) { 4214 NVGpoint* a = &pts[0]; 4215 NVGpoint* b = &pts[i-1]; 4216 NVGpoint* c = &pts[i]; 4217 area += nvg__triarea2(a.x, a.y, b.x, b.y, c.x, c.y); 4218 } 4219 return area*0.5f; 4220 } 4221 4222 void nvg__polyReverse (NVGpoint* pts, int npts) nothrow @trusted @nogc { 4223 NVGpoint tmp = void; 4224 int i = 0, j = npts-1; 4225 while (i < j) { 4226 tmp = pts[i]; 4227 pts[i] = pts[j]; 4228 pts[j] = tmp; 4229 ++i; 4230 --j; 4231 } 4232 } 4233 4234 void nvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc { 4235 vtx.x = x; 4236 vtx.y = y; 4237 vtx.u = u; 4238 vtx.v = v; 4239 } 4240 4241 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 { 4242 if (level > 10) return; 4243 4244 // check for collinear points, and use AFD tesselator on such curves (it is WAY faster for this case) 4245 /* 4246 if (level == 0 && ctx.tesselatortype == NVGTesselation.Combined) { 4247 static bool collinear (in float v0x, in float v0y, in float v1x, in float v1y, in float v2x, in float v2y) nothrow @trusted @nogc { 4248 immutable float cz = (v1x-v0x)*(v2y-v0y)-(v2x-v0x)*(v1y-v0y); 4249 return (nvg__absf(cz*cz) <= 0.01f); // arbitrary number, seems to work ok with NanoSVG output 4250 } 4251 if (collinear(x1, y1, x2, y2, x3, y3) && collinear(x2, y2, x3, y3, x3, y4)) { 4252 //{ import core.stdc.stdio; printf("AFD fallback!\n"); } 4253 ctx.nvg__tesselateBezierAFD(x1, y1, x2, y2, x3, y3, x4, y4, type); 4254 return; 4255 } 4256 } 4257 */ 4258 4259 immutable float x12 = (x1+x2)*0.5f; 4260 immutable float y12 = (y1+y2)*0.5f; 4261 immutable float x23 = (x2+x3)*0.5f; 4262 immutable float y23 = (y2+y3)*0.5f; 4263 immutable float x34 = (x3+x4)*0.5f; 4264 immutable float y34 = (y3+y4)*0.5f; 4265 immutable float x123 = (x12+x23)*0.5f; 4266 immutable float y123 = (y12+y23)*0.5f; 4267 4268 immutable float dx = x4-x1; 4269 immutable float dy = y4-y1; 4270 immutable float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx)); 4271 immutable float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx)); 4272 4273 if ((d2+d3)*(d2+d3) < ctx.tessTol*(dx*dx+dy*dy)) { 4274 nvg__addPoint(ctx, x4, y4, type); 4275 return; 4276 } 4277 4278 immutable float x234 = (x23+x34)*0.5f; 4279 immutable float y234 = (y23+y34)*0.5f; 4280 immutable float x1234 = (x123+x234)*0.5f; 4281 immutable float y1234 = (y123+y234)*0.5f; 4282 4283 // "taxicab" / "manhattan" check for flat curves 4284 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) { 4285 nvg__addPoint(ctx, x1234, y1234, type); 4286 return; 4287 } 4288 4289 nvg__tesselateBezier(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0); 4290 nvg__tesselateBezier(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type); 4291 } 4292 4293 // based on the ideas and code of Maxim Shemanarev. Rest in Peace, bro! 4294 // see http://www.antigrain.com/research/adaptive_bezier/index.html 4295 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 { 4296 enum CollinearEPS = 0.00000001f; // 0.00001f; 4297 enum AngleTolEPS = 0.01f; 4298 4299 static float distSquared (in float x1, in float y1, in float x2, in float y2) pure nothrow @safe @nogc { 4300 pragma(inline, true); 4301 immutable float dx = x2-x1; 4302 immutable float dy = y2-y1; 4303 return dx*dx+dy*dy; 4304 } 4305 4306 if (level == 0) { 4307 nvg__addPoint(ctx, x1, y1, 0); 4308 nvg__tesselateBezierMcSeem(ctx, x1, y1, x2, y2, x3, y3, x4, y4, 1, type); 4309 nvg__addPoint(ctx, x4, y4, type); 4310 return; 4311 } 4312 4313 if (level >= 32) return; // recurse limit; practically, it should be never reached, but... 4314 4315 // calculate all the mid-points of the line segments 4316 immutable float x12 = (x1+x2)*0.5f; 4317 immutable float y12 = (y1+y2)*0.5f; 4318 immutable float x23 = (x2+x3)*0.5f; 4319 immutable float y23 = (y2+y3)*0.5f; 4320 immutable float x34 = (x3+x4)*0.5f; 4321 immutable float y34 = (y3+y4)*0.5f; 4322 immutable float x123 = (x12+x23)*0.5f; 4323 immutable float y123 = (y12+y23)*0.5f; 4324 immutable float x234 = (x23+x34)*0.5f; 4325 immutable float y234 = (y23+y34)*0.5f; 4326 immutable float x1234 = (x123+x234)*0.5f; 4327 immutable float y1234 = (y123+y234)*0.5f; 4328 4329 // try to approximate the full cubic curve by a single straight line 4330 immutable float dx = x4-x1; 4331 immutable float dy = y4-y1; 4332 4333 float d2 = nvg__absf(((x2-x4)*dy-(y2-y4)*dx)); 4334 float d3 = nvg__absf(((x3-x4)*dy-(y3-y4)*dx)); 4335 //immutable float da1, da2, k; 4336 4337 final switch ((cast(int)(d2 > CollinearEPS)<<1)+cast(int)(d3 > CollinearEPS)) { 4338 case 0: 4339 // all collinear or p1 == p4 4340 float k = dx*dx+dy*dy; 4341 if (k == 0) { 4342 d2 = distSquared(x1, y1, x2, y2); 4343 d3 = distSquared(x4, y4, x3, y3); 4344 } else { 4345 k = 1.0f/k; 4346 float da1 = x2-x1; 4347 float da2 = y2-y1; 4348 d2 = k*(da1*dx+da2*dy); 4349 da1 = x3-x1; 4350 da2 = y3-y1; 4351 d3 = k*(da1*dx+da2*dy); 4352 if (d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1) { 4353 // Simple collinear case, 1---2---3---4 4354 // We can leave just two endpoints 4355 return; 4356 } 4357 if (d2 <= 0) d2 = distSquared(x2, y2, x1, y1); 4358 else if (d2 >= 1) d2 = distSquared(x2, y2, x4, y4); 4359 else d2 = distSquared(x2, y2, x1+d2*dx, y1+d2*dy); 4360 4361 if (d3 <= 0) d3 = distSquared(x3, y3, x1, y1); 4362 else if (d3 >= 1) d3 = distSquared(x3, y3, x4, y4); 4363 else d3 = distSquared(x3, y3, x1+d3*dx, y1+d3*dy); 4364 } 4365 if (d2 > d3) { 4366 if (d2 < ctx.tessTol) { 4367 nvg__addPoint(ctx, x2, y2, type); 4368 return; 4369 } 4370 } if (d3 < ctx.tessTol) { 4371 nvg__addPoint(ctx, x3, y3, type); 4372 return; 4373 } 4374 break; 4375 case 1: 4376 // p1,p2,p4 are collinear, p3 is significant 4377 if (d3*d3 <= ctx.tessTol*(dx*dx+dy*dy)) { 4378 if (ctx.angleTol < AngleTolEPS) { 4379 nvg__addPoint(ctx, x23, y23, type); 4380 return; 4381 } else { 4382 // angle condition 4383 float da1 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-nvg__atan2f(y3-y2, x3-x2)); 4384 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1; 4385 if (da1 < ctx.angleTol) { 4386 nvg__addPoint(ctx, x2, y2, type); 4387 nvg__addPoint(ctx, x3, y3, type); 4388 return; 4389 } 4390 if (ctx.cuspLimit != 0.0) { 4391 if (da1 > ctx.cuspLimit) { 4392 nvg__addPoint(ctx, x3, y3, type); 4393 return; 4394 } 4395 } 4396 } 4397 } 4398 break; 4399 case 2: 4400 // p1,p3,p4 are collinear, p2 is significant 4401 if (d2*d2 <= ctx.tessTol*(dx*dx+dy*dy)) { 4402 if (ctx.angleTol < AngleTolEPS) { 4403 nvg__addPoint(ctx, x23, y23, type); 4404 return; 4405 } else { 4406 // angle condition 4407 float da1 = nvg__absf(nvg__atan2f(y3-y2, x3-x2)-nvg__atan2f(y2-y1, x2-x1)); 4408 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1; 4409 if (da1 < ctx.angleTol) { 4410 nvg__addPoint(ctx, x2, y2, type); 4411 nvg__addPoint(ctx, x3, y3, type); 4412 return; 4413 } 4414 if (ctx.cuspLimit != 0.0) { 4415 if (da1 > ctx.cuspLimit) { 4416 nvg__addPoint(ctx, x2, y2, type); 4417 return; 4418 } 4419 } 4420 } 4421 } 4422 break; 4423 case 3: 4424 // regular case 4425 if ((d2+d3)*(d2+d3) <= ctx.tessTol*(dx*dx+dy*dy)) { 4426 // if the curvature doesn't exceed the distance tolerance value, we tend to finish subdivisions 4427 if (ctx.angleTol < AngleTolEPS) { 4428 nvg__addPoint(ctx, x23, y23, type); 4429 return; 4430 } else { 4431 // angle and cusp condition 4432 immutable float k = nvg__atan2f(y3-y2, x3-x2); 4433 float da1 = nvg__absf(k-nvg__atan2f(y2-y1, x2-x1)); 4434 float da2 = nvg__absf(nvg__atan2f(y4-y3, x4-x3)-k); 4435 if (da1 >= NVG_PI) da1 = 2*NVG_PI-da1; 4436 if (da2 >= NVG_PI) da2 = 2*NVG_PI-da2; 4437 if (da1+da2 < ctx.angleTol) { 4438 // finally we can stop the recursion 4439 nvg__addPoint(ctx, x23, y23, type); 4440 return; 4441 } 4442 if (ctx.cuspLimit != 0.0) { 4443 if (da1 > ctx.cuspLimit) { 4444 nvg__addPoint(ctx, x2, y2, type); 4445 return; 4446 } 4447 if (da2 > ctx.cuspLimit) { 4448 nvg__addPoint(ctx, x3, y3, type); 4449 return; 4450 } 4451 } 4452 } 4453 } 4454 break; 4455 } 4456 4457 // continue subdivision 4458 nvg__tesselateBezierMcSeem(ctx, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, 0); 4459 nvg__tesselateBezierMcSeem(ctx, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, type); 4460 } 4461 4462 4463 // Adaptive forward differencing for bezier tesselation. 4464 // See Lien, Sheue-Ling, Michael Shantz, and Vaughan Pratt. 4465 // "Adaptive forward differencing for rendering curves and surfaces." 4466 // ACM SIGGRAPH Computer Graphics. Vol. 21. No. 4. ACM, 1987. 4467 // original code by Taylor Holliday <taylor@audulus.com> 4468 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 { 4469 enum AFD_ONE = (1<<10); 4470 4471 // power basis 4472 immutable float ax = -x1+3*x2-3*x3+x4; 4473 immutable float ay = -y1+3*y2-3*y3+y4; 4474 immutable float bx = 3*x1-6*x2+3*x3; 4475 immutable float by = 3*y1-6*y2+3*y3; 4476 immutable float cx = -3*x1+3*x2; 4477 immutable float cy = -3*y1+3*y2; 4478 4479 // Transform to forward difference basis (stepsize 1) 4480 float px = x1; 4481 float py = y1; 4482 float dx = ax+bx+cx; 4483 float dy = ay+by+cy; 4484 float ddx = 6*ax+2*bx; 4485 float ddy = 6*ay+2*by; 4486 float dddx = 6*ax; 4487 float dddy = 6*ay; 4488 4489 //printf("dx: %f, dy: %f\n", dx, dy); 4490 //printf("ddx: %f, ddy: %f\n", ddx, ddy); 4491 //printf("dddx: %f, dddy: %f\n", dddx, dddy); 4492 4493 int t = 0; 4494 int dt = AFD_ONE; 4495 4496 immutable float tol = ctx.tessTol*4; 4497 4498 while (t < AFD_ONE) { 4499 // Flatness measure. 4500 float d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy; 4501 4502 // printf("d: %f, th: %f\n", d, th); 4503 4504 // Go to higher resolution if we're moving a lot or overshooting the end. 4505 while ((d > tol && dt > 1) || (t+dt > AFD_ONE)) { 4506 // printf("up\n"); 4507 4508 // Apply L to the curve. Increase curve resolution. 4509 dx = 0.5f*dx-(1.0f/8.0f)*ddx+(1.0f/16.0f)*dddx; 4510 dy = 0.5f*dy-(1.0f/8.0f)*ddy+(1.0f/16.0f)*dddy; 4511 ddx = (1.0f/4.0f)*ddx-(1.0f/8.0f)*dddx; 4512 ddy = (1.0f/4.0f)*ddy-(1.0f/8.0f)*dddy; 4513 dddx = (1.0f/8.0f)*dddx; 4514 dddy = (1.0f/8.0f)*dddy; 4515 4516 // Half the stepsize. 4517 dt >>= 1; 4518 4519 // Recompute d 4520 d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy; 4521 } 4522 4523 // Go to lower resolution if we're really flat 4524 // and we aren't going to overshoot the end. 4525 // XXX: tol/32 is just a guess for when we are too flat. 4526 while ((d > 0 && d < tol/32.0f && dt < AFD_ONE) && (t+2*dt <= AFD_ONE)) { 4527 // printf("down\n"); 4528 4529 // Apply L^(-1) to the curve. Decrease curve resolution. 4530 dx = 2*dx+ddx; 4531 dy = 2*dy+ddy; 4532 ddx = 4*ddx+4*dddx; 4533 ddy = 4*ddy+4*dddy; 4534 dddx = 8*dddx; 4535 dddy = 8*dddy; 4536 4537 // Double the stepsize. 4538 dt <<= 1; 4539 4540 // Recompute d 4541 d = ddx*ddx+ddy*ddy+dddx*dddx+dddy*dddy; 4542 } 4543 4544 // Forward differencing. 4545 px += dx; 4546 py += dy; 4547 dx += ddx; 4548 dy += ddy; 4549 ddx += dddx; 4550 ddy += dddy; 4551 4552 // Output a point. 4553 nvg__addPoint(ctx, px, py, (t > 0 ? type : 0)); 4554 4555 // Advance along the curve. 4556 t += dt; 4557 4558 // Ensure we don't overshoot. 4559 assert(t <= AFD_ONE); 4560 } 4561 } 4562 4563 4564 void nvg__dashLastPath (NVGContext ctx) nothrow @trusted @nogc { 4565 import core.stdc.stdlib : realloc; 4566 import core.stdc..string : memcpy; 4567 4568 NVGpathCache* cache = ctx.cache; 4569 if (cache.npaths == 0) return; 4570 4571 NVGpath* path = nvg__lastPath(ctx); 4572 if (path is null) return; 4573 4574 NVGstate* state = nvg__getState(ctx); 4575 if (!state.dasherActive) return; 4576 4577 static NVGpoint* pts = null; 4578 static uint ptsCount = 0; 4579 static uint ptsSize = 0; 4580 4581 if (path.count < 2) return; // just in case 4582 4583 // copy path points (reserve one point for closed pathes) 4584 if (ptsSize < path.count+1) { 4585 ptsSize = cast(uint)(path.count+1); 4586 pts = cast(NVGpoint*)realloc(pts, ptsSize*NVGpoint.sizeof); 4587 if (pts is null) assert(0, "NanoVega: out of memory"); 4588 } 4589 ptsCount = cast(uint)path.count; 4590 memcpy(pts, &cache.points[path.first], ptsCount*NVGpoint.sizeof); 4591 // add closing point for closed pathes 4592 if (path.closed && !nvg__ptEquals(pts[0].x, pts[0].y, pts[ptsCount-1].x, pts[ptsCount-1].y, ctx.distTol)) { 4593 pts[ptsCount++] = pts[0]; 4594 } 4595 4596 // remove last path (with its points) 4597 --cache.npaths; 4598 cache.npoints -= path.count; 4599 4600 // add stroked pathes 4601 const(float)* dashes = state.dashes.ptr; 4602 immutable uint dashCount = state.dashCount; 4603 float currDashStart = 0; 4604 uint currDashIdx = 0; 4605 immutable bool firstIsGap = state.firstDashIsGap; 4606 4607 // calculate lengthes 4608 { 4609 NVGpoint* v1 = &pts[0]; 4610 NVGpoint* v2 = &pts[1]; 4611 foreach (immutable _; 0..ptsCount) { 4612 float dx = v2.x-v1.x; 4613 float dy = v2.y-v1.y; 4614 v1.len = nvg__normalize(&dx, &dy); 4615 v1 = v2++; 4616 } 4617 } 4618 4619 void calcDashStart (float ds) { 4620 if (ds < 0) { 4621 ds = ds%state.totalDashLen; 4622 while (ds < 0) ds += state.totalDashLen; 4623 } 4624 currDashIdx = 0; 4625 currDashStart = 0; 4626 while (ds > 0) { 4627 if (ds > dashes[currDashIdx]) { 4628 ds -= dashes[currDashIdx]; 4629 ++currDashIdx; 4630 currDashStart = 0; 4631 if (currDashIdx >= dashCount) currDashIdx = 0; 4632 } else { 4633 currDashStart = ds; 4634 ds = 0; 4635 } 4636 } 4637 } 4638 4639 calcDashStart(state.dashStart); 4640 4641 uint srcPointIdx = 1; 4642 const(NVGpoint)* v1 = &pts[0]; 4643 const(NVGpoint)* v2 = &pts[1]; 4644 float currRest = v1.len; 4645 nvg__addPath(ctx); 4646 nvg__addPoint(ctx, v1.x, v1.y, PointFlag.Corner); 4647 4648 void fixLastPoint () { 4649 auto lpt = nvg__lastPath(ctx); 4650 if (lpt !is null && lpt.count > 0) { 4651 // fix last point 4652 if (auto lps = nvg__lastPoint(ctx)) lps.flags = PointFlag.Corner; 4653 // fix first point 4654 NVGpathCache* cache = ctx.cache; 4655 cache.points[lpt.first].flags = PointFlag.Corner; 4656 } 4657 } 4658 4659 for (;;) { 4660 immutable float dlen = dashes[currDashIdx]; 4661 if (dlen == 0) { 4662 ++currDashIdx; 4663 if (currDashIdx >= dashCount) currDashIdx = 0; 4664 continue; 4665 } 4666 immutable float dashRest = dlen-currDashStart; 4667 if ((currDashIdx&1) != firstIsGap) { 4668 // this is "moveto" command, so create new path 4669 fixLastPoint(); 4670 nvg__addPath(ctx); 4671 } 4672 if (currRest > dashRest) { 4673 currRest -= dashRest; 4674 ++currDashIdx; 4675 if (currDashIdx >= dashCount) currDashIdx = 0; 4676 currDashStart = 0; 4677 nvg__addPoint(ctx, 4678 v2.x-(v2.x-v1.x)*currRest/v1.len, 4679 v2.y-(v2.y-v1.y)*currRest/v1.len, 4680 PointFlag.Corner 4681 ); 4682 } else { 4683 currDashStart += currRest; 4684 nvg__addPoint(ctx, v2.x, v2.y, v1.flags); //k8:fix flags here? 4685 ++srcPointIdx; 4686 v1 = v2; 4687 currRest = v1.len; 4688 if (srcPointIdx >= ptsCount) break; 4689 v2 = &pts[srcPointIdx]; 4690 } 4691 } 4692 fixLastPoint(); 4693 } 4694 4695 4696 version(nanovg_bench_flatten) import iv.timer : Timer; 4697 4698 void nvg__flattenPaths(bool asStroke) (NVGContext ctx) nothrow @trusted @nogc { 4699 version(nanovg_bench_flatten) { 4700 Timer timer; 4701 char[128] tmbuf; 4702 int bzcount; 4703 } 4704 NVGpathCache* cache = ctx.cache; 4705 NVGstate* state = nvg__getState(ctx); 4706 4707 // check if we already did flattening 4708 static if (asStroke) { 4709 if (state.dashCount >= 2) { 4710 if (cache.npaths > 0 && state.lastFlattenDashCount == state.dashCount) return; // already flattened 4711 state.dasherActive = true; 4712 state.lastFlattenDashCount = state.dashCount; 4713 } else { 4714 if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened 4715 state.dasherActive = false; 4716 state.lastFlattenDashCount = 0; 4717 } 4718 } else { 4719 if (cache.npaths > 0 && state.lastFlattenDashCount == 0) return; // already flattened 4720 state.lastFlattenDashCount = 0; // so next stroke flattening will redo it 4721 state.dasherActive = false; 4722 } 4723 4724 // clear path cache 4725 cache.npaths = 0; 4726 cache.npoints = 0; 4727 4728 // flatten 4729 version(nanovg_bench_flatten) timer.restart(); 4730 int i = 0; 4731 while (i < ctx.ncommands) { 4732 final switch (cast(Command)ctx.commands[i]) { 4733 case Command.MoveTo: 4734 //assert(i+3 <= ctx.ncommands); 4735 static if (asStroke) { 4736 if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx); 4737 } 4738 nvg__addPath(ctx); 4739 const p = &ctx.commands[i+1]; 4740 nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner); 4741 i += 3; 4742 break; 4743 case Command.LineTo: 4744 //assert(i+3 <= ctx.ncommands); 4745 const p = &ctx.commands[i+1]; 4746 nvg__addPoint(ctx, p[0], p[1], PointFlag.Corner); 4747 i += 3; 4748 break; 4749 case Command.BezierTo: 4750 //assert(i+7 <= ctx.ncommands); 4751 const last = nvg__lastPoint(ctx); 4752 if (last !is null) { 4753 const cp1 = &ctx.commands[i+1]; 4754 const cp2 = &ctx.commands[i+3]; 4755 const p = &ctx.commands[i+5]; 4756 if (ctx.tesselatortype == NVGTesselation.DeCasteljau) { 4757 nvg__tesselateBezier(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner); 4758 } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) { 4759 nvg__tesselateBezierMcSeem(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], 0, PointFlag.Corner); 4760 } else { 4761 nvg__tesselateBezierAFD(ctx, last.x, last.y, cp1[0], cp1[1], cp2[0], cp2[1], p[0], p[1], PointFlag.Corner); 4762 } 4763 version(nanovg_bench_flatten) ++bzcount; 4764 } 4765 i += 7; 4766 break; 4767 case Command.Close: 4768 //assert(i+1 <= ctx.ncommands); 4769 nvg__closePath(ctx); 4770 i += 1; 4771 break; 4772 case Command.Winding: 4773 //assert(i+2 <= ctx.ncommands); 4774 nvg__pathWinding(ctx, cast(NVGWinding)ctx.commands[i+1]); 4775 i += 2; 4776 break; 4777 } 4778 } 4779 static if (asStroke) { 4780 if (cache.npaths > 0 && state.dasherActive) nvg__dashLastPath(ctx); 4781 } 4782 version(nanovg_bench_flatten) {{ 4783 timer.stop(); 4784 auto xb = timer.toBuffer(tmbuf[]); 4785 import core.stdc.stdio : printf; 4786 printf("flattening time: [%.*s] (%d beziers)\n", cast(uint)xb.length, xb.ptr, bzcount); 4787 }} 4788 4789 cache.bounds.ptr[0] = cache.bounds.ptr[1] = float.max; 4790 cache.bounds.ptr[2] = cache.bounds.ptr[3] = -float.max; 4791 4792 // calculate the direction and length of line segments 4793 version(nanovg_bench_flatten) timer.restart(); 4794 foreach (int j; 0..cache.npaths) { 4795 NVGpath* path = &cache.paths[j]; 4796 NVGpoint* pts = &cache.points[path.first]; 4797 4798 // if the first and last points are the same, remove the last, mark as closed path 4799 NVGpoint* p0 = &pts[path.count-1]; 4800 NVGpoint* p1 = &pts[0]; 4801 if (nvg__ptEquals(p0.x, p0.y, p1.x, p1.y, ctx.distTol)) { 4802 --path.count; 4803 p0 = &pts[path.count-1]; 4804 path.closed = true; 4805 } 4806 4807 // enforce winding 4808 if (path.count > 2) { 4809 immutable float area = nvg__polyArea(pts, path.count); 4810 if (path.mWinding == NVGWinding.CCW && area < 0.0f) nvg__polyReverse(pts, path.count); 4811 if (path.mWinding == NVGWinding.CW && area > 0.0f) nvg__polyReverse(pts, path.count); 4812 } 4813 4814 foreach (immutable _; 0..path.count) { 4815 // calculate segment direction and length 4816 p0.dx = p1.x-p0.x; 4817 p0.dy = p1.y-p0.y; 4818 p0.len = nvg__normalize(&p0.dx, &p0.dy); 4819 // update bounds 4820 cache.bounds.ptr[0] = nvg__min(cache.bounds.ptr[0], p0.x); 4821 cache.bounds.ptr[1] = nvg__min(cache.bounds.ptr[1], p0.y); 4822 cache.bounds.ptr[2] = nvg__max(cache.bounds.ptr[2], p0.x); 4823 cache.bounds.ptr[3] = nvg__max(cache.bounds.ptr[3], p0.y); 4824 // advance 4825 p0 = p1++; 4826 } 4827 } 4828 version(nanovg_bench_flatten) {{ 4829 timer.stop(); 4830 auto xb = timer.toBuffer(tmbuf[]); 4831 import core.stdc.stdio : printf; 4832 printf("segment calculation time: [%.*s]\n", cast(uint)xb.length, xb.ptr); 4833 }} 4834 } 4835 4836 int nvg__curveDivs (float r, float arc, float tol) nothrow @trusted @nogc { 4837 immutable float da = nvg__acosf(r/(r+tol))*2.0f; 4838 return nvg__max(2, cast(int)nvg__ceilf(arc/da)); 4839 } 4840 4841 void nvg__chooseBevel (int bevel, NVGpoint* p0, NVGpoint* p1, float w, float* x0, float* y0, float* x1, float* y1) nothrow @trusted @nogc { 4842 if (bevel) { 4843 *x0 = p1.x+p0.dy*w; 4844 *y0 = p1.y-p0.dx*w; 4845 *x1 = p1.x+p1.dy*w; 4846 *y1 = p1.y-p1.dx*w; 4847 } else { 4848 *x0 = p1.x+p1.dmx*w; 4849 *y0 = p1.y+p1.dmy*w; 4850 *x1 = p1.x+p1.dmx*w; 4851 *y1 = p1.y+p1.dmy*w; 4852 } 4853 } 4854 4855 NVGVertex* nvg__roundJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, int ncap, float fringe) nothrow @trusted @nogc { 4856 float dlx0 = p0.dy; 4857 float dly0 = -p0.dx; 4858 float dlx1 = p1.dy; 4859 float dly1 = -p1.dx; 4860 //NVG_NOTUSED(fringe); 4861 4862 if (p1.flags&PointFlag.Left) { 4863 float lx0 = void, ly0 = void, lx1 = void, ly1 = void; 4864 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1); 4865 immutable float a0 = nvg__atan2f(-dly0, -dlx0); 4866 float a1 = nvg__atan2f(-dly1, -dlx1); 4867 if (a1 > a0) a1 -= NVG_PI*2; 4868 4869 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4870 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4871 4872 int n = nvg__clamp(cast(int)nvg__ceilf(((a0-a1)/NVG_PI)*ncap), 2, ncap); 4873 for (int i = 0; i < n; ++i) { 4874 float u = i/cast(float)(n-1); 4875 float a = a0+u*(a1-a0); 4876 float rx = p1.x+nvg__cosf(a)*rw; 4877 float ry = p1.y+nvg__sinf(a)*rw; 4878 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4879 nvg__vset(dst, rx, ry, ru, 1); ++dst; 4880 } 4881 4882 nvg__vset(dst, lx1, ly1, lu, 1); ++dst; 4883 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4884 4885 } else { 4886 float rx0 = void, ry0 = void, rx1 = void, ry1 = void; 4887 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1); 4888 immutable float a0 = nvg__atan2f(dly0, dlx0); 4889 float a1 = nvg__atan2f(dly1, dlx1); 4890 if (a1 < a0) a1 += NVG_PI*2; 4891 4892 nvg__vset(dst, p1.x+dlx0*rw, p1.y+dly0*rw, lu, 1); ++dst; 4893 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4894 4895 int n = nvg__clamp(cast(int)nvg__ceilf(((a1-a0)/NVG_PI)*ncap), 2, ncap); 4896 for (int i = 0; i < n; i++) { 4897 float u = i/cast(float)(n-1); 4898 float a = a0+u*(a1-a0); 4899 float lx = p1.x+nvg__cosf(a)*lw; 4900 float ly = p1.y+nvg__sinf(a)*lw; 4901 nvg__vset(dst, lx, ly, lu, 1); ++dst; 4902 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4903 } 4904 4905 nvg__vset(dst, p1.x+dlx1*rw, p1.y+dly1*rw, lu, 1); ++dst; 4906 nvg__vset(dst, rx1, ry1, ru, 1); ++dst; 4907 4908 } 4909 return dst; 4910 } 4911 4912 NVGVertex* nvg__bevelJoin (NVGVertex* dst, NVGpoint* p0, NVGpoint* p1, float lw, float rw, float lu, float ru, float fringe) nothrow @trusted @nogc { 4913 float rx0, ry0, rx1, ry1; 4914 float lx0, ly0, lx1, ly1; 4915 float dlx0 = p0.dy; 4916 float dly0 = -p0.dx; 4917 float dlx1 = p1.dy; 4918 float dly1 = -p1.dx; 4919 //NVG_NOTUSED(fringe); 4920 4921 if (p1.flags&PointFlag.Left) { 4922 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, lw, &lx0, &ly0, &lx1, &ly1); 4923 4924 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4925 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4926 4927 if (p1.flags&PointFlag.Bevel) { 4928 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4929 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4930 4931 nvg__vset(dst, lx1, ly1, lu, 1); ++dst; 4932 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4933 } else { 4934 rx0 = p1.x-p1.dmx*rw; 4935 ry0 = p1.y-p1.dmy*rw; 4936 4937 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4938 nvg__vset(dst, p1.x-dlx0*rw, p1.y-dly0*rw, ru, 1); ++dst; 4939 4940 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4941 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4942 4943 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4944 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4945 } 4946 4947 nvg__vset(dst, lx1, ly1, lu, 1); ++dst; 4948 nvg__vset(dst, p1.x-dlx1*rw, p1.y-dly1*rw, ru, 1); ++dst; 4949 4950 } else { 4951 nvg__chooseBevel(p1.flags&PointFlag.InnerBevelPR, p0, p1, -rw, &rx0, &ry0, &rx1, &ry1); 4952 4953 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst; 4954 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4955 4956 if (p1.flags&PointFlag.Bevel) { 4957 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst; 4958 nvg__vset(dst, rx0, ry0, ru, 1); ++dst; 4959 4960 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst; 4961 nvg__vset(dst, rx1, ry1, ru, 1); ++dst; 4962 } else { 4963 lx0 = p1.x+p1.dmx*lw; 4964 ly0 = p1.y+p1.dmy*lw; 4965 4966 nvg__vset(dst, p1.x+dlx0*lw, p1.y+dly0*lw, lu, 1); ++dst; 4967 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4968 4969 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4970 nvg__vset(dst, lx0, ly0, lu, 1); ++dst; 4971 4972 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst; 4973 nvg__vset(dst, p1.x, p1.y, 0.5f, 1); ++dst; 4974 } 4975 4976 nvg__vset(dst, p1.x+dlx1*lw, p1.y+dly1*lw, lu, 1); ++dst; 4977 nvg__vset(dst, rx1, ry1, ru, 1); ++dst; 4978 } 4979 4980 return dst; 4981 } 4982 4983 NVGVertex* nvg__buttCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc { 4984 immutable float px = p.x-dx*d; 4985 immutable float py = p.y-dy*d; 4986 immutable float dlx = dy; 4987 immutable float dly = -dx; 4988 nvg__vset(dst, px+dlx*w-dx*aa, py+dly*w-dy*aa, 0, 0); ++dst; 4989 nvg__vset(dst, px-dlx*w-dx*aa, py-dly*w-dy*aa, 1, 0); ++dst; 4990 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 4991 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 4992 return dst; 4993 } 4994 4995 NVGVertex* nvg__buttCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, float d, float aa) nothrow @trusted @nogc { 4996 immutable float px = p.x+dx*d; 4997 immutable float py = p.y+dy*d; 4998 immutable float dlx = dy; 4999 immutable float dly = -dx; 5000 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5001 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5002 nvg__vset(dst, px+dlx*w+dx*aa, py+dly*w+dy*aa, 0, 0); ++dst; 5003 nvg__vset(dst, px-dlx*w+dx*aa, py-dly*w+dy*aa, 1, 0); ++dst; 5004 return dst; 5005 } 5006 5007 NVGVertex* nvg__roundCapStart (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc { 5008 immutable float px = p.x; 5009 immutable float py = p.y; 5010 immutable float dlx = dy; 5011 immutable float dly = -dx; 5012 //NVG_NOTUSED(aa); 5013 immutable float ncpf = cast(float)(ncap-1); 5014 foreach (int i; 0..ncap) { 5015 float a = i/*/cast(float)(ncap-1)*//ncpf*NVG_PI; 5016 float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w; 5017 nvg__vset(dst, px-dlx*ax-dx*ay, py-dly*ax-dy*ay, 0, 1); ++dst; 5018 nvg__vset(dst, px, py, 0.5f, 1); ++dst; 5019 } 5020 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5021 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5022 return dst; 5023 } 5024 5025 NVGVertex* nvg__roundCapEnd (NVGVertex* dst, NVGpoint* p, float dx, float dy, float w, int ncap, float aa) nothrow @trusted @nogc { 5026 immutable float px = p.x; 5027 immutable float py = p.y; 5028 immutable float dlx = dy; 5029 immutable float dly = -dx; 5030 //NVG_NOTUSED(aa); 5031 nvg__vset(dst, px+dlx*w, py+dly*w, 0, 1); ++dst; 5032 nvg__vset(dst, px-dlx*w, py-dly*w, 1, 1); ++dst; 5033 immutable float ncpf = cast(float)(ncap-1); 5034 foreach (int i; 0..ncap) { 5035 float a = i/*cast(float)(ncap-1)*//ncpf*NVG_PI; 5036 float ax = nvg__cosf(a)*w, ay = nvg__sinf(a)*w; 5037 nvg__vset(dst, px, py, 0.5f, 1); ++dst; 5038 nvg__vset(dst, px-dlx*ax+dx*ay, py-dly*ax+dy*ay, 0, 1); ++dst; 5039 } 5040 return dst; 5041 } 5042 5043 void nvg__calculateJoins (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc { 5044 NVGpathCache* cache = ctx.cache; 5045 float iw = 0.0f; 5046 5047 if (w > 0.0f) iw = 1.0f/w; 5048 5049 // Calculate which joins needs extra vertices to append, and gather vertex count. 5050 foreach (int i; 0..cache.npaths) { 5051 NVGpath* path = &cache.paths[i]; 5052 NVGpoint* pts = &cache.points[path.first]; 5053 NVGpoint* p0 = &pts[path.count-1]; 5054 NVGpoint* p1 = &pts[0]; 5055 int nleft = 0; 5056 5057 path.nbevel = 0; 5058 5059 foreach (int j; 0..path.count) { 5060 //float dlx0, dly0, dlx1, dly1, dmr2, cross, limit; 5061 immutable float dlx0 = p0.dy; 5062 immutable float dly0 = -p0.dx; 5063 immutable float dlx1 = p1.dy; 5064 immutable float dly1 = -p1.dx; 5065 // Calculate extrusions 5066 p1.dmx = (dlx0+dlx1)*0.5f; 5067 p1.dmy = (dly0+dly1)*0.5f; 5068 immutable float dmr2 = p1.dmx*p1.dmx+p1.dmy*p1.dmy; 5069 if (dmr2 > 0.000001f) { 5070 float scale = 1.0f/dmr2; 5071 if (scale > 600.0f) scale = 600.0f; 5072 p1.dmx *= scale; 5073 p1.dmy *= scale; 5074 } 5075 5076 // Clear flags, but keep the corner. 5077 p1.flags = (p1.flags&PointFlag.Corner) ? PointFlag.Corner : 0; 5078 5079 // Keep track of left turns. 5080 immutable float cross = p1.dx*p0.dy-p0.dx*p1.dy; 5081 if (cross > 0.0f) { 5082 nleft++; 5083 p1.flags |= PointFlag.Left; 5084 } 5085 5086 // Calculate if we should use bevel or miter for inner join. 5087 immutable float limit = nvg__max(1.01f, nvg__min(p0.len, p1.len)*iw); 5088 if ((dmr2*limit*limit) < 1.0f) p1.flags |= PointFlag.InnerBevelPR; 5089 5090 // Check to see if the corner needs to be beveled. 5091 if (p1.flags&PointFlag.Corner) { 5092 if ((dmr2*miterLimit*miterLimit) < 1.0f || lineJoin == NVGLineCap.Bevel || lineJoin == NVGLineCap.Round) { 5093 p1.flags |= PointFlag.Bevel; 5094 } 5095 } 5096 5097 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) path.nbevel++; 5098 5099 p0 = p1++; 5100 } 5101 5102 path.convex = (nleft == path.count); 5103 } 5104 } 5105 5106 void nvg__expandStroke (NVGContext ctx, float w, int lineCap, int lineJoin, float miterLimit) nothrow @trusted @nogc { 5107 NVGpathCache* cache = ctx.cache; 5108 immutable float aa = ctx.fringeWidth; 5109 int ncap = nvg__curveDivs(w, NVG_PI, ctx.tessTol); // Calculate divisions per half circle. 5110 5111 nvg__calculateJoins(ctx, w, lineJoin, miterLimit); 5112 5113 // Calculate max vertex usage. 5114 int cverts = 0; 5115 foreach (int i; 0..cache.npaths) { 5116 NVGpath* path = &cache.paths[i]; 5117 immutable bool loop = path.closed; 5118 if (lineJoin == NVGLineCap.Round) { 5119 cverts += (path.count+path.nbevel*(ncap+2)+1)*2; // plus one for loop 5120 } else { 5121 cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop 5122 } 5123 if (!loop) { 5124 // space for caps 5125 if (lineCap == NVGLineCap.Round) { 5126 cverts += (ncap*2+2)*2; 5127 } else { 5128 cverts += (3+3)*2; 5129 } 5130 } 5131 } 5132 5133 NVGVertex* verts = nvg__allocTempVerts(ctx, cverts); 5134 if (verts is null) return; 5135 5136 foreach (int i; 0..cache.npaths) { 5137 NVGpath* path = &cache.paths[i]; 5138 NVGpoint* pts = &cache.points[path.first]; 5139 NVGpoint* p0; 5140 NVGpoint* p1; 5141 int s, e; 5142 5143 path.fill = null; 5144 path.nfill = 0; 5145 5146 // Calculate fringe or stroke 5147 immutable bool loop = path.closed; 5148 NVGVertex* dst = verts; 5149 path.stroke = dst; 5150 5151 if (loop) { 5152 // Looping 5153 p0 = &pts[path.count-1]; 5154 p1 = &pts[0]; 5155 s = 0; 5156 e = path.count; 5157 } else { 5158 // Add cap 5159 p0 = &pts[0]; 5160 p1 = &pts[1]; 5161 s = 1; 5162 e = path.count-1; 5163 } 5164 5165 if (!loop) { 5166 // Add cap 5167 float dx = p1.x-p0.x; 5168 float dy = p1.y-p0.y; 5169 nvg__normalize(&dx, &dy); 5170 if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapStart(dst, p0, dx, dy, w, -aa*0.5f, aa); 5171 else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapStart(dst, p0, dx, dy, w, w-aa, aa); 5172 else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapStart(dst, p0, dx, dy, w, ncap, aa); 5173 } 5174 5175 foreach (int j; s..e) { 5176 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) { 5177 if (lineJoin == NVGLineCap.Round) { 5178 dst = nvg__roundJoin(dst, p0, p1, w, w, 0, 1, ncap, aa); 5179 } else { 5180 dst = nvg__bevelJoin(dst, p0, p1, w, w, 0, 1, aa); 5181 } 5182 } else { 5183 nvg__vset(dst, p1.x+(p1.dmx*w), p1.y+(p1.dmy*w), 0, 1); ++dst; 5184 nvg__vset(dst, p1.x-(p1.dmx*w), p1.y-(p1.dmy*w), 1, 1); ++dst; 5185 } 5186 p0 = p1++; 5187 } 5188 5189 if (loop) { 5190 // Loop it 5191 nvg__vset(dst, verts[0].x, verts[0].y, 0, 1); ++dst; 5192 nvg__vset(dst, verts[1].x, verts[1].y, 1, 1); ++dst; 5193 } else { 5194 // Add cap 5195 float dx = p1.x-p0.x; 5196 float dy = p1.y-p0.y; 5197 nvg__normalize(&dx, &dy); 5198 if (lineCap == NVGLineCap.Butt) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, -aa*0.5f, aa); 5199 else if (lineCap == NVGLineCap.Butt || lineCap == NVGLineCap.Square) dst = nvg__buttCapEnd(dst, p1, dx, dy, w, w-aa, aa); 5200 else if (lineCap == NVGLineCap.Round) dst = nvg__roundCapEnd(dst, p1, dx, dy, w, ncap, aa); 5201 } 5202 5203 path.nstroke = cast(int)(dst-verts); 5204 5205 verts = dst; 5206 } 5207 } 5208 5209 void nvg__expandFill (NVGContext ctx, float w, int lineJoin, float miterLimit) nothrow @trusted @nogc { 5210 NVGpathCache* cache = ctx.cache; 5211 immutable float aa = ctx.fringeWidth; 5212 bool fringe = (w > 0.0f); 5213 5214 nvg__calculateJoins(ctx, w, lineJoin, miterLimit); 5215 5216 // Calculate max vertex usage. 5217 int cverts = 0; 5218 foreach (int i; 0..cache.npaths) { 5219 NVGpath* path = &cache.paths[i]; 5220 cverts += path.count+path.nbevel+1; 5221 if (fringe) cverts += (path.count+path.nbevel*5+1)*2; // plus one for loop 5222 } 5223 5224 NVGVertex* verts = nvg__allocTempVerts(ctx, cverts); 5225 if (verts is null) return; 5226 5227 bool convex = (cache.npaths == 1 && cache.paths[0].convex); 5228 5229 foreach (int i; 0..cache.npaths) { 5230 NVGpath* path = &cache.paths[i]; 5231 NVGpoint* pts = &cache.points[path.first]; 5232 5233 // Calculate shape vertices. 5234 immutable float woff = 0.5f*aa; 5235 NVGVertex* dst = verts; 5236 path.fill = dst; 5237 5238 if (fringe) { 5239 // Looping 5240 NVGpoint* p0 = &pts[path.count-1]; 5241 NVGpoint* p1 = &pts[0]; 5242 foreach (int j; 0..path.count) { 5243 if (p1.flags&PointFlag.Bevel) { 5244 immutable float dlx0 = p0.dy; 5245 immutable float dly0 = -p0.dx; 5246 immutable float dlx1 = p1.dy; 5247 immutable float dly1 = -p1.dx; 5248 if (p1.flags&PointFlag.Left) { 5249 immutable float lx = p1.x+p1.dmx*woff; 5250 immutable float ly = p1.y+p1.dmy*woff; 5251 nvg__vset(dst, lx, ly, 0.5f, 1); ++dst; 5252 } else { 5253 immutable float lx0 = p1.x+dlx0*woff; 5254 immutable float ly0 = p1.y+dly0*woff; 5255 immutable float lx1 = p1.x+dlx1*woff; 5256 immutable float ly1 = p1.y+dly1*woff; 5257 nvg__vset(dst, lx0, ly0, 0.5f, 1); ++dst; 5258 nvg__vset(dst, lx1, ly1, 0.5f, 1); ++dst; 5259 } 5260 } else { 5261 nvg__vset(dst, p1.x+(p1.dmx*woff), p1.y+(p1.dmy*woff), 0.5f, 1); ++dst; 5262 } 5263 p0 = p1++; 5264 } 5265 } else { 5266 foreach (int j; 0..path.count) { 5267 nvg__vset(dst, pts[j].x, pts[j].y, 0.5f, 1); 5268 ++dst; 5269 } 5270 } 5271 5272 path.nfill = cast(int)(dst-verts); 5273 verts = dst; 5274 5275 // Calculate fringe 5276 if (fringe) { 5277 float lw = w+woff; 5278 immutable float rw = w-woff; 5279 float lu = 0; 5280 immutable float ru = 1; 5281 dst = verts; 5282 path.stroke = dst; 5283 5284 // Create only half a fringe for convex shapes so that 5285 // the shape can be rendered without stenciling. 5286 if (convex) { 5287 lw = woff; // This should generate the same vertex as fill inset above. 5288 lu = 0.5f; // Set outline fade at middle. 5289 } 5290 5291 // Looping 5292 NVGpoint* p0 = &pts[path.count-1]; 5293 NVGpoint* p1 = &pts[0]; 5294 5295 foreach (int j; 0..path.count) { 5296 if ((p1.flags&(PointFlag.Bevel|PointFlag.InnerBevelPR)) != 0) { 5297 dst = nvg__bevelJoin(dst, p0, p1, lw, rw, lu, ru, ctx.fringeWidth); 5298 } else { 5299 nvg__vset(dst, p1.x+(p1.dmx*lw), p1.y+(p1.dmy*lw), lu, 1); ++dst; 5300 nvg__vset(dst, p1.x-(p1.dmx*rw), p1.y-(p1.dmy*rw), ru, 1); ++dst; 5301 } 5302 p0 = p1++; 5303 } 5304 5305 // Loop it 5306 nvg__vset(dst, verts[0].x, verts[0].y, lu, 1); ++dst; 5307 nvg__vset(dst, verts[1].x, verts[1].y, ru, 1); ++dst; 5308 5309 path.nstroke = cast(int)(dst-verts); 5310 verts = dst; 5311 } else { 5312 path.stroke = null; 5313 path.nstroke = 0; 5314 } 5315 } 5316 } 5317 5318 5319 // ////////////////////////////////////////////////////////////////////////// // 5320 // Paths 5321 5322 /// Clears the current path and sub-paths. 5323 /// Group: paths 5324 @scriptable 5325 public void beginPath (NVGContext ctx) nothrow @trusted @nogc { 5326 ctx.ncommands = 0; 5327 ctx.pathPickRegistered &= NVGPickKind.All; // reset "registered" flags 5328 nvg__clearPathCache(ctx); 5329 } 5330 5331 public alias newPath = beginPath; /// Ditto. 5332 5333 /// Starts new sub-path with specified point as first point. 5334 /// Group: paths 5335 @scriptable 5336 public void moveTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 5337 nvg__appendCommands(ctx, Command.MoveTo, x, y); 5338 } 5339 5340 /// Starts new sub-path with specified point as first point. 5341 /// Arguments: [x, y]* 5342 /// Group: paths 5343 public void moveTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5344 enum ArgC = 2; 5345 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [moveTo] call"); 5346 if (args.length < ArgC) return; 5347 nvg__appendCommands(ctx, Command.MoveTo, args[$-2..$]); 5348 } 5349 5350 /// Adds line segment from the last point in the path to the specified point. 5351 /// Group: paths 5352 @scriptable 5353 public void lineTo (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 5354 nvg__appendCommands(ctx, Command.LineTo, x, y); 5355 } 5356 5357 /// Adds line segment from the last point in the path to the specified point. 5358 /// Arguments: [x, y]* 5359 /// Group: paths 5360 public void lineTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5361 enum ArgC = 2; 5362 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [lineTo] call"); 5363 if (args.length < ArgC) return; 5364 foreach (immutable idx; 0..args.length/ArgC) { 5365 nvg__appendCommands(ctx, Command.LineTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]); 5366 } 5367 } 5368 5369 /// Adds cubic bezier segment from last point in the path via two control points to the specified point. 5370 /// Group: paths 5371 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 { 5372 nvg__appendCommands(ctx, Command.BezierTo, c1x, c1y, c2x, c2y, x, y); 5373 } 5374 5375 /// Adds cubic bezier segment from last point in the path via two control points to the specified point. 5376 /// Arguments: [c1x, c1y, c2x, c2y, x, y]* 5377 /// Group: paths 5378 public void bezierTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5379 enum ArgC = 6; 5380 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [bezierTo] call"); 5381 if (args.length < ArgC) return; 5382 foreach (immutable idx; 0..args.length/ArgC) { 5383 nvg__appendCommands(ctx, Command.BezierTo, args.ptr[idx*ArgC..idx*ArgC+ArgC]); 5384 } 5385 } 5386 5387 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point. 5388 /// Group: paths 5389 public void quadTo (NVGContext ctx, in float cx, in float cy, in float x, in float y) nothrow @trusted @nogc { 5390 immutable float x0 = ctx.commandx; 5391 immutable float y0 = ctx.commandy; 5392 nvg__appendCommands(ctx, 5393 Command.BezierTo, 5394 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0), 5395 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y), 5396 x, y, 5397 ); 5398 } 5399 5400 /// Adds quadratic bezier segment from last point in the path via a control point to the specified point. 5401 /// Arguments: [cx, cy, x, y]* 5402 /// Group: paths 5403 public void quadTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5404 enum ArgC = 4; 5405 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [quadTo] call"); 5406 if (args.length < ArgC) return; 5407 const(float)* aptr = args.ptr; 5408 foreach (immutable idx; 0..args.length/ArgC) { 5409 immutable float x0 = ctx.commandx; 5410 immutable float y0 = ctx.commandy; 5411 immutable float cx = *aptr++; 5412 immutable float cy = *aptr++; 5413 immutable float x = *aptr++; 5414 immutable float y = *aptr++; 5415 nvg__appendCommands(ctx, 5416 Command.BezierTo, 5417 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0), 5418 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y), 5419 x, y, 5420 ); 5421 } 5422 } 5423 5424 /// Adds an arc segment at the corner defined by the last path point, and two specified points. 5425 /// Group: paths 5426 public void arcTo (NVGContext ctx, in float x1, in float y1, in float x2, in float y2, in float radius) nothrow @trusted @nogc { 5427 if (ctx.ncommands == 0) return; 5428 5429 immutable float x0 = ctx.commandx; 5430 immutable float y0 = ctx.commandy; 5431 5432 // handle degenerate cases 5433 if (nvg__ptEquals(x0, y0, x1, y1, ctx.distTol) || 5434 nvg__ptEquals(x1, y1, x2, y2, ctx.distTol) || 5435 nvg__distPtSeg(x1, y1, x0, y0, x2, y2) < ctx.distTol*ctx.distTol || 5436 radius < ctx.distTol) 5437 { 5438 ctx.lineTo(x1, y1); 5439 return; 5440 } 5441 5442 // calculate tangential circle to lines (x0, y0)-(x1, y1) and (x1, y1)-(x2, y2) 5443 float dx0 = x0-x1; 5444 float dy0 = y0-y1; 5445 float dx1 = x2-x1; 5446 float dy1 = y2-y1; 5447 nvg__normalize(&dx0, &dy0); 5448 nvg__normalize(&dx1, &dy1); 5449 immutable float a = nvg__acosf(dx0*dx1+dy0*dy1); 5450 immutable float d = radius/nvg__tanf(a/2.0f); 5451 5452 //printf("a=%f° d=%f\n", a/NVG_PI*180.0f, d); 5453 5454 if (d > 10000.0f) { 5455 ctx.lineTo(x1, y1); 5456 return; 5457 } 5458 5459 float cx = void, cy = void, a0 = void, a1 = void; 5460 NVGWinding dir; 5461 if (nvg__cross(dx0, dy0, dx1, dy1) > 0.0f) { 5462 cx = x1+dx0*d+dy0*radius; 5463 cy = y1+dy0*d+-dx0*radius; 5464 a0 = nvg__atan2f(dx0, -dy0); 5465 a1 = nvg__atan2f(-dx1, dy1); 5466 dir = NVGWinding.CW; 5467 //printf("CW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f); 5468 } else { 5469 cx = x1+dx0*d+-dy0*radius; 5470 cy = y1+dy0*d+dx0*radius; 5471 a0 = nvg__atan2f(-dx0, dy0); 5472 a1 = nvg__atan2f(dx1, -dy1); 5473 dir = NVGWinding.CCW; 5474 //printf("CCW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f); 5475 } 5476 5477 ctx.arc(dir, cx, cy, radius, a0, a1); // first is line 5478 } 5479 5480 5481 /// Adds an arc segment at the corner defined by the last path point, and two specified points. 5482 /// Arguments: [x1, y1, x2, y2, radius]* 5483 /// Group: paths 5484 public void arcTo (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5485 enum ArgC = 5; 5486 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arcTo] call"); 5487 if (args.length < ArgC) return; 5488 if (ctx.ncommands == 0) return; 5489 const(float)* aptr = args.ptr; 5490 foreach (immutable idx; 0..args.length/ArgC) { 5491 immutable float x0 = ctx.commandx; 5492 immutable float y0 = ctx.commandy; 5493 immutable float x1 = *aptr++; 5494 immutable float y1 = *aptr++; 5495 immutable float x2 = *aptr++; 5496 immutable float y2 = *aptr++; 5497 immutable float radius = *aptr++; 5498 ctx.arcTo(x1, y1, x2, y2, radius); 5499 } 5500 } 5501 5502 /// Closes current sub-path with a line segment. 5503 /// Group: paths 5504 @scriptable 5505 public void closePath (NVGContext ctx) nothrow @trusted @nogc { 5506 nvg__appendCommands(ctx, Command.Close); 5507 } 5508 5509 /// Sets the current sub-path winding, see NVGWinding and NVGSolidity. 5510 /// Group: paths 5511 public void pathWinding (NVGContext ctx, NVGWinding dir) nothrow @trusted @nogc { 5512 nvg__appendCommands(ctx, Command.Winding, cast(float)dir); 5513 } 5514 5515 /// Ditto. 5516 public void pathWinding (NVGContext ctx, NVGSolidity dir) nothrow @trusted @nogc { 5517 nvg__appendCommands(ctx, Command.Winding, cast(float)dir); 5518 } 5519 5520 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r, 5521 * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW). 5522 * Angles are specified in radians. 5523 * 5524 * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo 5525 * 5526 * Group: paths 5527 */ 5528 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 { 5529 static assert(mode == "original" || mode == "move" || mode == "line"); 5530 5531 float[3+5*7+100] vals = void; 5532 //int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo); 5533 static if (mode == "original") { 5534 immutable int move = (ctx.ncommands > 0 ? Command.LineTo : Command.MoveTo); 5535 } else static if (mode == "move") { 5536 enum move = Command.MoveTo; 5537 } else static if (mode == "line") { 5538 enum move = Command.LineTo; 5539 } else { 5540 static assert(0, "wtf?!"); 5541 } 5542 5543 // Clamp angles 5544 float da = a1-a0; 5545 if (dir == NVGWinding.CW) { 5546 if (nvg__absf(da) >= NVG_PI*2) { 5547 da = NVG_PI*2; 5548 } else { 5549 while (da < 0.0f) da += NVG_PI*2; 5550 } 5551 } else { 5552 if (nvg__absf(da) >= NVG_PI*2) { 5553 da = -NVG_PI*2; 5554 } else { 5555 while (da > 0.0f) da -= NVG_PI*2; 5556 } 5557 } 5558 5559 // Split arc into max 90 degree segments. 5560 immutable int ndivs = nvg__max(1, nvg__min(cast(int)(nvg__absf(da)/(NVG_PI*0.5f)+0.5f), 5)); 5561 immutable float hda = (da/cast(float)ndivs)/2.0f; 5562 float kappa = nvg__absf(4.0f/3.0f*(1.0f-nvg__cosf(hda))/nvg__sinf(hda)); 5563 5564 if (dir == NVGWinding.CCW) kappa = -kappa; 5565 5566 int nvals = 0; 5567 float px = 0, py = 0, ptanx = 0, ptany = 0; 5568 foreach (int i; 0..ndivs+1) { 5569 immutable float a = a0+da*(i/cast(float)ndivs); 5570 immutable float dx = nvg__cosf(a); 5571 immutable float dy = nvg__sinf(a); 5572 immutable float x = cx+dx*r; 5573 immutable float y = cy+dy*r; 5574 immutable float tanx = -dy*r*kappa; 5575 immutable float tany = dx*r*kappa; 5576 5577 if (i == 0) { 5578 if (vals.length-nvals < 3) { 5579 // flush 5580 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command 5581 nvals = 0; 5582 } 5583 vals.ptr[nvals++] = cast(float)move; 5584 vals.ptr[nvals++] = x; 5585 vals.ptr[nvals++] = y; 5586 } else { 5587 if (vals.length-nvals < 7) { 5588 // flush 5589 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command 5590 nvals = 0; 5591 } 5592 vals.ptr[nvals++] = Command.BezierTo; 5593 vals.ptr[nvals++] = px+ptanx; 5594 vals.ptr[nvals++] = py+ptany; 5595 vals.ptr[nvals++] = x-tanx; 5596 vals.ptr[nvals++] = y-tany; 5597 vals.ptr[nvals++] = x; 5598 vals.ptr[nvals++] = y; 5599 } 5600 px = x; 5601 py = y; 5602 ptanx = tanx; 5603 ptany = tany; 5604 } 5605 5606 nvg__appendCommands!false(ctx, Command.MoveTo, vals.ptr[0..nvals]); // ignore command 5607 } 5608 5609 5610 /** Creates new circle arc shaped sub-path. The arc center is at (cx, cy), the arc radius is r, 5611 * and the arc is drawn from angle a0 to a1, and swept in direction dir (NVGWinding.CCW, or NVGWinding.CW). 5612 * Angles are specified in radians. 5613 * 5614 * Arguments: [cx, cy, r, a0, a1]* 5615 * 5616 * [mode] is: "original", "move", "line" -- first command will be like original NanoVega, MoveTo, or LineTo 5617 * 5618 * Group: paths 5619 */ 5620 public void arc(string mode="original") (NVGContext ctx, NVGWinding dir, in float[] args) nothrow @trusted @nogc { 5621 static assert(mode == "original" || mode == "move" || mode == "line"); 5622 enum ArgC = 5; 5623 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [arc] call"); 5624 if (args.length < ArgC) return; 5625 const(float)* aptr = args.ptr; 5626 foreach (immutable idx; 0..args.length/ArgC) { 5627 immutable cx = *aptr++; 5628 immutable cy = *aptr++; 5629 immutable r = *aptr++; 5630 immutable a0 = *aptr++; 5631 immutable a1 = *aptr++; 5632 ctx.arc!mode(dir, cx, cy, r, a0, a1); 5633 } 5634 } 5635 5636 /// Creates new rectangle shaped sub-path. 5637 /// Group: paths 5638 @scriptable 5639 public void rect (NVGContext ctx, in float x, in float y, in float w, in float h) nothrow @trusted @nogc { 5640 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5641 Command.MoveTo, x, y, 5642 Command.LineTo, x, y+h, 5643 Command.LineTo, x+w, y+h, 5644 Command.LineTo, x+w, y, 5645 Command.Close, 5646 ); 5647 } 5648 5649 /// Creates new rectangle shaped sub-path. 5650 /// Arguments: [x, y, w, h]* 5651 /// Group: paths 5652 public void rect (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5653 enum ArgC = 4; 5654 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [rect] call"); 5655 if (args.length < ArgC) return; 5656 const(float)* aptr = args.ptr; 5657 foreach (immutable idx; 0..args.length/ArgC) { 5658 immutable x = *aptr++; 5659 immutable y = *aptr++; 5660 immutable w = *aptr++; 5661 immutable h = *aptr++; 5662 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5663 Command.MoveTo, x, y, 5664 Command.LineTo, x, y+h, 5665 Command.LineTo, x+w, y+h, 5666 Command.LineTo, x+w, y, 5667 Command.Close, 5668 ); 5669 } 5670 } 5671 5672 /// Creates new rounded rectangle shaped sub-path. 5673 /// Group: paths 5674 @scriptable 5675 public void roundedRect (NVGContext ctx, in float x, in float y, in float w, in float h, in float radius) nothrow @trusted @nogc { 5676 ctx.roundedRectVarying(x, y, w, h, radius, radius, radius, radius); 5677 } 5678 5679 /// Creates new rounded rectangle shaped sub-path. 5680 /// Arguments: [x, y, w, h, radius]* 5681 /// Group: paths 5682 public void roundedRect (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5683 enum ArgC = 5; 5684 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRect] call"); 5685 if (args.length < ArgC) return; 5686 const(float)* aptr = args.ptr; 5687 foreach (immutable idx; 0..args.length/ArgC) { 5688 immutable x = *aptr++; 5689 immutable y = *aptr++; 5690 immutable w = *aptr++; 5691 immutable h = *aptr++; 5692 immutable r = *aptr++; 5693 ctx.roundedRectVarying(x, y, w, h, r, r, r, r); 5694 } 5695 } 5696 5697 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it. 5698 /// Group: paths 5699 @scriptable 5700 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 { 5701 if (rw < 0.1f || rh < 0.1f) { 5702 rect(ctx, x, y, w, h); 5703 } else { 5704 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5705 Command.MoveTo, x+rw, y, 5706 Command.LineTo, x+w-rw, y, 5707 Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh, 5708 Command.LineTo, x+w, y+h-rh, 5709 Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h, 5710 Command.LineTo, x+rw, y+h, 5711 Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh, 5712 Command.LineTo, x, y+rh, 5713 Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y, 5714 Command.Close, 5715 ); 5716 } 5717 } 5718 5719 /// Creates new rounded rectangle shaped sub-path. Specify ellipse width and height to round corners according to it. 5720 /// Arguments: [x, y, w, h, rw, rh]* 5721 /// Group: paths 5722 public void roundedRectEllipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5723 enum ArgC = 6; 5724 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectEllipse] call"); 5725 if (args.length < ArgC) return; 5726 const(float)* aptr = args.ptr; 5727 foreach (immutable idx; 0..args.length/ArgC) { 5728 immutable x = *aptr++; 5729 immutable y = *aptr++; 5730 immutable w = *aptr++; 5731 immutable h = *aptr++; 5732 immutable rw = *aptr++; 5733 immutable rh = *aptr++; 5734 if (rw < 0.1f || rh < 0.1f) { 5735 rect(ctx, x, y, w, h); 5736 } else { 5737 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5738 Command.MoveTo, x+rw, y, 5739 Command.LineTo, x+w-rw, y, 5740 Command.BezierTo, x+w-rw*(1-NVG_KAPPA90), y, x+w, y+rh*(1-NVG_KAPPA90), x+w, y+rh, 5741 Command.LineTo, x+w, y+h-rh, 5742 Command.BezierTo, x+w, y+h-rh*(1-NVG_KAPPA90), x+w-rw*(1-NVG_KAPPA90), y+h, x+w-rw, y+h, 5743 Command.LineTo, x+rw, y+h, 5744 Command.BezierTo, x+rw*(1-NVG_KAPPA90), y+h, x, y+h-rh*(1-NVG_KAPPA90), x, y+h-rh, 5745 Command.LineTo, x, y+rh, 5746 Command.BezierTo, x, y+rh*(1-NVG_KAPPA90), x+rw*(1-NVG_KAPPA90), y, x+rw, y, 5747 Command.Close, 5748 ); 5749 } 5750 } 5751 } 5752 5753 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner. 5754 /// Group: paths 5755 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 { 5756 if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) { 5757 ctx.rect(x, y, w, h); 5758 } else { 5759 immutable float halfw = nvg__absf(w)*0.5f; 5760 immutable float halfh = nvg__absf(h)*0.5f; 5761 immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h); 5762 immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h); 5763 immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h); 5764 immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h); 5765 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5766 Command.MoveTo, x, y+ryTL, 5767 Command.LineTo, x, y+h-ryBL, 5768 Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h, 5769 Command.LineTo, x+w-rxBR, y+h, 5770 Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR, 5771 Command.LineTo, x+w, y+ryTR, 5772 Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y, 5773 Command.LineTo, x+rxTL, y, 5774 Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL, 5775 Command.Close, 5776 ); 5777 } 5778 } 5779 5780 /// Creates new rounded rectangle shaped sub-path. This one allows you to specify different rounding radii for each corner. 5781 /// Arguments: [x, y, w, h, radTopLeft, radTopRight, radBottomRight, radBottomLeft]* 5782 /// Group: paths 5783 public void roundedRectVarying (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5784 enum ArgC = 8; 5785 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [roundedRectVarying] call"); 5786 if (args.length < ArgC) return; 5787 const(float)* aptr = args.ptr; 5788 foreach (immutable idx; 0..args.length/ArgC) { 5789 immutable x = *aptr++; 5790 immutable y = *aptr++; 5791 immutable w = *aptr++; 5792 immutable h = *aptr++; 5793 immutable radTopLeft = *aptr++; 5794 immutable radTopRight = *aptr++; 5795 immutable radBottomRight = *aptr++; 5796 immutable radBottomLeft = *aptr++; 5797 if (radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) { 5798 ctx.rect(x, y, w, h); 5799 } else { 5800 immutable float halfw = nvg__absf(w)*0.5f; 5801 immutable float halfh = nvg__absf(h)*0.5f; 5802 immutable float rxBL = nvg__min(radBottomLeft, halfw)*nvg__sign(w), ryBL = nvg__min(radBottomLeft, halfh)*nvg__sign(h); 5803 immutable float rxBR = nvg__min(radBottomRight, halfw)*nvg__sign(w), ryBR = nvg__min(radBottomRight, halfh)*nvg__sign(h); 5804 immutable float rxTR = nvg__min(radTopRight, halfw)*nvg__sign(w), ryTR = nvg__min(radTopRight, halfh)*nvg__sign(h); 5805 immutable float rxTL = nvg__min(radTopLeft, halfw)*nvg__sign(w), ryTL = nvg__min(radTopLeft, halfh)*nvg__sign(h); 5806 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5807 Command.MoveTo, x, y+ryTL, 5808 Command.LineTo, x, y+h-ryBL, 5809 Command.BezierTo, x, y+h-ryBL*(1-NVG_KAPPA90), x+rxBL*(1-NVG_KAPPA90), y+h, x+rxBL, y+h, 5810 Command.LineTo, x+w-rxBR, y+h, 5811 Command.BezierTo, x+w-rxBR*(1-NVG_KAPPA90), y+h, x+w, y+h-ryBR*(1-NVG_KAPPA90), x+w, y+h-ryBR, 5812 Command.LineTo, x+w, y+ryTR, 5813 Command.BezierTo, x+w, y+ryTR*(1-NVG_KAPPA90), x+w-rxTR*(1-NVG_KAPPA90), y, x+w-rxTR, y, 5814 Command.LineTo, x+rxTL, y, 5815 Command.BezierTo, x+rxTL*(1-NVG_KAPPA90), y, x, y+ryTL*(1-NVG_KAPPA90), x, y+ryTL, 5816 Command.Close, 5817 ); 5818 } 5819 } 5820 } 5821 5822 /// Creates new ellipse shaped sub-path. 5823 /// Group: paths 5824 public void ellipse (NVGContext ctx, in float cx, in float cy, in float rx, in float ry) nothrow @trusted @nogc { 5825 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5826 Command.MoveTo, cx-rx, cy, 5827 Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry, 5828 Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy, 5829 Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry, 5830 Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy, 5831 Command.Close, 5832 ); 5833 } 5834 5835 /// Creates new ellipse shaped sub-path. 5836 /// Arguments: [cx, cy, rx, ry]* 5837 /// Group: paths 5838 public void ellipse (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5839 enum ArgC = 4; 5840 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [ellipse] call"); 5841 if (args.length < ArgC) return; 5842 const(float)* aptr = args.ptr; 5843 foreach (immutable idx; 0..args.length/ArgC) { 5844 immutable cx = *aptr++; 5845 immutable cy = *aptr++; 5846 immutable rx = *aptr++; 5847 immutable ry = *aptr++; 5848 nvg__appendCommands!false(ctx, Command.MoveTo, // ignore command 5849 Command.MoveTo, cx-rx, cy, 5850 Command.BezierTo, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry, 5851 Command.BezierTo, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy, 5852 Command.BezierTo, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry, 5853 Command.BezierTo, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy, 5854 Command.Close, 5855 ); 5856 } 5857 } 5858 5859 /// Creates new circle shaped sub-path. 5860 /// Group: paths 5861 public void circle (NVGContext ctx, in float cx, in float cy, in float r) nothrow @trusted @nogc { 5862 ctx.ellipse(cx, cy, r, r); 5863 } 5864 5865 /// Creates new circle shaped sub-path. 5866 /// Arguments: [cx, cy, r]* 5867 /// Group: paths 5868 public void circle (NVGContext ctx, in float[] args) nothrow @trusted @nogc { 5869 enum ArgC = 3; 5870 if (args.length%ArgC != 0) assert(0, "NanoVega: invalid [circle] call"); 5871 if (args.length < ArgC) return; 5872 const(float)* aptr = args.ptr; 5873 foreach (immutable idx; 0..args.length/ArgC) { 5874 immutable cx = *aptr++; 5875 immutable cy = *aptr++; 5876 immutable r = *aptr++; 5877 ctx.ellipse(cx, cy, r, r); 5878 } 5879 } 5880 5881 // Debug function to dump cached path data. 5882 debug public void debugDumpPathCache (NVGContext ctx) nothrow @trusted @nogc { 5883 import core.stdc.stdio : printf; 5884 const(NVGpath)* path; 5885 printf("Dumping %d cached paths\n", ctx.cache.npaths); 5886 for (int i = 0; i < ctx.cache.npaths; ++i) { 5887 path = &ctx.cache.paths[i]; 5888 printf("-Path %d\n", i); 5889 if (path.nfill) { 5890 printf("-fill: %d\n", path.nfill); 5891 for (int j = 0; j < path.nfill; ++j) printf("%f\t%f\n", path.fill[j].x, path.fill[j].y); 5892 } 5893 if (path.nstroke) { 5894 printf("-stroke: %d\n", path.nstroke); 5895 for (int j = 0; j < path.nstroke; ++j) printf("%f\t%f\n", path.stroke[j].x, path.stroke[j].y); 5896 } 5897 } 5898 } 5899 5900 // Flatten path, prepare it for fill operation. 5901 void nvg__prepareFill (NVGContext ctx) nothrow @trusted @nogc { 5902 NVGpathCache* cache = ctx.cache; 5903 NVGstate* state = nvg__getState(ctx); 5904 5905 nvg__flattenPaths!false(ctx); 5906 5907 if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) { 5908 nvg__expandFill(ctx, ctx.fringeWidth, NVGLineCap.Miter, 2.4f); 5909 } else { 5910 nvg__expandFill(ctx, 0.0f, NVGLineCap.Miter, 2.4f); 5911 } 5912 5913 cache.evenOddMode = state.evenOddMode; 5914 cache.fringeWidth = ctx.fringeWidth; 5915 cache.fillReady = true; 5916 cache.strokeReady = false; 5917 cache.clipmode = NVGClipMode.None; 5918 } 5919 5920 // Flatten path, prepare it for stroke operation. 5921 void nvg__prepareStroke (NVGContext ctx) nothrow @trusted @nogc { 5922 NVGstate* state = nvg__getState(ctx); 5923 NVGpathCache* cache = ctx.cache; 5924 5925 nvg__flattenPaths!true(ctx); 5926 5927 immutable float scale = nvg__getAverageScale(state.xform); 5928 float strokeWidth = nvg__clamp(state.strokeWidth*scale, 0.0f, 200.0f); 5929 5930 if (strokeWidth < ctx.fringeWidth) { 5931 // If the stroke width is less than pixel size, use alpha to emulate coverage. 5932 // Since coverage is area, scale by alpha*alpha. 5933 immutable float alpha = nvg__clamp(strokeWidth/ctx.fringeWidth, 0.0f, 1.0f); 5934 cache.strokeAlphaMul = alpha*alpha; 5935 strokeWidth = ctx.fringeWidth; 5936 } else { 5937 cache.strokeAlphaMul = 1.0f; 5938 } 5939 cache.strokeWidth = strokeWidth; 5940 5941 if (ctx.params.edgeAntiAlias && state.shapeAntiAlias) { 5942 nvg__expandStroke(ctx, strokeWidth*0.5f+ctx.fringeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit); 5943 } else { 5944 nvg__expandStroke(ctx, strokeWidth*0.5f, state.lineCap, state.lineJoin, state.miterLimit); 5945 } 5946 5947 cache.fringeWidth = ctx.fringeWidth; 5948 cache.fillReady = false; 5949 cache.strokeReady = true; 5950 cache.clipmode = NVGClipMode.None; 5951 } 5952 5953 /// Fills the current path with current fill style. 5954 /// Group: paths 5955 @scriptable 5956 public void fill (NVGContext ctx) nothrow @trusted @nogc { 5957 NVGstate* state = nvg__getState(ctx); 5958 5959 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) { 5960 ctx.pathPickRegistered |= NVGPickKind.Fill<<16; 5961 ctx.currFillHitId = ctx.pathPickId; 5962 } 5963 5964 nvg__prepareFill(ctx); 5965 5966 // apply global alpha 5967 NVGPaint fillPaint = state.fill; 5968 fillPaint.innerColor.a *= state.alpha; 5969 fillPaint.middleColor.a *= state.alpha; 5970 fillPaint.outerColor.a *= state.alpha; 5971 5972 ctx.appendCurrentPathToCache(ctx.recset, state.fill); 5973 5974 if (ctx.recblockdraw) return; 5975 5976 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); 5977 5978 // count triangles 5979 foreach (int i; 0..ctx.cache.npaths) { 5980 NVGpath* path = &ctx.cache.paths[i]; 5981 ctx.fillTriCount += path.nfill-2; 5982 ctx.fillTriCount += path.nstroke-2; 5983 ctx.drawCallCount += 2; 5984 } 5985 } 5986 5987 /// Fills the current path with current stroke style. 5988 /// Group: paths 5989 @scriptable 5990 public void stroke (NVGContext ctx) nothrow @trusted @nogc { 5991 NVGstate* state = nvg__getState(ctx); 5992 5993 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) { 5994 ctx.pathPickRegistered |= NVGPickKind.Stroke<<16; 5995 ctx.currStrokeHitId = ctx.pathPickId; 5996 } 5997 5998 nvg__prepareStroke(ctx); 5999 6000 NVGpathCache* cache = ctx.cache; 6001 6002 NVGPaint strokePaint = state.stroke; 6003 strokePaint.innerColor.a *= cache.strokeAlphaMul; 6004 strokePaint.middleColor.a *= cache.strokeAlphaMul; 6005 strokePaint.outerColor.a *= cache.strokeAlphaMul; 6006 6007 // apply global alpha 6008 strokePaint.innerColor.a *= state.alpha; 6009 strokePaint.middleColor.a *= state.alpha; 6010 strokePaint.outerColor.a *= state.alpha; 6011 6012 ctx.appendCurrentPathToCache(ctx.recset, state.stroke); 6013 6014 if (ctx.recblockdraw) return; 6015 6016 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths); 6017 6018 // count triangles 6019 foreach (int i; 0..ctx.cache.npaths) { 6020 NVGpath* path = &ctx.cache.paths[i]; 6021 ctx.strokeTriCount += path.nstroke-2; 6022 ++ctx.drawCallCount; 6023 } 6024 } 6025 6026 /// Sets current path as clipping region. 6027 /// Group: clipping 6028 public void clip (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc { 6029 NVGstate* state = nvg__getState(ctx); 6030 6031 if (aclipmode == NVGClipMode.None) return; 6032 if (ctx.recblockdraw) return; //??? 6033 6034 if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr); 6035 6036 /* 6037 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Fill|(NVGPickKind.Fill<<16))) == NVGPickKind.Fill) { 6038 ctx.pathPickRegistered |= NVGPickKind.Fill<<16; 6039 ctx.currFillHitId = ctx.pathPickId; 6040 } 6041 */ 6042 6043 nvg__prepareFill(ctx); 6044 6045 // apply global alpha 6046 NVGPaint fillPaint = state.fill; 6047 fillPaint.innerColor.a *= state.alpha; 6048 fillPaint.middleColor.a *= state.alpha; 6049 fillPaint.outerColor.a *= state.alpha; 6050 6051 //ctx.appendCurrentPathToCache(ctx.recset, state.fill); 6052 6053 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); 6054 6055 // count triangles 6056 foreach (int i; 0..ctx.cache.npaths) { 6057 NVGpath* path = &ctx.cache.paths[i]; 6058 ctx.fillTriCount += path.nfill-2; 6059 ctx.fillTriCount += path.nstroke-2; 6060 ctx.drawCallCount += 2; 6061 } 6062 } 6063 6064 /// Sets current path as clipping region. 6065 /// Group: clipping 6066 public alias clipFill = clip; 6067 6068 /// Sets current path' stroke as clipping region. 6069 /// Group: clipping 6070 public void clipStroke (NVGContext ctx, NVGClipMode aclipmode=NVGClipMode.Union) nothrow @trusted @nogc { 6071 NVGstate* state = nvg__getState(ctx); 6072 6073 if (aclipmode == NVGClipMode.None) return; 6074 if (ctx.recblockdraw) return; //??? 6075 6076 if (aclipmode == NVGClipMode.Replace) ctx.params.renderResetClip(ctx.params.userPtr); 6077 6078 /* 6079 if (ctx.pathPickId >= 0 && (ctx.pathPickRegistered&(NVGPickKind.Stroke|(NVGPickKind.Stroke<<16))) == NVGPickKind.Stroke) { 6080 ctx.pathPickRegistered |= NVGPickKind.Stroke<<16; 6081 ctx.currStrokeHitId = ctx.pathPickId; 6082 } 6083 */ 6084 6085 nvg__prepareStroke(ctx); 6086 6087 NVGpathCache* cache = ctx.cache; 6088 6089 NVGPaint strokePaint = state.stroke; 6090 strokePaint.innerColor.a *= cache.strokeAlphaMul; 6091 strokePaint.middleColor.a *= cache.strokeAlphaMul; 6092 strokePaint.outerColor.a *= cache.strokeAlphaMul; 6093 6094 // apply global alpha 6095 strokePaint.innerColor.a *= state.alpha; 6096 strokePaint.middleColor.a *= state.alpha; 6097 strokePaint.outerColor.a *= state.alpha; 6098 6099 //ctx.appendCurrentPathToCache(ctx.recset, state.stroke); 6100 6101 ctx.params.renderStroke(ctx.params.userPtr, state.compositeOperation, aclipmode, &strokePaint, &state.scissor, ctx.fringeWidth, cache.strokeWidth, ctx.cache.paths, ctx.cache.npaths); 6102 6103 // count triangles 6104 foreach (int i; 0..ctx.cache.npaths) { 6105 NVGpath* path = &ctx.cache.paths[i]; 6106 ctx.strokeTriCount += path.nstroke-2; 6107 ++ctx.drawCallCount; 6108 } 6109 } 6110 6111 6112 // ////////////////////////////////////////////////////////////////////////// // 6113 // Picking API 6114 6115 // most of the code is by Michael Wynne <mike@mikesspace.net> 6116 // https://github.com/memononen/nanovg/pull/230 6117 // https://github.com/MikeWW/nanovg 6118 6119 /// Pick type query. Used in [hitTest] and [hitTestAll]. 6120 /// Group: picking_api 6121 public enum NVGPickKind : ubyte { 6122 Fill = 0x01, /// 6123 Stroke = 0x02, /// 6124 All = 0x03, /// 6125 } 6126 6127 /// Marks the fill of the current path as pickable with the specified id. 6128 /// Note that you can create and mark path without rasterizing it. 6129 /// Group: picking_api 6130 public void currFillHitId (NVGContext ctx, int id) nothrow @trusted @nogc { 6131 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6132 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/false); 6133 nvg__pickSceneInsert(ps, pp); 6134 } 6135 6136 public alias currFillPickId = currFillHitId; /// Ditto. 6137 6138 /// Marks the stroke of the current path as pickable with the specified id. 6139 /// Note that you can create and mark path without rasterizing it. 6140 /// Group: picking_api 6141 public void currStrokeHitId (NVGContext ctx, int id) nothrow @trusted @nogc { 6142 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6143 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], id, /*forStroke:*/true); 6144 nvg__pickSceneInsert(ps, pp); 6145 } 6146 6147 public alias currStrokePickId = currStrokeHitId; /// Ditto. 6148 6149 // Marks the saved path set (fill) as pickable with the specified id. 6150 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!) 6151 // Group: picking_api 6152 /+ 6153 public void pathSetFillHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc { 6154 if (svp is null) return; 6155 if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context"); 6156 foreach (ref cp; svp.caches[0..svp.ncaches]) { 6157 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6158 NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/false); 6159 nvg__pickSceneInsert(ps, pp); 6160 } 6161 } 6162 +/ 6163 6164 // Marks the saved path set (stroke) as pickable with the specified id. 6165 // $(WARNING this doesn't work right yet (it is using current context transformation and other settings instead of record settings)!) 6166 // Group: picking_api 6167 /+ 6168 public void pathSetStrokeHitId (NVGContext ctx, NVGPathSet svp, int id) nothrow @trusted @nogc { 6169 if (svp is null) return; 6170 if (svp.svctx !is ctx) assert(0, "NanoVega: cannot register path set from different context"); 6171 foreach (ref cp; svp.caches[0..svp.ncaches]) { 6172 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6173 NVGpickPath* pp = nvg__pickPathCreate(ctx, cp.commands[0..cp.ncommands], id, /*forStroke:*/true); 6174 nvg__pickSceneInsert(ps, pp); 6175 } 6176 } 6177 +/ 6178 6179 private template IsGoodHitTestDG(DG) { 6180 enum IsGoodHitTestDG = 6181 __traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); }) || 6182 __traits(compiles, (){ DG dg; dg(cast(int)42, cast(int)666); }); 6183 } 6184 6185 private template IsGoodHitTestInternalDG(DG) { 6186 enum IsGoodHitTestInternalDG = 6187 __traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); }) || 6188 __traits(compiles, (){ DG dg; NVGpickPath* pp; dg(pp); }); 6189 } 6190 6191 /// Call delegate [dg] for each path under the specified position (in no particular order). 6192 /// Returns the id of the path for which delegate [dg] returned true or [NVGNoPick]. 6193 /// dg is: `bool delegate (int id, int order)` -- [order] is path ordering (ascending). 6194 /// Group: picking_api 6195 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) { 6196 if (ctx.pickScene is null || ctx.pickScene.npaths == 0 || (kind&NVGPickKind.All) == 0) return -1; 6197 6198 NVGpickScene* ps = ctx.pickScene; 6199 int levelwidth = 1<<(ps.nlevels-1); 6200 int cellx = nvg__clamp(cast(int)(x/ps.xdim), 0, levelwidth); 6201 int celly = nvg__clamp(cast(int)(y/ps.ydim), 0, levelwidth); 6202 int npicked = 0; 6203 6204 // if we are interested only in most-toplevel path, there is no reason to check paths with worser order. 6205 // but we cannot just get out on the first path found, 'cause we are using quad tree to speed up bounds 6206 // checking, so path walking order is not guaranteed. 6207 static if (bestOrder) { 6208 int lastBestOrder = int.min; 6209 } 6210 6211 //{ import core.stdc.stdio; printf("npaths=%d\n", ps.npaths); } 6212 for (int lvl = ps.nlevels-1; lvl >= 0; --lvl) { 6213 for (NVGpickPath* pp = ps.levels[lvl][celly*levelwidth+cellx]; pp !is null; pp = pp.next) { 6214 //{ 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); } 6215 static if (bestOrder) { 6216 // reject earlier paths 6217 if (pp.order <= lastBestOrder) continue; // not interesting 6218 } 6219 immutable uint kpx = kind&pp.flags&3; 6220 if (kpx == 0) continue; // not interesting 6221 if (!nvg__pickPathTestBounds(ctx, ps, pp, x, y)) continue; // not interesting 6222 //{ import core.stdc.stdio; printf("in bounds!\n"); } 6223 int hit = 0; 6224 if (kpx&NVGPickKind.Stroke) hit = nvg__pickPathStroke(ps, pp, x, y); 6225 if (!hit && (kpx&NVGPickKind.Fill)) hit = nvg__pickPath(ps, pp, x, y); 6226 if (!hit) continue; 6227 //{ import core.stdc.stdio; printf(" HIT!\n"); } 6228 static if (bestOrder) lastBestOrder = pp.order; 6229 static if (IsGoodHitTestDG!DG) { 6230 static if (__traits(compiles, (){ DG dg; bool res = dg(cast(int)42, cast(int)666); })) { 6231 if (dg(pp.id, cast(int)pp.order)) return pp.id; 6232 } else { 6233 dg(pp.id, cast(int)pp.order); 6234 } 6235 } else { 6236 static if (__traits(compiles, (){ DG dg; NVGpickPath* pp; bool res = dg(pp); })) { 6237 if (dg(pp)) return pp.id; 6238 } else { 6239 dg(pp); 6240 } 6241 } 6242 } 6243 cellx >>= 1; 6244 celly >>= 1; 6245 levelwidth >>= 1; 6246 } 6247 6248 return -1; 6249 } 6250 6251 /// Fills ids with a list of the top most hit ids (from bottom to top) under the specified position. 6252 /// Returns the slice of [ids]. 6253 /// Group: picking_api 6254 public int[] hitTestAll (NVGContext ctx, in float x, in float y, NVGPickKind kind, int[] ids) nothrow @trusted @nogc { 6255 if (ctx.pickScene is null || ids.length == 0) return ids[0..0]; 6256 6257 int npicked = 0; 6258 NVGpickScene* ps = ctx.pickScene; 6259 6260 ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) nothrow @trusted @nogc { 6261 if (npicked == ps.cpicked) { 6262 int cpicked = ps.cpicked+ps.cpicked; 6263 NVGpickPath** picked = cast(NVGpickPath**)realloc(ps.picked, (NVGpickPath*).sizeof*ps.cpicked); 6264 if (picked is null) return true; // abort 6265 ps.cpicked = cpicked; 6266 ps.picked = picked; 6267 } 6268 ps.picked[npicked] = pp; 6269 ++npicked; 6270 return false; // go on 6271 }); 6272 6273 qsort(ps.picked, npicked, (NVGpickPath*).sizeof, &nvg__comparePaths); 6274 6275 assert(npicked >= 0); 6276 if (npicked > ids.length) npicked = cast(int)ids.length; 6277 foreach (immutable nidx, ref int did; ids[0..npicked]) did = ps.picked[nidx].id; 6278 6279 return ids[0..npicked]; 6280 } 6281 6282 /// Returns the id of the pickable shape containing x,y or [NVGNoPick] if no shape was found. 6283 /// Group: picking_api 6284 public int hitTest (NVGContext ctx, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc { 6285 if (ctx.pickScene is null) return -1; 6286 6287 int bestOrder = int.min; 6288 int bestID = -1; 6289 6290 ctx.hitTestDG!true(x, y, kind, delegate (NVGpickPath* pp) { 6291 if (pp.order > bestOrder) { 6292 bestOrder = pp.order; 6293 bestID = pp.id; 6294 } 6295 }); 6296 6297 return bestID; 6298 } 6299 6300 /// Returns `true` if the path with the given id contains x,y. 6301 /// Group: picking_api 6302 public bool hitTestForId (NVGContext ctx, in int id, in float x, in float y, NVGPickKind kind=NVGPickKind.All) nothrow @trusted @nogc { 6303 if (ctx.pickScene is null || id == NVGNoPick) return false; 6304 6305 bool res = false; 6306 6307 ctx.hitTestDG!false(x, y, kind, delegate (NVGpickPath* pp) { 6308 if (pp.id == id) { 6309 res = true; 6310 return true; // stop 6311 } 6312 return false; // continue 6313 }); 6314 6315 return res; 6316 } 6317 6318 /// Returns `true` if the given point is within the fill of the currently defined path. 6319 /// This operation can be done before rasterizing the current path. 6320 /// Group: picking_api 6321 public bool hitTestCurrFill (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 6322 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6323 int oldnpoints = ps.npoints; 6324 int oldnsegments = ps.nsegments; 6325 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/false); 6326 if (pp is null) return false; // oops 6327 scope(exit) { 6328 nvg__freePickPath(ps, pp); 6329 ps.npoints = oldnpoints; 6330 ps.nsegments = oldnsegments; 6331 } 6332 return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPath(ps, pp, x, y) : false); 6333 } 6334 6335 public alias isPointInPath = hitTestCurrFill; /// Ditto. 6336 6337 /// Returns `true` if the given point is within the stroke of the currently defined path. 6338 /// This operation can be done before rasterizing the current path. 6339 /// Group: picking_api 6340 public bool hitTestCurrStroke (NVGContext ctx, in float x, in float y) nothrow @trusted @nogc { 6341 NVGpickScene* ps = nvg__pickSceneGet(ctx); 6342 int oldnpoints = ps.npoints; 6343 int oldnsegments = ps.nsegments; 6344 NVGpickPath* pp = nvg__pickPathCreate(ctx, ctx.commands[0..ctx.ncommands], 1, /*forStroke:*/true); 6345 if (pp is null) return false; // oops 6346 scope(exit) { 6347 nvg__freePickPath(ps, pp); 6348 ps.npoints = oldnpoints; 6349 ps.nsegments = oldnsegments; 6350 } 6351 return (nvg__pointInBounds(x, y, pp.bounds) ? nvg__pickPathStroke(ps, pp, x, y) : false); 6352 } 6353 6354 6355 nothrow @trusted @nogc { 6356 extern(C) { 6357 private alias _compare_fp_t = int function (const void*, const void*) nothrow @nogc; 6358 private extern(C) void qsort (scope void* base, size_t nmemb, size_t size, _compare_fp_t compar) nothrow @nogc; 6359 6360 extern(C) int nvg__comparePaths (const void* a, const void* b) { 6361 return (*cast(const(NVGpickPath)**)b).order-(*cast(const(NVGpickPath)**)a).order; 6362 } 6363 } 6364 6365 enum NVGPickEPS = 0.0001f; 6366 6367 // Segment flags 6368 enum NVGSegmentFlags { 6369 Corner = 1, 6370 Bevel = 2, 6371 InnerBevel = 4, 6372 Cap = 8, 6373 Endcap = 16, 6374 } 6375 6376 // Path flags 6377 enum NVGPathFlags : ushort { 6378 Fill = NVGPickKind.Fill, 6379 Stroke = NVGPickKind.Stroke, 6380 Scissor = 0x80, 6381 } 6382 6383 struct NVGsegment { 6384 int firstPoint; // Index into NVGpickScene.points 6385 short type; // NVG_LINETO or NVG_BEZIERTO 6386 short flags; // Flags relate to the corner between the prev segment and this one. 6387 float[4] bounds; 6388 float[2] startDir; // Direction at t == 0 6389 float[2] endDir; // Direction at t == 1 6390 float[2] miterDir; // Direction of miter of corner between the prev segment and this one. 6391 } 6392 6393 struct NVGpickSubPath { 6394 short winding; // TODO: Merge to flag field 6395 bool closed; // TODO: Merge to flag field 6396 6397 int firstSegment; // Index into NVGpickScene.segments 6398 int nsegments; 6399 6400 float[4] bounds; 6401 6402 NVGpickSubPath* next; 6403 } 6404 6405 struct NVGpickPath { 6406 int id; 6407 short flags; 6408 short order; 6409 float strokeWidth; 6410 float miterLimit; 6411 short lineCap; 6412 short lineJoin; 6413 bool evenOddMode; 6414 6415 float[4] bounds; 6416 int scissor; // Indexes into ps->points and defines scissor rect as XVec, YVec and Center 6417 6418 NVGpickSubPath* subPaths; 6419 NVGpickPath* next; 6420 NVGpickPath* cellnext; 6421 } 6422 6423 struct NVGpickScene { 6424 int npaths; 6425 6426 NVGpickPath* paths; // Linked list of paths 6427 NVGpickPath* lastPath; // The last path in the paths linked list (the first path added) 6428 NVGpickPath* freePaths; // Linked list of free paths 6429 6430 NVGpickSubPath* freeSubPaths; // Linked list of free sub paths 6431 6432 int width; 6433 int height; 6434 6435 // Points for all path sub paths. 6436 float* points; 6437 int npoints; 6438 int cpoints; 6439 6440 // Segments for all path sub paths 6441 NVGsegment* segments; 6442 int nsegments; 6443 int csegments; 6444 6445 // Implicit quadtree 6446 float xdim; // Width / (1 << nlevels) 6447 float ydim; // Height / (1 << nlevels) 6448 int ncells; // Total number of cells in all levels 6449 int nlevels; 6450 NVGpickPath*** levels; // Index: [Level][LevelY * LevelW + LevelX] Value: Linked list of paths 6451 6452 // Temp storage for picking 6453 int cpicked; 6454 NVGpickPath** picked; 6455 } 6456 6457 6458 // bounds utilities 6459 void nvg__initBounds (ref float[4] bounds) { 6460 bounds.ptr[0] = bounds.ptr[1] = float.max; 6461 bounds.ptr[2] = bounds.ptr[3] = -float.max; 6462 } 6463 6464 void nvg__expandBounds (ref float[4] bounds, const(float)* points, int npoints) { 6465 npoints *= 2; 6466 for (int i = 0; i < npoints; i += 2) { 6467 bounds.ptr[0] = nvg__min(bounds.ptr[0], points[i]); 6468 bounds.ptr[1] = nvg__min(bounds.ptr[1], points[i+1]); 6469 bounds.ptr[2] = nvg__max(bounds.ptr[2], points[i]); 6470 bounds.ptr[3] = nvg__max(bounds.ptr[3], points[i+1]); 6471 } 6472 } 6473 6474 void nvg__unionBounds (ref float[4] bounds, in ref float[4] boundsB) { 6475 bounds.ptr[0] = nvg__min(bounds.ptr[0], boundsB.ptr[0]); 6476 bounds.ptr[1] = nvg__min(bounds.ptr[1], boundsB.ptr[1]); 6477 bounds.ptr[2] = nvg__max(bounds.ptr[2], boundsB.ptr[2]); 6478 bounds.ptr[3] = nvg__max(bounds.ptr[3], boundsB.ptr[3]); 6479 } 6480 6481 void nvg__intersectBounds (ref float[4] bounds, in ref float[4] boundsB) { 6482 bounds.ptr[0] = nvg__max(boundsB.ptr[0], bounds.ptr[0]); 6483 bounds.ptr[1] = nvg__max(boundsB.ptr[1], bounds.ptr[1]); 6484 bounds.ptr[2] = nvg__min(boundsB.ptr[2], bounds.ptr[2]); 6485 bounds.ptr[3] = nvg__min(boundsB.ptr[3], bounds.ptr[3]); 6486 6487 bounds.ptr[2] = nvg__max(bounds.ptr[0], bounds.ptr[2]); 6488 bounds.ptr[3] = nvg__max(bounds.ptr[1], bounds.ptr[3]); 6489 } 6490 6491 bool nvg__pointInBounds (in float x, in float y, in ref float[4] bounds) { 6492 pragma(inline, true); 6493 return (x >= bounds.ptr[0] && x <= bounds.ptr[2] && y >= bounds.ptr[1] && y <= bounds.ptr[3]); 6494 } 6495 6496 // building paths & sub paths 6497 int nvg__pickSceneAddPoints (NVGpickScene* ps, const(float)* xy, int n) { 6498 import core.stdc..string : memcpy; 6499 if (ps.npoints+n > ps.cpoints) { 6500 import core.stdc.stdlib : realloc; 6501 int cpoints = ps.npoints+n+(ps.cpoints<<1); 6502 float* points = cast(float*)realloc(ps.points, float.sizeof*2*cpoints); 6503 if (points is null) assert(0, "NanoVega: out of memory"); 6504 ps.points = points; 6505 ps.cpoints = cpoints; 6506 } 6507 int i = ps.npoints; 6508 if (xy !is null) memcpy(&ps.points[i*2], xy, float.sizeof*2*n); 6509 ps.npoints += n; 6510 return i; 6511 } 6512 6513 void nvg__pickSubPathAddSegment (NVGpickScene* ps, NVGpickSubPath* psp, int firstPoint, int type, short flags) { 6514 NVGsegment* seg = null; 6515 if (ps.nsegments == ps.csegments) { 6516 int csegments = 1+ps.csegments+(ps.csegments<<1); 6517 NVGsegment* segments = cast(NVGsegment*)realloc(ps.segments, NVGsegment.sizeof*csegments); 6518 if (segments is null) assert(0, "NanoVega: out of memory"); 6519 ps.segments = segments; 6520 ps.csegments = csegments; 6521 } 6522 6523 if (psp.firstSegment == -1) psp.firstSegment = ps.nsegments; 6524 6525 seg = &ps.segments[ps.nsegments]; 6526 ++ps.nsegments; 6527 seg.firstPoint = firstPoint; 6528 seg.type = cast(short)type; 6529 seg.flags = flags; 6530 ++psp.nsegments; 6531 6532 nvg__segmentDir(ps, psp, seg, 0, seg.startDir); 6533 nvg__segmentDir(ps, psp, seg, 1, seg.endDir); 6534 } 6535 6536 void nvg__segmentDir (NVGpickScene* ps, NVGpickSubPath* psp, NVGsegment* seg, float t, ref float[2] d) { 6537 const(float)* points = &ps.points[seg.firstPoint*2]; 6538 immutable float x0 = points[0*2+0], x1 = points[1*2+0]; 6539 immutable float y0 = points[0*2+1], y1 = points[1*2+1]; 6540 switch (seg.type) { 6541 case Command.LineTo: 6542 d.ptr[0] = x1-x0; 6543 d.ptr[1] = y1-y0; 6544 nvg__normalize(&d.ptr[0], &d.ptr[1]); 6545 break; 6546 case Command.BezierTo: 6547 immutable float x2 = points[2*2+0]; 6548 immutable float y2 = points[2*2+1]; 6549 immutable float x3 = points[3*2+0]; 6550 immutable float y3 = points[3*2+1]; 6551 6552 immutable float omt = 1.0f-t; 6553 immutable float omt2 = omt*omt; 6554 immutable float t2 = t*t; 6555 6556 d.ptr[0] = 6557 3.0f*omt2*(x1-x0)+ 6558 6.0f*omt*t*(x2-x1)+ 6559 3.0f*t2*(x3-x2); 6560 6561 d.ptr[1] = 6562 3.0f*omt2*(y1-y0)+ 6563 6.0f*omt*t*(y2-y1)+ 6564 3.0f*t2*(y3-y2); 6565 6566 nvg__normalize(&d.ptr[0], &d.ptr[1]); 6567 break; 6568 default: 6569 break; 6570 } 6571 } 6572 6573 void nvg__pickSubPathAddFillSupports (NVGpickScene* ps, NVGpickSubPath* psp) { 6574 if (psp.firstSegment == -1) return; 6575 NVGsegment* segments = &ps.segments[psp.firstSegment]; 6576 for (int s = 0; s < psp.nsegments; ++s) { 6577 NVGsegment* seg = &segments[s]; 6578 const(float)* points = &ps.points[seg.firstPoint*2]; 6579 if (seg.type == Command.LineTo) { 6580 nvg__initBounds(seg.bounds); 6581 nvg__expandBounds(seg.bounds, points, 2); 6582 } else { 6583 nvg__bezierBounds(points, seg.bounds); 6584 } 6585 } 6586 } 6587 6588 void nvg__pickSubPathAddStrokeSupports (NVGpickScene* ps, NVGpickSubPath* psp, float strokeWidth, int lineCap, int lineJoin, float miterLimit) { 6589 if (psp.firstSegment == -1) return; 6590 immutable bool closed = psp.closed; 6591 const(float)* points = ps.points; 6592 NVGsegment* seg = null; 6593 NVGsegment* segments = &ps.segments[psp.firstSegment]; 6594 int nsegments = psp.nsegments; 6595 NVGsegment* prevseg = (closed ? &segments[psp.nsegments-1] : null); 6596 6597 int ns = 0; // nsupports 6598 float[32] supportingPoints = void; 6599 int firstPoint, lastPoint; 6600 6601 if (!closed) { 6602 segments[0].flags |= NVGSegmentFlags.Cap; 6603 segments[nsegments-1].flags |= NVGSegmentFlags.Endcap; 6604 } 6605 6606 for (int s = 0; s < nsegments; ++s) { 6607 seg = &segments[s]; 6608 nvg__initBounds(seg.bounds); 6609 6610 firstPoint = seg.firstPoint*2; 6611 lastPoint = firstPoint+(seg.type == Command.LineTo ? 2 : 6); 6612 6613 ns = 0; 6614 6615 // First two supporting points are either side of the start point 6616 supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[1]*strokeWidth; 6617 supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.startDir.ptr[0]*strokeWidth; 6618 6619 supportingPoints.ptr[ns++] = points[firstPoint]+seg.startDir.ptr[1]*strokeWidth; 6620 supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[0]*strokeWidth; 6621 6622 // Second two supporting points are either side of the end point 6623 supportingPoints.ptr[ns++] = points[lastPoint]-seg.endDir.ptr[1]*strokeWidth; 6624 supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[0]*strokeWidth; 6625 6626 supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[1]*strokeWidth; 6627 supportingPoints.ptr[ns++] = points[lastPoint+1]-seg.endDir.ptr[0]*strokeWidth; 6628 6629 if ((seg.flags&NVGSegmentFlags.Corner) && prevseg !is null) { 6630 seg.miterDir.ptr[0] = 0.5f*(-prevseg.endDir.ptr[1]-seg.startDir.ptr[1]); 6631 seg.miterDir.ptr[1] = 0.5f*(prevseg.endDir.ptr[0]+seg.startDir.ptr[0]); 6632 6633 immutable float M2 = seg.miterDir.ptr[0]*seg.miterDir.ptr[0]+seg.miterDir.ptr[1]*seg.miterDir.ptr[1]; 6634 6635 if (M2 > 0.000001f) { 6636 float scale = 1.0f/M2; 6637 if (scale > 600.0f) scale = 600.0f; 6638 seg.miterDir.ptr[0] *= scale; 6639 seg.miterDir.ptr[1] *= scale; 6640 } 6641 6642 //NVG_PICK_DEBUG_VECTOR_SCALE(&points[firstPoint], seg.miterDir, 10); 6643 6644 // Add an additional support at the corner on the other line 6645 supportingPoints.ptr[ns++] = points[firstPoint]-prevseg.endDir.ptr[1]*strokeWidth; 6646 supportingPoints.ptr[ns++] = points[firstPoint+1]+prevseg.endDir.ptr[0]*strokeWidth; 6647 6648 if (lineJoin == NVGLineCap.Miter || lineJoin == NVGLineCap.Bevel) { 6649 // Set a corner as beveled if the join type is bevel or mitered and 6650 // miterLimit is hit. 6651 if (lineJoin == NVGLineCap.Bevel || (M2*miterLimit*miterLimit) < 1.0f) { 6652 seg.flags |= NVGSegmentFlags.Bevel; 6653 } else { 6654 // Corner is mitered - add miter point as a support 6655 supportingPoints.ptr[ns++] = points[firstPoint]+seg.miterDir.ptr[0]*strokeWidth; 6656 supportingPoints.ptr[ns++] = points[firstPoint+1]+seg.miterDir.ptr[1]*strokeWidth; 6657 } 6658 } else if (lineJoin == NVGLineCap.Round) { 6659 // ... and at the midpoint of the corner arc 6660 float[2] vertexN = [ -seg.startDir.ptr[0]+prevseg.endDir.ptr[0], -seg.startDir.ptr[1]+prevseg.endDir.ptr[1] ]; 6661 nvg__normalize(&vertexN[0], &vertexN[1]); 6662 6663 supportingPoints.ptr[ns++] = points[firstPoint]+vertexN[0]*strokeWidth; 6664 supportingPoints.ptr[ns++] = points[firstPoint+1]+vertexN[1]*strokeWidth; 6665 } 6666 } 6667 6668 if (seg.flags&NVGSegmentFlags.Cap) { 6669 switch (lineCap) { 6670 case NVGLineCap.Butt: 6671 // supports for butt already added 6672 break; 6673 case NVGLineCap.Square: 6674 // square cap supports are just the original two supports moved out along the direction 6675 supportingPoints.ptr[ns++] = supportingPoints.ptr[0]-seg.startDir.ptr[0]*strokeWidth; 6676 supportingPoints.ptr[ns++] = supportingPoints.ptr[1]-seg.startDir.ptr[1]*strokeWidth; 6677 supportingPoints.ptr[ns++] = supportingPoints.ptr[2]-seg.startDir.ptr[0]*strokeWidth; 6678 supportingPoints.ptr[ns++] = supportingPoints.ptr[3]-seg.startDir.ptr[1]*strokeWidth; 6679 break; 6680 case NVGLineCap.Round: 6681 // add one additional support for the round cap along the dir 6682 supportingPoints.ptr[ns++] = points[firstPoint]-seg.startDir.ptr[0]*strokeWidth; 6683 supportingPoints.ptr[ns++] = points[firstPoint+1]-seg.startDir.ptr[1]*strokeWidth; 6684 break; 6685 default: 6686 break; 6687 } 6688 } 6689 6690 if (seg.flags&NVGSegmentFlags.Endcap) { 6691 // end supporting points, either side of line 6692 int end = 4; 6693 switch(lineCap) { 6694 case NVGLineCap.Butt: 6695 // supports for butt already added 6696 break; 6697 case NVGLineCap.Square: 6698 // square cap supports are just the original two supports moved out along the direction 6699 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+0]+seg.endDir.ptr[0]*strokeWidth; 6700 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+1]+seg.endDir.ptr[1]*strokeWidth; 6701 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+2]+seg.endDir.ptr[0]*strokeWidth; 6702 supportingPoints.ptr[ns++] = supportingPoints.ptr[end+3]+seg.endDir.ptr[1]*strokeWidth; 6703 break; 6704 case NVGLineCap.Round: 6705 // add one additional support for the round cap along the dir 6706 supportingPoints.ptr[ns++] = points[lastPoint]+seg.endDir.ptr[0]*strokeWidth; 6707 supportingPoints.ptr[ns++] = points[lastPoint+1]+seg.endDir.ptr[1]*strokeWidth; 6708 break; 6709 default: 6710 break; 6711 } 6712 } 6713 6714 nvg__expandBounds(seg.bounds, supportingPoints.ptr, ns/2); 6715 6716 prevseg = seg; 6717 } 6718 } 6719 6720 NVGpickPath* nvg__pickPathCreate (NVGContext context, const(float)[] acommands, int id, bool forStroke) { 6721 NVGpickScene* ps = nvg__pickSceneGet(context); 6722 if (ps is null) return null; 6723 6724 int i = 0; 6725 6726 int ncommands = cast(int)acommands.length; 6727 const(float)* commands = acommands.ptr; 6728 6729 NVGpickPath* pp = null; 6730 NVGpickSubPath* psp = null; 6731 float[2] start = void; 6732 int firstPoint; 6733 6734 //bool hasHoles = false; 6735 NVGpickSubPath* prev = null; 6736 6737 float[8] points = void; 6738 float[2] inflections = void; 6739 int ninflections = 0; 6740 6741 NVGstate* state = nvg__getState(context); 6742 float[4] totalBounds = void; 6743 NVGsegment* segments = null; 6744 const(NVGsegment)* seg = null; 6745 NVGpickSubPath *curpsp; 6746 6747 pp = nvg__allocPickPath(ps); 6748 if (pp is null) return null; 6749 6750 pp.id = id; 6751 6752 bool hasPoints = false; 6753 6754 void closeIt () { 6755 if (psp is null || !hasPoints) return; 6756 if (ps.points[(ps.npoints-1)*2] != start.ptr[0] || ps.points[(ps.npoints-1)*2+1] != start.ptr[1]) { 6757 firstPoint = nvg__pickSceneAddPoints(ps, start.ptr, 1); 6758 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, Command.LineTo, NVGSegmentFlags.Corner); 6759 } 6760 psp.closed = true; 6761 } 6762 6763 while (i < ncommands) { 6764 int cmd = cast(int)commands[i++]; 6765 switch (cmd) { 6766 case Command.MoveTo: // one coordinate pair 6767 const(float)* tfxy = commands+i; 6768 i += 2; 6769 6770 // new starting point 6771 start.ptr[0..2] = tfxy[0..2]; 6772 6773 // start a new path for each sub path to handle sub paths that intersect other sub paths 6774 prev = psp; 6775 psp = nvg__allocPickSubPath(ps); 6776 if (psp is null) { psp = prev; break; } 6777 psp.firstSegment = -1; 6778 psp.winding = NVGSolidity.Solid; 6779 psp.next = prev; 6780 6781 nvg__pickSceneAddPoints(ps, tfxy, 1); 6782 hasPoints = true; 6783 break; 6784 case Command.LineTo: // one coordinate pair 6785 const(float)* tfxy = commands+i; 6786 i += 2; 6787 firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 1); 6788 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner); 6789 hasPoints = true; 6790 break; 6791 case Command.BezierTo: // three coordinate pairs 6792 const(float)* tfxy = commands+i; 6793 i += 3*2; 6794 6795 // Split the curve at it's dx==0 or dy==0 inflection points. 6796 // Thus: 6797 // A horizontal line only ever interects the curves once. 6798 // and 6799 // Finding the closest point on any curve converges more reliably. 6800 6801 // NOTE: We could just split on dy==0 here. 6802 6803 memcpy(&points.ptr[0], &ps.points[(ps.npoints-1)*2], float.sizeof*2); 6804 memcpy(&points.ptr[2], tfxy, float.sizeof*2*3); 6805 6806 ninflections = 0; 6807 nvg__bezierInflections(points.ptr, 1, &ninflections, inflections.ptr); 6808 nvg__bezierInflections(points.ptr, 0, &ninflections, inflections.ptr); 6809 6810 if (ninflections) { 6811 float previnfl = 0; 6812 float[8] pointsA = void, pointsB = void; 6813 6814 nvg__smallsort(inflections.ptr, ninflections); 6815 6816 for (int infl = 0; infl < ninflections; ++infl) { 6817 if (nvg__absf(inflections.ptr[infl]-previnfl) < NVGPickEPS) continue; 6818 6819 immutable float t = (inflections.ptr[infl]-previnfl)*(1.0f/(1.0f-previnfl)); 6820 6821 previnfl = inflections.ptr[infl]; 6822 6823 nvg__splitBezier(points.ptr, t, pointsA.ptr, pointsB.ptr); 6824 6825 firstPoint = nvg__pickSceneAddPoints(ps, &pointsA.ptr[2], 3); 6826 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, (infl == 0) ? NVGSegmentFlags.Corner : 0); 6827 6828 memcpy(points.ptr, pointsB.ptr, float.sizeof*8); 6829 } 6830 6831 firstPoint = nvg__pickSceneAddPoints(ps, &pointsB.ptr[2], 3); 6832 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, 0); 6833 } else { 6834 firstPoint = nvg__pickSceneAddPoints(ps, tfxy, 3); 6835 nvg__pickSubPathAddSegment(ps, psp, firstPoint-1, cmd, NVGSegmentFlags.Corner); 6836 } 6837 hasPoints = true; 6838 break; 6839 case Command.Close: 6840 closeIt(); 6841 break; 6842 case Command.Winding: 6843 psp.winding = cast(short)cast(int)commands[i]; 6844 //if (psp.winding == NVGSolidity.Hole) hasHoles = true; 6845 i += 1; 6846 break; 6847 default: 6848 break; 6849 } 6850 } 6851 6852 // force-close filled paths 6853 if (psp !is null && !forStroke && hasPoints && !psp.closed) closeIt(); 6854 6855 pp.flags = (forStroke ? NVGPathFlags.Stroke : NVGPathFlags.Fill); 6856 pp.subPaths = psp; 6857 pp.strokeWidth = state.strokeWidth*0.5f; 6858 pp.miterLimit = state.miterLimit; 6859 pp.lineCap = cast(short)state.lineCap; 6860 pp.lineJoin = cast(short)state.lineJoin; 6861 pp.evenOddMode = nvg__getState(context).evenOddMode; 6862 6863 nvg__initBounds(totalBounds); 6864 6865 for (curpsp = psp; curpsp; curpsp = curpsp.next) { 6866 if (forStroke) { 6867 nvg__pickSubPathAddStrokeSupports(ps, curpsp, pp.strokeWidth, pp.lineCap, pp.lineJoin, pp.miterLimit); 6868 } else { 6869 nvg__pickSubPathAddFillSupports(ps, curpsp); 6870 } 6871 6872 if (curpsp.firstSegment == -1) continue; 6873 segments = &ps.segments[curpsp.firstSegment]; 6874 nvg__initBounds(curpsp.bounds); 6875 for (int s = 0; s < curpsp.nsegments; ++s) { 6876 seg = &segments[s]; 6877 //NVG_PICK_DEBUG_BOUNDS(seg.bounds); 6878 nvg__unionBounds(curpsp.bounds, seg.bounds); 6879 } 6880 6881 nvg__unionBounds(totalBounds, curpsp.bounds); 6882 } 6883 6884 // Store the scissor rect if present. 6885 if (state.scissor.extent.ptr[0] != -1.0f) { 6886 // Use points storage to store the scissor data 6887 pp.scissor = nvg__pickSceneAddPoints(ps, null, 4); 6888 float* scissor = &ps.points[pp.scissor*2]; 6889 6890 //memcpy(scissor, state.scissor.xform.ptr, 6*float.sizeof); 6891 scissor[0..6] = state.scissor.xform.mat[]; 6892 memcpy(scissor+6, state.scissor.extent.ptr, 2*float.sizeof); 6893 6894 pp.flags |= NVGPathFlags.Scissor; 6895 } 6896 6897 memcpy(pp.bounds.ptr, totalBounds.ptr, float.sizeof*4); 6898 6899 return pp; 6900 } 6901 6902 6903 // Struct management 6904 NVGpickPath* nvg__allocPickPath (NVGpickScene* ps) { 6905 NVGpickPath* pp = ps.freePaths; 6906 if (pp !is null) { 6907 ps.freePaths = pp.next; 6908 } else { 6909 pp = cast(NVGpickPath*)malloc(NVGpickPath.sizeof); 6910 } 6911 memset(pp, 0, NVGpickPath.sizeof); 6912 return pp; 6913 } 6914 6915 // Put a pick path and any sub paths (back) to the free lists. 6916 void nvg__freePickPath (NVGpickScene* ps, NVGpickPath* pp) { 6917 // Add all sub paths to the sub path free list. 6918 // Finds the end of the path sub paths, links that to the current 6919 // sub path free list head and replaces the head ptr with the 6920 // head path sub path entry. 6921 NVGpickSubPath* psp = null; 6922 for (psp = pp.subPaths; psp !is null && psp.next !is null; psp = psp.next) {} 6923 6924 if (psp) { 6925 psp.next = ps.freeSubPaths; 6926 ps.freeSubPaths = pp.subPaths; 6927 } 6928 pp.subPaths = null; 6929 6930 // Add the path to the path freelist 6931 pp.next = ps.freePaths; 6932 ps.freePaths = pp; 6933 if (pp.next is null) ps.lastPath = pp; 6934 } 6935 6936 NVGpickSubPath* nvg__allocPickSubPath (NVGpickScene* ps) { 6937 NVGpickSubPath* psp = ps.freeSubPaths; 6938 if (psp !is null) { 6939 ps.freeSubPaths = psp.next; 6940 } else { 6941 psp = cast(NVGpickSubPath*)malloc(NVGpickSubPath.sizeof); 6942 if (psp is null) return null; 6943 } 6944 memset(psp, 0, NVGpickSubPath.sizeof); 6945 return psp; 6946 } 6947 6948 void nvg__returnPickSubPath (NVGpickScene* ps, NVGpickSubPath* psp) { 6949 psp.next = ps.freeSubPaths; 6950 ps.freeSubPaths = psp; 6951 } 6952 6953 NVGpickScene* nvg__allocPickScene () { 6954 NVGpickScene* ps = cast(NVGpickScene*)malloc(NVGpickScene.sizeof); 6955 if (ps is null) return null; 6956 memset(ps, 0, NVGpickScene.sizeof); 6957 ps.nlevels = 5; 6958 return ps; 6959 } 6960 6961 void nvg__deletePickScene (NVGpickScene* ps) { 6962 NVGpickPath* pp; 6963 NVGpickSubPath* psp; 6964 6965 // Add all paths (and thus sub paths) to the free list(s). 6966 while (ps.paths !is null) { 6967 pp = ps.paths.next; 6968 nvg__freePickPath(ps, ps.paths); 6969 ps.paths = pp; 6970 } 6971 6972 // Delete all paths 6973 while (ps.freePaths !is null) { 6974 pp = ps.freePaths; 6975 ps.freePaths = pp.next; 6976 while (pp.subPaths !is null) { 6977 psp = pp.subPaths; 6978 pp.subPaths = psp.next; 6979 free(psp); 6980 } 6981 free(pp); 6982 } 6983 6984 // Delete all sub paths 6985 while (ps.freeSubPaths !is null) { 6986 psp = ps.freeSubPaths.next; 6987 free(ps.freeSubPaths); 6988 ps.freeSubPaths = psp; 6989 } 6990 6991 ps.npoints = 0; 6992 ps.nsegments = 0; 6993 6994 if (ps.levels !is null) { 6995 free(ps.levels[0]); 6996 free(ps.levels); 6997 } 6998 6999 if (ps.picked !is null) free(ps.picked); 7000 if (ps.points !is null) free(ps.points); 7001 if (ps.segments !is null) free(ps.segments); 7002 7003 free(ps); 7004 } 7005 7006 NVGpickScene* nvg__pickSceneGet (NVGContext ctx) { 7007 if (ctx.pickScene is null) ctx.pickScene = nvg__allocPickScene(); 7008 return ctx.pickScene; 7009 } 7010 7011 7012 // Applies Casteljau's algorithm to a cubic bezier for a given parameter t 7013 // points is 4 points (8 floats) 7014 // lvl1 is 3 points (6 floats) 7015 // lvl2 is 2 points (4 floats) 7016 // lvl3 is 1 point (2 floats) 7017 void nvg__casteljau (const(float)* points, float t, float* lvl1, float* lvl2, float* lvl3) { 7018 enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0; 7019 enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1; 7020 7021 // Level 1 7022 lvl1[x0] = (points[x1]-points[x0])*t+points[x0]; 7023 lvl1[y0] = (points[y1]-points[y0])*t+points[y0]; 7024 7025 lvl1[x1] = (points[x2]-points[x1])*t+points[x1]; 7026 lvl1[y1] = (points[y2]-points[y1])*t+points[y1]; 7027 7028 lvl1[x2] = (points[x3]-points[x2])*t+points[x2]; 7029 lvl1[y2] = (points[y3]-points[y2])*t+points[y2]; 7030 7031 // Level 2 7032 lvl2[x0] = (lvl1[x1]-lvl1[x0])*t+lvl1[x0]; 7033 lvl2[y0] = (lvl1[y1]-lvl1[y0])*t+lvl1[y0]; 7034 7035 lvl2[x1] = (lvl1[x2]-lvl1[x1])*t+lvl1[x1]; 7036 lvl2[y1] = (lvl1[y2]-lvl1[y1])*t+lvl1[y1]; 7037 7038 // Level 3 7039 lvl3[x0] = (lvl2[x1]-lvl2[x0])*t+lvl2[x0]; 7040 lvl3[y0] = (lvl2[y1]-lvl2[y0])*t+lvl2[y0]; 7041 } 7042 7043 // Calculates a point on a bezier at point t. 7044 void nvg__bezierEval (const(float)* points, float t, ref float[2] tpoint) { 7045 immutable float omt = 1-t; 7046 immutable float omt3 = omt*omt*omt; 7047 immutable float omt2 = omt*omt; 7048 immutable float t3 = t*t*t; 7049 immutable float t2 = t*t; 7050 7051 tpoint.ptr[0] = 7052 points[0]*omt3+ 7053 points[2]*3.0f*omt2*t+ 7054 points[4]*3.0f*omt*t2+ 7055 points[6]*t3; 7056 7057 tpoint.ptr[1] = 7058 points[1]*omt3+ 7059 points[3]*3.0f*omt2*t+ 7060 points[5]*3.0f*omt*t2+ 7061 points[7]*t3; 7062 } 7063 7064 // Splits a cubic bezier curve into two parts at point t. 7065 void nvg__splitBezier (const(float)* points, float t, float* pointsA, float* pointsB) { 7066 enum x0 = 0*2+0; enum x1 = 1*2+0; enum x2 = 2*2+0; enum x3 = 3*2+0; 7067 enum y0 = 0*2+1; enum y1 = 1*2+1; enum y2 = 2*2+1; enum y3 = 3*2+1; 7068 7069 float[6] lvl1 = void; 7070 float[4] lvl2 = void; 7071 float[2] lvl3 = void; 7072 7073 nvg__casteljau(points, t, lvl1.ptr, lvl2.ptr, lvl3.ptr); 7074 7075 // First half 7076 pointsA[x0] = points[x0]; 7077 pointsA[y0] = points[y0]; 7078 7079 pointsA[x1] = lvl1.ptr[x0]; 7080 pointsA[y1] = lvl1.ptr[y0]; 7081 7082 pointsA[x2] = lvl2.ptr[x0]; 7083 pointsA[y2] = lvl2.ptr[y0]; 7084 7085 pointsA[x3] = lvl3.ptr[x0]; 7086 pointsA[y3] = lvl3.ptr[y0]; 7087 7088 // Second half 7089 pointsB[x0] = lvl3.ptr[x0]; 7090 pointsB[y0] = lvl3.ptr[y0]; 7091 7092 pointsB[x1] = lvl2.ptr[x1]; 7093 pointsB[y1] = lvl2.ptr[y1]; 7094 7095 pointsB[x2] = lvl1.ptr[x2]; 7096 pointsB[y2] = lvl1.ptr[y2]; 7097 7098 pointsB[x3] = points[x3]; 7099 pointsB[y3] = points[y3]; 7100 } 7101 7102 // Calculates the inflection points in coordinate coord (X = 0, Y = 1) of a cubic bezier. 7103 // Appends any found inflection points to the array inflections and increments *ninflections. 7104 // So finds the parameters where dx/dt or dy/dt is 0 7105 void nvg__bezierInflections (const(float)* points, int coord, int* ninflections, float* inflections) { 7106 immutable float v0 = points[0*2+coord], v1 = points[1*2+coord], v2 = points[2*2+coord], v3 = points[3*2+coord]; 7107 float[2] t = void; 7108 int nvalid = *ninflections; 7109 7110 immutable float a = 3.0f*( -v0+3.0f*v1-3.0f*v2+v3 ); 7111 immutable float b = 6.0f*( v0-2.0f*v1+v2 ); 7112 immutable float c = 3.0f*( v1-v0 ); 7113 7114 float d = b*b-4.0f*a*c; 7115 if (nvg__absf(d-0.0f) < NVGPickEPS) { 7116 // Zero or one root 7117 t.ptr[0] = -b/2.0f*a; 7118 if (t.ptr[0] > NVGPickEPS && t.ptr[0] < (1.0f-NVGPickEPS)) { 7119 inflections[nvalid] = t.ptr[0]; 7120 ++nvalid; 7121 } 7122 } else if (d > NVGPickEPS) { 7123 // zero, one or two roots 7124 d = nvg__sqrtf(d); 7125 7126 t.ptr[0] = (-b+d)/(2.0f*a); 7127 t.ptr[1] = (-b-d)/(2.0f*a); 7128 7129 for (int i = 0; i < 2; ++i) { 7130 if (t.ptr[i] > NVGPickEPS && t.ptr[i] < (1.0f-NVGPickEPS)) { 7131 inflections[nvalid] = t.ptr[i]; 7132 ++nvalid; 7133 } 7134 } 7135 } else { 7136 // zero roots 7137 } 7138 7139 *ninflections = nvalid; 7140 } 7141 7142 // Sort a small number of floats in ascending order (0 < n < 6) 7143 void nvg__smallsort (float* values, int n) { 7144 bool bSwapped = true; 7145 for (int j = 0; j < n-1 && bSwapped; ++j) { 7146 bSwapped = false; 7147 for (int i = 0; i < n-1; ++i) { 7148 if (values[i] > values[i+1]) { 7149 auto tmp = values[i]; 7150 values[i] = values[i+1]; 7151 values[i+1] = tmp; 7152 } 7153 } 7154 } 7155 } 7156 7157 // Calculates the bounding rect of a given cubic bezier curve. 7158 void nvg__bezierBounds (const(float)* points, ref float[4] bounds) { 7159 float[4] inflections = void; 7160 int ninflections = 0; 7161 float[2] tpoint = void; 7162 7163 nvg__initBounds(bounds); 7164 7165 // Include start and end points in bounds 7166 nvg__expandBounds(bounds, &points[0], 1); 7167 nvg__expandBounds(bounds, &points[6], 1); 7168 7169 // Calculate dx==0 and dy==0 inflection points and add them to the bounds 7170 7171 nvg__bezierInflections(points, 0, &ninflections, inflections.ptr); 7172 nvg__bezierInflections(points, 1, &ninflections, inflections.ptr); 7173 7174 foreach (immutable int i; 0..ninflections) { 7175 nvg__bezierEval(points, inflections[i], tpoint); 7176 nvg__expandBounds(bounds, tpoint.ptr, 1); 7177 } 7178 } 7179 7180 // Checks to see if a line originating from x,y along the +ve x axis 7181 // intersects the given line (points[0],points[1]) -> (points[2], points[3]). 7182 // Returns `true` on intersection. 7183 // Horizontal lines are never hit. 7184 bool nvg__intersectLine (const(float)* points, float x, float y) { 7185 immutable float x1 = points[0]; 7186 immutable float y1 = points[1]; 7187 immutable float x2 = points[2]; 7188 immutable float y2 = points[3]; 7189 immutable float d = y2-y1; 7190 if (d > NVGPickEPS || d < -NVGPickEPS) { 7191 immutable float s = (x2-x1)/d; 7192 immutable float lineX = x1+(y-y1)*s; 7193 return (lineX > x); 7194 } else { 7195 return false; 7196 } 7197 } 7198 7199 // Checks to see if a line originating from x,y along the +ve x axis intersects the given bezier. 7200 // It is assumed that the line originates from within the bounding box of 7201 // the bezier and that the curve has no dy=0 inflection points. 7202 // Returns the number of intersections found (which is either 1 or 0). 7203 int nvg__intersectBezier (const(float)* points, float x, float y) { 7204 immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0]; 7205 immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1]; 7206 7207 if (y0 == y1 && y1 == y2 && y2 == y3) return 0; 7208 7209 // Initial t guess 7210 float t = void; 7211 if (y3 != y0) t = (y-y0)/(y3-y0); 7212 else if (x3 != x0) t = (x-x0)/(x3-x0); 7213 else t = 0.5f; 7214 7215 // A few Newton iterations 7216 for (int iter = 0; iter < 6; ++iter) { 7217 immutable float omt = 1-t; 7218 immutable float omt2 = omt*omt; 7219 immutable float t2 = t*t; 7220 immutable float omt3 = omt2*omt; 7221 immutable float t3 = t2*t; 7222 7223 immutable float ty = y0*omt3 + 7224 y1*3.0f*omt2*t + 7225 y2*3.0f*omt*t2 + 7226 y3*t3; 7227 7228 // Newton iteration 7229 immutable float dty = 3.0f*omt2*(y1-y0) + 7230 6.0f*omt*t*(y2-y1) + 7231 3.0f*t2*(y3-y2); 7232 7233 // dty will never == 0 since: 7234 // Either omt, omt2 are zero OR t2 is zero 7235 // y0 != y1 != y2 != y3 (checked above) 7236 t = t-(ty-y)/dty; 7237 } 7238 7239 { 7240 immutable float omt = 1-t; 7241 immutable float omt2 = omt*omt; 7242 immutable float t2 = t*t; 7243 immutable float omt3 = omt2*omt; 7244 immutable float t3 = t2*t; 7245 7246 immutable float tx = 7247 x0*omt3+ 7248 x1*3.0f*omt2*t+ 7249 x2*3.0f*omt*t2+ 7250 x3*t3; 7251 7252 return (tx > x ? 1 : 0); 7253 } 7254 } 7255 7256 // Finds the closest point on a line to a given point 7257 void nvg__closestLine (const(float)* points, float x, float y, float* closest, float* ot) { 7258 immutable float x1 = points[0]; 7259 immutable float y1 = points[1]; 7260 immutable float x2 = points[2]; 7261 immutable float y2 = points[3]; 7262 immutable float pqx = x2-x1; 7263 immutable float pqz = y2-y1; 7264 immutable float dx = x-x1; 7265 immutable float dz = y-y1; 7266 immutable float d = pqx*pqx+pqz*pqz; 7267 float t = pqx*dx+pqz*dz; 7268 if (d > 0) t /= d; 7269 if (t < 0) t = 0; else if (t > 1) t = 1; 7270 closest[0] = x1+t*pqx; 7271 closest[1] = y1+t*pqz; 7272 *ot = t; 7273 } 7274 7275 // Finds the closest point on a curve for a given point (x,y). 7276 // Assumes that the curve has no dx==0 or dy==0 inflection points. 7277 void nvg__closestBezier (const(float)* points, float x, float y, float* closest, float *ot) { 7278 immutable float x0 = points[0*2+0], x1 = points[1*2+0], x2 = points[2*2+0], x3 = points[3*2+0]; 7279 immutable float y0 = points[0*2+1], y1 = points[1*2+1], y2 = points[2*2+1], y3 = points[3*2+1]; 7280 7281 // This assumes that the curve has no dy=0 inflection points. 7282 7283 // Initial t guess 7284 float t = 0.5f; 7285 7286 // A few Newton iterations 7287 for (int iter = 0; iter < 6; ++iter) { 7288 immutable float omt = 1-t; 7289 immutable float omt2 = omt*omt; 7290 immutable float t2 = t*t; 7291 immutable float omt3 = omt2*omt; 7292 immutable float t3 = t2*t; 7293 7294 immutable float ty = 7295 y0*omt3+ 7296 y1*3.0f*omt2*t+ 7297 y2*3.0f*omt*t2+ 7298 y3*t3; 7299 7300 immutable float tx = 7301 x0*omt3+ 7302 x1*3.0f*omt2*t+ 7303 x2*3.0f*omt*t2+ 7304 x3*t3; 7305 7306 // Newton iteration 7307 immutable float dty = 7308 3.0f*omt2*(y1-y0)+ 7309 6.0f*omt*t*(y2-y1)+ 7310 3.0f*t2*(y3-y2); 7311 7312 immutable float ddty = 7313 6.0f*omt*(y2-2.0f*y1+y0)+ 7314 6.0f*t*(y3-2.0f*y2+y1); 7315 7316 immutable float dtx = 7317 3.0f*omt2*(x1-x0)+ 7318 6.0f*omt*t*(x2-x1)+ 7319 3.0f*t2*(x3-x2); 7320 7321 immutable float ddtx = 7322 6.0f*omt*(x2-2.0f*x1+x0)+ 7323 6.0f*t*(x3-2.0f*x2+x1); 7324 7325 immutable float errorx = tx-x; 7326 immutable float errory = ty-y; 7327 7328 immutable float n = errorx*dtx+errory*dty; 7329 if (n == 0) break; 7330 7331 immutable float d = dtx*dtx+dty*dty+errorx*ddtx+errory*ddty; 7332 if (d != 0) t = t-n/d; else break; 7333 } 7334 7335 t = nvg__max(0, nvg__min(1.0, t)); 7336 *ot = t; 7337 { 7338 immutable float omt = 1-t; 7339 immutable float omt2 = omt*omt; 7340 immutable float t2 = t*t; 7341 immutable float omt3 = omt2*omt; 7342 immutable float t3 = t2*t; 7343 7344 immutable float ty = 7345 y0*omt3+ 7346 y1*3.0f*omt2*t+ 7347 y2*3.0f*omt*t2+ 7348 y3*t3; 7349 7350 immutable float tx = 7351 x0*omt3+ 7352 x1*3.0f*omt2*t+ 7353 x2*3.0f*omt*t2+ 7354 x3*t3; 7355 7356 closest[0] = tx; 7357 closest[1] = ty; 7358 } 7359 } 7360 7361 // Returns: 7362 // 1 If (x,y) is contained by the stroke of the path 7363 // 0 If (x,y) is not contained by the path. 7364 int nvg__pickSubPathStroke (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, float strokeWidth, int lineCap, int lineJoin) { 7365 if (!nvg__pointInBounds(x, y, psp.bounds)) return 0; 7366 if (psp.firstSegment == -1) return 0; 7367 7368 float[2] closest = void; 7369 float[2] d = void; 7370 float t = void; 7371 7372 // trace a line from x,y out along the positive x axis and count the number of intersections 7373 int nsegments = psp.nsegments; 7374 const(NVGsegment)* seg = ps.segments+psp.firstSegment; 7375 const(NVGsegment)* prevseg = (psp.closed ? &ps.segments[psp.firstSegment+nsegments-1] : null); 7376 immutable float strokeWidthSqd = strokeWidth*strokeWidth; 7377 7378 for (int s = 0; s < nsegments; ++s, prevseg = seg, ++seg) { 7379 if (nvg__pointInBounds(x, y, seg.bounds)) { 7380 // Line potentially hits stroke. 7381 switch (seg.type) { 7382 case Command.LineTo: 7383 nvg__closestLine(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t); 7384 break; 7385 case Command.BezierTo: 7386 nvg__closestBezier(&ps.points[seg.firstPoint*2], x, y, closest.ptr, &t); 7387 break; 7388 default: 7389 continue; 7390 } 7391 7392 d.ptr[0] = x-closest.ptr[0]; 7393 d.ptr[1] = y-closest.ptr[1]; 7394 7395 if ((t >= NVGPickEPS && t <= 1.0f-NVGPickEPS) || 7396 (seg.flags&(NVGSegmentFlags.Corner|NVGSegmentFlags.Cap|NVGSegmentFlags.Endcap)) == 0 || 7397 (lineJoin == NVGLineCap.Round)) 7398 { 7399 // Closest point is in the middle of the line/curve, at a rounded join/cap 7400 // or at a smooth join 7401 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1]; 7402 if (distSqd < strokeWidthSqd) return 1; 7403 } else if ((t > 1.0f-NVGPickEPS && (seg.flags&NVGSegmentFlags.Endcap)) || 7404 (t < NVGPickEPS && (seg.flags&NVGSegmentFlags.Cap))) { 7405 switch (lineCap) { 7406 case NVGLineCap.Butt: 7407 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1]; 7408 immutable float dirD = (t < NVGPickEPS ? 7409 -(d.ptr[0]*seg.startDir.ptr[0]+d.ptr[1]*seg.startDir.ptr[1]) : 7410 d.ptr[0]*seg.endDir.ptr[0]+d.ptr[1]*seg.endDir.ptr[1]); 7411 if (dirD < -NVGPickEPS && distSqd < strokeWidthSqd) return 1; 7412 break; 7413 case NVGLineCap.Square: 7414 if (nvg__absf(d.ptr[0]) < strokeWidth && nvg__absf(d.ptr[1]) < strokeWidth) return 1; 7415 break; 7416 case NVGLineCap.Round: 7417 immutable float distSqd = d.ptr[0]*d.ptr[0]+d.ptr[1]*d.ptr[1]; 7418 if (distSqd < strokeWidthSqd) return 1; 7419 break; 7420 default: 7421 break; 7422 } 7423 } else if (seg.flags&NVGSegmentFlags.Corner) { 7424 // Closest point is at a corner 7425 const(NVGsegment)* seg0, seg1; 7426 7427 if (t < NVGPickEPS) { 7428 seg0 = prevseg; 7429 seg1 = seg; 7430 } else { 7431 seg0 = seg; 7432 seg1 = (s == nsegments-1 ? &ps.segments[psp.firstSegment] : seg+1); 7433 } 7434 7435 if (!(seg1.flags&NVGSegmentFlags.Bevel)) { 7436 immutable float prevNDist = -seg0.endDir.ptr[1]*d.ptr[0]+seg0.endDir.ptr[0]*d.ptr[1]; 7437 immutable float curNDist = seg1.startDir.ptr[1]*d.ptr[0]-seg1.startDir.ptr[0]*d.ptr[1]; 7438 if (nvg__absf(prevNDist) < strokeWidth && nvg__absf(curNDist) < strokeWidth) return 1; 7439 } else { 7440 d.ptr[0] -= -seg1.startDir.ptr[1]*strokeWidth; 7441 d.ptr[1] -= +seg1.startDir.ptr[0]*strokeWidth; 7442 if (seg1.miterDir.ptr[0]*d.ptr[0]+seg1.miterDir.ptr[1]*d.ptr[1] < 0) return 1; 7443 } 7444 } 7445 } 7446 } 7447 7448 return 0; 7449 } 7450 7451 // Returns: 7452 // 1 If (x,y) is contained by the path and the path is solid. 7453 // -1 If (x,y) is contained by the path and the path is a hole. 7454 // 0 If (x,y) is not contained by the path. 7455 int nvg__pickSubPath (const NVGpickScene* ps, const NVGpickSubPath* psp, float x, float y, bool evenOddMode) { 7456 if (!nvg__pointInBounds(x, y, psp.bounds)) return 0; 7457 if (psp.firstSegment == -1) return 0; 7458 7459 const(NVGsegment)* seg = &ps.segments[psp.firstSegment]; 7460 int nsegments = psp.nsegments; 7461 int nintersections = 0; 7462 7463 // trace a line from x,y out along the positive x axis and count the number of intersections 7464 for (int s = 0; s < nsegments; ++s, ++seg) { 7465 if ((seg.bounds.ptr[1]-NVGPickEPS) < y && 7466 (seg.bounds.ptr[3]-NVGPickEPS) > y && 7467 seg.bounds.ptr[2] > x) 7468 { 7469 // Line hits the box. 7470 switch (seg.type) { 7471 case Command.LineTo: 7472 if (seg.bounds.ptr[0] > x) { 7473 // line originates outside the box 7474 ++nintersections; 7475 } else { 7476 // line originates inside the box 7477 nintersections += nvg__intersectLine(&ps.points[seg.firstPoint*2], x, y); 7478 } 7479 break; 7480 case Command.BezierTo: 7481 if (seg.bounds.ptr[0] > x) { 7482 // line originates outside the box 7483 ++nintersections; 7484 } else { 7485 // line originates inside the box 7486 nintersections += nvg__intersectBezier(&ps.points[seg.firstPoint*2], x, y); 7487 } 7488 break; 7489 default: 7490 break; 7491 } 7492 } 7493 } 7494 7495 if (evenOddMode) { 7496 return nintersections; 7497 } else { 7498 return (nintersections&1 ? (psp.winding == NVGSolidity.Solid ? 1 : -1) : 0); 7499 } 7500 } 7501 7502 bool nvg__pickPath (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) { 7503 int pickCount = 0; 7504 const(NVGpickSubPath)* psp = pp.subPaths; 7505 while (psp !is null) { 7506 pickCount += nvg__pickSubPath(ps, psp, x, y, pp.evenOddMode); 7507 psp = psp.next; 7508 } 7509 return ((pp.evenOddMode ? pickCount&1 : pickCount) != 0); 7510 } 7511 7512 bool nvg__pickPathStroke (const(NVGpickScene)* ps, const(NVGpickPath)* pp, float x, float y) { 7513 const(NVGpickSubPath)* psp = pp.subPaths; 7514 while (psp !is null) { 7515 if (nvg__pickSubPathStroke(ps, psp, x, y, pp.strokeWidth, pp.lineCap, pp.lineJoin)) return true; 7516 psp = psp.next; 7517 } 7518 return false; 7519 } 7520 7521 bool nvg__pickPathTestBounds (NVGContext ctx, const NVGpickScene* ps, const NVGpickPath* pp, float x, float y) { 7522 if (nvg__pointInBounds(x, y, pp.bounds)) { 7523 //{ import core.stdc.stdio; printf(" (0): in bounds!\n"); } 7524 if (pp.flags&NVGPathFlags.Scissor) { 7525 const(float)* scissor = &ps.points[pp.scissor*2]; 7526 // untransform scissor translation 7527 float stx = void, sty = void; 7528 ctx.gpuUntransformPoint(&stx, &sty, scissor[4], scissor[5]); 7529 immutable float rx = x-stx; 7530 immutable float ry = y-sty; 7531 //{ 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]); } 7532 if (nvg__absf((scissor[0]*rx)+(scissor[1]*ry)) > scissor[6] || 7533 nvg__absf((scissor[2]*rx)+(scissor[3]*ry)) > scissor[7]) 7534 { 7535 //{ import core.stdc.stdio; printf(" (1): scissor reject!\n"); } 7536 return false; 7537 } 7538 } 7539 return true; 7540 } 7541 return false; 7542 } 7543 7544 int nvg__countBitsUsed (uint v) pure { 7545 pragma(inline, true); 7546 import core.bitop : bsr; 7547 return (v != 0 ? bsr(v)+1 : 0); 7548 } 7549 7550 void nvg__pickSceneInsert (NVGpickScene* ps, NVGpickPath* pp) { 7551 if (ps is null || pp is null) return; 7552 7553 int[4] cellbounds; 7554 int base = ps.nlevels-1; 7555 int level; 7556 int levelwidth; 7557 int levelshift; 7558 int levelx; 7559 int levely; 7560 NVGpickPath** cell = null; 7561 7562 // Bit tricks for inserting into an implicit quadtree. 7563 7564 // Calc bounds of path in cells at the lowest level 7565 cellbounds.ptr[0] = cast(int)(pp.bounds.ptr[0]/ps.xdim); 7566 cellbounds.ptr[1] = cast(int)(pp.bounds.ptr[1]/ps.ydim); 7567 cellbounds.ptr[2] = cast(int)(pp.bounds.ptr[2]/ps.xdim); 7568 cellbounds.ptr[3] = cast(int)(pp.bounds.ptr[3]/ps.ydim); 7569 7570 // Find which bits differ between the min/max x/y coords 7571 cellbounds.ptr[0] ^= cellbounds.ptr[2]; 7572 cellbounds.ptr[1] ^= cellbounds.ptr[3]; 7573 7574 // Use the number of bits used (countBitsUsed(x) == sizeof(int) * 8 - clz(x); 7575 // to calculate the level to insert at (the level at which the bounds fit in a single cell) 7576 level = nvg__min(base-nvg__countBitsUsed(cellbounds.ptr[0]), base-nvg__countBitsUsed(cellbounds.ptr[1])); 7577 if (level < 0) level = 0; 7578 //{ 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]); } 7579 //level = 0; 7580 7581 // Find the correct cell in the chosen level, clamping to the edges. 7582 levelwidth = 1<<level; 7583 levelshift = (ps.nlevels-level)-1; 7584 levelx = nvg__clamp(cellbounds.ptr[2]>>levelshift, 0, levelwidth-1); 7585 levely = nvg__clamp(cellbounds.ptr[3]>>levelshift, 0, levelwidth-1); 7586 7587 // Insert the path into the linked list at that cell. 7588 cell = &ps.levels[level][levely*levelwidth+levelx]; 7589 7590 pp.cellnext = *cell; 7591 *cell = pp; 7592 7593 if (ps.paths is null) ps.lastPath = pp; 7594 pp.next = ps.paths; 7595 ps.paths = pp; 7596 7597 // Store the order (depth) of the path for picking ops. 7598 pp.order = cast(short)ps.npaths; 7599 ++ps.npaths; 7600 } 7601 7602 void nvg__pickBeginFrame (NVGContext ctx, int width, int height) { 7603 NVGpickScene* ps = nvg__pickSceneGet(ctx); 7604 7605 //NVG_PICK_DEBUG_NEWFRAME(); 7606 7607 // Return all paths & sub paths from last frame to the free list 7608 while (ps.paths !is null) { 7609 NVGpickPath* pp = ps.paths.next; 7610 nvg__freePickPath(ps, ps.paths); 7611 ps.paths = pp; 7612 } 7613 7614 ps.paths = null; 7615 ps.npaths = 0; 7616 7617 // Store the screen metrics for the quadtree 7618 ps.width = width; 7619 ps.height = height; 7620 7621 immutable float lowestSubDiv = cast(float)(1<<(ps.nlevels-1)); 7622 ps.xdim = cast(float)width/lowestSubDiv; 7623 ps.ydim = cast(float)height/lowestSubDiv; 7624 7625 // Allocate the quadtree if required. 7626 if (ps.levels is null) { 7627 int ncells = 1; 7628 7629 ps.levels = cast(NVGpickPath***)malloc((NVGpickPath**).sizeof*ps.nlevels); 7630 for (int l = 0; l < ps.nlevels; ++l) { 7631 int leveldim = 1<<l; 7632 ncells += leveldim*leveldim; 7633 } 7634 7635 ps.levels[0] = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ncells); 7636 7637 int cell = 1; 7638 for (int l = 1; l < ps.nlevels; ++l) { 7639 ps.levels[l] = &ps.levels[0][cell]; 7640 int leveldim = 1<<l; 7641 cell += leveldim*leveldim; 7642 } 7643 7644 ps.ncells = ncells; 7645 } 7646 memset(ps.levels[0], 0, ps.ncells*(NVGpickPath*).sizeof); 7647 7648 // Allocate temporary storage for nvgHitTestAll results if required. 7649 if (ps.picked is null) { 7650 ps.cpicked = 16; 7651 ps.picked = cast(NVGpickPath**)malloc((NVGpickPath*).sizeof*ps.cpicked); 7652 } 7653 7654 ps.npoints = 0; 7655 ps.nsegments = 0; 7656 } 7657 } // nothrow @trusted @nogc 7658 7659 7660 /// Return outline of the current path. Returned outline is not flattened. 7661 /// Group: paths 7662 public NVGPathOutline getCurrPathOutline (NVGContext ctx) nothrow @trusted @nogc { 7663 if (ctx is null || !ctx.contextAlive || ctx.ncommands == 0) return NVGPathOutline.init; 7664 7665 auto res = NVGPathOutline.createNew(); 7666 7667 const(float)[] acommands = ctx.commands[0..ctx.ncommands]; 7668 int ncommands = cast(int)acommands.length; 7669 const(float)* commands = acommands.ptr; 7670 7671 float cx = 0, cy = 0; 7672 float[2] start = void; 7673 float[4] totalBounds = [float.max, float.max, -float.max, -float.max]; 7674 float[8] bcp = void; // bezier curve points; used to calculate bounds 7675 7676 void addToBounds (in float x, in float y) nothrow @trusted @nogc { 7677 totalBounds.ptr[0] = nvg__min(totalBounds.ptr[0], x); 7678 totalBounds.ptr[1] = nvg__min(totalBounds.ptr[1], y); 7679 totalBounds.ptr[2] = nvg__max(totalBounds.ptr[2], x); 7680 totalBounds.ptr[3] = nvg__max(totalBounds.ptr[3], y); 7681 } 7682 7683 bool hasPoints = false; 7684 7685 void closeIt () nothrow @trusted @nogc { 7686 if (!hasPoints) return; 7687 if (cx != start.ptr[0] || cy != start.ptr[1]) { 7688 res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo); 7689 res.ds.putArgs(start[]); 7690 cx = start.ptr[0]; 7691 cy = start.ptr[1]; 7692 addToBounds(cx, cy); 7693 } 7694 } 7695 7696 int i = 0; 7697 while (i < ncommands) { 7698 int cmd = cast(int)commands[i++]; 7699 switch (cmd) { 7700 case Command.MoveTo: // one coordinate pair 7701 const(float)* tfxy = commands+i; 7702 i += 2; 7703 // add command 7704 res.ds.putCommand(NVGPathOutline.Command.Kind.MoveTo); 7705 res.ds.putArgs(tfxy[0..2]); 7706 // new starting point 7707 start.ptr[0..2] = tfxy[0..2]; 7708 cx = tfxy[0]; 7709 cy = tfxy[0]; 7710 addToBounds(cx, cy); 7711 hasPoints = true; 7712 break; 7713 case Command.LineTo: // one coordinate pair 7714 const(float)* tfxy = commands+i; 7715 i += 2; 7716 // add command 7717 res.ds.putCommand(NVGPathOutline.Command.Kind.LineTo); 7718 res.ds.putArgs(tfxy[0..2]); 7719 cx = tfxy[0]; 7720 cy = tfxy[0]; 7721 addToBounds(cx, cy); 7722 hasPoints = true; 7723 break; 7724 case Command.BezierTo: // three coordinate pairs 7725 const(float)* tfxy = commands+i; 7726 i += 3*2; 7727 // add command 7728 res.ds.putCommand(NVGPathOutline.Command.Kind.BezierTo); 7729 res.ds.putArgs(tfxy[0..6]); 7730 // bounds 7731 bcp.ptr[0] = cx; 7732 bcp.ptr[1] = cy; 7733 bcp.ptr[2..8] = tfxy[0..6]; 7734 nvg__bezierBounds(bcp.ptr, totalBounds); 7735 cx = tfxy[4]; 7736 cy = tfxy[5]; 7737 hasPoints = true; 7738 break; 7739 case Command.Close: 7740 closeIt(); 7741 hasPoints = false; 7742 break; 7743 case Command.Winding: 7744 //psp.winding = cast(short)cast(int)commands[i]; 7745 i += 1; 7746 break; 7747 default: 7748 break; 7749 } 7750 } 7751 7752 res.ds.bounds[] = totalBounds[]; 7753 return res; 7754 } 7755 7756 7757 // ////////////////////////////////////////////////////////////////////////// // 7758 // Text 7759 7760 /** Creates font by loading it from the disk from specified file name. 7761 * Returns handle to the font or FONS_INVALID (aka -1) on error. 7762 * Use "fontname:noaa" as [name] to turn off antialiasing (if font driver supports that). 7763 * 7764 * On POSIX systems it is possible to use fontconfig font names too. 7765 * `:noaa` in font path is still allowed, but it must be the last option. 7766 * 7767 * Group: text_api 7768 */ 7769 public int createFont (NVGContext ctx, const(char)[] name, const(char)[] path) nothrow @trusted { 7770 return ctx.fs.addFont(name, path, ctx.params.fontAA); 7771 } 7772 7773 /** Creates font by loading it from the specified memory chunk. 7774 * Returns handle to the font or FONS_INVALID (aka -1) on error. 7775 * Won't free data on error. 7776 * 7777 * Group: text_api 7778 */ 7779 public int createFontMem (NVGContext ctx, const(char)[] name, ubyte* data, int ndata, bool freeData) nothrow @trusted @nogc { 7780 return ctx.fs.addFontMem(name, data, ndata, freeData, ctx.params.fontAA); 7781 } 7782 7783 /// Add fonts from another context. 7784 /// This is more effective than reloading fonts, 'cause font data will be shared. 7785 /// Group: text_api 7786 public void addFontsFrom (NVGContext ctx, NVGContext source) nothrow @trusted @nogc { 7787 if (ctx is null || source is null) return; 7788 ctx.fs.addFontsFrom(source.fs); 7789 } 7790 7791 /// Finds a loaded font of specified name, and returns handle to it, or FONS_INVALID (aka -1) if the font is not found. 7792 /// Group: text_api 7793 public int findFont (NVGContext ctx, const(char)[] name) nothrow @trusted @nogc { 7794 pragma(inline, true); 7795 return (name.length == 0 ? FONS_INVALID : ctx.fs.getFontByName(name)); 7796 } 7797 7798 /// Sets the font size of current text style. 7799 /// Group: text_api 7800 public void fontSize (NVGContext ctx, float size) nothrow @trusted @nogc { 7801 pragma(inline, true); 7802 nvg__getState(ctx).fontSize = size; 7803 } 7804 7805 /// Gets the font size of current text style. 7806 /// Group: text_api 7807 public float fontSize (NVGContext ctx) nothrow @trusted @nogc { 7808 pragma(inline, true); 7809 return nvg__getState(ctx).fontSize; 7810 } 7811 7812 /// Sets the blur of current text style. 7813 /// Group: text_api 7814 public void fontBlur (NVGContext ctx, float blur) nothrow @trusted @nogc { 7815 pragma(inline, true); 7816 nvg__getState(ctx).fontBlur = blur; 7817 } 7818 7819 /// Gets the blur of current text style. 7820 /// Group: text_api 7821 public float fontBlur (NVGContext ctx) nothrow @trusted @nogc { 7822 pragma(inline, true); 7823 return nvg__getState(ctx).fontBlur; 7824 } 7825 7826 /// Sets the letter spacing of current text style. 7827 /// Group: text_api 7828 public void textLetterSpacing (NVGContext ctx, float spacing) nothrow @trusted @nogc { 7829 pragma(inline, true); 7830 nvg__getState(ctx).letterSpacing = spacing; 7831 } 7832 7833 /// Gets the letter spacing of current text style. 7834 /// Group: text_api 7835 public float textLetterSpacing (NVGContext ctx) nothrow @trusted @nogc { 7836 pragma(inline, true); 7837 return nvg__getState(ctx).letterSpacing; 7838 } 7839 7840 /// Sets the proportional line height of current text style. The line height is specified as multiple of font size. 7841 /// Group: text_api 7842 public void textLineHeight (NVGContext ctx, float lineHeight) nothrow @trusted @nogc { 7843 pragma(inline, true); 7844 nvg__getState(ctx).lineHeight = lineHeight; 7845 } 7846 7847 /// Gets the proportional line height of current text style. The line height is specified as multiple of font size. 7848 /// Group: text_api 7849 public float textLineHeight (NVGContext ctx) nothrow @trusted @nogc { 7850 pragma(inline, true); 7851 return nvg__getState(ctx).lineHeight; 7852 } 7853 7854 /// Sets the text align of current text style, see [NVGTextAlign] for options. 7855 /// Group: text_api 7856 public void textAlign (NVGContext ctx, NVGTextAlign talign) nothrow @trusted @nogc { 7857 pragma(inline, true); 7858 nvg__getState(ctx).textAlign = talign; 7859 } 7860 7861 /// Ditto. 7862 public void textAlign (NVGContext ctx, NVGTextAlign.H h) nothrow @trusted @nogc { 7863 pragma(inline, true); 7864 nvg__getState(ctx).textAlign.horizontal = h; 7865 } 7866 7867 /// Ditto. 7868 public void textAlign (NVGContext ctx, NVGTextAlign.V v) nothrow @trusted @nogc { 7869 pragma(inline, true); 7870 nvg__getState(ctx).textAlign.vertical = v; 7871 } 7872 7873 /// Ditto. 7874 public void textAlign (NVGContext ctx, NVGTextAlign.H h, NVGTextAlign.V v) nothrow @trusted @nogc { 7875 pragma(inline, true); 7876 nvg__getState(ctx).textAlign.reset(h, v); 7877 } 7878 7879 /// Ditto. 7880 public void textAlign (NVGContext ctx, NVGTextAlign.V v, NVGTextAlign.H h) nothrow @trusted @nogc { 7881 pragma(inline, true); 7882 nvg__getState(ctx).textAlign.reset(h, v); 7883 } 7884 7885 /// Gets the text align of current text style, see [NVGTextAlign] for options. 7886 /// Group: text_api 7887 public NVGTextAlign textAlign (NVGContext ctx) nothrow @trusted @nogc { 7888 pragma(inline, true); 7889 return nvg__getState(ctx).textAlign; 7890 } 7891 7892 /// Sets the font face based on specified id of current text style. 7893 /// Group: text_api 7894 public void fontFaceId (NVGContext ctx, int font) nothrow @trusted @nogc { 7895 pragma(inline, true); 7896 nvg__getState(ctx).fontId = font; 7897 } 7898 7899 /// Gets the font face based on specified id of current text style. 7900 /// Group: text_api 7901 public int fontFaceId (NVGContext ctx) nothrow @trusted @nogc { 7902 pragma(inline, true); 7903 return nvg__getState(ctx).fontId; 7904 } 7905 7906 /** Sets the font face based on specified name of current text style. 7907 * 7908 * The underlying implementation is using O(1) data structure to lookup 7909 * font names, so you probably should use this function instead of [fontFaceId] 7910 * to make your code more robust and less error-prone. 7911 * 7912 * Group: text_api 7913 */ 7914 public void fontFace (NVGContext ctx, const(char)[] font) nothrow @trusted @nogc { 7915 pragma(inline, true); 7916 nvg__getState(ctx).fontId = ctx.fs.getFontByName(font); 7917 } 7918 7919 static if (is(typeof(&fons__nvg__toPath))) { 7920 public enum NanoVegaHasCharToPath = true; /// 7921 } else { 7922 public enum NanoVegaHasCharToPath = false; /// 7923 } 7924 7925 /// Adds glyph outlines to the current path. Vertical 0 is baseline. 7926 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead. 7927 /// Returns `false` if there is no such glyph, or current font is not scalable. 7928 /// Group: text_api 7929 public bool charToPath (NVGContext ctx, dchar dch, float[] bounds=null) nothrow @trusted @nogc { 7930 NVGstate* state = nvg__getState(ctx); 7931 ctx.fs.fontId = state.fontId; 7932 return ctx.fs.toPath(ctx, dch, bounds); 7933 } 7934 7935 static if (is(typeof(&fons__nvg__bounds))) { 7936 public enum NanoVegaHasCharPathBounds = true; /// 7937 } else { 7938 public enum NanoVegaHasCharPathBounds = false; /// 7939 } 7940 7941 /// Returns bounds of the glyph outlines. Vertical 0 is baseline. 7942 /// The glyph is not scaled in any way. 7943 /// Returns `false` if there is no such glyph, or current font is not scalable. 7944 /// Group: text_api 7945 public bool charPathBounds (NVGContext ctx, dchar dch, float[] bounds) nothrow @trusted @nogc { 7946 NVGstate* state = nvg__getState(ctx); 7947 ctx.fs.fontId = state.fontId; 7948 return ctx.fs.getPathBounds(dch, bounds); 7949 } 7950 7951 /** [charOutline] will return [NVGPathOutline]. 7952 7953 some usage samples: 7954 7955 --- 7956 float[4] bounds = void; 7957 7958 nvg.scale(0.5, 0.5); 7959 nvg.translate(500, 800); 7960 nvg.evenOddFill; 7961 7962 nvg.newPath(); 7963 nvg.charToPath('&', bounds[]); 7964 conwriteln(bounds[]); 7965 nvg.fillPaint(nvg.linearGradient(0, 0, 600, 600, NVGColor("#f70"), NVGColor("#ff0"))); 7966 nvg.strokeColor(NVGColor("#0f0")); 7967 nvg.strokeWidth = 3; 7968 nvg.fill(); 7969 nvg.stroke(); 7970 // glyph bounds 7971 nvg.newPath(); 7972 nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]); 7973 nvg.strokeColor(NVGColor("#00f")); 7974 nvg.stroke(); 7975 7976 nvg.newPath(); 7977 nvg.charToPath('g', bounds[]); 7978 conwriteln(bounds[]); 7979 nvg.fill(); 7980 nvg.strokeColor(NVGColor("#0f0")); 7981 nvg.stroke(); 7982 // glyph bounds 7983 nvg.newPath(); 7984 nvg.rect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]); 7985 nvg.strokeColor(NVGColor("#00f")); 7986 nvg.stroke(); 7987 7988 nvg.newPath(); 7989 nvg.moveTo(0, 0); 7990 nvg.lineTo(600, 0); 7991 nvg.strokeColor(NVGColor("#0ff")); 7992 nvg.stroke(); 7993 7994 if (auto ol = nvg.charOutline('Q')) { 7995 scope(exit) ol.kill(); 7996 nvg.newPath(); 7997 conwriteln("==== length: ", ol.length, " ===="); 7998 foreach (const ref cmd; ol.commands) { 7999 //conwriteln(" ", cmd.code, ": ", cmd.args[]); 8000 assert(cmd.valid); 8001 final switch (cmd.code) { 8002 case cmd.Kind.MoveTo: nvg.moveTo(cmd.args[0], cmd.args[1]); break; 8003 case cmd.Kind.LineTo: nvg.lineTo(cmd.args[0], cmd.args[1]); break; 8004 case cmd.Kind.QuadTo: nvg.quadTo(cmd.args[0], cmd.args[1], cmd.args[2], cmd.args[3]); break; 8005 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; 8006 } 8007 } 8008 nvg.strokeColor(NVGColor("#f00")); 8009 nvg.stroke(); 8010 } 8011 --- 8012 8013 Group: text_api 8014 */ 8015 public struct NVGPathOutline { 8016 private nothrow @trusted @nogc: 8017 struct DataStore { 8018 uint rc; // refcount 8019 ubyte* data; 8020 uint used; 8021 uint size; 8022 uint ccount; // number of commands 8023 float[4] bounds = 0; /// outline bounds 8024 nothrow @trusted @nogc: 8025 void putBytes (const(void)[] b) { 8026 if (b.length == 0) return; 8027 if (b.length >= int.max/8) assert(0, "NanoVega: out of memory"); 8028 if (int.max/8-used < b.length) assert(0, "NanoVega: out of memory"); 8029 if (used+cast(uint)b.length > size) { 8030 import core.stdc.stdlib : realloc; 8031 uint newsz = size; 8032 while (newsz < used+cast(uint)b.length) newsz = (newsz == 0 ? 1024 : newsz < 32768 ? newsz*2 : newsz+8192); 8033 assert(used+cast(uint)b.length <= newsz); 8034 data = cast(ubyte*)realloc(data, newsz); 8035 if (data is null) assert(0, "NanoVega: out of memory"); 8036 size = newsz; 8037 } 8038 import core.stdc..string : memcpy; 8039 memcpy(data+used, b.ptr, b.length); 8040 used += cast(uint)b.length; 8041 } 8042 void putCommand (ubyte cmd) { pragma(inline, true); ++ccount; putBytes((&cmd)[0..1]); } 8043 void putArgs (const(float)[] f...) { pragma(inline, true); putBytes(f[]); } 8044 } 8045 8046 static void incRef (DataStore* ds) { 8047 pragma(inline, true); 8048 if (ds !is null) { 8049 ++ds.rc; 8050 //{ import core.stdc.stdio; printf("ods(%p): incref: newrc=%u\n", ds, ds.rc); } 8051 } 8052 } 8053 8054 static void decRef (DataStore* ds) { 8055 version(aliced) pragma(inline, true); 8056 if (ds !is null) { 8057 //{ import core.stdc.stdio; printf("ods(%p): decref: newrc=%u\n", ds, ds.rc-1); } 8058 if (--ds.rc == 0) { 8059 import core.stdc.stdlib : free; 8060 import core.stdc..string : memset; 8061 if (ds.data !is null) free(ds.data); 8062 memset(ds, 0, DataStore.sizeof); // just in case 8063 free(ds); 8064 //{ import core.stdc.stdio; printf(" ods(%p): killed.\n"); } 8065 } 8066 } 8067 } 8068 8069 private: 8070 static NVGPathOutline createNew () { 8071 import core.stdc.stdlib : malloc; 8072 import core.stdc..string : memset; 8073 auto ds = cast(DataStore*)malloc(DataStore.sizeof); 8074 if (ds is null) assert(0, "NanoVega: out of memory"); 8075 memset(ds, 0, DataStore.sizeof); 8076 ds.rc = 1; 8077 NVGPathOutline res; 8078 res.dsaddr = cast(usize)ds; 8079 return res; 8080 } 8081 8082 private: 8083 usize dsaddr; // fool GC 8084 8085 @property inout(DataStore)* ds () inout pure { pragma(inline, true); return cast(DataStore*)dsaddr; } 8086 8087 public: 8088 /// commands 8089 static struct Command { 8090 /// 8091 enum Kind : ubyte { 8092 MoveTo, /// 8093 LineTo, /// 8094 QuadTo, /// 8095 BezierTo, /// 8096 End, /// no more commands (this command is not `valid`!) 8097 8098 } 8099 Kind code; /// 8100 const(float)[] args; /// 8101 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (code >= Kind.min && code < Kind.End && args.length >= 2); } /// 8102 8103 static uint arglen (Kind code) pure nothrow @safe @nogc { 8104 pragma(inline, true); 8105 return 8106 code == Kind.MoveTo || code == Kind.LineTo ? 2 : 8107 code == Kind.QuadTo ? 4 : 8108 code == Kind.BezierTo ? 6 : 8109 0; 8110 } 8111 8112 /// perform NanoVega command with stored data. 8113 void perform (NVGContext ctx) const nothrow @trusted @nogc { 8114 if (ctx is null) return; 8115 final switch (code) { 8116 case Kind.MoveTo: if (args.length > 1) ctx.moveTo(args.ptr[0..2]); break; 8117 case Kind.LineTo: if (args.length > 1) ctx.lineTo(args.ptr[0..2]); break; 8118 case Kind.QuadTo: if (args.length > 3) ctx.quadTo(args.ptr[0..4]); break; 8119 case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(args.ptr[0..6]); break; 8120 case Kind.End: break; 8121 } 8122 } 8123 8124 /// perform NanoVega command with stored data, transforming points with [xform] transformation matrix. 8125 void perform() (NVGContext ctx, in auto ref NVGMatrix xform) const nothrow @trusted @nogc { 8126 if (ctx is null || !valid) return; 8127 float[6] pts = void; 8128 pts[0..args.length] = args[]; 8129 foreach (immutable pidx; 0..args.length/2) xform.point(pts.ptr[pidx*2+0], pts.ptr[pidx*2+1]); 8130 final switch (code) { 8131 case Kind.MoveTo: if (args.length > 1) ctx.moveTo(pts.ptr[0..2]); break; 8132 case Kind.LineTo: if (args.length > 1) ctx.lineTo(pts.ptr[0..2]); break; 8133 case Kind.QuadTo: if (args.length > 3) ctx.quadTo(pts.ptr[0..4]); break; 8134 case Kind.BezierTo: if (args.length > 5) ctx.bezierTo(pts.ptr[0..6]); break; 8135 case Kind.End: break; 8136 } 8137 } 8138 } 8139 8140 public: 8141 /// Create new path with quadratic bezier (first command is MoveTo, second command is QuadTo). 8142 static NVGPathOutline createNewQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) { 8143 auto res = createNew(); 8144 res.ds.putCommand(Command.Kind.MoveTo); 8145 res.ds.putArgs(x0, y0); 8146 res.ds.putCommand(Command.Kind.QuadTo); 8147 res.ds.putArgs(cx, cy, x, y); 8148 return res; 8149 } 8150 8151 /// Create new path with cubic bezier (first command is MoveTo, second command is BezierTo). 8152 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) { 8153 auto res = createNew(); 8154 res.ds.putCommand(Command.Kind.MoveTo); 8155 res.ds.putArgs(x1, y1); 8156 res.ds.putCommand(Command.Kind.BezierTo); 8157 res.ds.putArgs(x2, y2, x3, y3, x4, y4); 8158 return res; 8159 } 8160 8161 public: 8162 this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); } 8163 ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); } 8164 8165 void opAssign() (in auto ref NVGPathOutline a) { 8166 incRef(cast(DataStore*)a.dsaddr); 8167 decRef(cast(DataStore*)dsaddr); 8168 dsaddr = a.dsaddr; 8169 } 8170 8171 /// Clear storage. 8172 void clear () { 8173 pragma(inline, true); 8174 decRef(ds); 8175 dsaddr = 0; 8176 } 8177 8178 /// Is this outline empty? 8179 @property empty () const pure { pragma(inline, true); return (dsaddr == 0 || ds.ccount == 0); } 8180 8181 /// Returns number of commands in outline. 8182 @property int length () const pure { pragma(inline, true); return (dsaddr ? ds.ccount : 0); } 8183 8184 /// Returns "flattened" path. Flattened path consists of only two commands kinds: MoveTo and LineTo. 8185 NVGPathOutline flatten () const { pragma(inline, true); return flattenInternal(null); } 8186 8187 /// Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo. 8188 NVGPathOutline flatten() (in auto ref NVGMatrix mt) const { pragma(inline, true); return flattenInternal(&mt); } 8189 8190 // Returns "flattened" path, transformed by the given matrix. Flattened path consists of only two commands kinds: MoveTo and LineTo. 8191 private NVGPathOutline flattenInternal (scope NVGMatrix* tfm) const { 8192 import core.stdc..string : memset; 8193 8194 NVGPathOutline res; 8195 if (dsaddr == 0 || ds.ccount == 0) { res = this; return res; } // nothing to do 8196 8197 // check if we need to flatten the path 8198 if (tfm is null) { 8199 bool dowork = false; 8200 foreach (const ref cs; commands) { 8201 if (cs.code != Command.Kind.MoveTo && cs.code != Command.Kind.LineTo) { 8202 dowork = true; 8203 break; 8204 } 8205 } 8206 if (!dowork) { res = this; return res; } // nothing to do 8207 } 8208 8209 NVGcontextinternal ctx; 8210 memset(&ctx, 0, ctx.sizeof); 8211 ctx.cache = nvg__allocPathCache(); 8212 scope(exit) { 8213 import core.stdc.stdlib : free; 8214 nvg__deletePathCache(ctx.cache); 8215 } 8216 8217 ctx.tessTol = 0.25f; 8218 ctx.angleTol = 0; // 0 -- angle tolerance for McSeem Bezier rasterizer 8219 ctx.cuspLimit = 0; // 0 -- cusp limit for McSeem Bezier rasterizer (0: real cusps) 8220 ctx.distTol = 0.01f; 8221 ctx.tesselatortype = NVGTesselation.DeCasteljau; 8222 8223 nvg__addPath(&ctx); // we need this for `nvg__addPoint()` 8224 8225 // has some curves or transformations, convert path 8226 res = createNew(); 8227 float[8] args = void; 8228 8229 res.ds.bounds = [float.max, float.max, -float.max, -float.max]; 8230 8231 float lastX = float.max, lastY = float.max; 8232 bool lastWasMove = false; 8233 8234 void addPoint (float x, float y, Command.Kind cmd=Command.Kind.LineTo) nothrow @trusted @nogc { 8235 if (tfm !is null) tfm.point(x, y); 8236 bool isMove = (cmd == Command.Kind.MoveTo); 8237 if (isMove) { 8238 // moveto 8239 if (lastWasMove && nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return; 8240 } else { 8241 // lineto 8242 if (nvg__ptEquals(lastX, lastY, x, y, ctx.distTol)) return; 8243 } 8244 lastWasMove = isMove; 8245 lastX = x; 8246 lastY = y; 8247 res.ds.putCommand(cmd); 8248 res.ds.putArgs(x, y); 8249 res.ds.bounds.ptr[0] = nvg__min(res.ds.bounds.ptr[0], x); 8250 res.ds.bounds.ptr[1] = nvg__min(res.ds.bounds.ptr[1], y); 8251 res.ds.bounds.ptr[2] = nvg__max(res.ds.bounds.ptr[2], x); 8252 res.ds.bounds.ptr[3] = nvg__max(res.ds.bounds.ptr[3], y); 8253 } 8254 8255 // sorry for this pasta 8256 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 { 8257 ctx.cache.npoints = 0; 8258 if (ctx.tesselatortype == NVGTesselation.DeCasteljau) { 8259 nvg__tesselateBezier(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner); 8260 } else if (ctx.tesselatortype == NVGTesselation.DeCasteljauMcSeem) { 8261 nvg__tesselateBezierMcSeem(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, 0, PointFlag.Corner); 8262 } else { 8263 nvg__tesselateBezierAFD(&ctx, x1, y1, x2, y2, x3, y3, x4, y4, PointFlag.Corner); 8264 } 8265 // add generated points 8266 foreach (const ref pt; ctx.cache.points[0..ctx.cache.npoints]) addPoint(pt.x, pt.y); 8267 } 8268 8269 void flattenQuad (in float x0, in float y0, in float cx, in float cy, in float x, in float y) { 8270 flattenBezier( 8271 x0, y0, 8272 x0+2.0f/3.0f*(cx-x0), y0+2.0f/3.0f*(cy-y0), 8273 x+2.0f/3.0f*(cx-x), y+2.0f/3.0f*(cy-y), 8274 x, y, 8275 0, 8276 ); 8277 } 8278 8279 float cx = 0, cy = 0; 8280 foreach (const ref cs; commands) { 8281 switch (cs.code) { 8282 case Command.Kind.LineTo: 8283 case Command.Kind.MoveTo: 8284 addPoint(cs.args[0], cs.args[1], cs.code); 8285 cx = cs.args[0]; 8286 cy = cs.args[1]; 8287 break; 8288 case Command.Kind.QuadTo: 8289 flattenQuad(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3]); 8290 cx = cs.args[2]; 8291 cy = cs.args[3]; 8292 break; 8293 case Command.Kind.BezierTo: 8294 flattenBezier(cx, cy, cs.args[0], cs.args[1], cs.args[2], cs.args[3], cs.args[4], cs.args[5], 0); 8295 cx = cs.args[4]; 8296 cy = cs.args[5]; 8297 break; 8298 default: 8299 break; 8300 } 8301 } 8302 8303 return res; 8304 } 8305 8306 /// Returns forward range with all glyph commands. 8307 auto commands () const nothrow @trusted @nogc { 8308 static struct Range { 8309 private nothrow @trusted @nogc: 8310 usize dsaddr; 8311 uint cpos; // current position in data 8312 uint cleft; // number of commands left 8313 @property const(ubyte)* data () inout pure { pragma(inline, true); return (dsaddr ? (cast(DataStore*)dsaddr).data : null); } 8314 public: 8315 this (this) { pragma(inline, true); incRef(cast(DataStore*)dsaddr); } 8316 ~this () { pragma(inline, true); decRef(cast(DataStore*)dsaddr); } 8317 void opAssign() (in auto ref Range a) { 8318 incRef(cast(DataStore*)a.dsaddr); 8319 decRef(cast(DataStore*)dsaddr); 8320 dsaddr = a.dsaddr; 8321 cpos = a.cpos; 8322 cleft = a.cleft; 8323 } 8324 float[4] bounds () const pure { float[4] res = 0; pragma(inline, true); if (dsaddr) res[] = (cast(DataStore*)dsaddr).bounds[]; return res; } /// outline bounds 8325 @property bool empty () const pure { pragma(inline, true); return (cleft == 0); } 8326 @property int length () const pure { pragma(inline, true); return cleft; } 8327 @property Range save () const { pragma(inline, true); Range res = this; return res; } 8328 @property Command front () const { 8329 Command res = void; 8330 if (cleft > 0) { 8331 res.code = cast(Command.Kind)data[cpos]; 8332 switch (res.code) { 8333 case Command.Kind.MoveTo: 8334 case Command.Kind.LineTo: 8335 res.args = (cast(const(float*))(data+cpos+1))[0..1*2]; 8336 break; 8337 case Command.Kind.QuadTo: 8338 res.args = (cast(const(float*))(data+cpos+1))[0..2*2]; 8339 break; 8340 case Command.Kind.BezierTo: 8341 res.args = (cast(const(float*))(data+cpos+1))[0..3*2]; 8342 break; 8343 default: 8344 res.code = Command.Kind.End; 8345 res.args = null; 8346 break; 8347 } 8348 } else { 8349 res.code = Command.Kind.End; 8350 res.args = null; 8351 } 8352 return res; 8353 } 8354 void popFront () { 8355 if (cleft <= 1) { cleft = 0; return; } // don't waste time skipping last command 8356 --cleft; 8357 switch (data[cpos]) { 8358 case Command.Kind.MoveTo: 8359 case Command.Kind.LineTo: 8360 cpos += 1+1*2*cast(uint)float.sizeof; 8361 break; 8362 case Command.Kind.QuadTo: 8363 cpos += 1+2*2*cast(uint)float.sizeof; 8364 break; 8365 case Command.Kind.BezierTo: 8366 cpos += 1+3*2*cast(uint)float.sizeof; 8367 break; 8368 default: 8369 cleft = 0; 8370 break; 8371 } 8372 } 8373 } 8374 if (dsaddr) { 8375 incRef(cast(DataStore*)dsaddr); // range anchors it 8376 return Range(dsaddr, 0, ds.ccount); 8377 } else { 8378 return Range.init; 8379 } 8380 } 8381 } 8382 8383 public alias NVGGlyphOutline = NVGPathOutline; /// For backwards compatibility. 8384 8385 /// Destroy glyph outiline and free allocated memory. 8386 /// Group: text_api 8387 public void kill (ref NVGPathOutline ol) nothrow @trusted @nogc { 8388 pragma(inline, true); 8389 ol.clear(); 8390 } 8391 8392 static if (is(typeof(&fons__nvg__toOutline))) { 8393 public enum NanoVegaHasCharOutline = true; /// 8394 } else { 8395 public enum NanoVegaHasCharOutline = false; /// 8396 } 8397 8398 /// Returns glyph outlines as array of commands. Vertical 0 is baseline. 8399 /// The glyph is not scaled in any way, so you have to use NanoVega transformations instead. 8400 /// Returns `null` if there is no such glyph, or current font is not scalable. 8401 /// Group: text_api 8402 public NVGPathOutline charOutline (NVGContext ctx, dchar dch) nothrow @trusted @nogc { 8403 import core.stdc.stdlib : malloc; 8404 import core.stdc..string : memcpy; 8405 NVGstate* state = nvg__getState(ctx); 8406 ctx.fs.fontId = state.fontId; 8407 auto oline = NVGPathOutline.createNew(); 8408 if (!ctx.fs.toOutline(dch, oline.ds)) oline.clear(); 8409 return oline; 8410 } 8411 8412 8413 float nvg__quantize (float a, float d) pure nothrow @safe @nogc { 8414 pragma(inline, true); 8415 return (cast(int)(a/d+0.5f))*d; 8416 } 8417 8418 float nvg__getFontScale (NVGstate* state) /*pure*/ nothrow @safe @nogc { 8419 pragma(inline, true); 8420 return nvg__min(nvg__quantize(nvg__getAverageScale(state.xform), 0.01f), 4.0f); 8421 } 8422 8423 void nvg__flushTextTexture (NVGContext ctx) nothrow @trusted @nogc { 8424 int[4] dirty = void; 8425 if (ctx.fs.validateTexture(dirty.ptr)) { 8426 auto fontImage = &ctx.fontImages[ctx.fontImageIdx]; 8427 // Update texture 8428 if (fontImage.valid) { 8429 int iw, ih; 8430 const(ubyte)* data = ctx.fs.getTextureData(&iw, &ih); 8431 int x = dirty[0]; 8432 int y = dirty[1]; 8433 int w = dirty[2]-dirty[0]; 8434 int h = dirty[3]-dirty[1]; 8435 ctx.params.renderUpdateTexture(ctx.params.userPtr, fontImage.id, x, y, w, h, data); 8436 } 8437 } 8438 } 8439 8440 bool nvg__allocTextAtlas (NVGContext ctx) nothrow @trusted @nogc { 8441 int iw, ih; 8442 nvg__flushTextTexture(ctx); 8443 if (ctx.fontImageIdx >= NVG_MAX_FONTIMAGES-1) return false; 8444 // if next fontImage already have a texture 8445 if (ctx.fontImages[ctx.fontImageIdx+1].valid) { 8446 ctx.imageSize(ctx.fontImages[ctx.fontImageIdx+1], iw, ih); 8447 } else { 8448 // calculate the new font image size and create it 8449 ctx.imageSize(ctx.fontImages[ctx.fontImageIdx], iw, ih); 8450 if (iw > ih) ih *= 2; else iw *= 2; 8451 if (iw > NVG_MAX_FONTIMAGE_SIZE || ih > NVG_MAX_FONTIMAGE_SIZE) iw = ih = NVG_MAX_FONTIMAGE_SIZE; 8452 ctx.fontImages[ctx.fontImageIdx+1].id = ctx.params.renderCreateTexture(ctx.params.userPtr, NVGtexture.Alpha, iw, ih, (ctx.params.fontAA ? 0 : NVGImageFlag.NoFiltering), null); 8453 if (ctx.fontImages[ctx.fontImageIdx+1].id > 0) { 8454 ctx.fontImages[ctx.fontImageIdx+1].ctx = ctx; 8455 ctx.nvg__imageIncRef(ctx.fontImages[ctx.fontImageIdx+1].id, false); // don't increment driver refcount 8456 } 8457 } 8458 ++ctx.fontImageIdx; 8459 ctx.fs.resetAtlas(iw, ih); 8460 return true; 8461 } 8462 8463 void nvg__renderText (NVGContext ctx, NVGVertex* verts, int nverts) nothrow @trusted @nogc { 8464 NVGstate* state = nvg__getState(ctx); 8465 NVGPaint paint = state.fill; 8466 8467 // Render triangles. 8468 paint.image = ctx.fontImages[ctx.fontImageIdx]; 8469 8470 // Apply global alpha 8471 paint.innerColor.a *= state.alpha; 8472 paint.middleColor.a *= state.alpha; 8473 paint.outerColor.a *= state.alpha; 8474 8475 ctx.params.renderTriangles(ctx.params.userPtr, state.compositeOperation, NVGClipMode.None, &paint, &state.scissor, verts, nverts); 8476 8477 ++ctx.drawCallCount; 8478 ctx.textTriCount += nverts/3; 8479 } 8480 8481 /// Draws text string at specified location. Returns next x position. 8482 /// Group: text_api 8483 public float text(T) (NVGContext ctx, float x, float y, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) { 8484 NVGstate* state = nvg__getState(ctx); 8485 FONSTextIter!T iter, prevIter; 8486 FONSQuad q; 8487 NVGVertex* verts; 8488 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8489 float invscale = 1.0f/scale; 8490 int cverts = 0; 8491 int nverts = 0; 8492 8493 if (state.fontId == FONS_INVALID) return x; 8494 if (str.length == 0) return x; 8495 8496 ctx.fs.size = state.fontSize*scale; 8497 ctx.fs.spacing = state.letterSpacing*scale; 8498 ctx.fs.blur = state.fontBlur*scale; 8499 ctx.fs.textAlign = state.textAlign; 8500 ctx.fs.fontId = state.fontId; 8501 8502 cverts = nvg__max(2, cast(int)(str.length))*6; // conservative estimate 8503 verts = nvg__allocTempVerts(ctx, cverts); 8504 if (verts is null) return x; 8505 8506 if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Required)) return x; 8507 prevIter = iter; 8508 while (iter.next(q)) { 8509 float[4*2] c = void; 8510 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph? 8511 if (nverts != 0) { 8512 // TODO: add back-end bit to do this just once per frame 8513 nvg__flushTextTexture(ctx); 8514 nvg__renderText(ctx, verts, nverts); 8515 nverts = 0; 8516 } 8517 if (!nvg__allocTextAtlas(ctx)) break; // no memory :( 8518 iter = prevIter; 8519 iter.next(q); // try again 8520 if (iter.prevGlyphIndex < 0) { 8521 // still can not find glyph, try replacement 8522 iter = prevIter; 8523 if (!iter.getDummyChar(q)) break; 8524 } 8525 } 8526 prevIter = iter; 8527 // transform corners 8528 state.xform.point(&c[0], &c[1], q.x0*invscale, q.y0*invscale); 8529 state.xform.point(&c[2], &c[3], q.x1*invscale, q.y0*invscale); 8530 state.xform.point(&c[4], &c[5], q.x1*invscale, q.y1*invscale); 8531 state.xform.point(&c[6], &c[7], q.x0*invscale, q.y1*invscale); 8532 // create triangles 8533 if (nverts+6 <= cverts) { 8534 nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts; 8535 nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts; 8536 nvg__vset(&verts[nverts], c[2], c[3], q.s1, q.t0); ++nverts; 8537 nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); ++nverts; 8538 nvg__vset(&verts[nverts], c[6], c[7], q.s0, q.t1); ++nverts; 8539 nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); ++nverts; 8540 } 8541 } 8542 8543 // TODO: add back-end bit to do this just once per frame 8544 if (nverts > 0) { 8545 nvg__flushTextTexture(ctx); 8546 nvg__renderText(ctx, verts, nverts); 8547 } 8548 8549 return iter.nextx/scale; 8550 } 8551 8552 /** Draws multi-line text string at specified location wrapped at the specified width. 8553 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 8554 * Words longer than the max width are slit at nearest character (i.e. no hyphenation). 8555 * 8556 * Group: text_api 8557 */ 8558 public void textBox(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) { 8559 NVGstate* state = nvg__getState(ctx); 8560 if (state.fontId == FONS_INVALID) return; 8561 8562 NVGTextRow!T[2] rows; 8563 auto oldAlign = state.textAlign; 8564 scope(exit) state.textAlign = oldAlign; 8565 auto halign = state.textAlign.horizontal; 8566 float lineh = 0; 8567 8568 ctx.textMetrics(null, null, &lineh); 8569 state.textAlign.horizontal = NVGTextAlign.H.Left; 8570 for (;;) { 8571 auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]); 8572 //{ import core.stdc.stdio : printf; printf("slen=%u; rlen=%u; bw=%f\n", cast(uint)str.length, cast(uint)rres.length, cast(double)breakRowWidth); } 8573 if (rres.length == 0) break; 8574 foreach (ref row; rres) { 8575 final switch (halign) { 8576 case NVGTextAlign.H.Left: ctx.text(x, y, row.row); break; 8577 case NVGTextAlign.H.Center: ctx.text(x+breakRowWidth*0.5f-row.width*0.5f, y, row.row); break; 8578 case NVGTextAlign.H.Right: ctx.text(x+breakRowWidth-row.width, y, row.row); break; 8579 } 8580 y += lineh*state.lineHeight; 8581 } 8582 str = rres[$-1].rest; 8583 } 8584 } 8585 8586 private template isGoodPositionDelegate(DG) { 8587 private DG dg; 8588 static if (is(typeof({ NVGGlyphPosition pos; bool res = dg(pos); })) || 8589 is(typeof({ NVGGlyphPosition pos; dg(pos); }))) 8590 enum isGoodPositionDelegate = true; 8591 else 8592 enum isGoodPositionDelegate = false; 8593 } 8594 8595 /** Calculates the glyph x positions of the specified text. 8596 * Measured values are returned in local coordinate space. 8597 * 8598 * Group: text_api 8599 */ 8600 public NVGGlyphPosition[] textGlyphPositions(T) (NVGContext ctx, float x, float y, const(T)[] str, NVGGlyphPosition[] positions) nothrow @trusted @nogc 8601 if (isAnyCharType!T) 8602 { 8603 if (str.length == 0 || positions.length == 0) return positions[0..0]; 8604 usize posnum; 8605 auto len = ctx.textGlyphPositions(x, y, str, (in ref NVGGlyphPosition pos) { 8606 positions.ptr[posnum++] = pos; 8607 return (posnum < positions.length); 8608 }); 8609 return positions[0..len]; 8610 } 8611 8612 /// Ditto. 8613 public int textGlyphPositions(T, DG) (NVGContext ctx, float x, float y, const(T)[] str, scope DG dg) 8614 if (isAnyCharType!T && isGoodPositionDelegate!DG) 8615 { 8616 import std.traits : ReturnType; 8617 static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true; 8618 8619 NVGstate* state = nvg__getState(ctx); 8620 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8621 float invscale = 1.0f/scale; 8622 FONSTextIter!T iter, prevIter; 8623 FONSQuad q; 8624 int npos = 0; 8625 8626 if (str.length == 0) return 0; 8627 8628 ctx.fs.size = state.fontSize*scale; 8629 ctx.fs.spacing = state.letterSpacing*scale; 8630 ctx.fs.blur = state.fontBlur*scale; 8631 ctx.fs.textAlign = state.textAlign; 8632 ctx.fs.fontId = state.fontId; 8633 8634 if (!iter.setup(ctx.fs, x*scale, y*scale, str, FONSBitmapFlag.Optional)) return npos; 8635 prevIter = iter; 8636 while (iter.next(q)) { 8637 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph? 8638 if (!nvg__allocTextAtlas(ctx)) break; // no memory 8639 iter = prevIter; 8640 iter.next(q); // try again 8641 if (iter.prevGlyphIndex < 0) { 8642 // still can not find glyph, try replacement 8643 iter = prevIter; 8644 if (!iter.getDummyChar(q)) break; 8645 } 8646 } 8647 prevIter = iter; 8648 NVGGlyphPosition position = void; //WARNING! 8649 position.strpos = cast(usize)(iter.stringp-str.ptr); 8650 position.x = iter.x*invscale; 8651 position.minx = nvg__min(iter.x, q.x0)*invscale; 8652 position.maxx = nvg__max(iter.nextx, q.x1)*invscale; 8653 ++npos; 8654 static if (RetBool) { if (!dg(position)) return npos; } else dg(position); 8655 } 8656 8657 return npos; 8658 } 8659 8660 private template isGoodRowDelegate(CT, DG) { 8661 private DG dg; 8662 static if (is(typeof({ NVGTextRow!CT row; bool res = dg(row); })) || 8663 is(typeof({ NVGTextRow!CT row; dg(row); }))) 8664 enum isGoodRowDelegate = true; 8665 else 8666 enum isGoodRowDelegate = false; 8667 } 8668 8669 /** Breaks the specified text into lines. 8670 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 8671 * Words longer than the max width are slit at nearest character (i.e. no hyphenation). 8672 * 8673 * Group: text_api 8674 */ 8675 public NVGTextRow!T[] textBreakLines(T) (NVGContext ctx, const(T)[] str, float breakRowWidth, NVGTextRow!T[] rows) nothrow @trusted @nogc 8676 if (isAnyCharType!T) 8677 { 8678 if (rows.length == 0) return rows; 8679 if (rows.length > int.max-1) rows = rows[0..int.max-1]; 8680 int nrow = 0; 8681 auto count = ctx.textBreakLines(str, breakRowWidth, (in ref NVGTextRow!T row) { 8682 rows[nrow++] = row; 8683 return (nrow < rows.length); 8684 }); 8685 return rows[0..count]; 8686 } 8687 8688 /** Breaks the specified text into lines. 8689 * White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. 8690 * Words longer than the max width are slit at nearest character (i.e. no hyphenation). 8691 * Returns number of rows. 8692 * 8693 * Group: text_api 8694 */ 8695 public int textBreakLines(T, DG) (NVGContext ctx, const(T)[] str, float breakRowWidth, scope DG dg) 8696 if (isAnyCharType!T && isGoodRowDelegate!(T, DG)) 8697 { 8698 import std.traits : ReturnType; 8699 static if (is(ReturnType!dg == void)) enum RetBool = false; else enum RetBool = true; 8700 8701 enum NVGcodepointType : int { 8702 Space, 8703 NewLine, 8704 Char, 8705 } 8706 8707 NVGstate* state = nvg__getState(ctx); 8708 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8709 float invscale = 1.0f/scale; 8710 FONSTextIter!T iter, prevIter; 8711 FONSQuad q; 8712 int nrows = 0; 8713 float rowStartX = 0; 8714 float rowWidth = 0; 8715 float rowMinX = 0; 8716 float rowMaxX = 0; 8717 int rowStart = 0; 8718 int rowEnd = 0; 8719 int wordStart = 0; 8720 float wordStartX = 0; 8721 float wordMinX = 0; 8722 int breakEnd = 0; 8723 float breakWidth = 0; 8724 float breakMaxX = 0; 8725 int type = NVGcodepointType.Space, ptype = NVGcodepointType.Space; 8726 uint pcodepoint = 0; 8727 8728 if (state.fontId == FONS_INVALID) return 0; 8729 if (str.length == 0 || dg is null) return 0; 8730 8731 ctx.fs.size = state.fontSize*scale; 8732 ctx.fs.spacing = state.letterSpacing*scale; 8733 ctx.fs.blur = state.fontBlur*scale; 8734 ctx.fs.textAlign = state.textAlign; 8735 ctx.fs.fontId = state.fontId; 8736 8737 breakRowWidth *= scale; 8738 8739 enum Phase { 8740 Normal, // searching for breaking point 8741 SkipBlanks, // skip leading blanks 8742 } 8743 Phase phase = Phase.SkipBlanks; // don't skip blanks on first line 8744 8745 if (!iter.setup(ctx.fs, 0, 0, str, FONSBitmapFlag.Optional)) return 0; 8746 prevIter = iter; 8747 while (iter.next(q)) { 8748 if (iter.prevGlyphIndex < 0) { // can not retrieve glyph? 8749 if (!nvg__allocTextAtlas(ctx)) break; // no memory 8750 iter = prevIter; 8751 iter.next(q); // try again 8752 if (iter.prevGlyphIndex < 0) { 8753 // still can not find glyph, try replacement 8754 iter = prevIter; 8755 if (!iter.getDummyChar(q)) break; 8756 } 8757 } 8758 prevIter = iter; 8759 switch (iter.codepoint) { 8760 case 9: // \t 8761 case 11: // \v 8762 case 12: // \f 8763 case 32: // space 8764 case 0x00a0: // NBSP 8765 type = NVGcodepointType.Space; 8766 break; 8767 case 10: // \n 8768 type = (pcodepoint == 13 ? NVGcodepointType.Space : NVGcodepointType.NewLine); 8769 break; 8770 case 13: // \r 8771 type = (pcodepoint == 10 ? NVGcodepointType.Space : NVGcodepointType.NewLine); 8772 break; 8773 case 0x0085: // NEL 8774 case 0x2028: // Line Separator 8775 case 0x2029: // Paragraph Separator 8776 type = NVGcodepointType.NewLine; 8777 break; 8778 default: 8779 type = NVGcodepointType.Char; 8780 break; 8781 } 8782 if (phase == Phase.SkipBlanks) { 8783 // fix row start 8784 rowStart = cast(int)(iter.stringp-str.ptr); 8785 rowEnd = rowStart; 8786 rowStartX = iter.x; 8787 rowWidth = iter.nextx-rowStartX; // q.x1-rowStartX; 8788 rowMinX = q.x0-rowStartX; 8789 rowMaxX = q.x1-rowStartX; 8790 wordStart = rowStart; 8791 wordStartX = iter.x; 8792 wordMinX = q.x0-rowStartX; 8793 breakEnd = rowStart; 8794 breakWidth = 0.0; 8795 breakMaxX = 0.0; 8796 if (type == NVGcodepointType.Space) continue; 8797 phase = Phase.Normal; 8798 } 8799 8800 if (type == NVGcodepointType.NewLine) { 8801 // always handle new lines 8802 NVGTextRow!T row; 8803 row..string = str; 8804 row.start = rowStart; 8805 row.end = rowEnd; 8806 row.width = rowWidth*invscale; 8807 row.minx = rowMinX*invscale; 8808 row.maxx = rowMaxX*invscale; 8809 ++nrows; 8810 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8811 phase = Phase.SkipBlanks; 8812 } else { 8813 float nextWidth = iter.nextx-rowStartX; 8814 // track last non-white space character 8815 if (type == NVGcodepointType.Char) { 8816 rowEnd = cast(int)(iter.nextp-str.ptr); 8817 rowWidth = iter.nextx-rowStartX; 8818 rowMaxX = q.x1-rowStartX; 8819 } 8820 // track last end of a word 8821 if (ptype == NVGcodepointType.Char && type == NVGcodepointType.Space) { 8822 breakEnd = cast(int)(iter.stringp-str.ptr); 8823 breakWidth = rowWidth; 8824 breakMaxX = rowMaxX; 8825 } 8826 // track last beginning of a word 8827 if (ptype == NVGcodepointType.Space && type == NVGcodepointType.Char) { 8828 wordStart = cast(int)(iter.stringp-str.ptr); 8829 wordStartX = iter.x; 8830 wordMinX = q.x0-rowStartX; 8831 } 8832 // break to new line when a character is beyond break width 8833 if (type == NVGcodepointType.Char && nextWidth > breakRowWidth) { 8834 // the run length is too long, need to break to new line 8835 NVGTextRow!T row; 8836 row..string = str; 8837 if (breakEnd == rowStart) { 8838 // the current word is longer than the row length, just break it from here 8839 row.start = rowStart; 8840 row.end = cast(int)(iter.stringp-str.ptr); 8841 row.width = rowWidth*invscale; 8842 row.minx = rowMinX*invscale; 8843 row.maxx = rowMaxX*invscale; 8844 ++nrows; 8845 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8846 rowStartX = iter.x; 8847 rowStart = cast(int)(iter.stringp-str.ptr); 8848 rowEnd = cast(int)(iter.nextp-str.ptr); 8849 rowWidth = iter.nextx-rowStartX; 8850 rowMinX = q.x0-rowStartX; 8851 rowMaxX = q.x1-rowStartX; 8852 wordStart = rowStart; 8853 wordStartX = iter.x; 8854 wordMinX = q.x0-rowStartX; 8855 } else { 8856 // break the line from the end of the last word, and start new line from the beginning of the new 8857 //{ import core.stdc.stdio : printf; printf("rowStart=%u; rowEnd=%u; breakEnd=%u; len=%u\n", rowStart, rowEnd, breakEnd, cast(uint)str.length); } 8858 row.start = rowStart; 8859 row.end = breakEnd; 8860 row.width = breakWidth*invscale; 8861 row.minx = rowMinX*invscale; 8862 row.maxx = breakMaxX*invscale; 8863 ++nrows; 8864 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8865 rowStartX = wordStartX; 8866 rowStart = wordStart; 8867 rowEnd = cast(int)(iter.nextp-str.ptr); 8868 rowWidth = iter.nextx-rowStartX; 8869 rowMinX = wordMinX; 8870 rowMaxX = q.x1-rowStartX; 8871 // no change to the word start 8872 } 8873 // set null break point 8874 breakEnd = rowStart; 8875 breakWidth = 0.0; 8876 breakMaxX = 0.0; 8877 } 8878 } 8879 8880 pcodepoint = iter.codepoint; 8881 ptype = type; 8882 } 8883 8884 // break the line from the end of the last word, and start new line from the beginning of the new 8885 if (phase != Phase.SkipBlanks && rowStart < str.length) { 8886 //{ import core.stdc.stdio : printf; printf(" rowStart=%u; len=%u\n", rowStart, cast(uint)str.length); } 8887 NVGTextRow!T row; 8888 row..string = str; 8889 row.start = rowStart; 8890 row.end = cast(int)str.length; 8891 row.width = rowWidth*invscale; 8892 row.minx = rowMinX*invscale; 8893 row.maxx = rowMaxX*invscale; 8894 ++nrows; 8895 static if (RetBool) { if (!dg(row)) return nrows; } else dg(row); 8896 } 8897 8898 return nrows; 8899 } 8900 8901 /** Returns iterator which you can use to calculate text bounds and advancement. 8902 * This is usable when you need to do some text layouting with wrapping, to avoid 8903 * guesswork ("will advancement for this space stay the same?"), and Schlemiel's 8904 * algorithm. Note that you can copy the returned struct to save iterator state. 8905 * 8906 * You can check if iterator is valid with [valid] property, put new chars with 8907 * [put] method, get current advance with [advance] property, and current 8908 * bounds with `getBounds(ref float[4] bounds)` method. 8909 * 8910 * $(WARNING Don't change font parameters while iterating! Or use [restoreFont] method.) 8911 * 8912 * Group: text_api 8913 */ 8914 public struct TextBoundsIterator { 8915 private: 8916 NVGContext ctx; 8917 FONSTextBoundsIterator fsiter; // fontstash iterator 8918 float scale, invscale, xscaled, yscaled; 8919 // font settings 8920 float fsSize, fsSpacing, fsBlur; 8921 int fsFontId; 8922 NVGTextAlign fsAlign; 8923 8924 public: 8925 /// Setups iteration. Takes current font parameters from the given NanoVega context. 8926 this (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc { reset(actx, ax, ay); } 8927 8928 /// Resets iteration. Takes current font parameters from the given NanoVega context. 8929 void reset (NVGContext actx, float ax=0, float ay=0) nothrow @trusted @nogc { 8930 fsiter = fsiter.init; 8931 this = this.init; 8932 if (actx is null) return; 8933 NVGstate* state = nvg__getState(actx); 8934 if (state is null) return; 8935 if (state.fontId == FONS_INVALID) { ctx = null; return; } 8936 8937 ctx = actx; 8938 scale = nvg__getFontScale(state)*ctx.devicePxRatio; 8939 invscale = 1.0f/scale; 8940 8941 fsSize = state.fontSize*scale; 8942 fsSpacing = state.letterSpacing*scale; 8943 fsBlur = state.fontBlur*scale; 8944 fsAlign = state.textAlign; 8945 fsFontId = state.fontId; 8946 restoreFont(); 8947 8948 xscaled = ax*scale; 8949 yscaled = ay*scale; 8950 fsiter.reset(ctx.fs, xscaled, yscaled); 8951 } 8952 8953 /// Restart iteration. Will not restore font. 8954 void restart () nothrow @trusted @nogc { 8955 if (ctx !is null) fsiter.reset(ctx.fs, xscaled, yscaled); 8956 } 8957 8958 /// Restore font settings for the context. 8959 void restoreFont () nothrow @trusted @nogc { 8960 if (ctx !is null) { 8961 ctx.fs.size = fsSize; 8962 ctx.fs.spacing = fsSpacing; 8963 ctx.fs.blur = fsBlur; 8964 ctx.fs.textAlign = fsAlign; 8965 ctx.fs.fontId = fsFontId; 8966 } 8967 } 8968 8969 /// Is this iterator valid? 8970 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null); } 8971 8972 /// Add chars. 8973 void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) { pragma(inline, true); if (ctx !is null) fsiter.put(str[]); } 8974 8975 /// Returns current advance 8976 @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (ctx !is null ? fsiter.advance*invscale : 0); } 8977 8978 /// Returns current text bounds. 8979 void getBounds (ref float[4] bounds) nothrow @trusted @nogc { 8980 if (ctx !is null) { 8981 fsiter.getBounds(bounds); 8982 ctx.fs.getLineBounds(yscaled, &bounds[1], &bounds[3]); 8983 bounds[0] *= invscale; 8984 bounds[1] *= invscale; 8985 bounds[2] *= invscale; 8986 bounds[3] *= invscale; 8987 } else { 8988 bounds[] = 0; 8989 } 8990 } 8991 8992 /// Returns current horizontal text bounds. 8993 void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc { 8994 if (ctx !is null) { 8995 fsiter.getHBounds(xmin, xmax); 8996 xmin *= invscale; 8997 xmax *= invscale; 8998 } 8999 } 9000 9001 /// Returns current vertical text bounds. 9002 void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc { 9003 if (ctx !is null) { 9004 //fsiter.getVBounds(ymin, ymax); 9005 ctx.fs.getLineBounds(yscaled, &ymin, &ymax); 9006 ymin *= invscale; 9007 ymax *= invscale; 9008 } 9009 } 9010 } 9011 9012 /// Returns font line height (without line spacing), measured in local coordinate space. 9013 /// Group: text_api 9014 public float textFontHeight (NVGContext ctx) nothrow @trusted @nogc { 9015 float res = void; 9016 ctx.textMetrics(null, null, &res); 9017 return res; 9018 } 9019 9020 /// Returns font ascender (positive), measured in local coordinate space. 9021 /// Group: text_api 9022 public float textFontAscender (NVGContext ctx) nothrow @trusted @nogc { 9023 float res = void; 9024 ctx.textMetrics(&res, null, null); 9025 return res; 9026 } 9027 9028 /// Returns font descender (negative), measured in local coordinate space. 9029 /// Group: text_api 9030 public float textFontDescender (NVGContext ctx) nothrow @trusted @nogc { 9031 float res = void; 9032 ctx.textMetrics(null, &res, null); 9033 return res; 9034 } 9035 9036 /** Measures the specified text string. Returns horizontal and vertical sizes of the measured text. 9037 * Measured values are returned in local coordinate space. 9038 * 9039 * Group: text_api 9040 */ 9041 public void textExtents(T) (NVGContext ctx, const(T)[] str, float *w, float *h) nothrow @trusted @nogc if (isAnyCharType!T) { 9042 float[4] bnd = void; 9043 ctx.textBounds(0, 0, str, bnd[]); 9044 if (!ctx.fs.getFontAA(nvg__getState(ctx).fontId)) { 9045 if (w !is null) *w = nvg__lrintf(bnd.ptr[2]-bnd.ptr[0]); 9046 if (h !is null) *h = nvg__lrintf(bnd.ptr[3]-bnd.ptr[1]); 9047 } else { 9048 if (w !is null) *w = bnd.ptr[2]-bnd.ptr[0]; 9049 if (h !is null) *h = bnd.ptr[3]-bnd.ptr[1]; 9050 } 9051 } 9052 9053 /** Measures the specified text string. Returns horizontal size of the measured text. 9054 * Measured values are returned in local coordinate space. 9055 * 9056 * Group: text_api 9057 */ 9058 public float textWidth(T) (NVGContext ctx, const(T)[] str) nothrow @trusted @nogc if (isAnyCharType!T) { 9059 float w = void; 9060 ctx.textExtents(str, &w, null); 9061 return w; 9062 } 9063 9064 /** Measures the specified text string. Parameter bounds should be a float[4], 9065 * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax] 9066 * Returns the horizontal advance of the measured text (i.e. where the next character should drawn). 9067 * Measured values are returned in local coordinate space. 9068 * 9069 * Group: text_api 9070 */ 9071 public float textBounds(T) (NVGContext ctx, float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc 9072 if (isAnyCharType!T) 9073 { 9074 NVGstate* state = nvg__getState(ctx); 9075 9076 if (state.fontId == FONS_INVALID) { 9077 bounds[] = 0; 9078 return 0; 9079 } 9080 9081 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 9082 ctx.fs.size = state.fontSize*scale; 9083 ctx.fs.spacing = state.letterSpacing*scale; 9084 ctx.fs.blur = state.fontBlur*scale; 9085 ctx.fs.textAlign = state.textAlign; 9086 ctx.fs.fontId = state.fontId; 9087 9088 float[4] b = void; 9089 immutable float width = ctx.fs.getTextBounds(x*scale, y*scale, str, b[]); 9090 immutable float invscale = 1.0f/scale; 9091 if (bounds.length) { 9092 // use line bounds for height 9093 ctx.fs.getLineBounds(y*scale, b.ptr+1, b.ptr+3); 9094 if (bounds.length > 0) bounds.ptr[0] = b.ptr[0]*invscale; 9095 if (bounds.length > 1) bounds.ptr[1] = b.ptr[1]*invscale; 9096 if (bounds.length > 2) bounds.ptr[2] = b.ptr[2]*invscale; 9097 if (bounds.length > 3) bounds.ptr[3] = b.ptr[3]*invscale; 9098 } 9099 return width*invscale; 9100 } 9101 9102 /// Ditto. 9103 public void textBoxBounds(T) (NVGContext ctx, float x, float y, float breakRowWidth, const(T)[] str, float[] bounds) if (isAnyCharType!T) { 9104 NVGstate* state = nvg__getState(ctx); 9105 NVGTextRow!T[2] rows; 9106 float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 9107 float invscale = 1.0f/scale; 9108 float lineh = 0, rminy = 0, rmaxy = 0; 9109 float minx, miny, maxx, maxy; 9110 9111 if (state.fontId == FONS_INVALID) { 9112 bounds[] = 0; 9113 return; 9114 } 9115 9116 auto oldAlign = state.textAlign; 9117 scope(exit) state.textAlign = oldAlign; 9118 auto halign = state.textAlign.horizontal; 9119 9120 ctx.textMetrics(null, null, &lineh); 9121 state.textAlign.horizontal = NVGTextAlign.H.Left; 9122 9123 minx = maxx = x; 9124 miny = maxy = y; 9125 9126 ctx.fs.size = state.fontSize*scale; 9127 ctx.fs.spacing = state.letterSpacing*scale; 9128 ctx.fs.blur = state.fontBlur*scale; 9129 ctx.fs.textAlign = state.textAlign; 9130 ctx.fs.fontId = state.fontId; 9131 ctx.fs.getLineBounds(0, &rminy, &rmaxy); 9132 rminy *= invscale; 9133 rmaxy *= invscale; 9134 9135 for (;;) { 9136 auto rres = ctx.textBreakLines(str, breakRowWidth, rows[]); 9137 if (rres.length == 0) break; 9138 foreach (ref row; rres) { 9139 float rminx, rmaxx, dx = 0; 9140 // horizontal bounds 9141 final switch (halign) { 9142 case NVGTextAlign.H.Left: dx = 0; break; 9143 case NVGTextAlign.H.Center: dx = breakRowWidth*0.5f-row.width*0.5f; break; 9144 case NVGTextAlign.H.Right: dx = breakRowWidth-row.width; break; 9145 } 9146 rminx = x+row.minx+dx; 9147 rmaxx = x+row.maxx+dx; 9148 minx = nvg__min(minx, rminx); 9149 maxx = nvg__max(maxx, rmaxx); 9150 // vertical bounds 9151 miny = nvg__min(miny, y+rminy); 9152 maxy = nvg__max(maxy, y+rmaxy); 9153 y += lineh*state.lineHeight; 9154 } 9155 str = rres[$-1].rest; 9156 } 9157 9158 if (bounds.length) { 9159 if (bounds.length > 0) bounds.ptr[0] = minx; 9160 if (bounds.length > 1) bounds.ptr[1] = miny; 9161 if (bounds.length > 2) bounds.ptr[2] = maxx; 9162 if (bounds.length > 3) bounds.ptr[3] = maxy; 9163 } 9164 } 9165 9166 /// Returns the vertical metrics based on the current text style. Measured values are returned in local coordinate space. 9167 /// Group: text_api 9168 public void textMetrics (NVGContext ctx, float* ascender, float* descender, float* lineh) nothrow @trusted @nogc { 9169 NVGstate* state = nvg__getState(ctx); 9170 9171 if (state.fontId == FONS_INVALID) { 9172 if (ascender !is null) *ascender *= 0; 9173 if (descender !is null) *descender *= 0; 9174 if (lineh !is null) *lineh *= 0; 9175 return; 9176 } 9177 9178 immutable float scale = nvg__getFontScale(state)*ctx.devicePxRatio; 9179 immutable float invscale = 1.0f/scale; 9180 9181 ctx.fs.size = state.fontSize*scale; 9182 ctx.fs.spacing = state.letterSpacing*scale; 9183 ctx.fs.blur = state.fontBlur*scale; 9184 ctx.fs.textAlign = state.textAlign; 9185 ctx.fs.fontId = state.fontId; 9186 9187 ctx.fs.getVertMetrics(ascender, descender, lineh); 9188 if (ascender !is null) *ascender *= invscale; 9189 if (descender !is null) *descender *= invscale; 9190 if (lineh !is null) *lineh *= invscale; 9191 } 9192 9193 9194 // ////////////////////////////////////////////////////////////////////////// // 9195 // fontstash 9196 // ////////////////////////////////////////////////////////////////////////// // 9197 import core.stdc.stdlib : malloc, realloc, free; 9198 import core.stdc..string : memset, memcpy, strncpy, strcmp, strlen; 9199 import core.stdc.stdio : FILE, fopen, fclose, fseek, ftell, fread, SEEK_END, SEEK_SET; 9200 9201 public: 9202 // welcome to version hell! 9203 version(nanovg_force_stb_ttf) { 9204 } else { 9205 version(nanovg_force_detect) {} else version(nanovg_use_freetype) { version = nanovg_use_freetype_ii; } 9206 } 9207 version(nanovg_ignore_iv_stb_ttf) enum nanovg_ignore_iv_stb_ttf = true; else enum nanovg_ignore_iv_stb_ttf = false; 9208 //version(nanovg_ignore_mono); 9209 9210 version(nanovg_force_stb_ttf) { 9211 private enum NanoVegaForceFreeType = false; 9212 } else { 9213 version (nanovg_builtin_freetype_bindings) { 9214 version(Posix) { 9215 private enum NanoVegaForceFreeType = true; 9216 } else { 9217 private enum NanoVegaForceFreeType = false; 9218 } 9219 } else { 9220 version(Posix) { 9221 private enum NanoVegaForceFreeType = true; 9222 } else { 9223 private enum NanoVegaForceFreeType = false; 9224 } 9225 } 9226 } 9227 9228 version(nanovg_use_freetype_ii) { 9229 enum NanoVegaIsUsingSTBTTF = false; 9230 //pragma(msg, "iv.freetype: forced"); 9231 } else { 9232 static if (NanoVegaForceFreeType) { 9233 enum NanoVegaIsUsingSTBTTF = false; 9234 } else { 9235 static if (!nanovg_ignore_iv_stb_ttf && __traits(compiles, { import iv.stb.ttf; })) { 9236 import iv.stb.ttf; 9237 enum NanoVegaIsUsingSTBTTF = true; 9238 version(nanovg_report_stb_ttf) pragma(msg, "iv.stb.ttf"); 9239 } else static if (__traits(compiles, { import arsd.ttf; })) { 9240 import arsd.ttf; 9241 enum NanoVegaIsUsingSTBTTF = true; 9242 version(nanovg_report_stb_ttf) pragma(msg, "arsd.ttf"); 9243 } else static if (__traits(compiles, { import stb_truetype; })) { 9244 import stb_truetype; 9245 enum NanoVegaIsUsingSTBTTF = true; 9246 version(nanovg_report_stb_ttf) pragma(msg, "stb_truetype"); 9247 } else static if (__traits(compiles, { import iv.freetype; })) { 9248 version (nanovg_builtin_freetype_bindings) { 9249 enum NanoVegaIsUsingSTBTTF = false; 9250 version = nanovg_builtin_freetype_bindings; 9251 } else { 9252 import iv.freetype; 9253 enum NanoVegaIsUsingSTBTTF = false; 9254 } 9255 version(nanovg_report_stb_ttf) pragma(msg, "freetype"); 9256 } else { 9257 static assert(0, "no stb_ttf/iv.freetype found!"); 9258 } 9259 } 9260 } 9261 9262 9263 // ////////////////////////////////////////////////////////////////////////// // 9264 //version = nanovg_ft_mono; 9265 9266 /// Invald font id. 9267 /// Group: font_stash 9268 public enum FONS_INVALID = -1; 9269 9270 public enum FONSBitmapFlag : uint { 9271 Required = 0, 9272 Optional = 1, 9273 } 9274 9275 public enum FONSError : int { 9276 NoError = 0, 9277 AtlasFull = 1, // Font atlas is full. 9278 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. 9279 StatesOverflow = 3, // Calls to fonsPushState has created too large stack, if you need deep state stack bump up FONS_MAX_STATES. 9280 StatesUnderflow = 4, // Trying to pop too many states fonsPopState(). 9281 } 9282 9283 /// Initial parameters for new FontStash. 9284 /// Group: font_stash 9285 public struct FONSParams { 9286 enum Flag : uint { 9287 ZeroTopLeft = 0U, // default 9288 ZeroBottomLeft = 1U, 9289 } 9290 int width, height; 9291 Flag flags = Flag.ZeroTopLeft; 9292 void* userPtr; 9293 bool function (void* uptr, int width, int height) nothrow @trusted @nogc renderCreate; 9294 int function (void* uptr, int width, int height) nothrow @trusted @nogc renderResize; 9295 void function (void* uptr, int* rect, const(ubyte)* data) nothrow @trusted @nogc renderUpdate; 9296 void function (void* uptr) nothrow @trusted @nogc renderDelete; 9297 @property bool isZeroTopLeft () const pure nothrow @trusted @nogc { pragma(inline, true); return ((flags&Flag.ZeroBottomLeft) == 0); } 9298 } 9299 9300 //TODO: document this 9301 public struct FONSQuad { 9302 float x0=0, y0=0, s0=0, t0=0; 9303 float x1=0, y1=0, s1=0, t1=0; 9304 } 9305 9306 //TODO: document this 9307 public struct FONSTextIter(CT) if (isAnyCharType!CT) { 9308 alias CharType = CT; 9309 float x=0, y=0, nextx=0, nexty=0, scale=0, spacing=0; 9310 uint codepoint; 9311 short isize, iblur; 9312 FONSContext stash; 9313 FONSfont* font; 9314 int prevGlyphIndex; 9315 const(CT)* s; // string 9316 const(CT)* n; // next 9317 const(CT)* e; // end 9318 FONSBitmapFlag bitmapOption; 9319 static if (is(CT == char)) { 9320 uint utf8state; 9321 } 9322 9323 this (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc { setup(astash, ax, ay, astr, abitmapOption); } 9324 ~this () nothrow @trusted @nogc { pragma(inline, true); static if (is(CT == char)) utf8state = 0; s = n = e = null; } 9325 9326 @property const(CT)* stringp () const pure nothrow @trusted @nogc { pragma(inline, true); return s; } 9327 @property const(CT)* nextp () const pure nothrow @trusted @nogc { pragma(inline, true); return n; } 9328 @property const(CT)* endp () const pure nothrow @trusted @nogc { pragma(inline, true); return e; } 9329 9330 bool setup (FONSContext astash, float ax, float ay, const(CharType)[] astr, FONSBitmapFlag abitmapOption) nothrow @trusted @nogc { 9331 import core.stdc..string : memset; 9332 9333 memset(&this, 0, this.sizeof); 9334 if (astash is null) return false; 9335 9336 FONSstate* state = astash.getState; 9337 9338 if (state.font < 0 || state.font >= astash.nfonts) return false; 9339 font = astash.fonts[state.font]; 9340 if (font is null || font.fdata is null) return false; 9341 9342 isize = cast(short)(state.size*10.0f); 9343 iblur = cast(short)state.blur; 9344 scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f); 9345 9346 // align horizontally 9347 if (state.talign.left) { 9348 // empty 9349 } else if (state.talign.right) { 9350 immutable float width = astash.getTextBounds(ax, ay, astr, null); 9351 ax -= width; 9352 } else if (state.talign.center) { 9353 immutable float width = astash.getTextBounds(ax, ay, astr, null); 9354 ax -= width*0.5f; 9355 } 9356 9357 // align vertically 9358 ay += astash.getVertAlign(font, state.talign, isize); 9359 9360 x = nextx = ax; 9361 y = nexty = ay; 9362 spacing = state.spacing; 9363 9364 if (astr.ptr is null) { 9365 static if (is(CharType == char)) astr = ""; 9366 else static if (is(CharType == wchar)) astr = ""w; 9367 else static if (is(CharType == dchar)) astr = ""d; 9368 else static assert(0, "wtf?!"); 9369 } 9370 s = astr.ptr; 9371 n = astr.ptr; 9372 e = astr.ptr+astr.length; 9373 9374 codepoint = 0; 9375 prevGlyphIndex = -1; 9376 bitmapOption = abitmapOption; 9377 stash = astash; 9378 9379 return true; 9380 } 9381 9382 bool getDummyChar (ref FONSQuad quad) nothrow @trusted @nogc { 9383 if (stash is null || font is null) return false; 9384 // get glyph and quad 9385 x = nextx; 9386 y = nexty; 9387 FONSglyph* glyph = stash.getGlyph(font, 0xFFFD, isize, iblur, bitmapOption); 9388 if (glyph !is null) { 9389 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad); 9390 prevGlyphIndex = glyph.index; 9391 return true; 9392 } else { 9393 prevGlyphIndex = -1; 9394 return false; 9395 } 9396 } 9397 9398 bool next (ref FONSQuad quad) nothrow @trusted @nogc { 9399 if (stash is null || font is null) return false; 9400 FONSglyph* glyph = null; 9401 static if (is(CharType == char)) { 9402 const(char)* str = this.n; 9403 this.s = this.n; 9404 if (str is this.e) return false; 9405 const(char)* e = this.e; 9406 for (; str !is e; ++str) { 9407 /*if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue;*/ 9408 mixin(DecUtfMixin!("this.utf8state", "this.codepoint", "*cast(const(ubyte)*)str")); 9409 if (utf8state) continue; 9410 ++str; // 'cause we'll break anyway 9411 // get glyph and quad 9412 x = nextx; 9413 y = nexty; 9414 glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption); 9415 if (glyph !is null) { 9416 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad); 9417 prevGlyphIndex = glyph.index; 9418 } else { 9419 prevGlyphIndex = -1; 9420 } 9421 break; 9422 } 9423 this.n = str; 9424 } else { 9425 const(CharType)* str = this.n; 9426 this.s = this.n; 9427 if (str is this.e) return false; 9428 codepoint = cast(uint)(*str++); 9429 if (codepoint > dchar.max) codepoint = 0xFFFD; 9430 // get glyph and quad 9431 x = nextx; 9432 y = nexty; 9433 glyph = stash.getGlyph(font, codepoint, isize, iblur, bitmapOption); 9434 if (glyph !is null) { 9435 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, spacing, &nextx, &nexty, &quad); 9436 prevGlyphIndex = glyph.index; 9437 } else { 9438 prevGlyphIndex = -1; 9439 } 9440 this.n = str; 9441 } 9442 return true; 9443 } 9444 } 9445 9446 9447 // ////////////////////////////////////////////////////////////////////////// // 9448 //static if (!HasAST) version = nanovg_use_freetype_ii_x; 9449 9450 /*version(nanovg_use_freetype_ii_x)*/ static if (!NanoVegaIsUsingSTBTTF) { 9451 version(nanovg_builtin_freetype_bindings) { 9452 pragma(lib, "freetype"); 9453 private extern(C) nothrow @trusted @nogc { 9454 private import core.stdc.config : c_long, c_ulong; 9455 alias FT_Pos = c_long; 9456 // config/ftconfig.h 9457 alias FT_Int16 = short; 9458 alias FT_UInt16 = ushort; 9459 alias FT_Int32 = int; 9460 alias FT_UInt32 = uint; 9461 alias FT_Fast = int; 9462 alias FT_UFast = uint; 9463 alias FT_Int64 = long; 9464 alias FT_Uint64 = ulong; 9465 // fttypes.h 9466 alias FT_Bool = ubyte; 9467 alias FT_FWord = short; 9468 alias FT_UFWord = ushort; 9469 alias FT_Char = char; 9470 alias FT_Byte = ubyte; 9471 alias FT_Bytes = FT_Byte*; 9472 alias FT_Tag = FT_UInt32; 9473 alias FT_String = char; 9474 alias FT_Short = short; 9475 alias FT_UShort = ushort; 9476 alias FT_Int = int; 9477 alias FT_UInt = uint; 9478 alias FT_Long = c_long; 9479 alias FT_ULong = c_ulong; 9480 alias FT_F2Dot14 = short; 9481 alias FT_F26Dot6 = c_long; 9482 alias FT_Fixed = c_long; 9483 alias FT_Error = int; 9484 alias FT_Pointer = void*; 9485 alias FT_Offset = usize; 9486 alias FT_PtrDist = ptrdiff_t; 9487 9488 struct FT_UnitVector { 9489 FT_F2Dot14 x; 9490 FT_F2Dot14 y; 9491 } 9492 9493 struct FT_Matrix { 9494 FT_Fixed xx, xy; 9495 FT_Fixed yx, yy; 9496 } 9497 9498 struct FT_Data { 9499 const(FT_Byte)* pointer; 9500 FT_Int length; 9501 } 9502 alias FT_Face = FT_FaceRec*; 9503 struct FT_FaceRec { 9504 FT_Long num_faces; 9505 FT_Long face_index; 9506 FT_Long face_flags; 9507 FT_Long style_flags; 9508 FT_Long num_glyphs; 9509 FT_String* family_name; 9510 FT_String* style_name; 9511 FT_Int num_fixed_sizes; 9512 FT_Bitmap_Size* available_sizes; 9513 FT_Int num_charmaps; 9514 FT_CharMap* charmaps; 9515 FT_Generic generic; 9516 FT_BBox bbox; 9517 FT_UShort units_per_EM; 9518 FT_Short ascender; 9519 FT_Short descender; 9520 FT_Short height; 9521 FT_Short max_advance_width; 9522 FT_Short max_advance_height; 9523 FT_Short underline_position; 9524 FT_Short underline_thickness; 9525 FT_GlyphSlot glyph; 9526 FT_Size size; 9527 FT_CharMap charmap; 9528 FT_Driver driver; 9529 FT_Memory memory; 9530 FT_Stream stream; 9531 FT_ListRec sizes_list; 9532 FT_Generic autohint; 9533 void* extensions; 9534 FT_Face_Internal internal; 9535 } 9536 struct FT_Bitmap_Size { 9537 FT_Short height; 9538 FT_Short width; 9539 FT_Pos size; 9540 FT_Pos x_ppem; 9541 FT_Pos y_ppem; 9542 } 9543 alias FT_CharMap = FT_CharMapRec*; 9544 struct FT_CharMapRec { 9545 FT_Face face; 9546 FT_Encoding encoding; 9547 FT_UShort platform_id; 9548 FT_UShort encoding_id; 9549 } 9550 extern(C) nothrow @nogc { alias FT_Generic_Finalizer = void function (void* object); } 9551 struct FT_Generic { 9552 void* data; 9553 FT_Generic_Finalizer finalizer; 9554 } 9555 struct FT_Vector { 9556 FT_Pos x; 9557 FT_Pos y; 9558 } 9559 struct FT_BBox { 9560 FT_Pos xMin, yMin; 9561 FT_Pos xMax, yMax; 9562 } 9563 alias FT_Pixel_Mode = int; 9564 enum { 9565 FT_PIXEL_MODE_NONE = 0, 9566 FT_PIXEL_MODE_MONO, 9567 FT_PIXEL_MODE_GRAY, 9568 FT_PIXEL_MODE_GRAY2, 9569 FT_PIXEL_MODE_GRAY4, 9570 FT_PIXEL_MODE_LCD, 9571 FT_PIXEL_MODE_LCD_V, 9572 FT_PIXEL_MODE_MAX 9573 } 9574 struct FT_Bitmap { 9575 uint rows; 9576 uint width; 9577 int pitch; 9578 ubyte* buffer; 9579 ushort num_grays; 9580 ubyte pixel_mode; 9581 ubyte palette_mode; 9582 void* palette; 9583 } 9584 struct FT_Outline { 9585 short n_contours; 9586 short n_points; 9587 FT_Vector* points; 9588 byte* tags; 9589 short* contours; 9590 int flags; 9591 } 9592 alias FT_GlyphSlot = FT_GlyphSlotRec*; 9593 struct FT_GlyphSlotRec { 9594 FT_Library library; 9595 FT_Face face; 9596 FT_GlyphSlot next; 9597 FT_UInt reserved; 9598 FT_Generic generic; 9599 FT_Glyph_Metrics metrics; 9600 FT_Fixed linearHoriAdvance; 9601 FT_Fixed linearVertAdvance; 9602 FT_Vector advance; 9603 FT_Glyph_Format format; 9604 FT_Bitmap bitmap; 9605 FT_Int bitmap_left; 9606 FT_Int bitmap_top; 9607 FT_Outline outline; 9608 FT_UInt num_subglyphs; 9609 FT_SubGlyph subglyphs; 9610 void* control_data; 9611 c_long control_len; 9612 FT_Pos lsb_delta; 9613 FT_Pos rsb_delta; 9614 void* other; 9615 FT_Slot_Internal internal; 9616 } 9617 alias FT_Size = FT_SizeRec*; 9618 struct FT_SizeRec { 9619 FT_Face face; 9620 FT_Generic generic; 9621 FT_Size_Metrics metrics; 9622 FT_Size_Internal internal; 9623 } 9624 alias FT_Encoding = FT_Tag; 9625 alias FT_Face_Internal = void*; 9626 alias FT_Driver = void*; 9627 alias FT_Memory = void*; 9628 alias FT_Stream = void*; 9629 alias FT_Library = void*; 9630 alias FT_SubGlyph = void*; 9631 alias FT_Slot_Internal = void*; 9632 alias FT_Size_Internal = void*; 9633 alias FT_ListNode = FT_ListNodeRec*; 9634 alias FT_List = FT_ListRec*; 9635 struct FT_ListNodeRec { 9636 FT_ListNode prev; 9637 FT_ListNode next; 9638 void* data; 9639 } 9640 struct FT_ListRec { 9641 FT_ListNode head; 9642 FT_ListNode tail; 9643 } 9644 struct FT_Glyph_Metrics { 9645 FT_Pos width; 9646 FT_Pos height; 9647 FT_Pos horiBearingX; 9648 FT_Pos horiBearingY; 9649 FT_Pos horiAdvance; 9650 FT_Pos vertBearingX; 9651 FT_Pos vertBearingY; 9652 FT_Pos vertAdvance; 9653 } 9654 alias FT_Glyph_Format = FT_Tag; 9655 FT_Tag FT_MAKE_TAG (char x1, char x2, char x3, char x4) pure nothrow @safe @nogc { 9656 pragma(inline, true); 9657 return cast(FT_UInt32)((x1<<24)|(x2<<16)|(x3<<8)|x4); 9658 } 9659 enum : FT_Tag { 9660 FT_GLYPH_FORMAT_NONE = 0, 9661 FT_GLYPH_FORMAT_COMPOSITE = FT_MAKE_TAG('c','o','m','p'), 9662 FT_GLYPH_FORMAT_BITMAP = FT_MAKE_TAG('b','i','t','s'), 9663 FT_GLYPH_FORMAT_OUTLINE = FT_MAKE_TAG('o','u','t','l'), 9664 FT_GLYPH_FORMAT_PLOTTER = FT_MAKE_TAG('p','l','o','t'), 9665 } 9666 struct FT_Size_Metrics { 9667 FT_UShort x_ppem; 9668 FT_UShort y_ppem; 9669 9670 FT_Fixed x_scale; 9671 FT_Fixed y_scale; 9672 9673 FT_Pos ascender; 9674 FT_Pos descender; 9675 FT_Pos height; 9676 FT_Pos max_advance; 9677 } 9678 enum FT_LOAD_DEFAULT = 0x0U; 9679 enum FT_LOAD_NO_SCALE = 1U<<0; 9680 enum FT_LOAD_NO_HINTING = 1U<<1; 9681 enum FT_LOAD_RENDER = 1U<<2; 9682 enum FT_LOAD_NO_BITMAP = 1U<<3; 9683 enum FT_LOAD_VERTICAL_LAYOUT = 1U<<4; 9684 enum FT_LOAD_FORCE_AUTOHINT = 1U<<5; 9685 enum FT_LOAD_CROP_BITMAP = 1U<<6; 9686 enum FT_LOAD_PEDANTIC = 1U<<7; 9687 enum FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH = 1U<<9; 9688 enum FT_LOAD_NO_RECURSE = 1U<<10; 9689 enum FT_LOAD_IGNORE_TRANSFORM = 1U<<11; 9690 enum FT_LOAD_MONOCHROME = 1U<<12; 9691 enum FT_LOAD_LINEAR_DESIGN = 1U<<13; 9692 enum FT_LOAD_NO_AUTOHINT = 1U<<15; 9693 enum FT_LOAD_COLOR = 1U<<20; 9694 enum FT_LOAD_COMPUTE_METRICS = 1U<<21; 9695 enum FT_FACE_FLAG_KERNING = 1U<<6; 9696 alias FT_Kerning_Mode = int; 9697 enum /*FT_Kerning_Mode*/ { 9698 FT_KERNING_DEFAULT = 0, 9699 FT_KERNING_UNFITTED, 9700 FT_KERNING_UNSCALED 9701 } 9702 extern(C) nothrow @nogc { 9703 alias FT_Outline_MoveToFunc = int function (const(FT_Vector)*, void*); 9704 alias FT_Outline_LineToFunc = int function (const(FT_Vector)*, void*); 9705 alias FT_Outline_ConicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, void*); 9706 alias FT_Outline_CubicToFunc = int function (const(FT_Vector)*, const(FT_Vector)*, const(FT_Vector)*, void*); 9707 } 9708 struct FT_Outline_Funcs { 9709 FT_Outline_MoveToFunc move_to; 9710 FT_Outline_LineToFunc line_to; 9711 FT_Outline_ConicToFunc conic_to; 9712 FT_Outline_CubicToFunc cubic_to; 9713 int shift; 9714 FT_Pos delta; 9715 } 9716 9717 FT_Error FT_Init_FreeType (FT_Library*); 9718 FT_Error FT_New_Memory_Face (FT_Library, const(FT_Byte)*, FT_Long, FT_Long, FT_Face*); 9719 FT_UInt FT_Get_Char_Index (FT_Face, FT_ULong); 9720 FT_Error FT_Set_Pixel_Sizes (FT_Face, FT_UInt, FT_UInt); 9721 FT_Error FT_Load_Glyph (FT_Face, FT_UInt, FT_Int32); 9722 FT_Error FT_Get_Advance (FT_Face, FT_UInt, FT_Int32, FT_Fixed*); 9723 FT_Error FT_Get_Kerning (FT_Face, FT_UInt, FT_UInt, FT_UInt, FT_Vector*); 9724 void FT_Outline_Get_CBox (const(FT_Outline)*, FT_BBox*); 9725 FT_Error FT_Outline_Decompose (FT_Outline*, const(FT_Outline_Funcs)*, void*); 9726 } 9727 } else version(bindbc) { 9728 import bindbc.freetype; 9729 alias FT_KERNING_DEFAULT = FT_Kerning_Mode.FT_KERNING_DEFAULT; 9730 alias FT_KERNING_UNFITTED = FT_Kerning_Mode.FT_KERNING_UNFITTED; 9731 alias FT_KERNING_UNSCALED = FT_Kerning_Mode.FT_KERNING_UNSCALED; 9732 } else { 9733 import iv.freetype; 9734 } 9735 9736 struct FONSttFontImpl { 9737 FT_Face font; 9738 bool mono; // no aa? 9739 } 9740 9741 __gshared FT_Library ftLibrary; 9742 9743 int fons__tt_init (FONSContext context) nothrow @trusted @nogc { 9744 FT_Error ftError; 9745 //FONS_NOTUSED(context); 9746 ftError = FT_Init_FreeType(&ftLibrary); 9747 return (ftError == 0); 9748 } 9749 9750 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc { 9751 font.mono = v; 9752 } 9753 9754 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc { 9755 return font.mono; 9756 } 9757 9758 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc { 9759 FT_Error ftError; 9760 //font.font.userdata = stash; 9761 ftError = FT_New_Memory_Face(ftLibrary, cast(const(FT_Byte)*)data, dataSize, 0, &font.font); 9762 return ftError == 0; 9763 } 9764 9765 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc { 9766 *ascent = font.font.ascender; 9767 *descent = font.font.descender; 9768 *lineGap = font.font.height-(*ascent - *descent); 9769 } 9770 9771 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc { 9772 return size/(font.font.ascender-font.font.descender); 9773 } 9774 9775 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc { 9776 return FT_Get_Char_Index(font.font, codepoint); 9777 } 9778 9779 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 { 9780 FT_Error ftError; 9781 FT_GlyphSlot ftGlyph; 9782 //version(nanovg_ignore_mono) enum exflags = 0; 9783 //else version(nanovg_ft_mono) enum exflags = FT_LOAD_MONOCHROME; else enum exflags = 0; 9784 uint exflags = (font.mono ? FT_LOAD_MONOCHROME : 0); 9785 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))); 9786 if (ftError) return 0; 9787 ftError = FT_Load_Glyph(font.font, glyph, FT_LOAD_RENDER|/*FT_LOAD_NO_AUTOHINT|*/exflags); 9788 if (ftError) return 0; 9789 ftError = FT_Get_Advance(font.font, glyph, FT_LOAD_NO_SCALE|/*FT_LOAD_NO_AUTOHINT|*/exflags, cast(FT_Fixed*)advance); 9790 if (ftError) return 0; 9791 ftGlyph = font.font.glyph; 9792 *lsb = cast(int)ftGlyph.metrics.horiBearingX; 9793 *x0 = ftGlyph.bitmap_left; 9794 *x1 = *x0+ftGlyph.bitmap.width; 9795 *y0 = -ftGlyph.bitmap_top; 9796 *y1 = *y0+ftGlyph.bitmap.rows; 9797 return 1; 9798 } 9799 9800 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc { 9801 FT_GlyphSlot ftGlyph = font.font.glyph; 9802 //FONS_NOTUSED(glyph); // glyph has already been loaded by fons__tt_buildGlyphBitmap 9803 //version(nanovg_ignore_mono) enum RenderAA = true; 9804 //else version(nanovg_ft_mono) enum RenderAA = false; 9805 //else enum RenderAA = true; 9806 if (font.mono) { 9807 auto src = ftGlyph.bitmap.buffer; 9808 auto dst = output; 9809 auto spt = ftGlyph.bitmap.pitch; 9810 if (spt < 0) spt = -spt; 9811 foreach (int y; 0..ftGlyph.bitmap.rows) { 9812 ubyte count = 0, b = 0; 9813 auto s = src; 9814 auto d = dst; 9815 foreach (int x; 0..ftGlyph.bitmap.width) { 9816 if (count-- == 0) { count = 7; b = *s++; } else b <<= 1; 9817 *d++ = (b&0x80 ? 255 : 0); 9818 } 9819 src += spt; 9820 dst += outStride; 9821 } 9822 } else { 9823 auto src = ftGlyph.bitmap.buffer; 9824 auto dst = output; 9825 auto spt = ftGlyph.bitmap.pitch; 9826 if (spt < 0) spt = -spt; 9827 foreach (int y; 0..ftGlyph.bitmap.rows) { 9828 import core.stdc..string : memcpy; 9829 //dst[0..ftGlyph.bitmap.width] = src[0..ftGlyph.bitmap.width]; 9830 memcpy(dst, src, ftGlyph.bitmap.width); 9831 src += spt; 9832 dst += outStride; 9833 } 9834 } 9835 } 9836 9837 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc { 9838 FT_Vector ftKerning; 9839 version(none) { 9840 // fitted kerning 9841 FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning); 9842 //{ import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d\n", glyph1, glyph2, ftKerning.x, ftKerning.y); } 9843 return cast(int)ftKerning.x; // round up and convert to integer 9844 } else { 9845 // unfitted kerning 9846 //FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNFITTED, &ftKerning); 9847 if (glyph1 <= 0 || glyph2 <= 0 || (font.font.face_flags&FT_FACE_FLAG_KERNING) == 0) return 0; 9848 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; 9849 if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning)) return 0; 9850 version(none) { 9851 if (ftKerning.x) { 9852 //{ import core.stdc.stdio : printf; printf("has kerning: %u\n", cast(uint)(font.font.face_flags&FT_FACE_FLAG_KERNING)); } 9853 { import core.stdc.stdio : printf; printf("kern for %u:%u: %d %d (size=%g)\n", glyph1, glyph2, ftKerning.x, ftKerning.y, cast(double)size); } 9854 } 9855 } 9856 version(none) { 9857 FT_Vector kk; 9858 if (FT_Get_Kerning(font.font, glyph1, glyph2, FT_KERNING_UNSCALED, &kk)) assert(0, "wtf?!"); 9859 auto kadvfrac = FT_MulFix(kk.x, font.font.size.metrics.x_scale); // 1/64 of pixel 9860 //return cast(int)((kadvfrac/*+(kadvfrac < 0 ? -32 : 32)*/)>>6); 9861 //assert(ftKerning.x == kadvfrac); 9862 if (ftKerning.x || kadvfrac) { 9863 { 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); } 9864 } 9865 //return cast(int)(kadvfrac+(kadvfrac < 0 ? -31 : 32)>>6); // round up and convert to integer 9866 return kadvfrac/64.0f; 9867 } 9868 //return cast(int)(ftKerning.x+(ftKerning.x < 0 ? -31 : 32)>>6); // round up and convert to integer 9869 return ftKerning.x/64.0f; 9870 } 9871 } 9872 9873 extern(C) nothrow @trusted @nogc { 9874 static struct OutlinerData { 9875 @disable this (this); 9876 void opAssign() (in auto ref OutlinerData a) { static assert(0, "no copies!"); } 9877 NVGContext vg; 9878 NVGPathOutline.DataStore* ol; 9879 FT_BBox outlineBBox; 9880 nothrow @trusted @nogc: 9881 static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; } 9882 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; } 9883 } 9884 9885 int fons__nvg__moveto_cb (const(FT_Vector)* to, void* user) { 9886 auto odata = cast(OutlinerData*)user; 9887 if (odata.vg !is null) odata.vg.moveTo(odata.transx(to.x), odata.transy(to.y)); 9888 if (odata.ol !is null) { 9889 odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo); 9890 odata.ol.putArgs(odata.transx(to.x)); 9891 odata.ol.putArgs(odata.transy(to.y)); 9892 } 9893 return 0; 9894 } 9895 9896 int fons__nvg__lineto_cb (const(FT_Vector)* to, void* user) { 9897 auto odata = cast(OutlinerData*)user; 9898 if (odata.vg !is null) odata.vg.lineTo(odata.transx(to.x), odata.transy(to.y)); 9899 if (odata.ol !is null) { 9900 odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo); 9901 odata.ol.putArgs(odata.transx(to.x)); 9902 odata.ol.putArgs(odata.transy(to.y)); 9903 } 9904 return 0; 9905 } 9906 9907 int fons__nvg__quadto_cb (const(FT_Vector)* c1, const(FT_Vector)* to, void* user) { 9908 auto odata = cast(OutlinerData*)user; 9909 if (odata.vg !is null) odata.vg.quadTo(odata.transx(c1.x), odata.transy(c1.y), odata.transx(to.x), odata.transy(to.y)); 9910 if (odata.ol !is null) { 9911 odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo); 9912 odata.ol.putArgs(odata.transx(c1.x)); 9913 odata.ol.putArgs(odata.transy(c1.y)); 9914 odata.ol.putArgs(odata.transx(to.x)); 9915 odata.ol.putArgs(odata.transy(to.y)); 9916 } 9917 return 0; 9918 } 9919 9920 int fons__nvg__cubicto_cb (const(FT_Vector)* c1, const(FT_Vector)* c2, const(FT_Vector)* to, void* user) { 9921 auto odata = cast(OutlinerData*)user; 9922 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)); 9923 if (odata.ol !is null) { 9924 odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo); 9925 odata.ol.putArgs(odata.transx(c1.x)); 9926 odata.ol.putArgs(odata.transy(c1.y)); 9927 odata.ol.putArgs(odata.transx(c2.x)); 9928 odata.ol.putArgs(odata.transy(c2.y)); 9929 odata.ol.putArgs(odata.transx(to.x)); 9930 odata.ol.putArgs(odata.transy(to.y)); 9931 } 9932 return 0; 9933 } 9934 } 9935 9936 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc { 9937 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 9938 9939 FT_Outline_Funcs funcs; 9940 funcs.move_to = &fons__nvg__moveto_cb; 9941 funcs.line_to = &fons__nvg__lineto_cb; 9942 funcs.conic_to = &fons__nvg__quadto_cb; 9943 funcs.cubic_to = &fons__nvg__cubicto_cb; 9944 9945 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE); 9946 if (err) { bounds[] = 0; return false; } 9947 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; } 9948 9949 FT_Outline outline = font.font.glyph.outline; 9950 9951 OutlinerData odata; 9952 odata.vg = vg; 9953 FT_Outline_Get_CBox(&outline, &odata.outlineBBox); 9954 9955 err = FT_Outline_Decompose(&outline, &funcs, &odata); 9956 if (err) { bounds[] = 0; return false; } 9957 if (bounds.length > 0) bounds.ptr[0] = odata.outlineBBox.xMin; 9958 if (bounds.length > 1) bounds.ptr[1] = -odata.outlineBBox.yMax; 9959 if (bounds.length > 2) bounds.ptr[2] = odata.outlineBBox.xMax; 9960 if (bounds.length > 3) bounds.ptr[3] = -odata.outlineBBox.yMin; 9961 return true; 9962 } 9963 9964 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc { 9965 FT_Outline_Funcs funcs; 9966 funcs.move_to = &fons__nvg__moveto_cb; 9967 funcs.line_to = &fons__nvg__lineto_cb; 9968 funcs.conic_to = &fons__nvg__quadto_cb; 9969 funcs.cubic_to = &fons__nvg__cubicto_cb; 9970 9971 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE); 9972 if (err) return false; 9973 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) return false; 9974 9975 FT_Outline outline = font.font.glyph.outline; 9976 9977 OutlinerData odata; 9978 odata.ol = ol; 9979 FT_Outline_Get_CBox(&outline, &odata.outlineBBox); 9980 9981 err = FT_Outline_Decompose(&outline, &funcs, &odata); 9982 if (err) return false; 9983 ol.bounds.ptr[0] = odata.outlineBBox.xMin; 9984 ol.bounds.ptr[1] = -odata.outlineBBox.yMax; 9985 ol.bounds.ptr[2] = odata.outlineBBox.xMax; 9986 ol.bounds.ptr[3] = -odata.outlineBBox.yMin; 9987 return true; 9988 } 9989 9990 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc { 9991 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 9992 9993 auto err = FT_Load_Glyph(font.font, glyphidx, FT_LOAD_NO_BITMAP|FT_LOAD_NO_SCALE); 9994 if (err) return false; 9995 if (font.font.glyph.format != FT_GLYPH_FORMAT_OUTLINE) { bounds[] = 0; return false; } 9996 9997 FT_Outline outline = font.font.glyph.outline; 9998 FT_BBox outlineBBox; 9999 FT_Outline_Get_CBox(&outline, &outlineBBox); 10000 if (bounds.length > 0) bounds.ptr[0] = outlineBBox.xMin; 10001 if (bounds.length > 1) bounds.ptr[1] = -outlineBBox.yMax; 10002 if (bounds.length > 2) bounds.ptr[2] = outlineBBox.xMax; 10003 if (bounds.length > 3) bounds.ptr[3] = -outlineBBox.yMin; 10004 return true; 10005 } 10006 10007 10008 } else { 10009 // ////////////////////////////////////////////////////////////////////////// // 10010 // sorry 10011 import std.traits : isFunctionPointer, isDelegate; 10012 private auto assumeNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) { 10013 import std.traits; 10014 enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_; 10015 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 10016 } 10017 10018 private auto forceNoThrowNoGC(T) (scope T t) if (isFunctionPointer!T || isDelegate!T) { 10019 try { 10020 return assumeNoThrowNoGC(t)(); 10021 } catch (Exception e) { 10022 assert(0, "OOPS!"); 10023 } 10024 } 10025 10026 struct FONSttFontImpl { 10027 stbtt_fontinfo font; 10028 bool mono; // no aa? 10029 } 10030 10031 int fons__tt_init (FONSContext context) nothrow @trusted @nogc { 10032 return 1; 10033 } 10034 10035 void fons__tt_setMono (FONSContext context, FONSttFontImpl* font, bool v) nothrow @trusted @nogc { 10036 font.mono = v; 10037 } 10038 10039 bool fons__tt_getMono (FONSContext context, FONSttFontImpl* font) nothrow @trusted @nogc { 10040 return font.mono; 10041 } 10042 10043 int fons__tt_loadFont (FONSContext context, FONSttFontImpl* font, ubyte* data, int dataSize) nothrow @trusted @nogc { 10044 int stbError; 10045 font.font.userdata = context; 10046 forceNoThrowNoGC({ stbError = stbtt_InitFont(&font.font, data, 0); }); 10047 return stbError; 10048 } 10049 10050 void fons__tt_getFontVMetrics (FONSttFontImpl* font, int* ascent, int* descent, int* lineGap) nothrow @trusted @nogc { 10051 forceNoThrowNoGC({ stbtt_GetFontVMetrics(&font.font, ascent, descent, lineGap); }); 10052 } 10053 10054 float fons__tt_getPixelHeightScale (FONSttFontImpl* font, float size) nothrow @trusted @nogc { 10055 float res = void; 10056 forceNoThrowNoGC({ res = stbtt_ScaleForPixelHeight(&font.font, size); }); 10057 return res; 10058 } 10059 10060 int fons__tt_getGlyphIndex (FONSttFontImpl* font, int codepoint) nothrow @trusted @nogc { 10061 int res; 10062 forceNoThrowNoGC({ res = stbtt_FindGlyphIndex(&font.font, codepoint); }); 10063 return res; 10064 } 10065 10066 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 { 10067 forceNoThrowNoGC({ stbtt_GetGlyphHMetrics(&font.font, glyph, advance, lsb); }); 10068 forceNoThrowNoGC({ stbtt_GetGlyphBitmapBox(&font.font, glyph, scale, scale, x0, y0, x1, y1); }); 10069 return 1; 10070 } 10071 10072 void fons__tt_renderGlyphBitmap (FONSttFontImpl* font, ubyte* output, int outWidth, int outHeight, int outStride, float scaleX, float scaleY, int glyph) nothrow @trusted @nogc { 10073 forceNoThrowNoGC({ stbtt_MakeGlyphBitmap(&font.font, output, outWidth, outHeight, outStride, scaleX, scaleY, glyph); }); 10074 } 10075 10076 float fons__tt_getGlyphKernAdvance (FONSttFontImpl* font, float size, int glyph1, int glyph2) nothrow @trusted @nogc { 10077 // FUnits -> pixels: pointSize * resolution / (72 points per inch * units_per_em) 10078 // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#converting 10079 float res = void; 10080 forceNoThrowNoGC({ 10081 res = stbtt_GetGlyphKernAdvance(&font.font, glyph1, glyph2); 10082 res *= stbtt_ScaleForPixelHeight(&font.font, size); 10083 }); 10084 /* 10085 if (res != 0) { 10086 { 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)); } 10087 } 10088 */ 10089 //k8: dunno if this is right; i guess it isn't but... 10090 return res; 10091 } 10092 10093 // old arsd.ttf sux! ;-) 10094 static if (is(typeof(STBTT_vcubic))) { 10095 10096 static struct OutlinerData { 10097 @disable this (this); 10098 void opAssign() (in auto ref OutlinerData a) { static assert(0, "no copies!"); } 10099 NVGPathOutline.DataStore* ol; 10100 nothrow @trusted @nogc: 10101 static float transx(T) (T v) pure { pragma(inline, true); return cast(float)v; } 10102 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; } 10103 } 10104 10105 10106 bool fons__nvg__toPath (NVGContext vg, FONSttFontImpl* font, uint glyphidx, float[] bounds=null) nothrow @trusted @nogc { 10107 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 10108 10109 bool okflag = false; 10110 10111 forceNoThrowNoGC({ 10112 int x0, y0, x1, y1; 10113 if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) { 10114 bounds[] = 0; 10115 return; 10116 } 10117 10118 if (bounds.length > 0) bounds.ptr[0] = x0; 10119 if (bounds.length > 1) bounds.ptr[1] = -y1; 10120 if (bounds.length > 2) bounds.ptr[2] = x1; 10121 if (bounds.length > 3) bounds.ptr[3] = -y0; 10122 10123 static float transy(T) (T v) pure { pragma(inline, true); return -cast(float)v; } 10124 10125 stbtt_vertex* verts = null; 10126 scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); } 10127 int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts); 10128 if (vcount < 1) return; 10129 10130 foreach (const ref vt; verts[0..vcount]) { 10131 switch (vt.type) { 10132 case STBTT_vmove: vg.moveTo(vt.x, transy(vt.y)); break; 10133 case STBTT_vline: vg.lineTo(vt.x, transy(vt.y)); break; 10134 case STBTT_vcurve: vg.quadTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy)); break; 10135 case STBTT_vcubic: vg.bezierTo(vt.x, transy(vt.y), vt.cx, transy(vt.cy), vt.cx1, transy(vt.cy1)); break; 10136 default: 10137 } 10138 } 10139 10140 okflag = true; 10141 }); 10142 10143 return okflag; 10144 } 10145 10146 bool fons__nvg__toOutline (FONSttFontImpl* font, uint glyphidx, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc { 10147 bool okflag = false; 10148 10149 forceNoThrowNoGC({ 10150 int x0, y0, x1, y1; 10151 10152 if (!stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) { 10153 ol.bounds[] = 0; 10154 return; 10155 } 10156 10157 ol.bounds.ptr[0] = x0; 10158 ol.bounds.ptr[1] = -y1; 10159 ol.bounds.ptr[2] = x1; 10160 ol.bounds.ptr[3] = -y0; 10161 10162 stbtt_vertex* verts = null; 10163 scope(exit) { import core.stdc.stdlib : free; if (verts !is null) free(verts); } 10164 int vcount = stbtt_GetGlyphShape(&font.font, glyphidx, &verts); 10165 if (vcount < 1) return; 10166 10167 OutlinerData odata; 10168 odata.ol = ol; 10169 10170 foreach (const ref vt; verts[0..vcount]) { 10171 switch (vt.type) { 10172 case STBTT_vmove: 10173 odata.ol.putCommand(NVGPathOutline.Command.Kind.MoveTo); 10174 odata.ol.putArgs(odata.transx(vt.x)); 10175 odata.ol.putArgs(odata.transy(vt.y)); 10176 break; 10177 case STBTT_vline: 10178 odata.ol.putCommand(NVGPathOutline.Command.Kind.LineTo); 10179 odata.ol.putArgs(odata.transx(vt.x)); 10180 odata.ol.putArgs(odata.transy(vt.y)); 10181 break; 10182 case STBTT_vcurve: 10183 odata.ol.putCommand(NVGPathOutline.Command.Kind.QuadTo); 10184 odata.ol.putArgs(odata.transx(vt.x)); 10185 odata.ol.putArgs(odata.transy(vt.y)); 10186 odata.ol.putArgs(odata.transx(vt.cx)); 10187 odata.ol.putArgs(odata.transy(vt.cy)); 10188 break; 10189 case STBTT_vcubic: 10190 odata.ol.putCommand(NVGPathOutline.Command.Kind.BezierTo); 10191 odata.ol.putArgs(odata.transx(vt.x)); 10192 odata.ol.putArgs(odata.transy(vt.y)); 10193 odata.ol.putArgs(odata.transx(vt.cx)); 10194 odata.ol.putArgs(odata.transy(vt.cy)); 10195 odata.ol.putArgs(odata.transx(vt.cx1)); 10196 odata.ol.putArgs(odata.transy(vt.cy1)); 10197 break; 10198 default: 10199 } 10200 } 10201 10202 okflag = true; 10203 }); 10204 10205 return okflag; 10206 } 10207 10208 bool fons__nvg__bounds (FONSttFontImpl* font, uint glyphidx, float[] bounds) nothrow @trusted @nogc { 10209 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 10210 10211 bool okflag = false; 10212 10213 forceNoThrowNoGC({ 10214 int x0, y0, x1, y1; 10215 if (stbtt_GetGlyphBox(&font.font, glyphidx, &x0, &y0, &x1, &y1)) { 10216 if (bounds.length > 0) bounds.ptr[0] = x0; 10217 if (bounds.length > 1) bounds.ptr[1] = -y1; 10218 if (bounds.length > 2) bounds.ptr[2] = x1; 10219 if (bounds.length > 3) bounds.ptr[3] = -y0; 10220 okflag = true; 10221 } else { 10222 bounds[] = 0; 10223 } 10224 }); 10225 10226 return okflag; 10227 } 10228 10229 } // check for old stb_ttf 10230 10231 10232 } // version 10233 10234 10235 // ////////////////////////////////////////////////////////////////////////// // 10236 private: 10237 enum FONS_SCRATCH_BUF_SIZE = 64000; 10238 enum FONS_HASH_LUT_SIZE = 256; 10239 enum FONS_INIT_FONTS = 4; 10240 enum FONS_INIT_GLYPHS = 256; 10241 enum FONS_INIT_ATLAS_NODES = 256; 10242 enum FONS_VERTEX_COUNT = 1024; 10243 enum FONS_MAX_STATES = 20; 10244 enum FONS_MAX_FALLBACKS = 20; 10245 10246 10247 struct FONSglyph { 10248 uint codepoint; 10249 int index; 10250 int next; 10251 short size, blur; 10252 short x0, y0, x1, y1; 10253 short xadv, xoff, yoff; 10254 } 10255 10256 // refcounted 10257 struct FONSfontData { 10258 ubyte* data; 10259 int dataSize; 10260 bool freeData; 10261 int rc; 10262 10263 @disable this (this); // no copies 10264 void opAssign() (in auto ref FONSfontData a) { static assert(0, "no copies!"); } 10265 } 10266 10267 // won't set rc to 1 10268 FONSfontData* fons__createFontData (ubyte* adata, int asize, bool afree) nothrow @trusted @nogc { 10269 import core.stdc.stdlib : malloc; 10270 assert(adata !is null); 10271 assert(asize > 0); 10272 auto res = cast(FONSfontData*)malloc(FONSfontData.sizeof); 10273 if (res is null) assert(0, "FONS: out of memory"); 10274 res.data = adata; 10275 res.dataSize = asize; 10276 res.freeData = afree; 10277 res.rc = 0; 10278 return res; 10279 } 10280 10281 void incref (FONSfontData* fd) pure nothrow @trusted @nogc { 10282 pragma(inline, true); 10283 if (fd !is null) ++fd.rc; 10284 } 10285 10286 void decref (ref FONSfontData* fd) nothrow @trusted @nogc { 10287 if (fd !is null) { 10288 if (--fd.rc == 0) { 10289 import core.stdc.stdlib : free; 10290 if (fd.freeData && fd.data !is null) { 10291 free(fd.data); 10292 fd.data = null; 10293 } 10294 free(fd); 10295 fd = null; 10296 } 10297 } 10298 } 10299 10300 // as creating and destroying fonts is a rare operation, malloc some data 10301 struct FONSfont { 10302 FONSttFontImpl font; 10303 char* name; // malloced, strz, always lowercase 10304 uint namelen; 10305 uint namehash; 10306 char* path; // malloced, strz 10307 FONSfontData* fdata; 10308 float ascender; 10309 float descender; 10310 float lineh; 10311 FONSglyph* glyphs; 10312 int cglyphs; 10313 int nglyphs; 10314 int[FONS_HASH_LUT_SIZE] lut; 10315 int[FONS_MAX_FALLBACKS] fallbacks; 10316 int nfallbacks; 10317 10318 @disable this (this); 10319 void opAssign() (in auto ref FONSfont a) { static assert(0, "no copies"); } 10320 10321 static uint djbhash (const(void)[] s) pure nothrow @safe @nogc { 10322 uint hash = 5381; 10323 foreach (ubyte b; cast(const(ubyte)[])s) { 10324 if (b >= 'A' && b <= 'Z') b += 32; // poor man's tolower 10325 hash = ((hash<<5)+hash)+b; 10326 } 10327 return hash; 10328 } 10329 10330 // except glyphs 10331 void freeMemory () nothrow @trusted @nogc { 10332 import core.stdc.stdlib : free; 10333 if (name !is null) { free(name); name = null; } 10334 namelen = namehash = 0; 10335 if (path !is null) { free(path); path = null; } 10336 fdata.decref(); 10337 } 10338 10339 // this also calcs name hash 10340 void setName (const(char)[] aname) nothrow @trusted @nogc { 10341 //{ import core.stdc.stdio; printf("setname: [%.*s]\n", cast(uint)aname.length, aname.ptr); } 10342 import core.stdc.stdlib : realloc; 10343 if (aname.length > int.max/32) assert(0, "FONS: invalid font name"); 10344 namelen = cast(uint)aname.length; 10345 name = cast(char*)realloc(name, namelen+1); 10346 if (name is null) assert(0, "FONS: out of memory"); 10347 if (aname.length) name[0..aname.length] = aname[]; 10348 name[namelen] = 0; 10349 // lowercase it 10350 foreach (ref char ch; name[0..namelen]) if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower 10351 namehash = djbhash(name[0..namelen]); 10352 //{ import core.stdc.stdio; printf(" [%s] [%.*s] [0x%08x]\n", name, namelen, name, namehash); } 10353 } 10354 10355 void setPath (const(char)[] apath) nothrow @trusted @nogc { 10356 import core.stdc.stdlib : realloc; 10357 if (apath.length > int.max/32) assert(0, "FONS: invalid font path"); 10358 path = cast(char*)realloc(path, apath.length+1); 10359 if (path is null) assert(0, "FONS: out of memory"); 10360 if (apath.length) path[0..apath.length] = apath[]; 10361 path[apath.length] = 0; 10362 } 10363 10364 // this won't check hash 10365 bool nameEqu (const(char)[] aname) const pure nothrow @trusted @nogc { 10366 //{ import core.stdc.stdio; printf("nameEqu: aname=[%.*s]; namelen=%u; aslen=%u\n", cast(uint)aname.length, aname.ptr, namelen, cast(uint)aname.length); } 10367 if (namelen != aname.length) return false; 10368 const(char)* ns = name; 10369 // name part 10370 foreach (char ch; aname) { 10371 if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's tolower 10372 if (ch != *ns++) return false; 10373 } 10374 // done (length was checked earlier) 10375 return true; 10376 } 10377 10378 void clear () nothrow @trusted @nogc { 10379 import core.stdc.stdlib : free; 10380 import core.stdc..string : memset; 10381 if (glyphs !is null) free(glyphs); 10382 freeMemory(); 10383 memset(&this, 0, this.sizeof); 10384 } 10385 10386 FONSglyph* allocGlyph () nothrow @trusted @nogc { 10387 if (nglyphs+1 > cglyphs) { 10388 import core.stdc.stdlib : realloc; 10389 cglyphs = (cglyphs == 0 ? 8 : cglyphs*2); 10390 glyphs = cast(FONSglyph*)realloc(glyphs, FONSglyph.sizeof*cglyphs); 10391 if (glyphs is null) assert(0, "FontStash: out of memory"); 10392 } 10393 ++nglyphs; 10394 return &glyphs[nglyphs-1]; 10395 } 10396 } 10397 10398 void kill (ref FONSfont* font) nothrow @trusted @nogc { 10399 if (font !is null) { 10400 import core.stdc.stdlib : free; 10401 font.clear(); 10402 free(font); 10403 font = null; 10404 } 10405 } 10406 10407 10408 // ////////////////////////////////////////////////////////////////////////// // 10409 struct FONSstate { 10410 int font; 10411 NVGTextAlign talign; 10412 float size = 0; 10413 float blur = 0; 10414 float spacing = 0; 10415 } 10416 10417 10418 // ////////////////////////////////////////////////////////////////////////// // 10419 // atlas based on Skyline Bin Packer by Jukka Jylänki 10420 alias FONSAtlas = FONSatlasInternal*; 10421 10422 struct FONSatlasInternal { 10423 static struct Node { 10424 short x, y, width; 10425 } 10426 10427 int width, height; 10428 Node* nodes; 10429 int nnodes; 10430 int cnodes; 10431 10432 @disable this (this); 10433 void opAssign() (in auto ref FONSatlasInternal a) { static assert(0, "no copies"); } 10434 10435 nothrow @trusted @nogc: 10436 static FONSAtlas create (int w, int h, int nnodes) { 10437 import core.stdc.stdlib : malloc; 10438 import core.stdc..string : memset; 10439 10440 FONSAtlas atlas = cast(FONSAtlas)malloc(FONSatlasInternal.sizeof); 10441 if (atlas is null) assert(0, "FontStash: out of memory"); 10442 memset(atlas, 0, FONSatlasInternal.sizeof); 10443 10444 atlas.width = w; 10445 atlas.height = h; 10446 10447 // allocate space for skyline nodes 10448 atlas.nodes = cast(Node*)malloc(Node.sizeof*nnodes); 10449 if (atlas.nodes is null) assert(0, "FontStash: out of memory"); 10450 memset(atlas.nodes, 0, Node.sizeof*nnodes); 10451 atlas.nnodes = 0; 10452 atlas.cnodes = nnodes; 10453 10454 // init root node 10455 atlas.nodes[0].x = 0; 10456 atlas.nodes[0].y = 0; 10457 atlas.nodes[0].width = cast(short)w; 10458 ++atlas.nnodes; 10459 10460 return atlas; 10461 } 10462 10463 void clear () { 10464 import core.stdc.stdlib : free; 10465 import core.stdc..string : memset; 10466 10467 if (nodes !is null) free(nodes); 10468 memset(&this, 0, this.sizeof); 10469 } 10470 10471 void insertNode (int idx, int x, int y, int w) { 10472 if (nnodes+1 > cnodes) { 10473 import core.stdc.stdlib : realloc; 10474 cnodes = (cnodes == 0 ? 8 : cnodes*2); 10475 nodes = cast(Node*)realloc(nodes, Node.sizeof*cnodes); 10476 if (nodes is null) assert(0, "FontStash: out of memory"); 10477 } 10478 for (int i = nnodes; i > idx; --i) nodes[i] = nodes[i-1]; 10479 nodes[idx].x = cast(short)x; 10480 nodes[idx].y = cast(short)y; 10481 nodes[idx].width = cast(short)w; 10482 ++nnodes; 10483 } 10484 10485 void removeNode (int idx) { 10486 if (nnodes == 0) return; 10487 foreach (immutable int i; idx+1..nnodes) nodes[i-1] = nodes[i]; 10488 --nnodes; 10489 } 10490 10491 // insert node for empty space 10492 void expand (int w, int h) { 10493 if (w > width) insertNode(nnodes, width, 0, w-width); 10494 width = w; 10495 height = h; 10496 } 10497 10498 void reset (int w, int h) { 10499 width = w; 10500 height = h; 10501 nnodes = 0; 10502 // init root node 10503 nodes[0].x = 0; 10504 nodes[0].y = 0; 10505 nodes[0].width = cast(short)w; 10506 ++nnodes; 10507 } 10508 10509 void addSkylineLevel (int idx, int x, int y, int w, int h) { 10510 insertNode(idx, x, y+h, w); 10511 10512 // delete skyline segments that fall under the shadow of the new segment 10513 for (int i = idx+1; i < nnodes; ++i) { 10514 if (nodes[i].x < nodes[i-1].x+nodes[i-1].width) { 10515 int shrink = nodes[i-1].x+nodes[i-1].width-nodes[i].x; 10516 nodes[i].x += cast(short)shrink; 10517 nodes[i].width -= cast(short)shrink; 10518 if (nodes[i].width <= 0) { 10519 removeNode(i); 10520 --i; 10521 } else { 10522 break; 10523 } 10524 } else { 10525 break; 10526 } 10527 } 10528 10529 // Merge same height skyline segments that are next to each other 10530 for (int i = 0; i < nnodes-1; ++i) { 10531 if (nodes[i].y == nodes[i+1].y) { 10532 nodes[i].width += nodes[i+1].width; 10533 removeNode(i+1); 10534 --i; 10535 } 10536 } 10537 } 10538 10539 // checks if there is enough space at the location of skyline span 'i', 10540 // and return the max height of all skyline spans under that at that location, 10541 // (think tetris block being dropped at that position); or -1 if no space found 10542 int rectFits (int i, int w, int h) { 10543 int x = nodes[i].x; 10544 int y = nodes[i].y; 10545 if (x+w > width) return -1; 10546 int spaceLeft = w; 10547 while (spaceLeft > 0) { 10548 if (i == nnodes) return -1; 10549 y = nvg__max(y, nodes[i].y); 10550 if (y+h > height) return -1; 10551 spaceLeft -= nodes[i].width; 10552 ++i; 10553 } 10554 return y; 10555 } 10556 10557 bool addRect (int rw, int rh, int* rx, int* ry) { 10558 int besth = height, bestw = width, besti = -1; 10559 int bestx = -1, besty = -1; 10560 10561 // Bottom left fit heuristic. 10562 for (int i = 0; i < nnodes; ++i) { 10563 int y = rectFits(i, rw, rh); 10564 if (y != -1) { 10565 if (y+rh < besth || (y+rh == besth && nodes[i].width < bestw)) { 10566 besti = i; 10567 bestw = nodes[i].width; 10568 besth = y+rh; 10569 bestx = nodes[i].x; 10570 besty = y; 10571 } 10572 } 10573 } 10574 10575 if (besti == -1) return false; 10576 10577 // perform the actual packing 10578 addSkylineLevel(besti, bestx, besty, rw, rh); 10579 10580 *rx = bestx; 10581 *ry = besty; 10582 10583 return true; 10584 } 10585 } 10586 10587 void kill (ref FONSAtlas atlas) nothrow @trusted @nogc { 10588 if (atlas !is null) { 10589 import core.stdc.stdlib : free; 10590 atlas.clear(); 10591 free(atlas); 10592 atlas = null; 10593 } 10594 } 10595 10596 10597 // ////////////////////////////////////////////////////////////////////////// // 10598 /// FontStash context (internal definition). Don't use it derectly, it was made public only to generate documentation. 10599 /// Group: font_stash 10600 public struct FONScontextInternal { 10601 private: 10602 FONSParams params; 10603 float itw, ith; 10604 ubyte* texData; 10605 int[4] dirtyRect; 10606 FONSfont** fonts; // actually, a simple hash table; can't grow yet 10607 int cfonts; // allocated 10608 int nfonts; // used (so we can track hash table stats) 10609 int* hashidx; // [hsize] items; holds indicies in [fonts] array 10610 int hused, hsize;// used items and total items in [hashidx] 10611 FONSAtlas atlas; 10612 ubyte* scratch; 10613 int nscratch; 10614 FONSstate[FONS_MAX_STATES] states; 10615 int nstates; 10616 10617 void delegate (FONSError error, int val) nothrow @trusted @nogc handleError; 10618 10619 @disable this (this); 10620 void opAssign() (in auto ref FONScontextInternal ctx) { static assert(0, "FONS copying is not allowed"); } 10621 10622 private: 10623 static bool strequci (const(char)[] s0, const(char)[] s1) pure nothrow @trusted @nogc { 10624 if (s0.length != s1.length) return false; 10625 const(char)* sp0 = s0.ptr; 10626 const(char)* sp1 = s1.ptr; 10627 foreach (immutable _; 0..s0.length) { 10628 char c0 = *sp0++; 10629 char c1 = *sp1++; 10630 if (c0 != c1) { 10631 if (c0 >= 'A' && c0 <= 'Z') c0 += 32; // poor man tolower 10632 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man tolower 10633 if (c0 != c1) return false; 10634 } 10635 } 10636 return true; 10637 } 10638 10639 inout(FONSstate)* getState () inout pure nothrow @trusted @nogc { 10640 pragma(inline, true); 10641 return cast(inout)(&states[(nstates > 0 ? nstates-1 : 0)]); 10642 } 10643 10644 // simple linear probing; returns [FONS_INVALID] if not found 10645 int findNameInHash (const(char)[] name) const pure nothrow @trusted @nogc { 10646 if (nfonts == 0) return FONS_INVALID; 10647 auto nhash = FONSfont.djbhash(name); 10648 //{ import core.stdc.stdio; printf("findinhash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); } 10649 auto res = nhash%hsize; 10650 // hash will never be 100% full, so this loop is safe 10651 for (;;) { 10652 int idx = hashidx[res]; 10653 if (idx == -1) break; 10654 auto font = fonts[idx]; 10655 if (font is null) assert(0, "FONS internal error"); 10656 if (font.namehash == nhash && font.nameEqu(name)) return idx; 10657 //{ import core.stdc.stdio; printf("findinhash chained: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); } 10658 res = (res+1)%hsize; 10659 } 10660 return FONS_INVALID; 10661 } 10662 10663 // should be called $(B before) freeing `fonts[fidx]` 10664 void removeIndexFromHash (int fidx) nothrow @trusted @nogc { 10665 if (fidx < 0 || fidx >= nfonts) assert(0, "FONS internal error"); 10666 if (fonts[fidx] is null) assert(0, "FONS internal error"); 10667 if (hused != nfonts) assert(0, "FONS internal error"); 10668 auto nhash = fonts[fidx].namehash; 10669 auto res = nhash%hsize; 10670 // hash will never be 100% full, so this loop is safe 10671 for (;;) { 10672 int idx = hashidx[res]; 10673 if (idx == -1) assert(0, "FONS INTERNAL ERROR"); 10674 if (idx == fidx) { 10675 // i found her! copy rest here 10676 int nidx = (res+1)%hsize; 10677 for (;;) { 10678 if ((hashidx[res] = hashidx[nidx]) == -1) break; // so it will copy `-1` too 10679 res = nidx; 10680 nidx = (nidx+1)%hsize; 10681 } 10682 return; 10683 } 10684 res = (res+1)%hsize; 10685 } 10686 } 10687 10688 // add font with the given index to hash 10689 // prerequisite: font should not exists in hash 10690 void addIndexToHash (int idx) nothrow @trusted @nogc { 10691 if (idx < 0 || idx >= nfonts) assert(0, "FONS internal error"); 10692 if (fonts[idx] is null) assert(0, "FONS internal error"); 10693 import core.stdc.stdlib : realloc; 10694 auto nhash = fonts[idx].namehash; 10695 //{ import core.stdc.stdio; printf("addtohash: name=[%.*s]; nhash=0x%08x\n", cast(uint)name.length, name.ptr, nhash); } 10696 // allocate new hash table if there was none 10697 if (hsize == 0) { 10698 enum InitSize = 256; 10699 auto newlist = cast(int*)realloc(null, InitSize*hashidx[0].sizeof); 10700 if (newlist is null) assert(0, "FONS: out of memory"); 10701 newlist[0..InitSize] = -1; 10702 hsize = InitSize; 10703 hused = 0; 10704 hashidx = newlist; 10705 } 10706 int res = cast(int)(nhash%hsize); 10707 // need to rehash? we want our hash table 50% full at max 10708 if (hashidx[res] != -1 && hused >= hsize/2) { 10709 uint nsz = hsize*2; 10710 if (nsz > 1024*1024) assert(0, "FONS: out of memory for fonts"); 10711 auto newlist = cast(int*)realloc(fonts, nsz*hashidx[0].sizeof); 10712 if (newlist is null) assert(0, "FONS: out of memory"); 10713 newlist[0..nsz] = -1; 10714 hused = 0; 10715 // rehash 10716 foreach (immutable fidx, FONSfont* ff; fonts[0..nfonts]) { 10717 if (ff is null) continue; 10718 // find slot for this font (guaranteed to have one) 10719 uint newslot = ff.namehash%nsz; 10720 while (newlist[newslot] != -1) newslot = (newslot+1)%nsz; 10721 newlist[newslot] = cast(int)fidx; 10722 ++hused; 10723 } 10724 hsize = nsz; 10725 hashidx = newlist; 10726 // we added everything, including [idx], so nothing more to do here 10727 } else { 10728 // find slot (guaranteed to have one) 10729 while (hashidx[res] != -1) res = (res+1)%hsize; 10730 // i found her! 10731 hashidx[res] = idx; 10732 ++hused; 10733 } 10734 } 10735 10736 void addWhiteRect (int w, int h) nothrow @trusted @nogc { 10737 int gx, gy; 10738 ubyte* dst; 10739 10740 if (!atlas.addRect(w, h, &gx, &gy)) return; 10741 10742 // Rasterize 10743 dst = &texData[gx+gy*params.width]; 10744 foreach (int y; 0..h) { 10745 foreach (int x; 0..w) { 10746 dst[x] = 0xff; 10747 } 10748 dst += params.width; 10749 } 10750 10751 dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], gx); 10752 dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], gy); 10753 dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], gx+w); 10754 dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], gy+h); 10755 } 10756 10757 // returns fid, not hash slot 10758 int allocFontAt (int atidx) nothrow @trusted @nogc { 10759 if (atidx >= 0 && atidx >= nfonts) assert(0, "internal NanoVega fontstash error"); 10760 10761 if (atidx < 0) { 10762 if (nfonts >= cfonts) { 10763 import core.stdc.stdlib : realloc; 10764 import core.stdc..string : memset; 10765 assert(nfonts == cfonts); 10766 int newsz = cfonts+64; 10767 if (newsz > 65535) assert(0, "FONS: too many fonts"); 10768 auto newlist = cast(FONSfont**)realloc(fonts, newsz*(FONSfont*).sizeof); 10769 if (newlist is null) assert(0, "FONS: out of memory"); 10770 memset(newlist+cfonts, 0, (newsz-cfonts)*(FONSfont*).sizeof); 10771 fonts = newlist; 10772 cfonts = newsz; 10773 } 10774 assert(nfonts < cfonts); 10775 } 10776 10777 FONSfont* font = cast(FONSfont*)malloc(FONSfont.sizeof); 10778 if (font is null) assert(0, "FONS: out of memory"); 10779 memset(font, 0, FONSfont.sizeof); 10780 10781 font.glyphs = cast(FONSglyph*)malloc(FONSglyph.sizeof*FONS_INIT_GLYPHS); 10782 if (font.glyphs is null) assert(0, "FONS: out of memory"); 10783 font.cglyphs = FONS_INIT_GLYPHS; 10784 font.nglyphs = 0; 10785 10786 if (atidx < 0) { 10787 fonts[nfonts] = font; 10788 return nfonts++; 10789 } else { 10790 fonts[atidx] = font; 10791 return atidx; 10792 } 10793 } 10794 10795 // 0: ooops 10796 int findGlyphForCP (FONSfont *font, dchar dch, FONSfont** renderfont) nothrow @trusted @nogc { 10797 if (renderfont !is null) *renderfont = font; 10798 if (font is null || font.fdata is null) return 0; 10799 auto g = fons__tt_getGlyphIndex(&font.font, cast(uint)dch); 10800 // try to find the glyph in fallback fonts 10801 if (g == 0) { 10802 foreach (immutable i; 0..font.nfallbacks) { 10803 FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]]; 10804 if (fallbackFont !is null) { 10805 int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, cast(uint)dch); 10806 if (fallbackIndex != 0) { 10807 if (renderfont !is null) *renderfont = fallbackFont; 10808 return g; 10809 } 10810 } 10811 } 10812 // no char, try to find replacement one 10813 if (dch != 0xFFFD) { 10814 g = fons__tt_getGlyphIndex(&font.font, 0xFFFD); 10815 if (g == 0) { 10816 foreach (immutable i; 0..font.nfallbacks) { 10817 FONSfont* fallbackFont = fonts[font.fallbacks.ptr[i]]; 10818 if (fallbackFont !is null) { 10819 int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont.font, 0xFFFD); 10820 if (fallbackIndex != 0) { 10821 if (renderfont !is null) *renderfont = fallbackFont; 10822 return g; 10823 } 10824 } 10825 } 10826 } 10827 } 10828 } 10829 return g; 10830 } 10831 10832 void clear () nothrow @trusted @nogc { 10833 import core.stdc.stdlib : free; 10834 10835 if (params.renderDelete !is null) params.renderDelete(params.userPtr); 10836 foreach (immutable int i; 0..nfonts) fonts[i].kill(); 10837 10838 if (atlas !is null) atlas.kill(); 10839 if (fonts !is null) free(fonts); 10840 if (texData !is null) free(texData); 10841 if (scratch !is null) free(scratch); 10842 if (hashidx !is null) free(hashidx); 10843 } 10844 10845 // add font from another fontstash 10846 int addCookedFont (FONSfont* font) nothrow @trusted @nogc { 10847 if (font is null || font.fdata is null) return FONS_INVALID; 10848 font.fdata.incref(); 10849 auto res = addFontWithData(font.name[0..font.namelen], font.fdata, !font.font.mono); 10850 if (res == FONS_INVALID) font.fdata.decref(); // oops 10851 return res; 10852 } 10853 10854 // fdata refcount must be already increased; it won't be changed 10855 int addFontWithData (const(char)[] name, FONSfontData* fdata, bool defAA) nothrow @trusted @nogc { 10856 int i, ascent, descent, fh, lineGap; 10857 10858 if (name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID; 10859 if (name.length > 32767) return FONS_INVALID; 10860 if (fdata is null) return FONS_INVALID; 10861 10862 // find a font with the given name 10863 int newidx; 10864 FONSfont* oldfont = null; 10865 int oldidx = findNameInHash(name); 10866 if (oldidx != FONS_INVALID) { 10867 // replacement font 10868 oldfont = fonts[oldidx]; 10869 newidx = oldidx; 10870 } else { 10871 // new font, allocate new bucket 10872 newidx = -1; 10873 } 10874 10875 newidx = allocFontAt(newidx); 10876 FONSfont* font = fonts[newidx]; 10877 font.setName(name); 10878 font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1; // init hash lookup 10879 font.fdata = fdata; // set the font data (don't change reference count) 10880 fons__tt_setMono(&this, &font.font, !defAA); 10881 10882 // init font 10883 nscratch = 0; 10884 if (!fons__tt_loadFont(&this, &font.font, fdata.data, fdata.dataSize)) { 10885 // we promised to not free data on error, so just clear the data store (it will be freed by the caller) 10886 font.fdata = null; 10887 font.kill(); 10888 if (oldidx != FONS_INVALID) { 10889 assert(oldidx == newidx); 10890 fonts[oldidx] = oldfont; 10891 } else { 10892 assert(newidx == nfonts-1); 10893 fonts[newidx] = null; 10894 --nfonts; 10895 } 10896 return FONS_INVALID; 10897 } else { 10898 // free old font data, if any 10899 if (oldfont !is null) oldfont.kill(); 10900 } 10901 10902 // add font to name hash 10903 if (oldidx == FONS_INVALID) addIndexToHash(newidx); 10904 10905 // store normalized line height 10906 // the real line height is got by multiplying the lineh by font size 10907 fons__tt_getFontVMetrics(&font.font, &ascent, &descent, &lineGap); 10908 fh = ascent-descent; 10909 font.ascender = cast(float)ascent/cast(float)fh; 10910 font.descender = cast(float)descent/cast(float)fh; 10911 font.lineh = cast(float)(fh+lineGap)/cast(float)fh; 10912 10913 //{ import core.stdc.stdio; printf("created font [%.*s] (idx=%d)...\n", cast(uint)name.length, name.ptr, idx); } 10914 return newidx; 10915 } 10916 10917 // isize: size*10 10918 float getVertAlign (FONSfont* font, NVGTextAlign talign, short isize) pure nothrow @trusted @nogc { 10919 if (params.isZeroTopLeft) { 10920 final switch (talign.vertical) { 10921 case NVGTextAlign.V.Top: return font.ascender*cast(float)isize/10.0f; 10922 case NVGTextAlign.V.Middle: return (font.ascender+font.descender)/2.0f*cast(float)isize/10.0f; 10923 case NVGTextAlign.V.Baseline: return 0.0f; 10924 case NVGTextAlign.V.Bottom: return font.descender*cast(float)isize/10.0f; 10925 } 10926 } else { 10927 final switch (talign.vertical) { 10928 case NVGTextAlign.V.Top: return -font.ascender*cast(float)isize/10.0f; 10929 case NVGTextAlign.V.Middle: return -(font.ascender+font.descender)/2.0f*cast(float)isize/10.0f; 10930 case NVGTextAlign.V.Baseline: return 0.0f; 10931 case NVGTextAlign.V.Bottom: return -font.descender*cast(float)isize/10.0f; 10932 } 10933 } 10934 assert(0); 10935 } 10936 10937 public: 10938 /** Create new FontStash context. It can be destroyed with `fs.kill()` later. 10939 * 10940 * Note that if you don't plan to rasterize glyphs (i.e. you will use created 10941 * FontStash only to measure text), you can simply pass `FONSParams.init`). 10942 */ 10943 static FONSContext create() (in auto ref FONSParams params) nothrow @trusted @nogc { 10944 import core.stdc..string : memcpy; 10945 10946 FONSContext stash = null; 10947 10948 // allocate memory for the font stash 10949 stash = cast(FONSContext)malloc(FONScontextInternal.sizeof); 10950 if (stash is null) goto error; 10951 memset(stash, 0, FONScontextInternal.sizeof); 10952 10953 memcpy(&stash.params, ¶ms, params.sizeof); 10954 if (stash.params.width < 1) stash.params.width = 32; 10955 if (stash.params.height < 1) stash.params.height = 32; 10956 10957 // allocate scratch buffer 10958 stash.scratch = cast(ubyte*)malloc(FONS_SCRATCH_BUF_SIZE); 10959 if (stash.scratch is null) goto error; 10960 10961 // initialize implementation library 10962 if (!fons__tt_init(stash)) goto error; 10963 10964 if (stash.params.renderCreate !is null) { 10965 if (!stash.params.renderCreate(stash.params.userPtr, stash.params.width, stash.params.height)) goto error; 10966 } 10967 10968 stash.atlas = FONSAtlas.create(stash.params.width, stash.params.height, FONS_INIT_ATLAS_NODES); 10969 if (stash.atlas is null) goto error; 10970 10971 // don't allocate space for fonts: hash manager will do that for us later 10972 //stash.cfonts = 0; 10973 //stash.nfonts = 0; 10974 10975 // create texture for the cache 10976 stash.itw = 1.0f/stash.params.width; 10977 stash.ith = 1.0f/stash.params.height; 10978 stash.texData = cast(ubyte*)malloc(stash.params.width*stash.params.height); 10979 if (stash.texData is null) goto error; 10980 memset(stash.texData, 0, stash.params.width*stash.params.height); 10981 10982 stash.dirtyRect.ptr[0] = stash.params.width; 10983 stash.dirtyRect.ptr[1] = stash.params.height; 10984 stash.dirtyRect.ptr[2] = 0; 10985 stash.dirtyRect.ptr[3] = 0; 10986 10987 // add white rect at 0, 0 for debug drawing 10988 stash.addWhiteRect(2, 2); 10989 10990 stash.pushState(); 10991 stash.clearState(); 10992 10993 return stash; 10994 10995 error: 10996 stash.kill(); 10997 return null; 10998 } 10999 11000 public: 11001 /// Add fallback font (FontStash will try to find missing glyph in all fallback fonts before giving up). 11002 bool addFallbackFont (int base, int fallback) nothrow @trusted @nogc { 11003 FONSfont* baseFont = fonts[base]; 11004 if (baseFont !is null && baseFont.nfallbacks < FONS_MAX_FALLBACKS) { 11005 baseFont.fallbacks.ptr[baseFont.nfallbacks++] = fallback; 11006 return true; 11007 } 11008 return false; 11009 } 11010 11011 @property void size (float size) nothrow @trusted @nogc { pragma(inline, true); getState.size = size; } /// Set current font size. 11012 @property float size () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.size; } /// Get current font size. 11013 11014 @property void spacing (float spacing) nothrow @trusted @nogc { pragma(inline, true); getState.spacing = spacing; } /// Set current letter spacing. 11015 @property float spacing () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.spacing; } /// Get current letter spacing. 11016 11017 @property void blur (float blur) nothrow @trusted @nogc { pragma(inline, true); getState.blur = blur; } /// Set current letter blur. 11018 @property float blur () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.blur; } /// Get current letter blur. 11019 11020 @property void textAlign (NVGTextAlign talign) nothrow @trusted @nogc { pragma(inline, true); getState.talign = talign; } /// Set current text align. 11021 @property NVGTextAlign textAlign () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.talign; } /// Get current text align. 11022 11023 @property void fontId (int font) nothrow @trusted @nogc { pragma(inline, true); getState.font = font; } /// Set current font id. 11024 @property int fontId () const pure nothrow @trusted @nogc { pragma(inline, true); return getState.font; } /// Get current font id. 11025 11026 @property void fontId (const(char)[] name) nothrow @trusted @nogc { pragma(inline, true); getState.font = getFontByName(name); } /// Set current font using its name. 11027 11028 /// Check if FontStash has a font with the given name loaded. 11029 bool hasFont (const(char)[] name) const pure nothrow @trusted @nogc { pragma(inline, true); return (getFontByName(name) >= 0); } 11030 11031 /// Get AA for the current font, or for the specified font. 11032 bool getFontAA (int font=-1) nothrow @trusted @nogc { 11033 FONSstate* state = getState; 11034 if (font < 0) font = state.font; 11035 if (font < 0 || font >= nfonts) return false; 11036 FONSfont* f = fonts[font]; 11037 return (f !is null ? !f.font.mono : false); 11038 } 11039 11040 /// Push current state. Returns `false` if state stack overflowed. 11041 bool pushState () nothrow @trusted @nogc { 11042 if (nstates >= FONS_MAX_STATES) { 11043 if (handleError !is null) handleError(FONSError.StatesOverflow, 0); 11044 return false; 11045 } 11046 if (nstates > 0) { 11047 import core.stdc..string : memcpy; 11048 memcpy(&states[nstates], &states[nstates-1], FONSstate.sizeof); 11049 } 11050 ++nstates; 11051 return true; 11052 } 11053 11054 /// Pop current state. Returns `false` if state stack underflowed. 11055 bool popState () nothrow @trusted @nogc { 11056 if (nstates <= 1) { 11057 if (handleError !is null) handleError(FONSError.StatesUnderflow, 0); 11058 return false; 11059 } 11060 --nstates; 11061 return true; 11062 } 11063 11064 /// Clear current state (i.e. set it to some sane defaults). 11065 void clearState () nothrow @trusted @nogc { 11066 FONSstate* state = getState; 11067 state.size = 12.0f; 11068 state.font = 0; 11069 state.blur = 0; 11070 state.spacing = 0; 11071 state.talign.reset; 11072 } 11073 11074 private enum NoAlias = ":noaa"; 11075 11076 /** Add font to FontStash. 11077 * 11078 * Load scalable font from disk, and add it to FontStash. If you will try to load a font 11079 * with same name and path several times, FontStash will load it only once. Also, you can 11080 * load new disk font for any existing logical font. 11081 * 11082 * Params: 11083 * name = logical font name, that will be used to select this font later. 11084 * path = path to disk file with your font. 11085 * defAA = should FontStash use antialiased font rasterizer? 11086 * 11087 * Returns: 11088 * font id or [FONS_INVALID]. 11089 */ 11090 int addFont (const(char)[] name, const(char)[] path, bool defAA=false) nothrow @trusted { 11091 if (path.length == 0 || name.length == 0 || strequci(name, NoAlias)) return FONS_INVALID; 11092 if (path.length > 32768) return FONS_INVALID; // arbitrary limit 11093 11094 // if font path ends with ":noaa", turn off antialiasing 11095 if (path.length >= NoAlias.length && strequci(path[$-NoAlias.length..$], NoAlias)) { 11096 path = path[0..$-NoAlias.length]; 11097 if (path.length == 0) return FONS_INVALID; 11098 defAA = false; 11099 } 11100 11101 // if font name ends with ":noaa", turn off antialiasing 11102 if (name.length > NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) { 11103 name = name[0..$-NoAlias.length]; 11104 defAA = false; 11105 } 11106 11107 // find a font with the given name 11108 int fidx = findNameInHash(name); 11109 //{ import core.stdc.stdio; printf("loading font '%.*s' [%s] (fidx=%d)...\n", cast(uint)path.length, path.ptr, fontnamebuf.ptr, fidx); } 11110 11111 int loadFontFile (const(char)[] path) { 11112 // check if existing font (if any) has the same path 11113 if (fidx >= 0) { 11114 import core.stdc..string : strlen; 11115 auto plen = (fonts[fidx].path !is null ? strlen(fonts[fidx].path) : 0); 11116 version(Posix) { 11117 //{ 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); } 11118 if (plen == path.length && fonts[fidx].path[0..plen] == path) { 11119 //{ import core.stdc.stdio; printf("*** font [%.*s] already loaded from [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)plen, path.ptr); } 11120 // i found her! 11121 return fidx; 11122 } 11123 } else { 11124 if (plen == path.length && strequci(fonts[fidx].path[0..plen], path)) { 11125 // i found her! 11126 return fidx; 11127 } 11128 } 11129 } 11130 version(Windows) { 11131 // special shitdows check: this will reject fontconfig font names (but still allow things like "c:myfont") 11132 foreach (immutable char ch; path[(path.length >= 2 && path[1] == ':' ? 2 : 0)..$]) if (ch == ':') return FONS_INVALID; 11133 } 11134 // either no such font, or different path 11135 //{ import core.stdc.stdio; printf("trying font [%.*s] from file [%.*s]\n", cast(uint)blen, fontnamebuf.ptr, cast(uint)path.length, path.ptr); } 11136 int xres = FONS_INVALID; 11137 try { 11138 import core.stdc.stdlib : free, malloc; 11139 static if (NanoVegaHasIVVFS) { 11140 auto fl = VFile(path); 11141 auto dataSize = fl.size; 11142 if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID; 11143 ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize); 11144 if (data is null) assert(0, "out of memory in NanoVega fontstash"); 11145 scope(failure) free(data); // oops 11146 fl.rawReadExact(data[0..cast(uint)dataSize]); 11147 fl.close(); 11148 } else { 11149 import core.stdc.stdio : FILE, fopen, fclose, fread, ftell, fseek; 11150 import std.internal.cstring : tempCString; 11151 auto fl = fopen(path.tempCString, "rb"); 11152 if (fl is null) return FONS_INVALID; 11153 scope(exit) fclose(fl); 11154 if (fseek(fl, 0, 2/*SEEK_END*/) != 0) return FONS_INVALID; 11155 auto dataSize = ftell(fl); 11156 if (fseek(fl, 0, 0/*SEEK_SET*/) != 0) return FONS_INVALID; 11157 if (dataSize < 16 || dataSize > int.max/32) return FONS_INVALID; 11158 ubyte* data = cast(ubyte*)malloc(cast(uint)dataSize); 11159 if (data is null) assert(0, "out of memory in NanoVega fontstash"); 11160 scope(failure) free(data); // oops 11161 ubyte* dptr = data; 11162 auto left = cast(uint)dataSize; 11163 while (left > 0) { 11164 auto rd = fread(dptr, 1, left, fl); 11165 if (rd == 0) { free(data); return FONS_INVALID; } // unexpected EOF or reading error, it doesn't matter 11166 dptr += rd; 11167 left -= rd; 11168 } 11169 } 11170 scope(failure) free(data); // oops 11171 // create font data 11172 FONSfontData* fdata = fons__createFontData(data, cast(int)dataSize, true); // free data 11173 fdata.incref(); 11174 xres = addFontWithData(name, fdata, defAA); 11175 if (xres == FONS_INVALID) { 11176 fdata.decref(); // this will free [data] and [fdata] 11177 } else { 11178 // remember path 11179 fonts[xres].setPath(path); 11180 } 11181 } catch (Exception e) { 11182 // oops; sorry 11183 } 11184 return xres; 11185 } 11186 11187 // first try direct path 11188 auto res = loadFontFile(path); 11189 // if loading failed, try fontconfig (if fontconfig is available) 11190 static if (NanoVegaHasFontConfig) { 11191 if (res == FONS_INVALID && fontconfigAvailable) { 11192 // idiotic fontconfig NEVER fails; let's skip it if `path` looks like a path 11193 bool ok = true; 11194 if (path.length > 4 && (path[$-4..$] == ".ttf" || path[$-4..$] == ".ttc")) ok = false; 11195 if (ok) { foreach (immutable char ch; path) if (ch == '/') { ok = false; break; } } 11196 if (ok) { 11197 import std.internal.cstring : tempCString; 11198 FcPattern* pat = FcNameParse(path.tempCString); 11199 if (pat !is null) { 11200 scope(exit) FcPatternDestroy(pat); 11201 if (FcConfigSubstitute(null, pat, FcMatchPattern)) { 11202 FcDefaultSubstitute(pat); 11203 // find the font 11204 FcResult result; 11205 FcPattern* font = FcFontMatch(null, pat, &result); 11206 if (font !is null) { 11207 scope(exit) FcPatternDestroy(font); 11208 char* file = null; 11209 if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) { 11210 if (file !is null && file[0]) { 11211 import core.stdc..string : strlen; 11212 res = loadFontFile(file[0..strlen(file)]); 11213 } 11214 } 11215 } 11216 } 11217 } 11218 } 11219 } 11220 } 11221 return res; 11222 } 11223 11224 /** Add font to FontStash, using data from memory. 11225 * 11226 * And already loaded font to FontStash. You can replace existing logical fonts. 11227 * But note that you can't remove logical font by passing "empty" data. 11228 * 11229 * $(WARNING If [FONS_INVALID] returned, `data` won't be freed even if `freeData` is `true`.) 11230 * 11231 * Params: 11232 * name = logical font name, that will be used to select this font later. 11233 * data = font data. 11234 * dataSize = font data size. 11235 * freeData = should FontStash take ownership of the font data? 11236 * defAA = should FontStash use antialiased font rasterizer? 11237 * 11238 * Returns: 11239 * font id or [FONS_INVALID]. 11240 */ 11241 int addFontMem (const(char)[] name, ubyte* data, int dataSize, bool freeData, bool defAA=false) nothrow @trusted @nogc { 11242 if (data is null || dataSize < 16) return FONS_INVALID; 11243 FONSfontData* fdata = fons__createFontData(data, dataSize, freeData); 11244 fdata.incref(); 11245 auto res = addFontWithData(name, fdata, defAA); 11246 if (res == FONS_INVALID) { 11247 // we promised to not free data on error 11248 fdata.freeData = false; 11249 fdata.decref(); // this will free [fdata] 11250 } 11251 return res; 11252 } 11253 11254 /** Add fonts from another FontStash. 11255 * 11256 * This is more effective (and faster) than reloading fonts, because internally font data 11257 * is reference counted. 11258 */ 11259 void addFontsFrom (FONSContext source) nothrow @trusted @nogc { 11260 if (source is null) return; 11261 foreach (FONSfont* font; source.fonts[0..source.nfonts]) { 11262 if (font !is null) { 11263 auto newidx = addCookedFont(font); 11264 FONSfont* newfont = fonts[newidx]; 11265 assert(newfont !is null); 11266 assert(newfont.path is null); 11267 // copy path 11268 if (font.path !is null && font.path[0]) { 11269 import core.stdc.stdlib : malloc; 11270 import core.stdc..string : strcpy, strlen; 11271 newfont.path = cast(char*)malloc(strlen(font.path)+1); 11272 if (newfont.path is null) assert(0, "FONS: out of memory"); 11273 strcpy(newfont.path, font.path); 11274 } 11275 } 11276 } 11277 } 11278 11279 /// Returns logical font name corresponding to the given font id, or `null`. 11280 /// $(WARNING Copy returned name, as name buffer can be invalidated by next FontStash API call!) 11281 const(char)[] getNameById (int idx) const pure nothrow @trusted @nogc { 11282 if (idx < 0 || idx >= nfonts || fonts[idx] is null) return null; 11283 return fonts[idx].name[0..fonts[idx].namelen]; 11284 } 11285 11286 /// Returns font id corresponding to the given logical font name, or [FONS_INVALID]. 11287 int getFontByName (const(char)[] name) const pure nothrow @trusted @nogc { 11288 //{ import core.stdc.stdio; printf("fonsGetFontByName: [%.*s]\n", cast(uint)name.length, name.ptr); } 11289 // remove ":noaa" suffix 11290 if (name.length >= NoAlias.length && strequci(name[$-NoAlias.length..$], NoAlias)) { 11291 name = name[0..$-NoAlias.length]; 11292 } 11293 if (name.length == 0) return FONS_INVALID; 11294 return findNameInHash(name); 11295 } 11296 11297 /** Measures the specified text string. Parameter bounds should be a float[4], 11298 * if the bounding box of the text should be returned. The bounds value are [xmin, ymin, xmax, ymax] 11299 * Returns the horizontal advance of the measured text (i.e. where the next character should drawn). 11300 */ 11301 float getTextBounds(T) (float x, float y, const(T)[] str, float[] bounds) nothrow @trusted @nogc if (isAnyCharType!T) { 11302 FONSstate* state = getState; 11303 uint codepoint; 11304 uint utf8state = 0; 11305 FONSQuad q; 11306 FONSglyph* glyph = null; 11307 int prevGlyphIndex = -1; 11308 short isize = cast(short)(state.size*10.0f); 11309 short iblur = cast(short)state.blur; 11310 FONSfont* font; 11311 11312 if (state.font < 0 || state.font >= nfonts) return 0; 11313 font = fonts[state.font]; 11314 if (font is null || font.fdata is null) return 0; 11315 11316 float scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f); 11317 11318 // Align vertically. 11319 y += getVertAlign(font, state.talign, isize); 11320 11321 float minx = x, maxx = x; 11322 float miny = y, maxy = y; 11323 float startx = x; 11324 11325 foreach (T ch; str) { 11326 static if (T.sizeof == 1) { 11327 //if (fons__decutf8(&utf8state, &codepoint, *cast(const(ubyte)*)str)) continue; 11328 mixin(DecUtfMixin!("utf8state", "codepoint", "(cast(ubyte)ch)")); 11329 if (utf8state) continue; 11330 } else { 11331 static if (T.sizeof == 4) { 11332 if (ch > dchar.max) ch = 0xFFFD; 11333 } 11334 codepoint = cast(uint)ch; 11335 } 11336 glyph = getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional); 11337 if (glyph !is null) { 11338 getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q); 11339 if (q.x0 < minx) minx = q.x0; 11340 if (q.x1 > maxx) maxx = q.x1; 11341 if (params.isZeroTopLeft) { 11342 if (q.y0 < miny) miny = q.y0; 11343 if (q.y1 > maxy) maxy = q.y1; 11344 } else { 11345 if (q.y1 < miny) miny = q.y1; 11346 if (q.y0 > maxy) maxy = q.y0; 11347 } 11348 prevGlyphIndex = glyph.index; 11349 } else { 11350 //{ import core.stdc.stdio; printf("NO GLYPH FOR 0x%04x\n", cast(uint)codepoint); } 11351 prevGlyphIndex = -1; 11352 } 11353 } 11354 11355 float advance = x-startx; 11356 //{ import core.stdc.stdio; printf("***: x=%g; startx=%g; advance=%g\n", cast(double)x, cast(double)startx, cast(double)advance); } 11357 11358 // Align horizontally 11359 if (state.talign.left) { 11360 // empty 11361 } else if (state.talign.right) { 11362 minx -= advance; 11363 maxx -= advance; 11364 } else if (state.talign.center) { 11365 minx -= advance*0.5f; 11366 maxx -= advance*0.5f; 11367 } 11368 11369 if (bounds.length) { 11370 if (bounds.length > 0) bounds.ptr[0] = minx; 11371 if (bounds.length > 1) bounds.ptr[1] = miny; 11372 if (bounds.length > 2) bounds.ptr[2] = maxx; 11373 if (bounds.length > 3) bounds.ptr[3] = maxy; 11374 } 11375 11376 return advance; 11377 } 11378 11379 /// Returns various font metrics. Any argument can be `null` if you aren't interested in its value. 11380 void getVertMetrics (float* ascender, float* descender, float* lineh) nothrow @trusted @nogc { 11381 FONSstate* state = getState; 11382 if (state.font < 0 || state.font >= nfonts) { 11383 if (ascender !is null) *ascender = 0; 11384 if (descender !is null) *descender = 0; 11385 if (lineh !is null) *lineh = 0; 11386 } else { 11387 FONSfont* font = fonts[state.font]; 11388 if (font is null || font.fdata is null) { 11389 if (ascender !is null) *ascender = 0; 11390 if (descender !is null) *descender = 0; 11391 if (lineh !is null) *lineh = 0; 11392 } else { 11393 short isize = cast(short)(state.size*10.0f); 11394 if (ascender !is null) *ascender = font.ascender*isize/10.0f; 11395 if (descender !is null) *descender = font.descender*isize/10.0f; 11396 if (lineh !is null) *lineh = font.lineh*isize/10.0f; 11397 } 11398 } 11399 } 11400 11401 /// Returns line bounds. Any argument can be `null` if you aren't interested in its value. 11402 void getLineBounds (float y, float* minyp, float* maxyp) nothrow @trusted @nogc { 11403 FONSfont* font; 11404 FONSstate* state = getState; 11405 short isize; 11406 11407 if (minyp !is null) *minyp = 0; 11408 if (maxyp !is null) *maxyp = 0; 11409 11410 if (state.font < 0 || state.font >= nfonts) return; 11411 font = fonts[state.font]; 11412 isize = cast(short)(state.size*10.0f); 11413 if (font is null || font.fdata is null) return; 11414 11415 y += getVertAlign(font, state.talign, isize); 11416 11417 if (params.isZeroTopLeft) { 11418 immutable float miny = y-font.ascender*cast(float)isize/10.0f; 11419 immutable float maxy = miny+font.lineh*isize/10.0f; 11420 if (minyp !is null) *minyp = miny; 11421 if (maxyp !is null) *maxyp = maxy; 11422 } else { 11423 immutable float maxy = y+font.descender*cast(float)isize/10.0f; 11424 immutable float miny = maxy-font.lineh*isize/10.0f; 11425 if (minyp !is null) *minyp = miny; 11426 if (maxyp !is null) *maxyp = maxy; 11427 } 11428 } 11429 11430 /// Returns font line height. 11431 float fontHeight () nothrow @trusted @nogc { 11432 float res = void; 11433 getVertMetrics(null, null, &res); 11434 return res; 11435 } 11436 11437 /// Returns font ascender (positive). 11438 float fontAscender () nothrow @trusted @nogc { 11439 float res = void; 11440 getVertMetrics(&res, null, null); 11441 return res; 11442 } 11443 11444 /// Returns font descender (negative). 11445 float fontDescender () nothrow @trusted @nogc { 11446 float res = void; 11447 getVertMetrics(null, &res, null); 11448 return res; 11449 } 11450 11451 //TODO: document this 11452 const(ubyte)* getTextureData (int* width, int* height) nothrow @trusted @nogc { 11453 if (width !is null) *width = params.width; 11454 if (height !is null) *height = params.height; 11455 return texData; 11456 } 11457 11458 //TODO: document this 11459 bool validateTexture (int* dirty) nothrow @trusted @nogc { 11460 if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) { 11461 dirty[0] = dirtyRect.ptr[0]; 11462 dirty[1] = dirtyRect.ptr[1]; 11463 dirty[2] = dirtyRect.ptr[2]; 11464 dirty[3] = dirtyRect.ptr[3]; 11465 // reset dirty rect 11466 dirtyRect.ptr[0] = params.width; 11467 dirtyRect.ptr[1] = params.height; 11468 dirtyRect.ptr[2] = 0; 11469 dirtyRect.ptr[3] = 0; 11470 return true; 11471 } 11472 return false; 11473 } 11474 11475 //TODO: document this 11476 void errorCallback (void delegate (FONSError error, int val) nothrow @trusted @nogc callback) nothrow @trusted @nogc { 11477 handleError = callback; 11478 } 11479 11480 //TODO: document this 11481 void getAtlasSize (int* width, int* height) const pure nothrow @trusted @nogc { 11482 if (width !is null) *width = params.width; 11483 if (height !is null) *height = params.height; 11484 } 11485 11486 //TODO: document this 11487 bool expandAtlas (int width, int height) nothrow @trusted @nogc { 11488 import core.stdc.stdlib : free; 11489 import core.stdc..string : memcpy, memset; 11490 11491 int maxy = 0; 11492 ubyte* data = null; 11493 11494 width = nvg__max(width, params.width); 11495 height = nvg__max(height, params.height); 11496 11497 if (width == params.width && height == params.height) return true; 11498 11499 // Flush pending glyphs. 11500 flush(); 11501 11502 // Create new texture 11503 if (params.renderResize !is null) { 11504 if (params.renderResize(params.userPtr, width, height) == 0) return false; 11505 } 11506 // Copy old texture data over. 11507 data = cast(ubyte*)malloc(width*height); 11508 if (data is null) return 0; 11509 foreach (immutable int i; 0..params.height) { 11510 ubyte* dst = &data[i*width]; 11511 ubyte* src = &texData[i*params.width]; 11512 memcpy(dst, src, params.width); 11513 if (width > params.width) memset(dst+params.width, 0, width-params.width); 11514 } 11515 if (height > params.height) memset(&data[params.height*width], 0, (height-params.height)*width); 11516 11517 free(texData); 11518 texData = data; 11519 11520 // Increase atlas size 11521 atlas.expand(width, height); 11522 11523 // Add existing data as dirty. 11524 foreach (immutable int i; 0..atlas.nnodes) maxy = nvg__max(maxy, atlas.nodes[i].y); 11525 dirtyRect.ptr[0] = 0; 11526 dirtyRect.ptr[1] = 0; 11527 dirtyRect.ptr[2] = params.width; 11528 dirtyRect.ptr[3] = maxy; 11529 11530 params.width = width; 11531 params.height = height; 11532 itw = 1.0f/params.width; 11533 ith = 1.0f/params.height; 11534 11535 return true; 11536 } 11537 11538 //TODO: document this 11539 bool resetAtlas (int width, int height) nothrow @trusted @nogc { 11540 import core.stdc.stdlib : realloc; 11541 import core.stdc..string : memcpy, memset; 11542 11543 // flush pending glyphs 11544 flush(); 11545 11546 // create new texture 11547 if (params.renderResize !is null) { 11548 if (params.renderResize(params.userPtr, width, height) == 0) return false; 11549 } 11550 11551 // reset atlas 11552 atlas.reset(width, height); 11553 11554 // clear texture data 11555 texData = cast(ubyte*)realloc(texData, width*height); 11556 if (texData is null) assert(0, "FONS: out of memory"); 11557 memset(texData, 0, width*height); 11558 11559 // reset dirty rect 11560 dirtyRect.ptr[0] = width; 11561 dirtyRect.ptr[1] = height; 11562 dirtyRect.ptr[2] = 0; 11563 dirtyRect.ptr[3] = 0; 11564 11565 // Reset cached glyphs 11566 foreach (FONSfont* font; fonts[0..nfonts]) { 11567 if (font !is null) { 11568 font.nglyphs = 0; 11569 font.lut.ptr[0..FONS_HASH_LUT_SIZE] = -1; 11570 } 11571 } 11572 11573 params.width = width; 11574 params.height = height; 11575 itw = 1.0f/params.width; 11576 ith = 1.0f/params.height; 11577 11578 // Add white rect at 0, 0 for debug drawing. 11579 addWhiteRect(2, 2); 11580 11581 return true; 11582 } 11583 11584 //TODO: document this 11585 bool getPathBounds (dchar dch, float[] bounds) nothrow @trusted @nogc { 11586 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 11587 static if (is(typeof(&fons__nvg__bounds))) { 11588 FONSstate* state = getState; 11589 if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; } 11590 FONSfont* font; 11591 auto g = findGlyphForCP(fonts[state.font], dch, &font); 11592 if (g == 0) { bounds[] = 0; return false; } 11593 assert(font !is null); 11594 return fons__nvg__bounds(&font.font, g, bounds); 11595 } else { 11596 bounds[] = 0; 11597 return false; 11598 } 11599 } 11600 11601 //TODO: document this 11602 bool toPath() (NVGContext vg, dchar dch, float[] bounds=null) nothrow @trusted @nogc { 11603 if (bounds.length > 4) bounds = bounds.ptr[0..4]; 11604 static if (is(typeof(&fons__nvg__toPath))) { 11605 if (vg is null) { bounds[] = 0; return false; } 11606 FONSstate* state = getState; 11607 if (state.font < 0 || state.font >= nfonts) { bounds[] = 0; return false; } 11608 FONSfont* font; 11609 auto g = findGlyphForCP(fonts[state.font], dch, &font); 11610 if (g == 0) { bounds[] = 0; return false; } 11611 assert(font !is null); 11612 return fons__nvg__toPath(vg, &font.font, g, bounds); 11613 } else { 11614 bounds[] = 0; 11615 return false; 11616 } 11617 } 11618 11619 //TODO: document this 11620 bool toOutline (dchar dch, NVGPathOutline.DataStore* ol) nothrow @trusted @nogc { 11621 if (ol is null) return false; 11622 static if (is(typeof(&fons__nvg__toOutline))) { 11623 FONSstate* state = getState; 11624 if (state.font < 0 || state.font >= nfonts) return false; 11625 FONSfont* font; 11626 auto g = findGlyphForCP(fonts[state.font], dch, &font); 11627 if (g == 0) return false; 11628 assert(font !is null); 11629 return fons__nvg__toOutline(&font.font, g, ol); 11630 } else { 11631 return false; 11632 } 11633 } 11634 11635 //TODO: document this 11636 FONSglyph* getGlyph (FONSfont* font, uint codepoint, short isize, short iblur, FONSBitmapFlag bitmapOption) nothrow @trusted @nogc { 11637 static uint fons__hashint() (uint a) pure nothrow @safe @nogc { 11638 pragma(inline, true); 11639 a += ~(a<<15); 11640 a ^= (a>>10); 11641 a += (a<<3); 11642 a ^= (a>>6); 11643 a += ~(a<<11); 11644 a ^= (a>>16); 11645 return a; 11646 } 11647 11648 // based on Exponential blur, Jani Huhtanen, 2006 11649 enum APREC = 16; 11650 enum ZPREC = 7; 11651 11652 static void fons__blurCols (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc { 11653 foreach (immutable int y; 0..h) { 11654 int z = 0; // force zero border 11655 foreach (int x; 1..w) { 11656 z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC; 11657 dst[x] = cast(ubyte)(z>>ZPREC); 11658 } 11659 dst[w-1] = 0; // force zero border 11660 z = 0; 11661 for (int x = w-2; x >= 0; --x) { 11662 z += (alpha*((cast(int)(dst[x])<<ZPREC)-z))>>APREC; 11663 dst[x] = cast(ubyte)(z>>ZPREC); 11664 } 11665 dst[0] = 0; // force zero border 11666 dst += dstStride; 11667 } 11668 } 11669 11670 static void fons__blurRows (ubyte* dst, int w, int h, int dstStride, int alpha) nothrow @trusted @nogc { 11671 foreach (immutable int x; 0..w) { 11672 int z = 0; // force zero border 11673 for (int y = dstStride; y < h*dstStride; y += dstStride) { 11674 z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC; 11675 dst[y] = cast(ubyte)(z>>ZPREC); 11676 } 11677 dst[(h-1)*dstStride] = 0; // force zero border 11678 z = 0; 11679 for (int y = (h-2)*dstStride; y >= 0; y -= dstStride) { 11680 z += (alpha*((cast(int)(dst[y])<<ZPREC)-z))>>APREC; 11681 dst[y] = cast(ubyte)(z>>ZPREC); 11682 } 11683 dst[0] = 0; // force zero border 11684 ++dst; 11685 } 11686 } 11687 11688 static void fons__blur (ubyte* dst, int w, int h, int dstStride, int blur) nothrow @trusted @nogc { 11689 import std.math : expf = exp; 11690 if (blur < 1) return; 11691 // Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity) 11692 immutable float sigma = cast(float)blur*0.57735f; // 1/sqrt(3) 11693 int alpha = cast(int)((1<<APREC)*(1.0f-expf(-2.3f/(sigma+1.0f)))); 11694 fons__blurRows(dst, w, h, dstStride, alpha); 11695 fons__blurCols(dst, w, h, dstStride, alpha); 11696 fons__blurRows(dst, w, h, dstStride, alpha); 11697 fons__blurCols(dst, w, h, dstStride, alpha); 11698 //fons__blurrows(dst, w, h, dstStride, alpha); 11699 //fons__blurcols(dst, w, h, dstStride, alpha); 11700 } 11701 11702 int advance, lsb, x0, y0, x1, y1, gx, gy; 11703 FONSglyph* glyph = null; 11704 float size = isize/10.0f; 11705 FONSfont* renderFont = font; 11706 11707 if (isize < 2) return null; 11708 if (iblur > 20) iblur = 20; 11709 int pad = iblur+2; 11710 11711 // Reset allocator. 11712 nscratch = 0; 11713 11714 // Find code point and size. 11715 uint h = fons__hashint(codepoint)&(FONS_HASH_LUT_SIZE-1); 11716 int i = font.lut.ptr[h]; 11717 while (i != -1) { 11718 //if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) return &font.glyphs[i]; 11719 if (font.glyphs[i].codepoint == codepoint && font.glyphs[i].size == isize && font.glyphs[i].blur == iblur) { 11720 glyph = &font.glyphs[i]; 11721 // Negative coordinate indicates there is no bitmap data created. 11722 if (bitmapOption == FONSBitmapFlag.Optional || (glyph.x0 >= 0 && glyph.y0 >= 0)) return glyph; 11723 // At this point, glyph exists but the bitmap data is not yet created. 11724 break; 11725 } 11726 i = font.glyphs[i].next; 11727 } 11728 11729 // Create a new glyph or rasterize bitmap data for a cached glyph. 11730 //scale = fons__tt_getPixelHeightScale(&font.font, size); 11731 int g = findGlyphForCP(font, cast(dchar)codepoint, &renderFont); 11732 // It is possible that we did not find a fallback glyph. 11733 // In that case the glyph index 'g' is 0, and we'll proceed below and cache empty glyph. 11734 11735 float scale = fons__tt_getPixelHeightScale(&renderFont.font, size); 11736 fons__tt_buildGlyphBitmap(&renderFont.font, g, size, scale, &advance, &lsb, &x0, &y0, &x1, &y1); 11737 int gw = x1-x0+pad*2; 11738 int gh = y1-y0+pad*2; 11739 11740 // Determines the spot to draw glyph in the atlas. 11741 if (bitmapOption == FONSBitmapFlag.Required) { 11742 // Find free spot for the rect in the atlas. 11743 bool added = atlas.addRect(gw, gh, &gx, &gy); 11744 if (!added && handleError !is null) { 11745 // Atlas is full, let the user to resize the atlas (or not), and try again. 11746 handleError(FONSError.AtlasFull, 0); 11747 added = atlas.addRect(gw, gh, &gx, &gy); 11748 } 11749 if (!added) return null; 11750 } else { 11751 // Negative coordinate indicates there is no bitmap data created. 11752 gx = -1; 11753 gy = -1; 11754 } 11755 11756 // Init glyph. 11757 if (glyph is null) { 11758 glyph = font.allocGlyph(); 11759 glyph.codepoint = codepoint; 11760 glyph.size = isize; 11761 glyph.blur = iblur; 11762 glyph.next = 0; 11763 11764 // Insert char to hash lookup. 11765 glyph.next = font.lut.ptr[h]; 11766 font.lut.ptr[h] = font.nglyphs-1; 11767 } 11768 glyph.index = g; 11769 glyph.x0 = cast(short)gx; 11770 glyph.y0 = cast(short)gy; 11771 glyph.x1 = cast(short)(glyph.x0+gw); 11772 glyph.y1 = cast(short)(glyph.y0+gh); 11773 glyph.xadv = cast(short)(scale*advance*10.0f); 11774 glyph.xoff = cast(short)(x0-pad); 11775 glyph.yoff = cast(short)(y0-pad); 11776 11777 if (bitmapOption == FONSBitmapFlag.Optional) return glyph; 11778 11779 // Rasterize 11780 ubyte* dst = &texData[(glyph.x0+pad)+(glyph.y0+pad)*params.width]; 11781 fons__tt_renderGlyphBitmap(&font.font, dst, gw-pad*2, gh-pad*2, params.width, scale, scale, g); 11782 11783 // Make sure there is one pixel empty border. 11784 dst = &texData[glyph.x0+glyph.y0*params.width]; 11785 foreach (immutable int y; 0..gh) { 11786 dst[y*params.width] = 0; 11787 dst[gw-1+y*params.width] = 0; 11788 } 11789 foreach (immutable int x; 0..gw) { 11790 dst[x] = 0; 11791 dst[x+(gh-1)*params.width] = 0; 11792 } 11793 11794 // Debug code to color the glyph background 11795 version(none) { 11796 foreach (immutable yy; 0..gh) { 11797 foreach (immutable xx; 0..gw) { 11798 int a = cast(int)dst[xx+yy*params.width]+42; 11799 if (a > 255) a = 255; 11800 dst[xx+yy*params.width] = cast(ubyte)a; 11801 } 11802 } 11803 } 11804 11805 // Blur 11806 if (iblur > 0) { 11807 nscratch = 0; 11808 ubyte* bdst = &texData[glyph.x0+glyph.y0*params.width]; 11809 fons__blur(bdst, gw, gh, params.width, iblur); 11810 } 11811 11812 dirtyRect.ptr[0] = nvg__min(dirtyRect.ptr[0], glyph.x0); 11813 dirtyRect.ptr[1] = nvg__min(dirtyRect.ptr[1], glyph.y0); 11814 dirtyRect.ptr[2] = nvg__max(dirtyRect.ptr[2], glyph.x1); 11815 dirtyRect.ptr[3] = nvg__max(dirtyRect.ptr[3], glyph.y1); 11816 11817 return glyph; 11818 } 11819 11820 //TODO: document this 11821 void getQuad (FONSfont* font, int prevGlyphIndex, FONSglyph* glyph, float size, float scale, float spacing, float* x, float* y, FONSQuad* q) nothrow @trusted @nogc { 11822 if (prevGlyphIndex >= 0) { 11823 immutable float adv = fons__tt_getGlyphKernAdvance(&font.font, size, prevGlyphIndex, glyph.index)/**scale*/; //k8: do we really need scale here? 11824 //if (adv != 0) { import core.stdc.stdio; printf("adv=%g (scale=%g; spacing=%g)\n", cast(double)adv, cast(double)scale, cast(double)spacing); } 11825 *x += cast(int)(adv+spacing /*+0.5f*/); //k8: for me, it looks better this way (with non-aa fonts) 11826 } 11827 11828 // Each glyph has 2px border to allow good interpolation, 11829 // one pixel to prevent leaking, and one to allow good interpolation for rendering. 11830 // Inset the texture region by one pixel for correct interpolation. 11831 immutable float xoff = cast(short)(glyph.xoff+1); 11832 immutable float yoff = cast(short)(glyph.yoff+1); 11833 immutable float x0 = cast(float)(glyph.x0+1); 11834 immutable float y0 = cast(float)(glyph.y0+1); 11835 immutable float x1 = cast(float)(glyph.x1-1); 11836 immutable float y1 = cast(float)(glyph.y1-1); 11837 11838 if (params.isZeroTopLeft) { 11839 immutable float rx = cast(float)cast(int)(*x+xoff); 11840 immutable float ry = cast(float)cast(int)(*y+yoff); 11841 11842 q.x0 = rx; 11843 q.y0 = ry; 11844 q.x1 = rx+x1-x0; 11845 q.y1 = ry+y1-y0; 11846 11847 q.s0 = x0*itw; 11848 q.t0 = y0*ith; 11849 q.s1 = x1*itw; 11850 q.t1 = y1*ith; 11851 } else { 11852 immutable float rx = cast(float)cast(int)(*x+xoff); 11853 immutable float ry = cast(float)cast(int)(*y-yoff); 11854 11855 q.x0 = rx; 11856 q.y0 = ry; 11857 q.x1 = rx+x1-x0; 11858 q.y1 = ry-y1+y0; 11859 11860 q.s0 = x0*itw; 11861 q.t0 = y0*ith; 11862 q.s1 = x1*itw; 11863 q.t1 = y1*ith; 11864 } 11865 11866 *x += cast(int)(glyph.xadv/10.0f+0.5f); 11867 } 11868 11869 void flush () nothrow @trusted @nogc { 11870 // flush texture 11871 if (dirtyRect.ptr[0] < dirtyRect.ptr[2] && dirtyRect.ptr[1] < dirtyRect.ptr[3]) { 11872 if (params.renderUpdate !is null) params.renderUpdate(params.userPtr, dirtyRect.ptr, texData); 11873 // reset dirty rect 11874 dirtyRect.ptr[0] = params.width; 11875 dirtyRect.ptr[1] = params.height; 11876 dirtyRect.ptr[2] = 0; 11877 dirtyRect.ptr[3] = 0; 11878 } 11879 } 11880 } 11881 11882 /// Free all resources used by the `stash`, and `stash` itself. 11883 /// Group: font_stash 11884 public void kill (ref FONSContext stash) nothrow @trusted @nogc { 11885 import core.stdc.stdlib : free; 11886 if (stash is null) return; 11887 stash.clear(); 11888 free(stash); 11889 stash = null; 11890 } 11891 11892 11893 // ////////////////////////////////////////////////////////////////////////// // 11894 void* fons__tmpalloc (usize size, void* up) nothrow @trusted @nogc { 11895 ubyte* ptr; 11896 FONSContext stash = cast(FONSContext)up; 11897 // 16-byte align the returned pointer 11898 size = (size+0xf)&~0xf; 11899 if (stash.nscratch+cast(int)size > FONS_SCRATCH_BUF_SIZE) { 11900 if (stash.handleError !is null) stash.handleError(FONSError.ScratchFull, stash.nscratch+cast(int)size); 11901 return null; 11902 } 11903 ptr = stash.scratch+stash.nscratch; 11904 stash.nscratch += cast(int)size; 11905 return ptr; 11906 } 11907 11908 void fons__tmpfree (void* ptr, void* up) nothrow @trusted @nogc { 11909 // empty 11910 } 11911 11912 11913 // ////////////////////////////////////////////////////////////////////////// // 11914 // Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de> 11915 // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. 11916 11917 enum FONS_UTF8_ACCEPT = 0; 11918 enum FONS_UTF8_REJECT = 12; 11919 11920 static immutable ubyte[364] utf8d = [ 11921 // The first part of the table maps bytes to character classes that 11922 // to reduce the size of the transition table and create bitmasks. 11923 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, 11924 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, 11925 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, 11926 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, 11927 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, 11928 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, 11929 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, 11930 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, 11931 11932 // The second part is a transition table that maps a combination 11933 // of a state of the automaton and a character class to a state. 11934 0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 11935 12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12, 11936 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 11937 12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 11938 12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 11939 ]; 11940 11941 private enum DecUtfMixin(string state, string codep, string byte_) = 11942 `{ 11943 uint type_ = utf8d.ptr[`~byte_~`]; 11944 `~codep~` = (`~state~` != FONS_UTF8_ACCEPT ? (`~byte_~`&0x3fu)|(`~codep~`<<6) : (0xff>>type_)&`~byte_~`); 11945 if ((`~state~` = utf8d.ptr[256+`~state~`+type_]) == FONS_UTF8_REJECT) { 11946 `~state~` = FONS_UTF8_ACCEPT; 11947 `~codep~` = 0xFFFD; 11948 } 11949 }`; 11950 11951 /* 11952 uint fons__decutf8 (uint* state, uint* codep, uint byte_) { 11953 pragma(inline, true); 11954 uint type = utf8d.ptr[byte_]; 11955 *codep = (*state != FONS_UTF8_ACCEPT ? (byte_&0x3fu)|(*codep<<6) : (0xff>>type)&byte_); 11956 *state = utf8d.ptr[256 + *state+type]; 11957 return *state; 11958 } 11959 */ 11960 11961 11962 // ////////////////////////////////////////////////////////////////////////// // 11963 /// This iterator can be used to do text measurement. 11964 /// $(WARNING Don't add new fonts to stash while you are iterating, or you WILL get segfault!) 11965 /// Group: font_stash 11966 public struct FONSTextBoundsIterator { 11967 private: 11968 FONSContext stash; 11969 FONSstate state; 11970 uint codepoint = 0xFFFD; 11971 uint utf8state = 0; 11972 int prevGlyphIndex = -1; 11973 short isize, iblur; 11974 float scale = 0; 11975 FONSfont* font; 11976 float startx = 0, x = 0, y = 0; 11977 float minx = 0, miny = 0, maxx = 0, maxy = 0; 11978 11979 private: 11980 void clear () nothrow @trusted @nogc { 11981 import core.stdc..string : memset; 11982 memset(&this, 0, this.sizeof); 11983 this.prevGlyphIndex = -1; 11984 this.codepoint = 0xFFFD; 11985 } 11986 11987 public: 11988 /// Initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator. 11989 this (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc { reset(astash, ax, ay); } 11990 11991 /// (Re)initialize iterator with the current FontStash state. FontStash state can be changed after initialization without affecting the iterator. 11992 void reset (FONSContext astash, float ax=0, float ay=0) nothrow @trusted @nogc { 11993 clear(); 11994 11995 if (astash is null || astash.nstates == 0) return; 11996 11997 stash = astash; 11998 state = *stash.getState; 11999 12000 if (state.font < 0 || state.font >= stash.nfonts) { clear(); return; } 12001 font = stash.fonts[state.font]; 12002 if (font is null || font.fdata is null) { clear(); return; } 12003 12004 x = ax; 12005 y = ay; 12006 isize = cast(short)(state.size*10.0f); 12007 iblur = cast(short)state.blur; 12008 scale = fons__tt_getPixelHeightScale(&font.font, cast(float)isize/10.0f); 12009 12010 // align vertically 12011 y += astash.getVertAlign(font, state.talign, isize); 12012 12013 minx = maxx = x; 12014 miny = maxy = y; 12015 startx = x; 12016 } 12017 12018 /// Can this iterator be used? 12019 @property bool valid () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null); } 12020 12021 /// Put some text into iterator, calculate new values. 12022 void put(T) (const(T)[] str...) nothrow @trusted @nogc if (isAnyCharType!T) { 12023 enum DoCodePointMixin = q{ 12024 glyph = stash.getGlyph(font, codepoint, isize, iblur, FONSBitmapFlag.Optional); 12025 if (glyph !is null) { 12026 stash.getQuad(font, prevGlyphIndex, glyph, isize/10.0f, scale, state.spacing, &x, &y, &q); 12027 if (q.x0 < minx) minx = q.x0; 12028 if (q.x1 > maxx) maxx = q.x1; 12029 if (stash.params.isZeroTopLeft) { 12030 if (q.y0 < miny) miny = q.y0; 12031 if (q.y1 > maxy) maxy = q.y1; 12032 } else { 12033 if (q.y1 < miny) miny = q.y1; 12034 if (q.y0 > maxy) maxy = q.y0; 12035 } 12036 prevGlyphIndex = glyph.index; 12037 } else { 12038 prevGlyphIndex = -1; 12039 } 12040 }; 12041 12042 if (stash is null || str.length == 0) return; // alas 12043 12044 FONSQuad q; 12045 FONSglyph* glyph; 12046 12047 static if (is(T == char)) { 12048 foreach (char ch; str) { 12049 mixin(DecUtfMixin!("utf8state", "codepoint", "cast(ubyte)ch")); 12050 if (utf8state) continue; // full char is not collected yet 12051 mixin(DoCodePointMixin); 12052 } 12053 } else { 12054 if (utf8state) { 12055 utf8state = 0; 12056 codepoint = 0xFFFD; 12057 mixin(DoCodePointMixin); 12058 } 12059 foreach (T dch; str) { 12060 static if (is(T == dchar)) { 12061 if (dch > dchar.max) dch = 0xFFFD; 12062 } 12063 codepoint = cast(uint)dch; 12064 mixin(DoCodePointMixin); 12065 } 12066 } 12067 } 12068 12069 /// Returns current advance. 12070 @property float advance () const pure nothrow @safe @nogc { pragma(inline, true); return (stash !is null ? x-startx : 0); } 12071 12072 /// Returns current text bounds. 12073 void getBounds (ref float[4] bounds) const pure nothrow @safe @nogc { 12074 if (stash is null) { bounds[] = 0; return; } 12075 float lminx = minx, lmaxx = maxx; 12076 // align horizontally 12077 if (state.talign.left) { 12078 // empty 12079 } else if (state.talign.right) { 12080 float ca = advance; 12081 lminx -= ca; 12082 lmaxx -= ca; 12083 } else if (state.talign.center) { 12084 float ca = advance*0.5f; 12085 lminx -= ca; 12086 lmaxx -= ca; 12087 } 12088 bounds[0] = lminx; 12089 bounds[1] = miny; 12090 bounds[2] = lmaxx; 12091 bounds[3] = maxy; 12092 } 12093 12094 /// Returns current horizontal text bounds. 12095 void getHBounds (out float xmin, out float xmax) nothrow @trusted @nogc { 12096 if (stash !is null) { 12097 float lminx = minx, lmaxx = maxx; 12098 // align horizontally 12099 if (state.talign.left) { 12100 // empty 12101 } else if (state.talign.right) { 12102 float ca = advance; 12103 lminx -= ca; 12104 lmaxx -= ca; 12105 } else if (state.talign.center) { 12106 float ca = advance*0.5f; 12107 lminx -= ca; 12108 lmaxx -= ca; 12109 } 12110 xmin = lminx; 12111 xmax = lmaxx; 12112 } else { 12113 xmin = xmax = 0; 12114 } 12115 } 12116 12117 /// Returns current vertical text bounds. 12118 void getVBounds (out float ymin, out float ymax) nothrow @trusted @nogc { 12119 pragma(inline, true); 12120 if (stash !is null) { 12121 ymin = miny; 12122 ymax = maxy; 12123 } else { 12124 ymin = ymax = 0; 12125 } 12126 } 12127 12128 /// Returns font line height. 12129 float lineHeight () nothrow @trusted @nogc { 12130 pragma(inline, true); 12131 return (stash !is null ? stash.fonts[state.font].lineh*cast(short)(state.size*10.0f)/10.0f : 0); 12132 } 12133 12134 /// Returns font ascender (positive). 12135 float ascender () nothrow @trusted @nogc { 12136 pragma(inline, true); 12137 return (stash !is null ? stash.fonts[state.font].ascender*cast(short)(state.size*10.0f)/10.0f : 0); 12138 } 12139 12140 /// Returns font descender (negative). 12141 float descender () nothrow @trusted @nogc { 12142 pragma(inline, true); 12143 return (stash !is null ? stash.fonts[state.font].descender*cast(short)(state.size*10.0f)/10.0f : 0); 12144 } 12145 } 12146 12147 12148 // ////////////////////////////////////////////////////////////////////////// // 12149 // backgl 12150 // ////////////////////////////////////////////////////////////////////////// // 12151 import core.stdc.stdlib : malloc, realloc, free; 12152 import core.stdc..string : memcpy, memset; 12153 12154 static if (__VERSION__ < 2076) { 12155 private auto DGNoThrowNoGC(T) (scope T t) /*if (isFunctionPointer!T || isDelegate!T)*/ { 12156 import std.traits; 12157 enum attrs = functionAttributes!T|FunctionAttribute.nogc|FunctionAttribute.nothrow_; 12158 return cast(SetFunctionAttributes!(T, functionLinkage!T, attrs)) t; 12159 } 12160 } 12161 12162 12163 //import arsd.simpledisplay; 12164 version(nanovg_bindbc_opengl_bindings) { 12165 import bindbc.opengl; 12166 } else version(nanovg_builtin_opengl_bindings) { 12167 import arsd.simpledisplay; 12168 12169 /++ 12170 A SimpleWindow subclass that encapsulates some nanovega defaults. You just set a `redrawNVGScene` delegate and, optionally, your nromal event handlers for simpledisplay, and the rest is set up for you. 12171 12172 History: 12173 Added January 22, 2021 (version 9.2 release) 12174 +/ 12175 public class NVGWindow : SimpleWindow { 12176 NVGContext nvg; 12177 12178 /++ 12179 12180 +/ 12181 this(int width, int height, string title) { 12182 setOpenGLContextVersion(3, 0); 12183 super(width, height, title, OpenGlOptions.yes, Resizability.allowResizing); 12184 12185 this.onClosing = delegate() { 12186 nvg.kill(); 12187 }; 12188 12189 this.visibleForTheFirstTime = delegate() { 12190 nvg = nvgCreateContext(); 12191 if(nvg is null) throw new Exception("cannot initialize NanoVega"); 12192 }; 12193 12194 this.redrawOpenGlScene = delegate() { 12195 if(redrawNVGScene is null) 12196 return; 12197 glViewport(0, 0, this.width, this.height); 12198 if(clearOnEachFrame) { 12199 glClearColor(0, 0, 0, 0); 12200 glClear(glNVGClearFlags); 12201 } 12202 12203 nvg.beginFrame(this.width, this.height); 12204 scope(exit) nvg.endFrame(); 12205 12206 redrawNVGScene(nvg); 12207 }; 12208 12209 this.setEventHandlers( 12210 &redrawOpenGlSceneNow, 12211 (KeyEvent ke) { 12212 if(ke.key == Key.Escape || ke.key == Key.Q) 12213 this.close(); 12214 } 12215 ); 12216 } 12217 12218 /++ 12219 12220 +/ 12221 bool clearOnEachFrame = true; 12222 12223 /++ 12224 12225 +/ 12226 void delegate(NVGContext nvg) redrawNVGScene; 12227 12228 /++ 12229 12230 +/ 12231 void redrawNVGSceneNow() { 12232 redrawOpenGlSceneNow(); 12233 } 12234 } 12235 12236 } else { 12237 import iv.glbinds; 12238 } 12239 12240 private: 12241 // sdpy is missing that yet 12242 static if (!is(typeof(GL_STENCIL_BUFFER_BIT))) enum uint GL_STENCIL_BUFFER_BIT = 0x00000400; 12243 12244 12245 12246 version(bindbc){ 12247 private extern(System) nothrow @nogc: 12248 // this definition doesn't exist in regular OpenGL (?) 12249 enum uint GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9U; 12250 private void nanovgInitOpenGL () { 12251 // i'm not aware of calling the load multiple times having negative side effects, so i don't do an initialization check 12252 GLSupport support = loadOpenGL(); 12253 if (support == GLSupport.noLibrary) 12254 assert(0, "OpenGL initialization failed: shared library failed to load"); 12255 else if (support == GLSupport.badLibrary) 12256 assert(0, "OpenGL initialization failed: a context-independent symbol failed to load"); 12257 else if (support == GLSupport.noContext) 12258 assert(0, "OpenGL initialization failed: a context needs to be created prior to initialization"); 12259 } 12260 } else { // OpenGL API missing from simpledisplay 12261 private void nanovgInitOpenGL () @nogc nothrow { 12262 __gshared bool initialized = false; 12263 if (initialized) return; 12264 12265 try 12266 gl3.loadDynamicLibrary(); 12267 catch(Exception) 12268 assert(0, "GL 3 failed to load"); 12269 12270 initialized = true; 12271 } 12272 } 12273 12274 12275 12276 /// Context creation flags. 12277 /// Group: context_management 12278 public enum NVGContextFlag : int { 12279 /// Nothing special, i.e. empty flag. 12280 None = 0, 12281 /// Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA). 12282 Antialias = 1U<<0, 12283 /** Flag indicating if strokes should be drawn using stencil buffer. The rendering will be a little 12284 * slower, but path overlaps (i.e. self-intersecting or sharp turns) will be drawn just once. */ 12285 StencilStrokes = 1U<<1, 12286 /// Flag indicating that additional debug checks are done. 12287 Debug = 1U<<2, 12288 /// Filter (antialias) fonts 12289 FontAA = 1U<<7, 12290 /// Don't filter (antialias) fonts 12291 FontNoAA = 1U<<8, 12292 /// You can use this as a substitute for default flags, for cases like this: `nvgCreateContext(NVGContextFlag.Default, NVGContextFlag.Debug);`. 12293 Default = 1U<<31, 12294 } 12295 12296 public enum NANOVG_GL_USE_STATE_FILTER = true; 12297 12298 /// Returns flags for glClear(). 12299 /// Group: context_management 12300 public uint glNVGClearFlags () pure nothrow @safe @nogc { 12301 pragma(inline, true); 12302 return (GL_COLOR_BUFFER_BIT|/*GL_DEPTH_BUFFER_BIT|*/GL_STENCIL_BUFFER_BIT); 12303 } 12304 12305 12306 // ////////////////////////////////////////////////////////////////////////// // 12307 private: 12308 12309 version = nanovega_shared_stencil; 12310 //version = nanovega_debug_clipping; 12311 12312 enum GLNVGuniformLoc { 12313 ViewSize, 12314 Tex, 12315 Frag, 12316 TMat, 12317 TTr, 12318 ClipTex, 12319 } 12320 12321 alias GLNVGshaderType = int; 12322 enum /*GLNVGshaderType*/ { 12323 NSVG_SHADER_FILLCOLOR, 12324 NSVG_SHADER_FILLGRAD, 12325 NSVG_SHADER_FILLIMG, 12326 NSVG_SHADER_SIMPLE, // also used for clipfill 12327 NSVG_SHADER_IMG, 12328 } 12329 12330 struct GLNVGshader { 12331 GLuint prog; 12332 GLuint frag; 12333 GLuint vert; 12334 GLint[GLNVGuniformLoc.max+1] loc; 12335 } 12336 12337 struct GLNVGtexture { 12338 int id; 12339 GLuint tex; 12340 int width, height; 12341 NVGtexture type; 12342 int flags; 12343 shared int rc; // this can be 0 with tex != 0 -- postponed deletion 12344 int nextfree; 12345 } 12346 12347 struct GLNVGblend { 12348 bool simple; 12349 GLenum srcRGB; 12350 GLenum dstRGB; 12351 GLenum srcAlpha; 12352 GLenum dstAlpha; 12353 } 12354 12355 alias GLNVGcallType = int; 12356 enum /*GLNVGcallType*/ { 12357 GLNVG_NONE = 0, 12358 GLNVG_FILL, 12359 GLNVG_CONVEXFILL, 12360 GLNVG_STROKE, 12361 GLNVG_TRIANGLES, 12362 GLNVG_AFFINE, // change affine transformation matrix 12363 GLNVG_PUSHCLIP, 12364 GLNVG_POPCLIP, 12365 GLNVG_RESETCLIP, 12366 GLNVG_CLIP_DDUMP_ON, 12367 GLNVG_CLIP_DDUMP_OFF, 12368 } 12369 12370 struct GLNVGcall { 12371 int type; 12372 int evenOdd; // for fill 12373 int image; 12374 int pathOffset; 12375 int pathCount; 12376 int triangleOffset; 12377 int triangleCount; 12378 int uniformOffset; 12379 NVGMatrix affine; 12380 GLNVGblend blendFunc; 12381 NVGClipMode clipmode; 12382 } 12383 12384 struct GLNVGpath { 12385 int fillOffset; 12386 int fillCount; 12387 int strokeOffset; 12388 int strokeCount; 12389 } 12390 12391 align(1) struct GLNVGfragUniforms { 12392 align(1): 12393 enum UNIFORM_ARRAY_SIZE = 13; 12394 // note: after modifying layout or size of uniform array, 12395 // don't forget to also update the fragment shader source! 12396 align(1) union { 12397 align(1): 12398 align(1) struct { 12399 align(1): 12400 float[12] scissorMat; // matrices are actually 3 vec4s 12401 float[12] paintMat; 12402 NVGColor innerCol; 12403 NVGColor middleCol; 12404 NVGColor outerCol; 12405 float[2] scissorExt; 12406 float[2] scissorScale; 12407 float[2] extent; 12408 float radius; 12409 float feather; 12410 float strokeMult; 12411 float strokeThr; 12412 float texType; 12413 float type; 12414 float doclip; 12415 float midp; // for gradients 12416 float unused2, unused3; 12417 } 12418 float[4][UNIFORM_ARRAY_SIZE] uniformArray; 12419 } 12420 } 12421 12422 enum GLMaskState { 12423 DontMask = -1, 12424 Uninitialized = 0, 12425 Initialized = 1, 12426 JustCleared = 2, 12427 } 12428 12429 final class GLNVGTextureLocker {} 12430 12431 struct GLNVGcontext { 12432 private import core.thread : ThreadID; 12433 12434 GLNVGshader shader; 12435 GLNVGtexture* textures; 12436 float[2] view; 12437 int freetexid; // -1: none 12438 int ntextures; 12439 int ctextures; 12440 GLuint vertBuf; 12441 int fragSize; 12442 int flags; 12443 // FBOs for masks 12444 GLuint[NVG_MAX_STATES] fbo; 12445 GLuint[2][NVG_MAX_STATES] fboTex; // FBO textures: [0] is color, [1] is stencil 12446 int fboWidth, fboHeight; 12447 GLMaskState[NVG_MAX_STATES] maskStack; 12448 int msp; // mask stack pointer; starts from `0`; points to next free item; see below for logic description 12449 int lastClipFBO; // -666: cache invalidated; -1: don't mask 12450 int lastClipUniOfs; 12451 bool doClipUnion; // specal mode 12452 GLNVGshader shaderFillFBO; 12453 GLNVGshader shaderCopyFBO; 12454 12455 bool inFrame; // will be `true` if we can perform OpenGL operations (used in texture deletion) 12456 shared bool mustCleanTextures; // will be `true` if we should delete some textures 12457 ThreadID mainTID; 12458 uint mainFBO; 12459 12460 // Per frame buffers 12461 GLNVGcall* calls; 12462 int ccalls; 12463 int ncalls; 12464 GLNVGpath* paths; 12465 int cpaths; 12466 int npaths; 12467 NVGVertex* verts; 12468 int cverts; 12469 int nverts; 12470 ubyte* uniforms; 12471 int cuniforms; 12472 int nuniforms; 12473 NVGMatrix lastAffine; 12474 12475 // cached state 12476 static if (NANOVG_GL_USE_STATE_FILTER) { 12477 GLuint boundTexture; 12478 GLuint stencilMask; 12479 GLenum stencilFunc; 12480 GLint stencilFuncRef; 12481 GLuint stencilFuncMask; 12482 GLNVGblend blendFunc; 12483 } 12484 } 12485 12486 int glnvg__maxi() (int a, int b) { pragma(inline, true); return (a > b ? a : b); } 12487 12488 void glnvg__bindTexture (GLNVGcontext* gl, GLuint tex) nothrow @trusted @nogc { 12489 static if (NANOVG_GL_USE_STATE_FILTER) { 12490 if (gl.boundTexture != tex) { 12491 gl.boundTexture = tex; 12492 glBindTexture(GL_TEXTURE_2D, tex); 12493 } 12494 } else { 12495 glBindTexture(GL_TEXTURE_2D, tex); 12496 } 12497 } 12498 12499 void glnvg__stencilMask (GLNVGcontext* gl, GLuint mask) nothrow @trusted @nogc { 12500 static if (NANOVG_GL_USE_STATE_FILTER) { 12501 if (gl.stencilMask != mask) { 12502 gl.stencilMask = mask; 12503 glStencilMask(mask); 12504 } 12505 } else { 12506 glStencilMask(mask); 12507 } 12508 } 12509 12510 void glnvg__stencilFunc (GLNVGcontext* gl, GLenum func, GLint ref_, GLuint mask) nothrow @trusted @nogc { 12511 static if (NANOVG_GL_USE_STATE_FILTER) { 12512 if (gl.stencilFunc != func || gl.stencilFuncRef != ref_ || gl.stencilFuncMask != mask) { 12513 gl.stencilFunc = func; 12514 gl.stencilFuncRef = ref_; 12515 gl.stencilFuncMask = mask; 12516 glStencilFunc(func, ref_, mask); 12517 } 12518 } else { 12519 glStencilFunc(func, ref_, mask); 12520 } 12521 } 12522 12523 // texture id is never zero 12524 // sets refcount to one 12525 GLNVGtexture* glnvg__allocTexture (GLNVGcontext* gl) nothrow @trusted @nogc { 12526 GLNVGtexture* tex = null; 12527 12528 int tid = gl.freetexid; 12529 if (tid == -1) { 12530 if (gl.ntextures >= gl.ctextures) { 12531 assert(gl.ntextures == gl.ctextures); 12532 //pragma(msg, GLNVGtexture.sizeof*32); 12533 int ctextures = (gl.ctextures == 0 ? 32 : gl.ctextures+gl.ctextures/2); // 1.5x overallocate 12534 GLNVGtexture* textures = cast(GLNVGtexture*)realloc(gl.textures, GLNVGtexture.sizeof*ctextures); 12535 if (textures is null) assert(0, "NanoVega: out of memory for textures"); 12536 memset(&textures[gl.ctextures], 0, (ctextures-gl.ctextures)*GLNVGtexture.sizeof); 12537 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated more textures (n=%d; c=%d; nc=%d)\n", gl.ntextures, gl.ctextures, ctextures); }} 12538 gl.textures = textures; 12539 gl.ctextures = ctextures; 12540 } 12541 assert(gl.ntextures+1 <= gl.ctextures); 12542 tid = gl.ntextures++; 12543 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf(" got next free texture id %d, ntextures=%d\n", tid+1, gl.ntextures); }} 12544 } else { 12545 gl.freetexid = gl.textures[tid].nextfree; 12546 } 12547 assert(tid <= gl.ntextures); 12548 12549 assert(gl.textures[tid].id == 0); 12550 tex = &gl.textures[tid]; 12551 memset(tex, 0, (*tex).sizeof); 12552 tex.id = tid+1; 12553 tex.rc = 1; 12554 tex.nextfree = -1; 12555 12556 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("allocated texture with id %d (%d)\n", tex.id, tid+1); }} 12557 12558 return tex; 12559 } 12560 12561 GLNVGtexture* glnvg__findTexture (GLNVGcontext* gl, int id) nothrow @trusted @nogc { 12562 if (id <= 0 || id > gl.ntextures) return null; 12563 if (gl.textures[id-1].id == 0) return null; // free one 12564 assert(gl.textures[id-1].id == id); 12565 return &gl.textures[id-1]; 12566 } 12567 12568 bool glnvg__deleteTexture (GLNVGcontext* gl, ref int id) nothrow @trusted @nogc { 12569 if (id <= 0 || id > gl.ntextures) return false; 12570 auto tx = &gl.textures[id-1]; 12571 if (tx.id == 0) { id = 0; return false; } // free one 12572 assert(tx.id == id); 12573 assert(tx.tex != 0); 12574 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("decrefing texture with id %d (%d)\n", tx.id, id); }} 12575 import core.atomic : atomicOp; 12576 if (atomicOp!"-="(tx.rc, 1) == 0) { 12577 import core.thread : ThreadID; 12578 ThreadID mytid; 12579 static if (__VERSION__ < 2076) { 12580 DGNoThrowNoGC(() { 12581 import core.thread; mytid = Thread.getThis.id; 12582 })(); 12583 } else { 12584 try { import core.thread; mytid = Thread.getThis.id; } catch (Exception e) {} 12585 } 12586 if (gl.mainTID == mytid && gl.inFrame) { 12587 // can delete it right now 12588 if ((tx.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tx.tex); 12589 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** deleted texture with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }} 12590 memset(tx, 0, (*tx).sizeof); 12591 //{ import core.stdc.stdio; printf("deleting texture with id %d\n", id); } 12592 tx.nextfree = gl.freetexid; 12593 gl.freetexid = id-1; 12594 } else { 12595 // alas, we aren't doing frame business, so we should postpone deletion 12596 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** POSTPONED texture deletion with id %d (%d); glid=%u\n", tx.id, id, tx.tex); }} 12597 version(aliced) { 12598 synchronized(GLNVGTextureLocker.classinfo) { 12599 tx.id = 0; // mark it as dead 12600 gl.mustCleanTextures = true; // set "need cleanup" flag 12601 } 12602 } else { 12603 try { 12604 synchronized(GLNVGTextureLocker.classinfo) { 12605 tx.id = 0; // mark it as dead 12606 gl.mustCleanTextures = true; // set "need cleanup" flag 12607 } 12608 } catch (Exception e) {} 12609 } 12610 } 12611 } 12612 id = 0; 12613 return true; 12614 } 12615 12616 void glnvg__dumpShaderError (GLuint shader, const(char)* name, const(char)* type) nothrow @trusted @nogc { 12617 import core.stdc.stdio : fprintf, stderr; 12618 GLchar[512+1] str = 0; 12619 GLsizei len = 0; 12620 glGetShaderInfoLog(shader, 512, &len, str.ptr); 12621 if (len > 512) len = 512; 12622 str[len] = '\0'; 12623 fprintf(stderr, "Shader %s/%s error:\n%s\n", name, type, str.ptr); 12624 } 12625 12626 void glnvg__dumpProgramError (GLuint prog, const(char)* name) nothrow @trusted @nogc { 12627 import core.stdc.stdio : fprintf, stderr; 12628 GLchar[512+1] str = 0; 12629 GLsizei len = 0; 12630 glGetProgramInfoLog(prog, 512, &len, str.ptr); 12631 if (len > 512) len = 512; 12632 str[len] = '\0'; 12633 fprintf(stderr, "Program %s error:\n%s\n", name, str.ptr); 12634 } 12635 12636 void glnvg__resetError(bool force=false) (GLNVGcontext* gl) nothrow @trusted @nogc { 12637 static if (!force) { 12638 if ((gl.flags&NVGContextFlag.Debug) == 0) return; 12639 } 12640 glGetError(); 12641 } 12642 12643 void glnvg__checkError(bool force=false) (GLNVGcontext* gl, const(char)* str) nothrow @trusted @nogc { 12644 GLenum err; 12645 static if (!force) { 12646 if ((gl.flags&NVGContextFlag.Debug) == 0) return; 12647 } 12648 err = glGetError(); 12649 if (err != GL_NO_ERROR) { 12650 import core.stdc.stdio : fprintf, stderr; 12651 fprintf(stderr, "Error %08x after %s\n", err, str); 12652 return; 12653 } 12654 } 12655 12656 bool glnvg__createShader (GLNVGshader* shader, const(char)* name, const(char)* header, const(char)* opts, const(char)* vshader, const(char)* fshader) nothrow @trusted @nogc { 12657 GLint status; 12658 GLuint prog, vert, frag; 12659 const(char)*[3] str; 12660 12661 memset(shader, 0, (*shader).sizeof); 12662 12663 prog = glCreateProgram(); 12664 vert = glCreateShader(GL_VERTEX_SHADER); 12665 frag = glCreateShader(GL_FRAGMENT_SHADER); 12666 str[0] = header; 12667 str[1] = (opts !is null ? opts : ""); 12668 str[2] = vshader; 12669 glShaderSource(vert, 3, cast(const(char)**)str.ptr, null); 12670 12671 glCompileShader(vert); 12672 glGetShaderiv(vert, GL_COMPILE_STATUS, &status); 12673 if (status != GL_TRUE) { 12674 glnvg__dumpShaderError(vert, name, "vert"); 12675 return false; 12676 } 12677 12678 str[0] = header; 12679 str[1] = (opts !is null ? opts : ""); 12680 str[2] = fshader; 12681 glShaderSource(frag, 3, cast(const(char)**)str.ptr, null); 12682 12683 glCompileShader(frag); 12684 glGetShaderiv(frag, GL_COMPILE_STATUS, &status); 12685 if (status != GL_TRUE) { 12686 glnvg__dumpShaderError(frag, name, "frag"); 12687 return false; 12688 } 12689 12690 glAttachShader(prog, vert); 12691 glAttachShader(prog, frag); 12692 12693 glBindAttribLocation(prog, 0, "vertex"); 12694 glBindAttribLocation(prog, 1, "tcoord"); 12695 12696 glLinkProgram(prog); 12697 glGetProgramiv(prog, GL_LINK_STATUS, &status); 12698 if (status != GL_TRUE) { 12699 glnvg__dumpProgramError(prog, name); 12700 return false; 12701 } 12702 12703 shader.prog = prog; 12704 shader.vert = vert; 12705 shader.frag = frag; 12706 12707 return true; 12708 } 12709 12710 void glnvg__deleteShader (GLNVGshader* shader) nothrow @trusted @nogc { 12711 if (shader.prog != 0) glDeleteProgram(shader.prog); 12712 if (shader.vert != 0) glDeleteShader(shader.vert); 12713 if (shader.frag != 0) glDeleteShader(shader.frag); 12714 } 12715 12716 void glnvg__getUniforms (GLNVGshader* shader) nothrow @trusted @nogc { 12717 shader.loc[GLNVGuniformLoc.ViewSize] = glGetUniformLocation(shader.prog, "viewSize"); 12718 shader.loc[GLNVGuniformLoc.Tex] = glGetUniformLocation(shader.prog, "tex"); 12719 shader.loc[GLNVGuniformLoc.Frag] = glGetUniformLocation(shader.prog, "frag"); 12720 shader.loc[GLNVGuniformLoc.TMat] = glGetUniformLocation(shader.prog, "tmat"); 12721 shader.loc[GLNVGuniformLoc.TTr] = glGetUniformLocation(shader.prog, "ttr"); 12722 shader.loc[GLNVGuniformLoc.ClipTex] = glGetUniformLocation(shader.prog, "clipTex"); 12723 } 12724 12725 void glnvg__killFBOs (GLNVGcontext* gl) nothrow @trusted @nogc { 12726 foreach (immutable fidx, ref GLuint fbo; gl.fbo[]) { 12727 if (fbo != 0) { 12728 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); 12729 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); 12730 foreach (ref GLuint tid; gl.fboTex.ptr[fidx][]) if (tid != 0) { glDeleteTextures(1, &tid); tid = 0; } 12731 glDeleteFramebuffers(1, &fbo); 12732 fbo = 0; 12733 } 12734 } 12735 gl.fboWidth = gl.fboHeight = 0; 12736 } 12737 12738 // returns `true` is new FBO was created 12739 // will not unbind buffer, if it was created 12740 bool glnvg__allocFBO (GLNVGcontext* gl, int fidx, bool doclear=true) nothrow @trusted @nogc { 12741 assert(fidx >= 0 && fidx < gl.fbo.length); 12742 assert(gl.fboWidth > 0); 12743 assert(gl.fboHeight > 0); 12744 12745 if (gl.fbo.ptr[fidx] != 0) return false; // nothing to do, this FBO is already initialized 12746 12747 glnvg__resetError(gl); 12748 12749 // allocate FBO object 12750 GLuint fbo = 0; 12751 glGenFramebuffers(1, &fbo); 12752 if (fbo == 0) assert(0, "NanoVega: cannot create FBO"); 12753 glnvg__checkError(gl, "glnvg__allocFBO: glGenFramebuffers"); 12754 glBindFramebuffer(GL_FRAMEBUFFER, fbo); 12755 //scope(exit) glBindFramebuffer(GL_FRAMEBUFFER, 0); 12756 12757 // attach 2D texture to this FBO 12758 GLuint tidColor = 0; 12759 glGenTextures(1, &tidColor); 12760 if (tidColor == 0) assert(0, "NanoVega: cannot create RGBA texture for FBO"); 12761 glBindTexture(GL_TEXTURE_2D, tidColor); 12762 //scope(exit) glBindTexture(GL_TEXTURE_2D, 0); 12763 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 12764 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_S"); 12765 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 12766 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_WRAP_T"); 12767 //FIXME: linear or nearest? 12768 //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 12769 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 12770 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MIN_FILTER"); 12771 //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 12772 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 12773 glnvg__checkError(gl, "glnvg__allocFBO: glTexParameterf: GL_TEXTURE_MAG_FILTER"); 12774 // empty texture 12775 //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null); 12776 // create texture with only one color channel 12777 glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, gl.fboWidth, gl.fboHeight, 0, GL_RED, GL_UNSIGNED_BYTE, null); 12778 //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gl.fboWidth, gl.fboHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, null); 12779 glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (color)"); 12780 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tidColor, 0); 12781 glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (color)"); 12782 12783 // attach stencil texture to this FBO 12784 GLuint tidStencil = 0; 12785 version(nanovega_shared_stencil) { 12786 if (gl.fboTex.ptr[0].ptr[0] == 0) { 12787 glGenTextures(1, &tidStencil); 12788 if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO"); 12789 gl.fboTex.ptr[0].ptr[0] = tidStencil; 12790 } else { 12791 tidStencil = gl.fboTex.ptr[0].ptr[0]; 12792 } 12793 if (fidx != 0) gl.fboTex.ptr[fidx].ptr[1] = 0; // stencil texture is shared among FBOs 12794 } else { 12795 glGenTextures(1, &tidStencil); 12796 if (tidStencil == 0) assert(0, "NanoVega: cannot create stencil texture for FBO"); 12797 gl.fboTex.ptr[0].ptr[0] = tidStencil; 12798 } 12799 glBindTexture(GL_TEXTURE_2D, tidStencil); 12800 glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, gl.fboWidth, gl.fboHeight, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, null); 12801 glnvg__checkError(gl, "glnvg__allocFBO: glTexImage2D (stencil)"); 12802 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, tidStencil, 0); 12803 glnvg__checkError(gl, "glnvg__allocFBO: glFramebufferTexture2D (stencil)"); 12804 12805 { 12806 GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); 12807 if (status != GL_FRAMEBUFFER_COMPLETE) { 12808 version(all) { 12809 import core.stdc.stdio; 12810 if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT) printf("fucked attachement\n"); 12811 if (status == GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS) printf("fucked dimensions\n"); 12812 if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT) printf("missing attachement\n"); 12813 if (status == GL_FRAMEBUFFER_UNSUPPORTED) printf("unsupported\n"); 12814 } 12815 assert(0, "NanoVega: framebuffer creation failed"); 12816 } 12817 } 12818 12819 // clear 'em all 12820 if (doclear) { 12821 glClearColor(0, 0, 0, 0); 12822 glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT); 12823 } 12824 12825 // save texture ids 12826 gl.fbo.ptr[fidx] = fbo; 12827 gl.fboTex.ptr[fidx].ptr[0] = tidColor; 12828 version(nanovega_shared_stencil) {} else { 12829 gl.fboTex.ptr[fidx].ptr[1] = tidStencil; 12830 } 12831 12832 static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture); 12833 12834 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): created with index %d\n", gl.msp-1, fidx); } 12835 12836 return true; 12837 } 12838 12839 // will not unbind buffer 12840 void glnvg__clearFBO (GLNVGcontext* gl, int fidx) nothrow @trusted @nogc { 12841 assert(fidx >= 0 && fidx < gl.fbo.length); 12842 assert(gl.fboWidth > 0); 12843 assert(gl.fboHeight > 0); 12844 assert(gl.fbo.ptr[fidx] != 0); 12845 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[fidx]); 12846 glClearColor(0, 0, 0, 0); 12847 glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT); 12848 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cleared with index %d\n", gl.msp-1, fidx); } 12849 } 12850 12851 // will not unbind buffer 12852 void glnvg__copyFBOToFrom (GLNVGcontext* gl, int didx, int sidx) nothrow @trusted @nogc { 12853 import core.stdc..string : memset; 12854 assert(didx >= 0 && didx < gl.fbo.length); 12855 assert(sidx >= 0 && sidx < gl.fbo.length); 12856 assert(gl.fboWidth > 0); 12857 assert(gl.fboHeight > 0); 12858 assert(gl.fbo.ptr[didx] != 0); 12859 assert(gl.fbo.ptr[sidx] != 0); 12860 if (didx == sidx) return; 12861 12862 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copy FBO: %d -> %d\n", gl.msp-1, sidx, didx); } 12863 12864 glUseProgram(gl.shaderCopyFBO.prog); 12865 12866 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[didx]); 12867 glDisable(GL_CULL_FACE); 12868 glDisable(GL_BLEND); 12869 glDisable(GL_SCISSOR_TEST); 12870 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[sidx].ptr[0]); 12871 // copy texture by drawing full quad 12872 enum x = 0; 12873 enum y = 0; 12874 immutable int w = gl.fboWidth; 12875 immutable int h = gl.fboHeight; 12876 immutable(NVGVertex[4]) vertices = 12877 [NVGVertex(x, y), // top-left 12878 NVGVertex(w, y), // top-right 12879 NVGVertex(w, h), // bottom-right 12880 NVGVertex(x, h)]; // bottom-left 12881 12882 glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices); 12883 glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 12884 12885 // restore state (but don't unbind FBO) 12886 static if (NANOVG_GL_USE_STATE_FILTER) glBindTexture(GL_TEXTURE_2D, gl.boundTexture); 12887 glEnable(GL_CULL_FACE); 12888 glEnable(GL_BLEND); 12889 glUseProgram(gl.shader.prog); 12890 } 12891 12892 void glnvg__resetFBOClipTextureCache (GLNVGcontext* gl) nothrow @trusted @nogc { 12893 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): texture cache invalidated (%d)\n", gl.msp-1, gl.lastClipFBO); } 12894 /* 12895 if (gl.lastClipFBO >= 0) { 12896 glActiveTexture(GL_TEXTURE1); 12897 glBindTexture(GL_TEXTURE_2D, 0); 12898 glActiveTexture(GL_TEXTURE0); 12899 } 12900 */ 12901 gl.lastClipFBO = -666; 12902 } 12903 12904 void glnvg__setFBOClipTexture (GLNVGcontext* gl, GLNVGfragUniforms* frag) nothrow @trusted @nogc { 12905 //assert(gl.msp > 0 && gl.msp <= gl.maskStack.length); 12906 if (gl.lastClipFBO != -666) { 12907 // cached 12908 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): cached (%d)\n", gl.msp-1, gl.lastClipFBO); } 12909 frag.doclip = (gl.lastClipFBO >= 0 ? 1 : 0); 12910 return; 12911 } 12912 12913 // no cache 12914 int fboidx = -1; 12915 mainloop: foreach_reverse (immutable sp, GLMaskState mst; gl.maskStack.ptr[0..gl.msp]/*; reverse*/) { 12916 final switch (mst) { 12917 case GLMaskState.DontMask: fboidx = -1; break mainloop; 12918 case GLMaskState.Uninitialized: break; 12919 case GLMaskState.Initialized: fboidx = cast(int)sp; break mainloop; 12920 case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__setFBOClipTexture()` internal error"); 12921 } 12922 } 12923 12924 if (fboidx < 0) { 12925 // don't mask 12926 gl.lastClipFBO = -1; 12927 frag.doclip = 0; 12928 } else { 12929 // do masking 12930 assert(gl.fbo.ptr[fboidx] != 0); 12931 gl.lastClipFBO = fboidx; 12932 frag.doclip = 1; 12933 } 12934 12935 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): new cache (new:%d)\n", gl.msp-1, gl.lastClipFBO); } 12936 12937 if (gl.lastClipFBO >= 0) { 12938 assert(gl.fboTex.ptr[gl.lastClipFBO].ptr[0]); 12939 glActiveTexture(GL_TEXTURE1); 12940 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]); 12941 glActiveTexture(GL_TEXTURE0); 12942 } 12943 } 12944 12945 // returns index in `gl.fbo`, or -1 for "don't mask" 12946 int glnvg__generateFBOClipTexture (GLNVGcontext* gl) nothrow @trusted @nogc { 12947 assert(gl.msp > 0 && gl.msp <= gl.maskStack.length); 12948 // we need initialized FBO, even for "don't mask" case 12949 // for this, look back in stack, and either copy initialized FBO, 12950 // or stop at first uninitialized one, and clear it 12951 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.Initialized) { 12952 // shortcut 12953 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); } 12954 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[gl.msp-1]); 12955 return gl.msp-1; 12956 } 12957 foreach_reverse (immutable sp; 0..gl.msp/*; reverse*/) { 12958 final switch (gl.maskStack.ptr[sp]) { 12959 case GLMaskState.DontMask: 12960 // clear it 12961 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture\n", gl.msp-1); } 12962 if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1); 12963 gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared; 12964 return gl.msp-1; 12965 case GLMaskState.Uninitialized: break; // do nothing 12966 case GLMaskState.Initialized: 12967 // i found her! copy to TOS 12968 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): copying texture from %d\n", gl.msp-1, cast(int)sp); } 12969 glnvg__allocFBO(gl, gl.msp-1, false); 12970 glnvg__copyFBOToFrom(gl, gl.msp-1, sp); 12971 gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized; 12972 return gl.msp-1; 12973 case GLMaskState.JustCleared: assert(0, "NanoVega: `glnvg__generateFBOClipTexture()` internal error"); 12974 } 12975 } 12976 // nothing was initialized, lol 12977 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf("FBO(%d): generating new clean texture (first one)\n", gl.msp-1); } 12978 if (!glnvg__allocFBO(gl, gl.msp-1)) glnvg__clearFBO(gl, gl.msp-1); 12979 gl.maskStack.ptr[gl.msp-1] = GLMaskState.JustCleared; 12980 return gl.msp-1; 12981 } 12982 12983 void glnvg__renderPushClip (void* uptr) nothrow @trusted @nogc { 12984 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 12985 GLNVGcall* call = glnvg__allocCall(gl); 12986 if (call is null) return; 12987 call.type = GLNVG_PUSHCLIP; 12988 } 12989 12990 void glnvg__renderPopClip (void* uptr) nothrow @trusted @nogc { 12991 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 12992 GLNVGcall* call = glnvg__allocCall(gl); 12993 if (call is null) return; 12994 call.type = GLNVG_POPCLIP; 12995 } 12996 12997 void glnvg__renderResetClip (void* uptr) nothrow @trusted @nogc { 12998 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 12999 GLNVGcall* call = glnvg__allocCall(gl); 13000 if (call is null) return; 13001 call.type = GLNVG_RESETCLIP; 13002 } 13003 13004 void glnvg__clipDebugDump (void* uptr, bool doit) nothrow @trusted @nogc { 13005 version(nanovega_debug_clipping) { 13006 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13007 GLNVGcall* call = glnvg__allocCall(gl); 13008 call.type = (doit ? GLNVG_CLIP_DDUMP_ON : GLNVG_CLIP_DDUMP_OFF); 13009 } 13010 } 13011 13012 bool glnvg__renderCreate (void* uptr) nothrow @trusted @nogc { 13013 import core.stdc.stdio : snprintf; 13014 13015 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13016 enum align_ = 4; 13017 13018 char[64] shaderHeader = void; 13019 //enum shaderHeader = "#define UNIFORM_ARRAY_SIZE 12\n"; 13020 snprintf(shaderHeader.ptr, shaderHeader.length, "#define UNIFORM_ARRAY_SIZE %u\n", cast(uint)GLNVGfragUniforms.UNIFORM_ARRAY_SIZE); 13021 13022 enum fillVertShader = q{ 13023 uniform vec2 viewSize; 13024 attribute vec2 vertex; 13025 attribute vec2 tcoord; 13026 varying vec2 ftcoord; 13027 varying vec2 fpos; 13028 uniform vec4 tmat; /* abcd of affine matrix: xyzw */ 13029 uniform vec2 ttr; /* tx and ty of affine matrix */ 13030 void main (void) { 13031 /* affine transformation */ 13032 float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x; 13033 float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y; 13034 ftcoord = tcoord; 13035 fpos = vec2(nx, ny); 13036 gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1); 13037 } 13038 }; 13039 13040 enum fillFragShader = ` 13041 uniform vec4 frag[UNIFORM_ARRAY_SIZE]; 13042 uniform sampler2D tex; 13043 uniform sampler2D clipTex; 13044 uniform vec2 viewSize; 13045 varying vec2 ftcoord; 13046 varying vec2 fpos; 13047 #define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz) 13048 #define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz) 13049 #define innerCol frag[6] 13050 #define middleCol frag[7] 13051 #define outerCol frag[7+1] 13052 #define scissorExt frag[8+1].xy 13053 #define scissorScale frag[8+1].zw 13054 #define extent frag[9+1].xy 13055 #define radius frag[9+1].z 13056 #define feather frag[9+1].w 13057 #define strokeMult frag[10+1].x 13058 #define strokeThr frag[10+1].y 13059 #define texType int(frag[10+1].z) 13060 #define type int(frag[10+1].w) 13061 #define doclip int(frag[11+1].x) 13062 #define midp frag[11+1].y 13063 13064 float sdroundrect (in vec2 pt, in vec2 ext, in float rad) { 13065 vec2 ext2 = ext-vec2(rad, rad); 13066 vec2 d = abs(pt)-ext2; 13067 return min(max(d.x, d.y), 0.0)+length(max(d, 0.0))-rad; 13068 } 13069 13070 // Scissoring 13071 float scissorMask (in vec2 p) { 13072 vec2 sc = (abs((scissorMat*vec3(p, 1.0)).xy)-scissorExt); 13073 sc = vec2(0.5, 0.5)-sc*scissorScale; 13074 return clamp(sc.x, 0.0, 1.0)*clamp(sc.y, 0.0, 1.0); 13075 } 13076 13077 #ifdef EDGE_AA 13078 // Stroke - from [0..1] to clipped pyramid, where the slope is 1px. 13079 float strokeMask () { 13080 return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult)*min(1.0, ftcoord.y); 13081 } 13082 #endif 13083 13084 void main (void) { 13085 // clipping 13086 if (doclip != 0) { 13087 /*vec4 clr = texelFetch(clipTex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0);*/ 13088 vec4 clr = texture2D(clipTex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y)); 13089 if (clr.r == 0.0) discard; 13090 } 13091 float scissor = scissorMask(fpos); 13092 if (scissor <= 0.0) discard; //k8: is it really faster? 13093 #ifdef EDGE_AA 13094 float strokeAlpha = strokeMask(); 13095 if (strokeAlpha < strokeThr) discard; 13096 #else 13097 float strokeAlpha = 1.0; 13098 #endif 13099 // rendering 13100 vec4 color; 13101 if (type == 0) { /* NSVG_SHADER_FILLCOLOR */ 13102 color = innerCol; 13103 // Combine alpha 13104 color *= strokeAlpha*scissor; 13105 } else if (type == 1) { /* NSVG_SHADER_FILLGRAD */ 13106 // Gradient 13107 // Calculate gradient color using box gradient 13108 vec2 pt = (paintMat*vec3(fpos, 1.0)).xy; 13109 float d = clamp((sdroundrect(pt, extent, radius)+feather*0.5)/feather, 0.0, 1.0); 13110 if (midp <= 0.0) { 13111 color = mix(innerCol, outerCol, d); 13112 } else { 13113 float gdst = min(midp, 1.0); 13114 if (d < gdst) { 13115 color = mix(innerCol, middleCol, d/gdst); 13116 } else { 13117 color = mix(middleCol, outerCol, (d-gdst)/gdst); 13118 } 13119 } 13120 // Combine alpha 13121 color *= strokeAlpha*scissor; 13122 } else if (type == 2) { /* NSVG_SHADER_FILLIMG */ 13123 // Image 13124 // Calculate color from texture 13125 vec2 pt = (paintMat*vec3(fpos, 1.0)).xy/extent; 13126 color = texture2D(tex, pt); 13127 if (texType == 1) color = vec4(color.xyz*color.w, color.w); 13128 if (texType == 2) color = vec4(color.x); 13129 // Apply color tint and alpha 13130 color *= innerCol; 13131 // Combine alpha 13132 color *= strokeAlpha*scissor; 13133 } else if (type == 3) { /* NSVG_SHADER_SIMPLE */ 13134 // Stencil fill 13135 color = vec4(1, 1, 1, 1); 13136 } else if (type == 4) { /* NSVG_SHADER_IMG */ 13137 // Textured tris 13138 color = texture2D(tex, ftcoord); 13139 if (texType == 1) color = vec4(color.xyz*color.w, color.w); 13140 if (texType == 2) color = vec4(color.x); 13141 color *= scissor; 13142 color *= innerCol; // Apply color tint 13143 } 13144 gl_FragColor = color; 13145 } 13146 `; 13147 13148 enum clipVertShaderFill = q{ 13149 uniform vec2 viewSize; 13150 attribute vec2 vertex; 13151 uniform vec4 tmat; /* abcd of affine matrix: xyzw */ 13152 uniform vec2 ttr; /* tx and ty of affine matrix */ 13153 void main (void) { 13154 /* affine transformation */ 13155 float nx = vertex.x*tmat.x+vertex.y*tmat.z+ttr.x; 13156 float ny = vertex.x*tmat.y+vertex.y*tmat.w+ttr.y; 13157 gl_Position = vec4(2.0*nx/viewSize.x-1.0, 1.0-2.0*ny/viewSize.y, 0, 1); 13158 } 13159 }; 13160 13161 enum clipFragShaderFill = q{ 13162 uniform vec2 viewSize; 13163 void main (void) { 13164 gl_FragColor = vec4(1, 1, 1, 1); 13165 } 13166 }; 13167 13168 enum clipVertShaderCopy = q{ 13169 uniform vec2 viewSize; 13170 attribute vec2 vertex; 13171 void main (void) { 13172 gl_Position = vec4(2.0*vertex.x/viewSize.x-1.0, 1.0-2.0*vertex.y/viewSize.y, 0, 1); 13173 } 13174 }; 13175 13176 enum clipFragShaderCopy = q{ 13177 uniform sampler2D tex; 13178 uniform vec2 viewSize; 13179 void main (void) { 13180 //gl_FragColor = texelFetch(tex, ivec2(int(gl_FragCoord.x), int(gl_FragCoord.y)), 0); 13181 gl_FragColor = texture2D(tex, vec2(gl_FragCoord.x/viewSize.x, gl_FragCoord.y/viewSize.y)); 13182 } 13183 }; 13184 13185 glnvg__checkError(gl, "init"); 13186 13187 string defines = (gl.flags&NVGContextFlag.Antialias ? "#define EDGE_AA 1\n" : null); 13188 if (!glnvg__createShader(&gl.shader, "shader", shaderHeader.ptr, defines.ptr, fillVertShader, fillFragShader)) return false; 13189 if (!glnvg__createShader(&gl.shaderFillFBO, "shaderFillFBO", shaderHeader.ptr, defines.ptr, clipVertShaderFill, clipFragShaderFill)) return false; 13190 if (!glnvg__createShader(&gl.shaderCopyFBO, "shaderCopyFBO", shaderHeader.ptr, defines.ptr, clipVertShaderCopy, clipFragShaderCopy)) return false; 13191 13192 glnvg__checkError(gl, "uniform locations"); 13193 glnvg__getUniforms(&gl.shader); 13194 glnvg__getUniforms(&gl.shaderFillFBO); 13195 glnvg__getUniforms(&gl.shaderCopyFBO); 13196 13197 // Create dynamic vertex array 13198 glGenBuffers(1, &gl.vertBuf); 13199 13200 gl.fragSize = GLNVGfragUniforms.sizeof+align_-GLNVGfragUniforms.sizeof%align_; 13201 13202 glnvg__checkError(gl, "create done"); 13203 13204 glFinish(); 13205 13206 return true; 13207 } 13208 13209 int glnvg__renderCreateTexture (void* uptr, NVGtexture type, int w, int h, int imageFlags, const(ubyte)* data) nothrow @trusted @nogc { 13210 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13211 GLNVGtexture* tex = glnvg__allocTexture(gl); 13212 13213 if (tex is null) return 0; 13214 13215 glGenTextures(1, &tex.tex); 13216 tex.width = w; 13217 tex.height = h; 13218 tex.type = type; 13219 tex.flags = imageFlags; 13220 glnvg__bindTexture(gl, tex.tex); 13221 13222 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("created texture with id %d; glid=%u\n", tex.id, tex.tex); }} 13223 13224 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 13225 glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width); 13226 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); 13227 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); 13228 13229 13230 13231 immutable ttype = (type == NVGtexture.RGBA ? GL_RGBA : GL_RED); 13232 glTexImage2D(GL_TEXTURE_2D, 0, ttype, w, h, 0, ttype, GL_UNSIGNED_BYTE, data); 13233 // GL 3.0 and later have support for a dedicated function for generating mipmaps 13234 // it needs to be called after the glTexImage2D call 13235 if (imageFlags & NVGImageFlag.GenerateMipmaps) 13236 glGenerateMipmap(GL_TEXTURE_2D); 13237 13238 immutable tfmin = 13239 (imageFlags & NVGImageFlag.GenerateMipmaps ? GL_LINEAR_MIPMAP_LINEAR : 13240 imageFlags & NVGImageFlag.NoFiltering ? GL_NEAREST : 13241 GL_LINEAR); 13242 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -1.0); 13243 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tfmin); 13244 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (imageFlags&NVGImageFlag.NoFiltering ? GL_NEAREST : GL_LINEAR)); 13245 13246 int flag; 13247 if (imageFlags&NVGImageFlag.RepeatX) 13248 flag = GL_REPEAT; 13249 else if (imageFlags&NVGImageFlag.ClampToBorderX) 13250 flag = GL_CLAMP_TO_BORDER; 13251 else 13252 flag = GL_CLAMP_TO_EDGE; 13253 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, flag); 13254 13255 13256 if (imageFlags&NVGImageFlag.RepeatY) 13257 flag = GL_REPEAT; 13258 else if (imageFlags&NVGImageFlag.ClampToBorderY) 13259 flag = GL_CLAMP_TO_BORDER; 13260 else 13261 flag = GL_CLAMP_TO_EDGE; 13262 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, flag); 13263 13264 glPixelStorei(GL_UNPACK_ALIGNMENT, 4); 13265 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 13266 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); 13267 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); 13268 13269 glnvg__checkError(gl, "create tex"); 13270 glnvg__bindTexture(gl, 0); 13271 13272 return tex.id; 13273 } 13274 13275 bool glnvg__renderDeleteTexture (void* uptr, int image) nothrow @trusted @nogc { 13276 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13277 return glnvg__deleteTexture(gl, image); 13278 } 13279 13280 bool glnvg__renderTextureIncRef (void* uptr, int image) nothrow @trusted @nogc { 13281 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13282 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13283 if (tex is null) { 13284 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT incref texture with id %d\n", image); }} 13285 return false; 13286 } 13287 import core.atomic : atomicOp; 13288 atomicOp!"+="(tex.rc, 1); 13289 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("texture #%d: incref; newref=%d\n", image, tex.rc); }} 13290 return true; 13291 } 13292 13293 bool glnvg__renderUpdateTexture (void* uptr, int image, int x, int y, int w, int h, const(ubyte)* data) nothrow @trusted @nogc { 13294 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13295 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13296 13297 if (tex is null) { 13298 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("CANNOT update texture with id %d\n", image); }} 13299 return false; 13300 } 13301 13302 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("updated texture with id %d; glid=%u\n", tex.id, image, tex.tex); }} 13303 13304 glnvg__bindTexture(gl, tex.tex); 13305 13306 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 13307 glPixelStorei(GL_UNPACK_ROW_LENGTH, tex.width); 13308 glPixelStorei(GL_UNPACK_SKIP_PIXELS, x); 13309 glPixelStorei(GL_UNPACK_SKIP_ROWS, y); 13310 13311 immutable ttype = (tex.type == NVGtexture.RGBA ? GL_RGBA : GL_RED); 13312 glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, ttype, GL_UNSIGNED_BYTE, data); 13313 13314 glPixelStorei(GL_UNPACK_ALIGNMENT, 4); 13315 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 13316 glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); 13317 glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); 13318 13319 glnvg__bindTexture(gl, 0); 13320 13321 return true; 13322 } 13323 13324 bool glnvg__renderGetTextureSize (void* uptr, int image, int* w, int* h) nothrow @trusted @nogc { 13325 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13326 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13327 if (tex is null) { 13328 if (w !is null) *w = 0; 13329 if (h !is null) *h = 0; 13330 return false; 13331 } else { 13332 if (w !is null) *w = tex.width; 13333 if (h !is null) *h = tex.height; 13334 return true; 13335 } 13336 } 13337 13338 void glnvg__xformToMat3x4 (float[] m3, const(float)[] t) nothrow @trusted @nogc { 13339 assert(t.length >= 6); 13340 assert(m3.length >= 12); 13341 m3.ptr[0] = t.ptr[0]; 13342 m3.ptr[1] = t.ptr[1]; 13343 m3.ptr[2] = 0.0f; 13344 m3.ptr[3] = 0.0f; 13345 m3.ptr[4] = t.ptr[2]; 13346 m3.ptr[5] = t.ptr[3]; 13347 m3.ptr[6] = 0.0f; 13348 m3.ptr[7] = 0.0f; 13349 m3.ptr[8] = t.ptr[4]; 13350 m3.ptr[9] = t.ptr[5]; 13351 m3.ptr[10] = 1.0f; 13352 m3.ptr[11] = 0.0f; 13353 } 13354 13355 NVGColor glnvg__premulColor() (in auto ref NVGColor c) nothrow @trusted @nogc { 13356 //pragma(inline, true); 13357 NVGColor res = void; 13358 res.r = c.r*c.a; 13359 res.g = c.g*c.a; 13360 res.b = c.b*c.a; 13361 res.a = c.a; 13362 return res; 13363 } 13364 13365 bool glnvg__convertPaint (GLNVGcontext* gl, GLNVGfragUniforms* frag, NVGPaint* paint, NVGscissor* scissor, float width, float fringe, float strokeThr) nothrow @trusted @nogc { 13366 import core.stdc.math : sqrtf; 13367 GLNVGtexture* tex = null; 13368 NVGMatrix invxform = void; 13369 13370 memset(frag, 0, (*frag).sizeof); 13371 13372 frag.innerCol = glnvg__premulColor(paint.innerColor); 13373 frag.middleCol = glnvg__premulColor(paint.middleColor); 13374 frag.outerCol = glnvg__premulColor(paint.outerColor); 13375 frag.midp = paint.midp; 13376 13377 if (scissor.extent.ptr[0] < -0.5f || scissor.extent.ptr[1] < -0.5f) { 13378 memset(frag.scissorMat.ptr, 0, frag.scissorMat.sizeof); 13379 frag.scissorExt.ptr[0] = 1.0f; 13380 frag.scissorExt.ptr[1] = 1.0f; 13381 frag.scissorScale.ptr[0] = 1.0f; 13382 frag.scissorScale.ptr[1] = 1.0f; 13383 } else { 13384 //nvgTransformInverse(invxform[], scissor.xform[]); 13385 invxform = scissor.xform.inverted; 13386 glnvg__xformToMat3x4(frag.scissorMat[], invxform.mat[]); 13387 frag.scissorExt.ptr[0] = scissor.extent.ptr[0]; 13388 frag.scissorExt.ptr[1] = scissor.extent.ptr[1]; 13389 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; 13390 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; 13391 } 13392 13393 memcpy(frag.extent.ptr, paint.extent.ptr, frag.extent.sizeof); 13394 frag.strokeMult = (width*0.5f+fringe*0.5f)/fringe; 13395 frag.strokeThr = strokeThr; 13396 13397 if (paint.image.valid) { 13398 tex = glnvg__findTexture(gl, paint.image.id); 13399 if (tex is null) return false; 13400 if ((tex.flags&NVGImageFlag.FlipY) != 0) { 13401 /* 13402 NVGMatrix flipped; 13403 nvgTransformScale(flipped[], 1.0f, -1.0f); 13404 nvgTransformMultiply(flipped[], paint.xform[]); 13405 nvgTransformInverse(invxform[], flipped[]); 13406 */ 13407 /* 13408 NVGMatrix m1 = void, m2 = void; 13409 nvgTransformTranslate(m1[], 0.0f, frag.extent.ptr[1]*0.5f); 13410 nvgTransformMultiply(m1[], paint.xform[]); 13411 nvgTransformScale(m2[], 1.0f, -1.0f); 13412 nvgTransformMultiply(m2[], m1[]); 13413 nvgTransformTranslate(m1[], 0.0f, -frag.extent.ptr[1]*0.5f); 13414 nvgTransformMultiply(m1[], m2[]); 13415 nvgTransformInverse(invxform[], m1[]); 13416 */ 13417 NVGMatrix m1 = NVGMatrix.Translated(0.0f, frag.extent.ptr[1]*0.5f); 13418 m1.mul(paint.xform); 13419 NVGMatrix m2 = NVGMatrix.Scaled(1.0f, -1.0f); 13420 m2.mul(m1); 13421 m1 = NVGMatrix.Translated(0.0f, -frag.extent.ptr[1]*0.5f); 13422 m1.mul(m2); 13423 invxform = m1.inverted; 13424 } else { 13425 //nvgTransformInverse(invxform[], paint.xform[]); 13426 invxform = paint.xform.inverted; 13427 } 13428 frag.type = NSVG_SHADER_FILLIMG; 13429 13430 if (tex.type == NVGtexture.RGBA) { 13431 frag.texType = (tex.flags&NVGImageFlag.Premultiplied ? 0 : 1); 13432 } else { 13433 frag.texType = 2; 13434 } 13435 //printf("frag.texType = %d\n", frag.texType); 13436 } else { 13437 frag.type = (paint.simpleColor ? NSVG_SHADER_FILLCOLOR : NSVG_SHADER_FILLGRAD); 13438 frag.radius = paint.radius; 13439 frag.feather = paint.feather; 13440 //nvgTransformInverse(invxform[], paint.xform[]); 13441 invxform = paint.xform.inverted; 13442 } 13443 13444 glnvg__xformToMat3x4(frag.paintMat[], invxform.mat[]); 13445 13446 return true; 13447 } 13448 13449 void glnvg__setUniforms (GLNVGcontext* gl, int uniformOffset, int image) nothrow @trusted @nogc { 13450 GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset); 13451 glnvg__setFBOClipTexture(gl, frag); 13452 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.Frag], frag.UNIFORM_ARRAY_SIZE, &(frag.uniformArray.ptr[0].ptr[0])); 13453 glnvg__checkError(gl, "glnvg__setUniforms"); 13454 if (image != 0) { 13455 GLNVGtexture* tex = glnvg__findTexture(gl, image); 13456 glnvg__bindTexture(gl, (tex !is null ? tex.tex : 0)); 13457 glnvg__checkError(gl, "tex paint tex"); 13458 } else { 13459 glnvg__bindTexture(gl, 0); 13460 } 13461 } 13462 13463 void glnvg__finishClip (GLNVGcontext* gl, NVGClipMode clipmode) nothrow @trusted @nogc { 13464 assert(clipmode != NVGClipMode.None); 13465 13466 // fill FBO, clear stencil buffer 13467 //TODO: optimize with bounds? 13468 version(all) { 13469 //glnvg__resetAffine(gl); 13470 //glUseProgram(gl.shaderFillFBO.prog); 13471 glDisable(GL_CULL_FACE); 13472 glDisable(GL_BLEND); 13473 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13474 glEnable(GL_STENCIL_TEST); 13475 if (gl.doClipUnion) { 13476 // for "and" we should clear everything that is NOT stencil-masked 13477 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); 13478 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13479 } else { 13480 glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x00, 0xff); 13481 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13482 } 13483 13484 immutable(NVGVertex[4]) vertices = 13485 [NVGVertex(0, 0, 0, 0), 13486 NVGVertex(0, gl.fboHeight, 0, 0), 13487 NVGVertex(gl.fboWidth, gl.fboHeight, 0, 0), 13488 NVGVertex(gl.fboWidth, 0, 0, 0)]; 13489 13490 glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.sizeof, &vertices); 13491 glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 13492 13493 //glnvg__restoreAffine(gl); 13494 } 13495 13496 glBindFramebuffer(GL_FRAMEBUFFER, gl.mainFBO); 13497 glDisable(GL_COLOR_LOGIC_OP); 13498 //glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // done above 13499 glEnable(GL_BLEND); 13500 glDisable(GL_STENCIL_TEST); 13501 glEnable(GL_CULL_FACE); 13502 glUseProgram(gl.shader.prog); 13503 13504 // set current FBO as used one 13505 assert(gl.msp > 0 && gl.fbo.ptr[gl.msp-1] > 0 && gl.fboTex.ptr[gl.msp-1].ptr[0] > 0); 13506 if (gl.lastClipFBO != gl.msp-1) { 13507 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); } 13508 gl.lastClipFBO = gl.msp-1; 13509 glActiveTexture(GL_TEXTURE1); 13510 glBindTexture(GL_TEXTURE_2D, gl.fboTex.ptr[gl.lastClipFBO].ptr[0]); 13511 glActiveTexture(GL_TEXTURE0); 13512 } 13513 } 13514 13515 void glnvg__setClipUniforms (GLNVGcontext* gl, int uniformOffset, NVGClipMode clipmode) nothrow @trusted @nogc { 13516 assert(clipmode != NVGClipMode.None); 13517 GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset); 13518 // save uniform offset for `glnvg__finishClip()` 13519 gl.lastClipUniOfs = uniformOffset; 13520 // get FBO index, bind this FBO 13521 immutable int clipTexId = glnvg__generateFBOClipTexture(gl); 13522 assert(clipTexId >= 0); 13523 glUseProgram(gl.shaderFillFBO.prog); 13524 glnvg__checkError(gl, "use"); 13525 glBindFramebuffer(GL_FRAMEBUFFER, gl.fbo.ptr[clipTexId]); 13526 // set logic op for clip 13527 gl.doClipUnion = false; 13528 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.JustCleared) { 13529 // it is cleared to zero, we can just draw a path 13530 glDisable(GL_COLOR_LOGIC_OP); 13531 gl.maskStack.ptr[gl.msp-1] = GLMaskState.Initialized; 13532 } else { 13533 glEnable(GL_COLOR_LOGIC_OP); 13534 final switch (clipmode) { 13535 case NVGClipMode.None: assert(0, "wtf?!"); 13536 case NVGClipMode.Union: glLogicOp(GL_CLEAR); gl.doClipUnion = true; break; // use `GL_CLEAR` to avoid adding another shader mode 13537 case NVGClipMode.Or: glLogicOp(GL_COPY); break; // GL_OR 13538 case NVGClipMode.Xor: glLogicOp(GL_XOR); break; 13539 case NVGClipMode.Sub: glLogicOp(GL_CLEAR); break; 13540 case NVGClipMode.Replace: glLogicOp(GL_COPY); break; 13541 } 13542 } 13543 // set affine matrix 13544 glUniform4fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TMat], 1, gl.lastAffine.mat.ptr); 13545 glnvg__checkError(gl, "affine 0"); 13546 glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.TTr], 1, gl.lastAffine.mat.ptr+4); 13547 glnvg__checkError(gl, "affine 1"); 13548 // setup common OpenGL parameters 13549 glDisable(GL_BLEND); 13550 glDisable(GL_CULL_FACE); 13551 glEnable(GL_STENCIL_TEST); 13552 glnvg__stencilMask(gl, 0xff); 13553 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); 13554 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); 13555 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 13556 } 13557 13558 void glnvg__renderViewport (void* uptr, int width, int height) nothrow @trusted @nogc { 13559 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13560 gl.inFrame = true; 13561 gl.view.ptr[0] = cast(float)width; 13562 gl.view.ptr[1] = cast(float)height; 13563 // kill FBOs if we need to create new ones (flushing will recreate 'em if necessary) 13564 if (width != gl.fboWidth || height != gl.fboHeight) { 13565 glnvg__killFBOs(gl); 13566 gl.fboWidth = width; 13567 gl.fboHeight = height; 13568 } 13569 gl.msp = 1; 13570 gl.maskStack.ptr[0] = GLMaskState.DontMask; 13571 // texture cleanup 13572 import core.atomic : atomicLoad; 13573 if (atomicLoad(gl.mustCleanTextures)) { 13574 try { 13575 import core.thread : Thread; 13576 static if (__VERSION__ < 2076) { 13577 DGNoThrowNoGC(() { 13578 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13579 })(); 13580 } else { 13581 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13582 } 13583 synchronized(GLNVGTextureLocker.classinfo) { 13584 gl.mustCleanTextures = false; 13585 foreach (immutable tidx, ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) { 13586 // no need to use atomic ops here, as we're locked 13587 if (tex.rc == 0 && tex.tex != 0 && tex.id == 0) { 13588 version(nanovega_debug_textures) {{ import core.stdc.stdio; printf("*** cleaned up texture with glid=%u\n", tex.tex); }} 13589 import core.stdc..string : memset; 13590 if ((tex.flags&NVGImageFlag.NoDelete) == 0) glDeleteTextures(1, &tex.tex); 13591 memset(&tex, 0, tex.sizeof); 13592 tex.nextfree = gl.freetexid; 13593 gl.freetexid = cast(int)tidx; 13594 } 13595 } 13596 } 13597 } catch (Exception e) {} 13598 } 13599 } 13600 13601 void glnvg__fill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13602 GLNVGpath* paths = &gl.paths[call.pathOffset]; 13603 int npaths = call.pathCount; 13604 13605 if (call.clipmode == NVGClipMode.None) { 13606 // Draw shapes 13607 glEnable(GL_STENCIL_TEST); 13608 glnvg__stencilMask(gl, 0xffU); 13609 glnvg__stencilFunc(gl, GL_ALWAYS, 0, 0xffU); 13610 13611 glnvg__setUniforms(gl, call.uniformOffset, 0); 13612 glnvg__checkError(gl, "fill simple"); 13613 13614 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 13615 if (call.evenOdd) { 13616 //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT); 13617 //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT); 13618 glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); 13619 } else { 13620 glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); 13621 glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); 13622 } 13623 glDisable(GL_CULL_FACE); 13624 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13625 glEnable(GL_CULL_FACE); 13626 13627 // Draw anti-aliased pixels 13628 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13629 glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image); 13630 glnvg__checkError(gl, "fill fill"); 13631 13632 if (gl.flags&NVGContextFlag.Antialias) { 13633 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xffU); 13634 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 13635 // Draw fringes 13636 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13637 } 13638 13639 // Draw fill 13640 glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x0, 0xffU); 13641 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13642 if (call.evenOdd) { 13643 glDisable(GL_CULL_FACE); 13644 glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount); 13645 //foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13646 glEnable(GL_CULL_FACE); 13647 } else { 13648 glDrawArrays(GL_TRIANGLE_STRIP, call.triangleOffset, call.triangleCount); 13649 } 13650 13651 glDisable(GL_STENCIL_TEST); 13652 } else { 13653 glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode); // this activates our FBO 13654 glnvg__checkError(gl, "fillclip simple"); 13655 glnvg__stencilFunc(gl, GL_ALWAYS, 0x00, 0xffU); 13656 if (call.evenOdd) { 13657 //glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INVERT); 13658 //glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INVERT); 13659 glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT); 13660 } else { 13661 glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); 13662 glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); 13663 } 13664 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13665 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state 13666 } 13667 } 13668 13669 void glnvg__convexFill (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13670 GLNVGpath* paths = &gl.paths[call.pathOffset]; 13671 int npaths = call.pathCount; 13672 13673 if (call.clipmode == NVGClipMode.None) { 13674 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13675 glnvg__checkError(gl, "convex fill"); 13676 if (call.evenOdd) glDisable(GL_CULL_FACE); 13677 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13678 if (gl.flags&NVGContextFlag.Antialias) { 13679 // Draw fringes 13680 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13681 } 13682 if (call.evenOdd) glEnable(GL_CULL_FACE); 13683 } else { 13684 glnvg__setClipUniforms(gl, call.uniformOffset, call.clipmode); // this activates our FBO 13685 glnvg__checkError(gl, "clip convex fill"); 13686 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); 13687 if (gl.flags&NVGContextFlag.Antialias) { 13688 // Draw fringes 13689 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13690 } 13691 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state 13692 } 13693 } 13694 13695 void glnvg__stroke (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13696 GLNVGpath* paths = &gl.paths[call.pathOffset]; 13697 int npaths = call.pathCount; 13698 13699 if (call.clipmode == NVGClipMode.None) { 13700 if (gl.flags&NVGContextFlag.StencilStrokes) { 13701 glEnable(GL_STENCIL_TEST); 13702 glnvg__stencilMask(gl, 0xff); 13703 13704 // Fill the stroke base without overlap 13705 glnvg__stencilFunc(gl, GL_EQUAL, 0x0, 0xff); 13706 glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); 13707 glnvg__setUniforms(gl, call.uniformOffset+gl.fragSize, call.image); 13708 glnvg__checkError(gl, "stroke fill 0"); 13709 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13710 13711 // Draw anti-aliased pixels. 13712 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13713 glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); 13714 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 13715 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13716 13717 // Clear stencil buffer. 13718 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 13719 glnvg__stencilFunc(gl, GL_ALWAYS, 0x0, 0xff); 13720 glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); 13721 glnvg__checkError(gl, "stroke fill 1"); 13722 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13723 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13724 13725 glDisable(GL_STENCIL_TEST); 13726 13727 //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f); 13728 } else { 13729 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13730 glnvg__checkError(gl, "stroke fill"); 13731 // Draw Strokes 13732 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13733 } 13734 } else { 13735 glnvg__setClipUniforms(gl, call.uniformOffset/*+gl.fragSize*/, call.clipmode); 13736 glnvg__checkError(gl, "stroke fill 0"); 13737 foreach (int i; 0..npaths) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); 13738 glnvg__finishClip(gl, call.clipmode); // deactivate FBO, restore rendering state 13739 } 13740 } 13741 13742 void glnvg__triangles (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13743 if (call.clipmode == NVGClipMode.None) { 13744 glnvg__setUniforms(gl, call.uniformOffset, call.image); 13745 glnvg__checkError(gl, "triangles fill"); 13746 glDrawArrays(GL_TRIANGLES, call.triangleOffset, call.triangleCount); 13747 } else { 13748 //TODO(?): use texture as mask? 13749 } 13750 } 13751 13752 void glnvg__affine (GLNVGcontext* gl, GLNVGcall* call) nothrow @trusted @nogc { 13753 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, call.affine.mat.ptr); 13754 glnvg__checkError(gl, "affine"); 13755 glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, call.affine.mat.ptr+4); 13756 glnvg__checkError(gl, "affine"); 13757 //glnvg__setUniforms(gl, call.uniformOffset, call.image); 13758 } 13759 13760 void glnvg__renderCancelInternal (GLNVGcontext* gl, bool clearTextures) nothrow @trusted @nogc { 13761 scope(exit) gl.inFrame = false; 13762 if (clearTextures && gl.inFrame) { 13763 try { 13764 import core.thread : Thread; 13765 static if (__VERSION__ < 2076) { 13766 DGNoThrowNoGC(() { 13767 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13768 })(); 13769 } else { 13770 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13771 } 13772 } catch (Exception e) {} 13773 foreach (ref GLNVGcall c; gl.calls[0..gl.ncalls]) if (c.image > 0) glnvg__deleteTexture(gl, c.image); 13774 } 13775 gl.nverts = 0; 13776 gl.npaths = 0; 13777 gl.ncalls = 0; 13778 gl.nuniforms = 0; 13779 gl.msp = 1; 13780 gl.maskStack.ptr[0] = GLMaskState.DontMask; 13781 } 13782 13783 void glnvg__renderCancel (void* uptr) nothrow @trusted @nogc { 13784 glnvg__renderCancelInternal(cast(GLNVGcontext*)uptr, true); 13785 } 13786 13787 GLenum glnvg_convertBlendFuncFactor (NVGBlendFactor factor) pure nothrow @trusted @nogc { 13788 if (factor == NVGBlendFactor.Zero) return GL_ZERO; 13789 if (factor == NVGBlendFactor.One) return GL_ONE; 13790 if (factor == NVGBlendFactor.SrcColor) return GL_SRC_COLOR; 13791 if (factor == NVGBlendFactor.OneMinusSrcColor) return GL_ONE_MINUS_SRC_COLOR; 13792 if (factor == NVGBlendFactor.DstColor) return GL_DST_COLOR; 13793 if (factor == NVGBlendFactor.OneMinusDstColor) return GL_ONE_MINUS_DST_COLOR; 13794 if (factor == NVGBlendFactor.SrcAlpha) return GL_SRC_ALPHA; 13795 if (factor == NVGBlendFactor.OneMinusSrcAlpha) return GL_ONE_MINUS_SRC_ALPHA; 13796 if (factor == NVGBlendFactor.DstAlpha) return GL_DST_ALPHA; 13797 if (factor == NVGBlendFactor.OneMinusDstAlpha) return GL_ONE_MINUS_DST_ALPHA; 13798 if (factor == NVGBlendFactor.SrcAlphaSaturate) return GL_SRC_ALPHA_SATURATE; 13799 return GL_INVALID_ENUM; 13800 } 13801 13802 GLNVGblend glnvg__buildBlendFunc (NVGCompositeOperationState op) pure nothrow @trusted @nogc { 13803 GLNVGblend res; 13804 res.simple = op.simple; 13805 res.srcRGB = glnvg_convertBlendFuncFactor(op.srcRGB); 13806 res.dstRGB = glnvg_convertBlendFuncFactor(op.dstRGB); 13807 res.srcAlpha = glnvg_convertBlendFuncFactor(op.srcAlpha); 13808 res.dstAlpha = glnvg_convertBlendFuncFactor(op.dstAlpha); 13809 if (res.simple) { 13810 if (res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) { 13811 res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM; 13812 } 13813 } else { 13814 if (res.srcRGB == GL_INVALID_ENUM || res.dstRGB == GL_INVALID_ENUM || res.srcAlpha == GL_INVALID_ENUM || res.dstAlpha == GL_INVALID_ENUM) { 13815 res.simple = true; 13816 res.srcRGB = res.srcAlpha = res.dstRGB = res.dstAlpha = GL_INVALID_ENUM; 13817 } 13818 } 13819 return res; 13820 } 13821 13822 void glnvg__blendCompositeOperation() (GLNVGcontext* gl, in auto ref GLNVGblend op) nothrow @trusted @nogc { 13823 //glBlendFuncSeparate(glnvg_convertBlendFuncFactor(op.srcRGB), glnvg_convertBlendFuncFactor(op.dstRGB), glnvg_convertBlendFuncFactor(op.srcAlpha), glnvg_convertBlendFuncFactor(op.dstAlpha)); 13824 static if (NANOVG_GL_USE_STATE_FILTER) { 13825 if (gl.blendFunc.simple == op.simple) { 13826 if (op.simple) { 13827 if (gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return; 13828 } else { 13829 if (gl.blendFunc.srcRGB == op.srcRGB && gl.blendFunc.dstRGB == op.dstRGB && gl.blendFunc.srcAlpha == op.srcAlpha && gl.blendFunc.dstAlpha == op.dstAlpha) return; 13830 } 13831 } 13832 gl.blendFunc = op; 13833 } 13834 if (op.simple) { 13835 if (op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) { 13836 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 13837 } else { 13838 glBlendFunc(op.srcAlpha, op.dstAlpha); 13839 } 13840 } else { 13841 if (op.srcRGB == GL_INVALID_ENUM || op.dstRGB == GL_INVALID_ENUM || op.srcAlpha == GL_INVALID_ENUM || op.dstAlpha == GL_INVALID_ENUM) { 13842 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 13843 } else { 13844 glBlendFuncSeparate(op.srcRGB, op.dstRGB, op.srcAlpha, op.dstAlpha); 13845 } 13846 } 13847 } 13848 13849 void glnvg__renderSetAffine (void* uptr, in ref NVGMatrix mat) nothrow @trusted @nogc { 13850 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13851 GLNVGcall* call; 13852 // if last operation was GLNVG_AFFINE, simply replace the matrix 13853 if (gl.ncalls > 0 && gl.calls[gl.ncalls-1].type == GLNVG_AFFINE) { 13854 call = &gl.calls[gl.ncalls-1]; 13855 } else { 13856 call = glnvg__allocCall(gl); 13857 if (call is null) return; 13858 call.type = GLNVG_AFFINE; 13859 } 13860 call.affine.mat.ptr[0..6] = mat.mat.ptr[0..6]; 13861 } 13862 13863 version(nanovega_debug_clipping) public __gshared bool nanovegaClipDebugDump = false; 13864 13865 void glnvg__renderFlush (void* uptr) nothrow @trusted @nogc { 13866 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 13867 if (!gl.inFrame) assert(0, "NanoVega: internal driver error"); 13868 try { 13869 import core.thread : Thread; 13870 static if (__VERSION__ < 2076) { 13871 DGNoThrowNoGC(() { 13872 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13873 })(); 13874 } else { 13875 if (gl.mainTID != Thread.getThis.id) assert(0, "NanoVega: cannot use context in alien thread"); 13876 } 13877 } catch (Exception e) {} 13878 scope(exit) gl.inFrame = false; 13879 13880 glnvg__resetError!true(gl); 13881 { 13882 int vv = 0; 13883 glGetIntegerv(GL_FRAMEBUFFER_BINDING, &vv); 13884 if (glGetError() || vv < 0) vv = 0; 13885 gl.mainFBO = cast(uint)vv; 13886 } 13887 13888 enum ShaderType { None, Fill, Clip } 13889 auto lastShader = ShaderType.None; 13890 if (gl.ncalls > 0) { 13891 gl.msp = 1; 13892 gl.maskStack.ptr[0] = GLMaskState.DontMask; 13893 13894 // Setup require GL state. 13895 glUseProgram(gl.shader.prog); 13896 13897 glActiveTexture(GL_TEXTURE1); 13898 glBindTexture(GL_TEXTURE_2D, 0); 13899 glActiveTexture(GL_TEXTURE0); 13900 glnvg__resetFBOClipTextureCache(gl); 13901 13902 //glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 13903 static if (NANOVG_GL_USE_STATE_FILTER) { 13904 gl.blendFunc.simple = true; 13905 gl.blendFunc.srcRGB = gl.blendFunc.dstRGB = gl.blendFunc.srcAlpha = gl.blendFunc.dstAlpha = GL_INVALID_ENUM; 13906 } 13907 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // just in case 13908 glEnable(GL_CULL_FACE); 13909 glCullFace(GL_BACK); 13910 glFrontFace(GL_CCW); 13911 glEnable(GL_BLEND); 13912 glDisable(GL_DEPTH_TEST); 13913 glDisable(GL_SCISSOR_TEST); 13914 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 13915 glStencilMask(0xffffffff); 13916 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); 13917 glStencilFunc(GL_ALWAYS, 0, 0xffffffff); 13918 glActiveTexture(GL_TEXTURE0); 13919 glBindTexture(GL_TEXTURE_2D, 0); 13920 static if (NANOVG_GL_USE_STATE_FILTER) { 13921 gl.boundTexture = 0; 13922 gl.stencilMask = 0xffffffff; 13923 gl.stencilFunc = GL_ALWAYS; 13924 gl.stencilFuncRef = 0; 13925 gl.stencilFuncMask = 0xffffffff; 13926 } 13927 glnvg__checkError(gl, "OpenGL setup"); 13928 13929 // Upload vertex data 13930 glBindBuffer(GL_ARRAY_BUFFER, gl.vertBuf); 13931 // ensure that there's space for at least 4 vertices in case we need to draw a quad (e. g. framebuffer copy) 13932 glBufferData(GL_ARRAY_BUFFER, (gl.nverts < 4 ? 4 : gl.nverts) * NVGVertex.sizeof, gl.verts, GL_STREAM_DRAW); 13933 glEnableVertexAttribArray(0); 13934 glEnableVertexAttribArray(1); 13935 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)cast(usize)0); 13936 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, NVGVertex.sizeof, cast(const(GLvoid)*)(0+2*float.sizeof)); 13937 glnvg__checkError(gl, "vertex data uploading"); 13938 13939 // Set view and texture just once per frame. 13940 glUniform1i(gl.shader.loc[GLNVGuniformLoc.Tex], 0); 13941 if (gl.shader.loc[GLNVGuniformLoc.ClipTex] != -1) { 13942 //{ import core.stdc.stdio; printf("%d\n", gl.shader.loc[GLNVGuniformLoc.ClipTex]); } 13943 glUniform1i(gl.shader.loc[GLNVGuniformLoc.ClipTex], 1); 13944 } 13945 if (gl.shader.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shader.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr); 13946 glnvg__checkError(gl, "render shader setup"); 13947 13948 // Reset affine transformations. 13949 glUniform4fv(gl.shader.loc[GLNVGuniformLoc.TMat], 1, NVGMatrix.IdentityMat.ptr); 13950 glUniform2fv(gl.shader.loc[GLNVGuniformLoc.TTr], 1, NVGMatrix.IdentityMat.ptr+4); 13951 glnvg__checkError(gl, "affine setup"); 13952 13953 // set clip shaders params 13954 // fill 13955 glUseProgram(gl.shaderFillFBO.prog); 13956 glnvg__checkError(gl, "clip shaders setup (fill 0)"); 13957 if (gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderFillFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr); 13958 glnvg__checkError(gl, "clip shaders setup (fill 1)"); 13959 // copy 13960 glUseProgram(gl.shaderCopyFBO.prog); 13961 glnvg__checkError(gl, "clip shaders setup (copy 0)"); 13962 if (gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize] != -1) glUniform2fv(gl.shaderCopyFBO.loc[GLNVGuniformLoc.ViewSize], 1, gl.view.ptr); 13963 glnvg__checkError(gl, "clip shaders setup (copy 1)"); 13964 //glUniform1i(gl.shaderFillFBO.loc[GLNVGuniformLoc.Tex], 0); 13965 glUniform1i(gl.shaderCopyFBO.loc[GLNVGuniformLoc.Tex], 0); 13966 glnvg__checkError(gl, "clip shaders setup (copy 2)"); 13967 // restore render shader 13968 glUseProgram(gl.shader.prog); 13969 13970 //{ 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]); } 13971 13972 gl.lastAffine.identity; 13973 13974 foreach (int i; 0..gl.ncalls) { 13975 GLNVGcall* call = &gl.calls[i]; 13976 switch (call.type) { 13977 case GLNVG_FILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__fill(gl, call); break; 13978 case GLNVG_CONVEXFILL: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__convexFill(gl, call); break; 13979 case GLNVG_STROKE: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__stroke(gl, call); break; 13980 case GLNVG_TRIANGLES: glnvg__blendCompositeOperation(gl, call.blendFunc); glnvg__triangles(gl, call); break; 13981 case GLNVG_AFFINE: gl.lastAffine = call.affine; glnvg__affine(gl, call); break; 13982 // clip region management 13983 case GLNVG_PUSHCLIP: 13984 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]); } 13985 if (gl.msp >= gl.maskStack.length) assert(0, "NanoVega: mask stack overflow in OpenGL backend"); 13986 if (gl.maskStack.ptr[gl.msp-1] == GLMaskState.DontMask) { 13987 gl.maskStack.ptr[gl.msp++] = GLMaskState.DontMask; 13988 } else { 13989 gl.maskStack.ptr[gl.msp++] = GLMaskState.Uninitialized; 13990 } 13991 // no need to reset FBO cache here, as nothing was changed 13992 break; 13993 case GLNVG_POPCLIP: 13994 if (gl.msp <= 1) assert(0, "NanoVega: mask stack underflow in OpenGL backend"); 13995 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]); } 13996 --gl.msp; 13997 assert(gl.msp > 0); 13998 //{ import core.stdc.stdio; printf("popped; new msp is %d; state is %d\n", gl.msp, gl.maskStack.ptr[gl.msp]); } 13999 // check popped item 14000 final switch (gl.maskStack.ptr[gl.msp]) { 14001 case GLMaskState.DontMask: 14002 // if last FBO was "don't mask", reset cache if current is not "don't mask" 14003 if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) { 14004 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); } 14005 glnvg__resetFBOClipTextureCache(gl); 14006 } 14007 break; 14008 case GLMaskState.Uninitialized: 14009 // if last FBO texture was uninitialized, it means that nothing was changed, 14010 // so we can keep using cached FBO 14011 break; 14012 case GLMaskState.Initialized: 14013 // if last FBO was initialized, it means that something was definitely changed 14014 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); } 14015 glnvg__resetFBOClipTextureCache(gl); 14016 break; 14017 case GLMaskState.JustCleared: assert(0, "NanoVega: internal FBO stack error"); 14018 } 14019 break; 14020 case GLNVG_RESETCLIP: 14021 // mark current mask as "don't mask" 14022 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]); } 14023 if (gl.msp > 0) { 14024 if (gl.maskStack.ptr[gl.msp-1] != GLMaskState.DontMask) { 14025 gl.maskStack.ptr[gl.msp-1] = GLMaskState.DontMask; 14026 version(nanovega_debug_clipping) if (nanovegaClipDebugDump) { import core.stdc.stdio; printf(" +++ need to reset FBO cache\n"); } 14027 glnvg__resetFBOClipTextureCache(gl); 14028 } 14029 } 14030 break; 14031 case GLNVG_CLIP_DDUMP_ON: 14032 version(nanovega_debug_clipping) nanovegaClipDebugDump = true; 14033 break; 14034 case GLNVG_CLIP_DDUMP_OFF: 14035 version(nanovega_debug_clipping) nanovegaClipDebugDump = false; 14036 break; 14037 case GLNVG_NONE: break; 14038 default: 14039 { 14040 import core.stdc.stdio; stderr.fprintf("NanoVega FATAL: invalid command in OpenGL backend: %d\n", call.type); 14041 } 14042 assert(0, "NanoVega: invalid command in OpenGL backend (fatal internal error)"); 14043 } 14044 // and free texture, why not 14045 glnvg__deleteTexture(gl, call.image); 14046 } 14047 14048 glDisableVertexAttribArray(0); 14049 glDisableVertexAttribArray(1); 14050 glDisable(GL_CULL_FACE); 14051 glBindBuffer(GL_ARRAY_BUFFER, 0); 14052 glUseProgram(0); 14053 glnvg__bindTexture(gl, 0); 14054 } 14055 14056 // this will do all necessary cleanup 14057 glnvg__renderCancelInternal(gl, false); // no need to clear textures 14058 } 14059 14060 int glnvg__maxVertCount (const(NVGpath)* paths, int npaths) nothrow @trusted @nogc { 14061 int count = 0; 14062 foreach (int i; 0..npaths) { 14063 count += paths[i].nfill; 14064 count += paths[i].nstroke; 14065 } 14066 return count; 14067 } 14068 14069 GLNVGcall* glnvg__allocCall (GLNVGcontext* gl) nothrow @trusted @nogc { 14070 GLNVGcall* ret = null; 14071 if (gl.ncalls+1 > gl.ccalls) { 14072 GLNVGcall* calls; 14073 int ccalls = glnvg__maxi(gl.ncalls+1, 128)+gl.ccalls/2; // 1.5x Overallocate 14074 calls = cast(GLNVGcall*)realloc(gl.calls, GLNVGcall.sizeof*ccalls); 14075 if (calls is null) return null; 14076 gl.calls = calls; 14077 gl.ccalls = ccalls; 14078 } 14079 ret = &gl.calls[gl.ncalls++]; 14080 memset(ret, 0, GLNVGcall.sizeof); 14081 return ret; 14082 } 14083 14084 int glnvg__allocPaths (GLNVGcontext* gl, int n) nothrow @trusted @nogc { 14085 int ret = 0; 14086 if (gl.npaths+n > gl.cpaths) { 14087 GLNVGpath* paths; 14088 int cpaths = glnvg__maxi(gl.npaths+n, 128)+gl.cpaths/2; // 1.5x Overallocate 14089 paths = cast(GLNVGpath*)realloc(gl.paths, GLNVGpath.sizeof*cpaths); 14090 if (paths is null) return -1; 14091 gl.paths = paths; 14092 gl.cpaths = cpaths; 14093 } 14094 ret = gl.npaths; 14095 gl.npaths += n; 14096 return ret; 14097 } 14098 14099 int glnvg__allocVerts (GLNVGcontext* gl, int n) nothrow @trusted @nogc { 14100 int ret = 0; 14101 if (gl.nverts+n > gl.cverts) { 14102 NVGVertex* verts; 14103 int cverts = glnvg__maxi(gl.nverts+n, 4096)+gl.cverts/2; // 1.5x Overallocate 14104 verts = cast(NVGVertex*)realloc(gl.verts, NVGVertex.sizeof*cverts); 14105 if (verts is null) return -1; 14106 gl.verts = verts; 14107 gl.cverts = cverts; 14108 } 14109 ret = gl.nverts; 14110 gl.nverts += n; 14111 return ret; 14112 } 14113 14114 int glnvg__allocFragUniforms (GLNVGcontext* gl, int n) nothrow @trusted @nogc { 14115 int ret = 0, structSize = gl.fragSize; 14116 if (gl.nuniforms+n > gl.cuniforms) { 14117 ubyte* uniforms; 14118 int cuniforms = glnvg__maxi(gl.nuniforms+n, 128)+gl.cuniforms/2; // 1.5x Overallocate 14119 uniforms = cast(ubyte*)realloc(gl.uniforms, structSize*cuniforms); 14120 if (uniforms is null) return -1; 14121 gl.uniforms = uniforms; 14122 gl.cuniforms = cuniforms; 14123 } 14124 ret = gl.nuniforms*structSize; 14125 gl.nuniforms += n; 14126 return ret; 14127 } 14128 14129 GLNVGfragUniforms* nvg__fragUniformPtr (GLNVGcontext* gl, int i) nothrow @trusted @nogc { 14130 return cast(GLNVGfragUniforms*)&gl.uniforms[i]; 14131 } 14132 14133 void glnvg__vset (NVGVertex* vtx, float x, float y, float u, float v) nothrow @trusted @nogc { 14134 vtx.x = x; 14135 vtx.y = y; 14136 vtx.u = u; 14137 vtx.v = v; 14138 } 14139 14140 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 { 14141 if (npaths < 1) return; 14142 14143 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14144 GLNVGcall* call = glnvg__allocCall(gl); 14145 NVGVertex* quad; 14146 GLNVGfragUniforms* frag; 14147 int maxverts, offset; 14148 14149 if (call is null) return; 14150 14151 call.type = GLNVG_FILL; 14152 call.evenOdd = evenOdd; 14153 call.clipmode = clipmode; 14154 //if (clipmode != NVGClipMode.None) { import core.stdc.stdio; printf("CLIP!\n"); } 14155 call.blendFunc = glnvg__buildBlendFunc(compositeOperation); 14156 call.triangleCount = 4; 14157 call.pathOffset = glnvg__allocPaths(gl, npaths); 14158 if (call.pathOffset == -1) goto error; 14159 call.pathCount = npaths; 14160 call.image = paint.image.id; 14161 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); 14162 14163 if (npaths == 1 && paths[0].convex) { 14164 call.type = GLNVG_CONVEXFILL; 14165 call.triangleCount = 0; // Bounding box fill quad not needed for convex fill 14166 } 14167 14168 // Allocate vertices for all the paths. 14169 maxverts = glnvg__maxVertCount(paths, npaths)+call.triangleCount; 14170 offset = glnvg__allocVerts(gl, maxverts); 14171 if (offset == -1) goto error; 14172 14173 foreach (int i; 0..npaths) { 14174 GLNVGpath* copy = &gl.paths[call.pathOffset+i]; 14175 const(NVGpath)* path = &paths[i]; 14176 memset(copy, 0, GLNVGpath.sizeof); 14177 if (path.nfill > 0) { 14178 copy.fillOffset = offset; 14179 copy.fillCount = path.nfill; 14180 memcpy(&gl.verts[offset], path.fill, NVGVertex.sizeof*path.nfill); 14181 offset += path.nfill; 14182 } 14183 if (path.nstroke > 0) { 14184 copy.strokeOffset = offset; 14185 copy.strokeCount = path.nstroke; 14186 memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke); 14187 offset += path.nstroke; 14188 } 14189 } 14190 14191 // Setup uniforms for draw calls 14192 if (call.type == GLNVG_FILL) { 14193 import core.stdc..string : memcpy; 14194 // Quad 14195 call.triangleOffset = offset; 14196 quad = &gl.verts[call.triangleOffset]; 14197 glnvg__vset(&quad[0], bounds[2], bounds[3], 0.5f, 1.0f); 14198 glnvg__vset(&quad[1], bounds[2], bounds[1], 0.5f, 1.0f); 14199 glnvg__vset(&quad[2], bounds[0], bounds[3], 0.5f, 1.0f); 14200 glnvg__vset(&quad[3], bounds[0], bounds[1], 0.5f, 1.0f); 14201 // Get uniform 14202 call.uniformOffset = glnvg__allocFragUniforms(gl, 2); 14203 if (call.uniformOffset == -1) goto error; 14204 // Simple shader for stencil 14205 frag = nvg__fragUniformPtr(gl, call.uniformOffset); 14206 memset(frag, 0, (*frag).sizeof); 14207 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f); 14208 memcpy(nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), frag, (*frag).sizeof); 14209 frag.strokeThr = -1.0f; 14210 frag.type = NSVG_SHADER_SIMPLE; 14211 // Fill shader 14212 //glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, fringe, fringe, -1.0f); 14213 } else { 14214 call.uniformOffset = glnvg__allocFragUniforms(gl, 1); 14215 if (call.uniformOffset == -1) goto error; 14216 // Fill shader 14217 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, fringe, fringe, -1.0f); 14218 } 14219 14220 return; 14221 14222 error: 14223 // We get here if call alloc was ok, but something else is not. 14224 // Roll back the last call to prevent drawing it. 14225 if (gl.ncalls > 0) --gl.ncalls; 14226 } 14227 14228 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 { 14229 if (npaths < 1) return; 14230 14231 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14232 GLNVGcall* call = glnvg__allocCall(gl); 14233 int maxverts, offset; 14234 14235 if (call is null) return; 14236 14237 call.type = GLNVG_STROKE; 14238 call.clipmode = clipmode; 14239 call.blendFunc = glnvg__buildBlendFunc(compositeOperation); 14240 call.pathOffset = glnvg__allocPaths(gl, npaths); 14241 if (call.pathOffset == -1) goto error; 14242 call.pathCount = npaths; 14243 call.image = paint.image.id; 14244 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); 14245 14246 // Allocate vertices for all the paths. 14247 maxverts = glnvg__maxVertCount(paths, npaths); 14248 offset = glnvg__allocVerts(gl, maxverts); 14249 if (offset == -1) goto error; 14250 14251 foreach (int i; 0..npaths) { 14252 GLNVGpath* copy = &gl.paths[call.pathOffset+i]; 14253 const(NVGpath)* path = &paths[i]; 14254 memset(copy, 0, GLNVGpath.sizeof); 14255 if (path.nstroke) { 14256 copy.strokeOffset = offset; 14257 copy.strokeCount = path.nstroke; 14258 memcpy(&gl.verts[offset], path.stroke, NVGVertex.sizeof*path.nstroke); 14259 offset += path.nstroke; 14260 } 14261 } 14262 14263 if (gl.flags&NVGContextFlag.StencilStrokes) { 14264 // Fill shader 14265 call.uniformOffset = glnvg__allocFragUniforms(gl, 2); 14266 if (call.uniformOffset == -1) goto error; 14267 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f); 14268 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset+gl.fragSize), paint, scissor, strokeWidth, fringe, 1.0f-0.5f/255.0f); 14269 } else { 14270 // Fill shader 14271 call.uniformOffset = glnvg__allocFragUniforms(gl, 1); 14272 if (call.uniformOffset == -1) goto error; 14273 glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call.uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f); 14274 } 14275 14276 return; 14277 14278 error: 14279 // We get here if call alloc was ok, but something else is not. 14280 // Roll back the last call to prevent drawing it. 14281 if (gl.ncalls > 0) --gl.ncalls; 14282 } 14283 14284 void glnvg__renderTriangles (void* uptr, NVGCompositeOperationState compositeOperation, NVGClipMode clipmode, NVGPaint* paint, NVGscissor* scissor, const(NVGVertex)* verts, int nverts) nothrow @trusted @nogc { 14285 if (nverts < 1) return; 14286 14287 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14288 GLNVGcall* call = glnvg__allocCall(gl); 14289 GLNVGfragUniforms* frag; 14290 14291 if (call is null) return; 14292 14293 call.type = GLNVG_TRIANGLES; 14294 call.clipmode = clipmode; 14295 call.blendFunc = glnvg__buildBlendFunc(compositeOperation); 14296 call.image = paint.image.id; 14297 if (call.image > 0) glnvg__renderTextureIncRef(uptr, call.image); 14298 14299 // Allocate vertices for all the paths. 14300 call.triangleOffset = glnvg__allocVerts(gl, nverts); 14301 if (call.triangleOffset == -1) goto error; 14302 call.triangleCount = nverts; 14303 14304 memcpy(&gl.verts[call.triangleOffset], verts, NVGVertex.sizeof*nverts); 14305 14306 // Fill shader 14307 call.uniformOffset = glnvg__allocFragUniforms(gl, 1); 14308 if (call.uniformOffset == -1) goto error; 14309 frag = nvg__fragUniformPtr(gl, call.uniformOffset); 14310 glnvg__convertPaint(gl, frag, paint, scissor, 1.0f, 1.0f, -1.0f); 14311 frag.type = NSVG_SHADER_IMG; 14312 14313 return; 14314 14315 error: 14316 // We get here if call alloc was ok, but something else is not. 14317 // Roll back the last call to prevent drawing it. 14318 if (gl.ncalls > 0) --gl.ncalls; 14319 } 14320 14321 void glnvg__renderDelete (void* uptr) nothrow @trusted @nogc { 14322 GLNVGcontext* gl = cast(GLNVGcontext*)uptr; 14323 if (gl is null) return; 14324 14325 glnvg__killFBOs(gl); 14326 glnvg__deleteShader(&gl.shader); 14327 glnvg__deleteShader(&gl.shaderFillFBO); 14328 glnvg__deleteShader(&gl.shaderCopyFBO); 14329 14330 if (gl.vertBuf != 0) glDeleteBuffers(1, &gl.vertBuf); 14331 14332 foreach (ref GLNVGtexture tex; gl.textures[0..gl.ntextures]) { 14333 if (tex.id != 0 && (tex.flags&NVGImageFlag.NoDelete) == 0) { 14334 assert(tex.tex != 0); 14335 glDeleteTextures(1, &tex.tex); 14336 } 14337 } 14338 free(gl.textures); 14339 14340 free(gl.paths); 14341 free(gl.verts); 14342 free(gl.uniforms); 14343 free(gl.calls); 14344 14345 free(gl); 14346 } 14347 14348 14349 /** Creates NanoVega contexts for OpenGL2+. 14350 * 14351 * Specify creation flags as additional arguments, like this: 14352 * `nvgCreateContext(NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes);` 14353 * 14354 * If you won't specify any flags, defaults will be used: 14355 * `[NVGContextFlag.Antialias, NVGContextFlag.StencilStrokes]`. 14356 * 14357 * Group: context_management 14358 */ 14359 public NVGContext nvgCreateContext (const(NVGContextFlag)[] flagList...) nothrow @trusted @nogc { 14360 version(aliced) { 14361 enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes|NVGContextFlag.FontNoAA; 14362 } else { 14363 enum DefaultFlags = NVGContextFlag.Antialias|NVGContextFlag.StencilStrokes; 14364 } 14365 uint flags = 0; 14366 if (flagList.length != 0) { 14367 foreach (immutable flg; flagList) flags |= (flg != NVGContextFlag.Default ? flg : DefaultFlags); 14368 } else { 14369 flags = DefaultFlags; 14370 } 14371 NVGparams params = void; 14372 NVGContext ctx = null; 14373 version(nanovg_builtin_opengl_bindings) nanovgInitOpenGL(); // why not? 14374 version(nanovg_bindbc_opengl_bindings) nanovgInitOpenGL(); 14375 GLNVGcontext* gl = cast(GLNVGcontext*)malloc(GLNVGcontext.sizeof); 14376 if (gl is null) goto error; 14377 memset(gl, 0, GLNVGcontext.sizeof); 14378 14379 memset(¶ms, 0, params.sizeof); 14380 params.renderCreate = &glnvg__renderCreate; 14381 params.renderCreateTexture = &glnvg__renderCreateTexture; 14382 params.renderTextureIncRef = &glnvg__renderTextureIncRef; 14383 params.renderDeleteTexture = &glnvg__renderDeleteTexture; 14384 params.renderUpdateTexture = &glnvg__renderUpdateTexture; 14385 params.renderGetTextureSize = &glnvg__renderGetTextureSize; 14386 params.renderViewport = &glnvg__renderViewport; 14387 params.renderCancel = &glnvg__renderCancel; 14388 params.renderFlush = &glnvg__renderFlush; 14389 params.renderPushClip = &glnvg__renderPushClip; 14390 params.renderPopClip = &glnvg__renderPopClip; 14391 params.renderResetClip = &glnvg__renderResetClip; 14392 params.renderFill = &glnvg__renderFill; 14393 params.renderStroke = &glnvg__renderStroke; 14394 params.renderTriangles = &glnvg__renderTriangles; 14395 params.renderSetAffine = &glnvg__renderSetAffine; 14396 params.renderDelete = &glnvg__renderDelete; 14397 params.userPtr = gl; 14398 params.edgeAntiAlias = (flags&NVGContextFlag.Antialias ? true : false); 14399 if (flags&(NVGContextFlag.FontAA|NVGContextFlag.FontNoAA)) { 14400 params.fontAA = (flags&NVGContextFlag.FontNoAA ? NVG_INVERT_FONT_AA : !NVG_INVERT_FONT_AA); 14401 } else { 14402 params.fontAA = NVG_INVERT_FONT_AA; 14403 } 14404 14405 gl.flags = flags; 14406 gl.freetexid = -1; 14407 14408 ctx = createInternal(¶ms); 14409 if (ctx is null) goto error; 14410 14411 static if (__VERSION__ < 2076) { 14412 DGNoThrowNoGC(() { import core.thread; gl.mainTID = Thread.getThis.id; })(); 14413 } else { 14414 try { import core.thread; gl.mainTID = Thread.getThis.id; } catch (Exception e) {} 14415 } 14416 14417 return ctx; 14418 14419 error: 14420 // 'gl' is freed by nvgDeleteInternal. 14421 if (ctx !is null) ctx.deleteInternal(); 14422 return null; 14423 } 14424 14425 /// Using OpenGL texture id creates GLNVGtexture and return its id. 14426 /// Group: images 14427 public int glCreateImageFromHandleGL2 (NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc { 14428 GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr; 14429 GLNVGtexture* tex = glnvg__allocTexture(gl); 14430 14431 if (tex is null) return 0; 14432 14433 tex.type = NVGtexture.RGBA; 14434 tex.tex = textureId; 14435 tex.flags = imageFlags; 14436 tex.width = w; 14437 tex.height = h; 14438 14439 return tex.id; 14440 } 14441 14442 /// Create NVGImage from OpenGL texture id. 14443 /// Group: images 14444 public NVGImage glCreateImageFromOpenGLTexture(NVGContext ctx, GLuint textureId, int w, int h, int imageFlags) nothrow @trusted @nogc { 14445 auto id = glCreateImageFromHandleGL2(ctx, textureId, w, h, imageFlags); 14446 14447 NVGImage res; 14448 if (id > 0) { 14449 res.id = id; 14450 version(nanovega_debug_image_manager_rc) { import core.stdc.stdio; printf("createImageRGBA: img=%p; imgid=%d\n", &res, res.id); } 14451 res.ctx = ctx; 14452 ctx.nvg__imageIncRef(res.id, false); // don't increment driver refcount 14453 } 14454 return res; 14455 } 14456 14457 /// Returns OpenGL texture id for NanoVega image. 14458 /// Group: images 14459 public GLuint glImageHandleGL2 (NVGContext ctx, int image) nothrow @trusted @nogc { 14460 GLNVGcontext* gl = cast(GLNVGcontext*)ctx.internalParams().userPtr; 14461 GLNVGtexture* tex = glnvg__findTexture(gl, image); 14462 return tex.tex; 14463 } 14464 14465 14466 // ////////////////////////////////////////////////////////////////////////// // 14467 private: 14468 14469 static if (NanoVegaHasFontConfig) { 14470 version(nanovg_builtin_fontconfig_bindings) { 14471 pragma(lib, "fontconfig"); 14472 14473 private extern(C) nothrow @trusted @nogc { 14474 enum FC_FILE = "file"; /* String */ 14475 alias FcBool = int; 14476 alias FcChar8 = char; 14477 struct FcConfig; 14478 struct FcPattern; 14479 alias FcMatchKind = int; 14480 enum : FcMatchKind { 14481 FcMatchPattern, 14482 FcMatchFont, 14483 FcMatchScan 14484 } 14485 alias FcResult = int; 14486 enum : FcResult { 14487 FcResultMatch, 14488 FcResultNoMatch, 14489 FcResultTypeMismatch, 14490 FcResultNoId, 14491 FcResultOutOfMemory 14492 } 14493 FcBool FcInit (); 14494 FcBool FcConfigSubstituteWithPat (FcConfig* config, FcPattern* p, FcPattern* p_pat, FcMatchKind kind); 14495 void FcDefaultSubstitute (FcPattern* pattern); 14496 FcBool FcConfigSubstitute (FcConfig* config, FcPattern* p, FcMatchKind kind); 14497 FcPattern* FcFontMatch (FcConfig* config, FcPattern* p, FcResult* result); 14498 FcPattern* FcNameParse (const(FcChar8)* name); 14499 void FcPatternDestroy (FcPattern* p); 14500 FcResult FcPatternGetString (const(FcPattern)* p, const(char)* object, int n, FcChar8** s); 14501 } 14502 } 14503 14504 __gshared bool fontconfigAvailable = false; 14505 // initialize fontconfig 14506 shared static this () { 14507 if (FcInit()) { 14508 fontconfigAvailable = true; 14509 } else { 14510 import core.stdc.stdio : stderr, fprintf; 14511 stderr.fprintf("***NanoVega WARNING: cannot init fontconfig!\n"); 14512 } 14513 } 14514 } 14515 14516 14517 // ////////////////////////////////////////////////////////////////////////// // 14518 public enum BaphometDims = 512.0f; // baphomet icon is 512x512 ([0..511]) 14519 14520 private static immutable ubyte[7641] baphometPath = [ 14521 0x01,0x04,0x06,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x08,0xa0,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43, 14522 0x00,0x80,0xff,0x43,0xa2,0x1d,0xc6,0x43,0x00,0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x08,0x00,0x80,0xff, 14523 0x43,0x7a,0x89,0xe5,0x42,0xa0,0x1d,0xc6,0x43,0x00,0x00,0x00,0x00,0x30,0x89,0x7f,0x43,0x00,0x00,0x00, 14524 0x00,0x08,0x7a,0x89,0xe5,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7a,0x89,0xe5,0x42,0x00,0x00, 14525 0x00,0x00,0x30,0x89,0x7f,0x43,0x08,0x00,0x00,0x00,0x00,0xa2,0x1d,0xc6,0x43,0x7a,0x89,0xe5,0x42,0x00, 14526 0x80,0xff,0x43,0x30,0x89,0x7f,0x43,0x00,0x80,0xff,0x43,0x09,0x06,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd, 14527 0x43,0x08,0x16,0x68,0xb3,0x43,0x72,0x87,0xdd,0x43,0x71,0x87,0xdd,0x43,0x17,0x68,0xb3,0x43,0x71,0x87, 14528 0xdd,0x43,0x30,0x89,0x7f,0x43,0x08,0x71,0x87,0xdd,0x43,0xd2,0x2f,0x18,0x43,0x16,0x68,0xb3,0x43,0x35, 14529 0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x35,0xe2,0x87,0x42,0x08,0xd1,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42, 14530 0x35,0xe2,0x87,0x42,0xd2,0x2f,0x18,0x43,0x35,0xe2,0x87,0x42,0x30,0x89,0x7f,0x43,0x08,0x35,0xe2,0x87, 14531 0x42,0x17,0x68,0xb3,0x43,0xd1,0x2f,0x18,0x43,0x72,0x87,0xdd,0x43,0x30,0x89,0x7f,0x43,0x72,0x87,0xdd, 14532 0x43,0x09,0x06,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x07,0xa4,0x3f,0x7f,0x43,0x0b,0x86,0xdc,0x43, 14533 0x07,0x6c,0xb9,0xb2,0x43,0xe8,0xd1,0xca,0x42,0x07,0x6e,0x4d,0xa0,0x42,0xa9,0x10,0x9c,0x43,0x07,0xb7, 14534 0x40,0xd7,0x43,0xa9,0x10,0x9c,0x43,0x07,0x79,0xcb,0x11,0x43,0x62,0xbf,0xd7,0x42,0x09,0x06,0x98,0x42, 14535 0x74,0x43,0xb1,0x8d,0x68,0x43,0x08,0xd7,0x24,0x79,0x43,0xba,0x83,0x6e,0x43,0xa9,0x16,0x7c,0x43,0x56, 14536 0xa1,0x76,0x43,0x74,0x2a,0x7d,0x43,0x44,0x73,0x80,0x43,0x08,0x55,0xd1,0x7e,0x43,0xe3,0xea,0x76,0x43, 14537 0xbc,0x18,0x81,0x43,0x7f,0xa8,0x6e,0x43,0x8f,0x0a,0x84,0x43,0x02,0xfc,0x68,0x43,0x09,0x06,0x92,0x29, 14538 0x8d,0x43,0x73,0xc3,0x67,0x43,0x08,0xa4,0xd9,0x8e,0x43,0xf2,0xa6,0x7a,0x43,0x8f,0x22,0x88,0x43,0x75, 14539 0x2a,0x7d,0x43,0x42,0x7f,0x82,0x43,0x08,0xc8,0x88,0x43,0x09,0x06,0xc1,0x79,0x74,0x43,0x50,0x64,0x89, 14540 0x43,0x08,0x68,0x2d,0x72,0x43,0xee,0x21,0x81,0x43,0xcd,0x97,0x55,0x43,0xe6,0xf1,0x7b,0x43,0x91,0xec, 14541 0x5d,0x43,0xa8,0xc7,0x6a,0x43,0x09,0x06,0xfa,0xa5,0x52,0x43,0x60,0x97,0x7c,0x43,0x08,0x19,0xff,0x50, 14542 0x43,0xe9,0x6e,0x8a,0x43,0xb0,0xbd,0x70,0x43,0x4c,0x51,0x82,0x43,0x04,0xeb,0x69,0x43,0x66,0x0f,0x8e, 14543 0x43,0x09,0x06,0x17,0xbf,0x71,0x43,0x2c,0x58,0x94,0x43,0x08,0x1c,0x96,0x6e,0x43,0x61,0x68,0x99,0x43, 14544 0x2d,0x3a,0x6e,0x43,0xc8,0x81,0x9e,0x43,0xb7,0x9b,0x72,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0x30,0xdb, 14545 0x82,0x43,0xdb,0xe9,0x93,0x43,0x08,0x11,0x82,0x84,0x43,0x61,0x68,0x99,0x43,0xe8,0x4a,0x84,0x43,0x8e, 14546 0xa6,0x9e,0x43,0x42,0x7f,0x82,0x43,0x61,0xa4,0xa3,0x43,0x09,0x06,0xc4,0x02,0x85,0x43,0xd1,0x0b,0x92, 14547 0x43,0x08,0xd6,0xb2,0x86,0x43,0x34,0x1e,0x92,0x43,0x4f,0x58,0x87,0x43,0xa4,0xf1,0x92,0x43,0x03,0xd9, 14548 0x87,0x43,0x7b,0xc6,0x94,0x43,0x09,0x06,0x87,0x3e,0x64,0x43,0x31,0x3b,0x93,0x43,0x08,0x3b,0xbf,0x64, 14549 0x43,0x6f,0xf9,0x91,0x43,0x96,0x0b,0x67,0x43,0xc5,0x4a,0x91,0x43,0xcf,0xfe,0x6a,0x43,0x31,0x2f,0x91, 14550 0x43,0x09,0x06,0x16,0x74,0xb5,0x43,0x08,0xec,0x8e,0x43,0x08,0x1b,0x4b,0xb2,0x43,0xee,0x5d,0x8b,0x43, 14551 0x48,0x4d,0xad,0x43,0x12,0xa6,0x8a,0x43,0xf3,0xd7,0xa7,0x43,0x74,0xb8,0x8a,0x43,0x08,0x8c,0xb2,0xa0, 14552 0x43,0xcd,0xf8,0x8a,0x43,0x68,0x46,0x9b,0x43,0x79,0x8f,0x87,0x43,0x49,0xc9,0x96,0x43,0xe9,0x3e,0x82, 14553 0x43,0x08,0x60,0x5c,0x97,0x43,0xa1,0xde,0x8b,0x43,0x4e,0xa0,0x93,0x43,0x31,0x3b,0x93,0x43,0x9f,0xea, 14554 0x8d,0x43,0x27,0x8d,0x99,0x43,0x08,0x07,0xe0,0x8c,0x43,0x06,0x34,0x9b,0x43,0x38,0xe9,0x8c,0x43,0x46, 14555 0x0a,0x9e,0x43,0x3d,0xcc,0x8b,0x43,0xb2,0x06,0xa2,0x43,0x08,0xf1,0x40,0x8a,0x43,0xb0,0x12,0xa4,0x43, 14556 0x39,0xd1,0x88,0x43,0x76,0x43,0xa6,0x43,0xfa,0x06,0x88,0x43,0xa4,0x75,0xa9,0x43,0x08,0x19,0x6c,0x88, 14557 0x43,0x9f,0x9e,0xac,0x43,0x66,0xeb,0x87,0x43,0x44,0x76,0xb0,0x43,0x6b,0xce,0x86,0x43,0x3b,0xbc,0xb4, 14558 0x43,0x08,0xa9,0x8c,0x85,0x43,0x06,0xd0,0xb5,0x43,0xfa,0xee,0x83,0x43,0x74,0xa3,0xb6,0x43,0x3d,0x90, 14559 0x81,0x43,0x31,0xf6,0xb6,0x43,0x08,0x9d,0x61,0x7d,0x43,0xee,0x48,0xb7,0x43,0x3b,0x1f,0x75,0x43,0xcf, 14560 0xe3,0xb6,0x43,0xee,0x6f,0x6d,0x43,0x68,0xe2,0xb5,0x43,0x08,0xd4,0xed,0x6b,0x43,0x87,0x2f,0xb2,0x43, 14561 0x0e,0xc9,0x6b,0x43,0xa7,0x7c,0xae,0x43,0x98,0xfa,0x67,0x43,0xab,0x53,0xab,0x43,0x08,0x25,0x2c,0x64, 14562 0x43,0x33,0xa2,0xa8,0x43,0x40,0x96,0x61,0x43,0xc3,0xc2,0xa5,0x43,0x64,0xde,0x60,0x43,0xfa,0xa2,0xa2, 14563 0x43,0x08,0xb0,0x5d,0x60,0x43,0x06,0x4c,0x9f,0x43,0x9a,0xca,0x5f,0x43,0x38,0x3d,0x9b,0x43,0x3b,0x8f, 14564 0x5c,0x43,0x85,0xb0,0x98,0x43,0x08,0x42,0x36,0x51,0x43,0x3d,0xf0,0x91,0x43,0xcd,0x4f,0x49,0x43,0xdb, 14565 0xb9,0x8b,0x43,0xe0,0xdb,0x44,0x43,0x42,0x8b,0x84,0x43,0x08,0x7e,0xc9,0x44,0x43,0x8a,0x57,0x8d,0x43, 14566 0xbc,0x6c,0x0f,0x43,0x23,0x62,0x8e,0x43,0xf5,0x17,0x07,0x43,0xc5,0x3e,0x8f,0x43,0x09,0x06,0xe0,0xea, 14567 0x76,0x43,0xab,0xef,0xc5,0x43,0x08,0x12,0x00,0x79,0x43,0xab,0xcb,0xbf,0x43,0x79,0xb9,0x6d,0x43,0x7e, 14568 0x8d,0xba,0x43,0xee,0x6f,0x6d,0x43,0x98,0xeb,0xb5,0x43,0x08,0xe0,0x02,0x7b,0x43,0x5f,0x1c,0xb8,0x43, 14569 0x85,0x2c,0x82,0x43,0xe9,0x65,0xb8,0x43,0xd6,0xb2,0x86,0x43,0xc6,0x05,0xb5,0x43,0x08,0x03,0xcd,0x85, 14570 0x43,0x5a,0x39,0xb9,0x43,0xe4,0x4f,0x81,0x43,0xdb,0xd4,0xbf,0x43,0xdf,0x6c,0x82,0x43,0xbc,0x93,0xc5, 14571 0x43,0x09,0x06,0xf0,0xd0,0x22,0x43,0x5d,0x19,0x08,0x43,0x08,0xbc,0xab,0x49,0x43,0x4a,0x35,0x29,0x43, 14572 0xcb,0xf7,0x65,0x43,0xce,0x37,0x45,0x43,0x0e,0x99,0x63,0x43,0x67,0xc6,0x5c,0x43,0x09,0x06,0x05,0x94, 14573 0xab,0x43,0xc2,0x13,0x04,0x43,0x08,0x9f,0x26,0x98,0x43,0x11,0x42,0x25,0x43,0x97,0x00,0x8a,0x43,0x32, 14574 0x32,0x41,0x43,0xf5,0x2f,0x8b,0x43,0xc7,0xc0,0x58,0x43,0x09,0x06,0x8f,0x85,0x48,0x43,0xe0,0xa8,0x8c, 14575 0x43,0x08,0x55,0xaa,0x48,0x43,0xe0,0xa8,0x8c,0x43,0x6b,0x3d,0x49,0x43,0xc1,0x43,0x8c,0x43,0x31,0x62, 14576 0x49,0x43,0xc1,0x43,0x8c,0x43,0x08,0x2f,0xe3,0x2f,0x43,0xad,0xe7,0x98,0x43,0xff,0x0d,0x0d,0x43,0xad, 14577 0xf3,0x9a,0x43,0xf0,0xaf,0xcc,0x42,0x74,0x00,0x97,0x43,0x08,0xbb,0xa2,0xf7,0x42,0x93,0x4d,0x93,0x43, 14578 0x5e,0x19,0x08,0x43,0x5a,0x2a,0x87,0x43,0x23,0x6e,0x10,0x43,0x42,0x97,0x86,0x43,0x08,0xca,0xe8,0x33, 14579 0x43,0x1b,0x3c,0x80,0x43,0x80,0xe8,0x4d,0x43,0xda,0xf4,0x70,0x43,0xae,0x0e,0x4f,0x43,0x2b,0x1b,0x65, 14580 0x43,0x08,0x66,0x96,0x54,0x43,0xa3,0xe1,0x3b,0x43,0x4e,0xc4,0x19,0x43,0xa0,0x1a,0x16,0x43,0x10,0xe2, 14581 0x14,0x43,0x26,0x14,0xe0,0x42,0x08,0x5c,0x91,0x1c,0x43,0xcb,0x27,0xee,0x42,0xa9,0x40,0x24,0x43,0x71, 14582 0x3b,0xfc,0x42,0xf3,0xef,0x2b,0x43,0x8b,0x27,0x05,0x43,0x08,0xe2,0x4b,0x2c,0x43,0x48,0x86,0x07,0x43, 14583 0x79,0x62,0x2f,0x43,0x05,0xe5,0x09,0x43,0x55,0x32,0x34,0x43,0xa0,0xd2,0x09,0x43,0x08,0x74,0xa3,0x36, 14584 0x43,0x3a,0xd1,0x08,0x43,0x7e,0x81,0x38,0x43,0x09,0xd4,0x0a,0x43,0x0d,0xba,0x39,0x43,0xa0,0xea,0x0d, 14585 0x43,0x08,0x6f,0xe4,0x3d,0x43,0x43,0xc7,0x0e,0x43,0xd6,0xe5,0x3e,0x43,0xc4,0x4a,0x11,0x43,0x55,0x7a, 14586 0x40,0x43,0x59,0x72,0x13,0x43,0x08,0x55,0x92,0x44,0x43,0xbf,0x73,0x14,0x43,0x23,0x95,0x46,0x43,0xa5, 14587 0x09,0x17,0x43,0xe0,0xf3,0x48,0x43,0xfe,0x55,0x19,0x43,0x08,0xcd,0x4f,0x49,0x43,0xaa,0x10,0x1c,0x43, 14588 0x61,0x77,0x4b,0x43,0xfe,0x6d,0x1d,0x43,0x80,0xe8,0x4d,0x43,0x2b,0x94,0x1e,0x43,0x08,0x58,0xc9,0x51, 14589 0x43,0x41,0x27,0x1f,0x43,0x9b,0x82,0x53,0x43,0x35,0x72,0x20,0x43,0x53,0xf2,0x54,0x43,0x88,0xcf,0x21, 14590 0x43,0x08,0x7b,0x29,0x55,0x43,0xe8,0x0a,0x25,0x43,0xb2,0x2d,0x58,0x43,0xef,0xe8,0x26,0x43,0x9b,0xb2, 14591 0x5b,0x43,0xd0,0x8f,0x28,0x43,0x08,0x5f,0xef,0x5f,0x43,0xeb,0x11,0x2a,0x43,0xfd,0xdc,0x5f,0x43,0x6e, 14592 0x95,0x2c,0x43,0x3b,0xa7,0x60,0x43,0x2b,0xf4,0x2e,0x43,0x08,0x06,0xbb,0x61,0x43,0xfd,0xe5,0x31,0x43, 14593 0xe7,0x61,0x63,0x43,0xef,0x30,0x33,0x43,0x53,0x52,0x65,0x43,0xa3,0xb1,0x33,0x43,0x08,0x12,0xa0,0x68, 14594 0x43,0x7f,0x69,0x34,0x43,0x40,0xc6,0x69,0x43,0x64,0xff,0x36,0x43,0x7e,0x90,0x6a,0x43,0x71,0xcc,0x39, 14595 0x43,0x08,0xbc,0x5a,0x6b,0x43,0x51,0x73,0x3b,0x43,0xc1,0x49,0x6c,0x43,0xa5,0xd0,0x3c,0x43,0xe0,0xba, 14596 0x6e,0x43,0xb8,0x74,0x3c,0x43,0x08,0x6b,0x1c,0x73,0x43,0x13,0xc1,0x3e,0x43,0x40,0xf6,0x71,0x43,0xce, 14597 0x1f,0x41,0x43,0x55,0x89,0x72,0x43,0x8d,0x7e,0x43,0x43,0x08,0x68,0x2d,0x72,0x43,0x89,0xae,0x4b,0x43, 14598 0xc1,0x79,0x74,0x43,0xcb,0x78,0x4c,0x43,0x55,0xa1,0x76,0x43,0x5b,0xb1,0x4d,0x43,0x08,0xa2,0x38,0x7a, 14599 0x43,0xd1,0x56,0x4e,0x43,0x85,0xb6,0x78,0x43,0xb1,0x15,0x54,0x43,0x83,0xc7,0x77,0x43,0x89,0x0e,0x5c, 14600 0x43,0x08,0xcf,0x46,0x77,0x43,0x0f,0x81,0x5f,0x43,0x1a,0xde,0x7a,0x43,0xce,0xc7,0x5d,0x43,0x42,0x73, 14601 0x80,0x43,0x99,0xc3,0x5a,0x43,0x08,0x85,0x2c,0x82,0x43,0xf6,0xe6,0x59,0x43,0x81,0x3d,0x81,0x43,0x16, 14602 0x10,0x50,0x43,0xd6,0x8e,0x80,0x43,0x5b,0x99,0x49,0x43,0x08,0xc4,0xea,0x80,0x43,0x22,0x95,0x46,0x43, 14603 0xfa,0xe2,0x81,0x43,0xda,0xec,0x43,0x43,0x78,0x77,0x83,0x43,0xe4,0xb2,0x41,0x43,0x08,0x8a,0x27,0x85, 14604 0x43,0x86,0x77,0x3e,0x43,0x0c,0x9f,0x85,0x43,0x07,0xf4,0x3b,0x43,0x8f,0x16,0x86,0x43,0xe6,0x82,0x39, 14605 0x43,0x08,0x85,0x44,0x86,0x43,0x37,0xd9,0x35,0x43,0x1e,0x4f,0x87,0x43,0xe1,0x7b,0x34,0x43,0xdf,0x90, 14606 0x88,0x43,0xb6,0x55,0x33,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0xe5,0x31,0x43,0xfa,0x12,0x8a,0x43,0xbf, 14607 0x03,0x2d,0x43,0x19,0x78,0x8a,0x43,0x45,0x5e,0x2c,0x43,0x08,0x03,0xf1,0x8b,0x43,0xac,0x47,0x29,0x43, 14608 0x2f,0x17,0x8d,0x43,0x45,0x46,0x28,0x43,0xc8,0x21,0x8e,0x43,0x30,0xb3,0x27,0x43,0x08,0xa9,0xc8,0x8f, 14609 0x43,0xef,0xe8,0x26,0x43,0xbf,0x5b,0x90,0x43,0x5b,0xc1,0x24,0x43,0x10,0xca,0x90,0x43,0xa0,0x62,0x22, 14610 0x43,0x08,0x26,0x5d,0x91,0x43,0xbb,0xcc,0x1f,0x43,0xf0,0x70,0x92,0x43,0x78,0x13,0x1e,0x43,0x77,0xd7, 14611 0x93,0x43,0x73,0x24,0x1d,0x43,0x08,0x65,0x3f,0x96,0x43,0xce,0x58,0x1b,0x43,0xbe,0x7f,0x96,0x43,0xbf, 14612 0x8b,0x18,0x43,0x60,0x5c,0x97,0x43,0xb6,0xad,0x16,0x43,0x08,0xba,0xa8,0x99,0x43,0x78,0xcb,0x11,0x43, 14613 0x49,0xe1,0x9a,0x43,0x78,0xcb,0x11,0x43,0x01,0x51,0x9c,0x43,0x73,0xdc,0x10,0x43,0x08,0x72,0x24,0x9d, 14614 0x43,0xd2,0xff,0x0f,0x43,0x1c,0xd3,0x9d,0x43,0x07,0xec,0x0e,0x43,0xeb,0xc9,0x9d,0x43,0xe8,0x7a,0x0c, 14615 0x43,0x08,0x60,0x80,0x9d,0x43,0xd7,0xbe,0x08,0x43,0x4d,0xe8,0x9f,0x43,0x86,0x50,0x08,0x43,0x25,0xbd, 14616 0xa1,0x43,0x5b,0x2a,0x07,0x43,0x08,0x99,0x7f,0xa3,0x43,0xc9,0xf1,0x05,0x43,0x48,0x1d,0xa5,0x43,0x86, 14617 0x38,0x04,0x43,0x6c,0x71,0xa6,0x43,0x18,0x59,0x01,0x43,0x08,0x32,0x96,0xa6,0x43,0x6e,0x64,0xff,0x42, 14618 0x48,0x29,0xa7,0x43,0xed,0xcf,0xfd,0x42,0x5f,0xbc,0xa7,0x43,0x71,0x3b,0xfc,0x42,0x08,0xf3,0xe3,0xa9, 14619 0x43,0xf7,0x7d,0xf7,0x42,0xd8,0x6d,0xaa,0x43,0x45,0xe5,0xf2,0x42,0x48,0x41,0xab,0x43,0xcb,0x27,0xee, 14620 0x42,0x08,0x24,0xf9,0xab,0x43,0x52,0x6a,0xe9,0x42,0xee,0x0c,0xad,0x43,0x4c,0x8c,0xe7,0x42,0x1b,0x33, 14621 0xae,0x43,0xcc,0xf7,0xe5,0x42,0x08,0xaa,0x6b,0xaf,0x43,0xe8,0x61,0xe3,0x42,0x90,0xf5,0xaf,0x43,0xc9, 14622 0xf0,0xe0,0x42,0xe0,0x63,0xb0,0x43,0xe5,0x5a,0xde,0x42,0x08,0xaa,0x83,0xb3,0x43,0x29,0x2d,0x09,0x43, 14623 0x6a,0xfe,0x8e,0x43,0xb8,0x74,0x3c,0x43,0xd5,0x06,0x95,0x43,0xe6,0x79,0x67,0x43,0x08,0x2f,0x53,0x97, 14624 0x43,0xe9,0xb0,0x74,0x43,0xa8,0x28,0xa0,0x43,0x43,0xfd,0x76,0x43,0x83,0x28,0xad,0x43,0x17,0x59,0x81, 14625 0x43,0x08,0x3d,0xe7,0xbf,0x43,0x4b,0x8d,0x8c,0x43,0xae,0x96,0xba,0x43,0x66,0x27,0x92,0x43,0x15,0xe0, 14626 0xc7,0x43,0x6f,0x11,0x96,0x43,0x08,0x7e,0x5d,0xb2,0x43,0xdb,0x01,0x98,0x43,0x9e,0x56,0xa0,0x43,0x80, 14627 0xc1,0x97,0x43,0x69,0x2e,0x97,0x43,0x31,0x17,0x8d,0x43,0x09,0x06,0xab,0xa7,0x39,0x43,0x67,0x0f,0x0e, 14628 0x43,0x08,0xdb,0xbc,0x3b,0x43,0xe8,0x92,0x10,0x43,0xb5,0x85,0x3b,0x43,0x97,0x3c,0x14,0x43,0xab,0xa7, 14629 0x39,0x43,0x0c,0x0b,0x18,0x43,0x09,0x06,0xca,0x30,0x40,0x43,0x30,0x3b,0x13,0x43,0x08,0x17,0xc8,0x43, 14630 0x43,0xa5,0x09,0x17,0x43,0x7e,0xc9,0x44,0x43,0x1a,0xd8,0x1a,0x43,0x9d,0x22,0x43,0x43,0x8d,0xa6,0x1e, 14631 0x43,0x09,0x06,0xc8,0x78,0x4c,0x43,0xed,0xc9,0x1d,0x43,0x08,0x0b,0x32,0x4e,0x43,0x22,0xce,0x20,0x43, 14632 0x23,0xc5,0x4e,0x43,0x58,0xd2,0x23,0x43,0x0b,0x32,0x4e,0x43,0x2b,0xc4,0x26,0x43,0x09,0x06,0xec,0x08, 14633 0x58,0x43,0xc7,0xb1,0x26,0x43,0x08,0x02,0x9c,0x58,0x43,0xef,0x00,0x2b,0x43,0xd9,0x64,0x58,0x43,0x02, 14634 0xbd,0x2e,0x43,0x10,0x51,0x57,0x43,0x37,0xc1,0x31,0x43,0x09,0x06,0xcb,0xdf,0x61,0x43,0x4a,0x65,0x31, 14635 0x43,0x08,0xbe,0x2a,0x63,0x43,0xbd,0x33,0x35,0x43,0x32,0xe1,0x62,0x43,0x56,0x4a,0x38,0x43,0xde,0x83, 14636 0x61,0x43,0x3c,0xe0,0x3a,0x43,0x09,0x06,0x1c,0x7e,0x6a,0x43,0x5b,0x39,0x39,0x43,0x08,0x31,0x11,0x6b, 14637 0x43,0x0c,0xd2,0x3d,0x43,0x1c,0x7e,0x6a,0x43,0x13,0xd9,0x42,0x43,0xd9,0xc4,0x68,0x43,0xcb,0x60,0x48, 14638 0x43,0x09,0x06,0xe5,0xc1,0x73,0x43,0x16,0xf8,0x4b,0x43,0x08,0xa6,0xf7,0x72,0x43,0xb1,0xfd,0x4f,0x43, 14639 0x3b,0x07,0x71,0x43,0x4a,0x14,0x53,0x43,0xa2,0xf0,0x6d,0x43,0x7c,0x29,0x55,0x43,0x09,0x06,0x00,0x8d, 14640 0xa6,0x43,0xef,0x21,0x01,0x43,0x08,0x52,0xfb,0xa6,0x43,0xce,0xc8,0x02,0x43,0xe6,0x16,0xa7,0x43,0x51, 14641 0x4c,0x05,0x43,0x3b,0x68,0xa6,0x43,0x4c,0x75,0x08,0x43,0x09,0x06,0xde,0x20,0xa1,0x43,0x86,0x50,0x08, 14642 0x43,0x08,0xd4,0x4e,0xa1,0x43,0xd3,0xe7,0x0b,0x43,0xb5,0xe9,0xa0,0x43,0x59,0x5a,0x0f,0x43,0xba,0xcc, 14643 0x9f,0x43,0x54,0x83,0x12,0x43,0x09,0x06,0x77,0xfb,0x99,0x43,0x6c,0x16,0x13,0x43,0x08,0xde,0xfc,0x9a, 14644 0x43,0x4a,0xbd,0x14,0x43,0x06,0x34,0x9b,0x43,0xfe,0x55,0x19,0x43,0x13,0xe9,0x99,0x43,0x41,0x27,0x1f, 14645 0x43,0x09,0x06,0x46,0xce,0x93,0x43,0x26,0xa5,0x1d,0x43,0x08,0xe7,0xaa,0x94,0x43,0xbb,0xcc,0x1f,0x43, 14646 0x18,0xb4,0x94,0x43,0xa8,0x40,0x24,0x43,0xe2,0xbb,0x93,0x43,0x21,0xfe,0x28,0x43,0x09,0x06,0xb1,0x8e, 14647 0x8d,0x43,0xa8,0x58,0x28,0x43,0x08,0x19,0x90,0x8e,0x43,0x54,0x13,0x2b,0x43,0xa4,0xd9,0x8e,0x43,0x84, 14648 0x40,0x31,0x43,0x46,0xaa,0x8d,0x43,0x29,0x24,0x37,0x43,0x09,0x06,0xd6,0xbe,0x88,0x43,0xef,0x30,0x33, 14649 0x43,0x08,0x0c,0xb7,0x89,0x43,0x0e,0xa2,0x35,0x43,0xc0,0x37,0x8a,0x43,0x7a,0xaa,0x3b,0x43,0xbb,0x48, 14650 0x89,0x43,0xbb,0x7b,0x41,0x43,0x09,0x06,0x3a,0xad,0x82,0x43,0xc4,0x59,0x43,0x43,0x08,0xd2,0xb7,0x83, 14651 0x43,0x2b,0x5b,0x44,0x43,0x35,0xd6,0x85,0x43,0x48,0xf5,0x49,0x43,0x42,0x97,0x86,0x43,0xc4,0xa1,0x4f, 14652 0x43,0x09,0x06,0x9c,0xb3,0x80,0x43,0x48,0x55,0x5a,0x43,0x08,0xff,0xc5,0x80,0x43,0x09,0x73,0x55,0x43, 14653 0x93,0xe1,0x80,0x43,0x0f,0x39,0x53,0x43,0xf1,0xbe,0x7e,0x43,0x18,0xe7,0x4c,0x43,0x09,0x06,0xe0,0x02, 14654 0x7b,0x43,0x92,0xec,0x5d,0x43,0x08,0x09,0x3a,0x7b,0x43,0xf0,0xf7,0x58,0x43,0x09,0x3a,0x7b,0x43,0xe6, 14655 0x31,0x5b,0x43,0xe0,0x02,0x7b,0x43,0xa8,0x4f,0x56,0x43,0x09,0x06,0x39,0x4f,0x7d,0x43,0x3e,0x8f,0x5c, 14656 0x43,0x08,0xe9,0xe0,0x7c,0x43,0x03,0x9c,0x58,0x43,0x1e,0x2b,0x81,0x43,0x7f,0x30,0x5a,0x43,0xff,0x73, 14657 0x7d,0x43,0xf6,0xb6,0x51,0x43,0x09,0x06,0x5c,0xb8,0x52,0x43,0x28,0x21,0x87,0x43,0x08,0xae,0x3e,0x57, 14658 0x43,0x12,0x9a,0x88,0x43,0x23,0xf5,0x56,0x43,0x04,0xf1,0x8b,0x43,0x25,0xfc,0x5b,0x43,0x85,0x74,0x8e, 14659 0x43,0x08,0x2f,0xf2,0x61,0x43,0x8e,0x52,0x90,0x43,0xd9,0xdc,0x6c,0x43,0x85,0x74,0x8e,0x43,0xc6,0x20, 14660 0x69,0x43,0x3d,0xd8,0x8d,0x43,0x08,0x6d,0x8c,0x5a,0x43,0xf5,0x3b,0x8d,0x43,0x3d,0x77,0x58,0x43,0xa1, 14661 0xc6,0x87,0x43,0xf8,0xed,0x5e,0x43,0x5e,0x0d,0x86,0x43,0x09,0x06,0xde,0xcc,0x92,0x43,0xf7,0x17,0x87, 14662 0x43,0x08,0xb6,0x89,0x90,0x43,0xae,0x87,0x88,0x43,0x4a,0xa5,0x90,0x43,0xa1,0xde,0x8b,0x43,0xf9,0x2a, 14663 0x8e,0x43,0x23,0x62,0x8e,0x43,0x08,0xf5,0x2f,0x8b,0x43,0x5c,0x49,0x90,0x43,0x35,0xd6,0x85,0x43,0x8e, 14664 0x46,0x8e,0x43,0x3d,0xb4,0x87,0x43,0x47,0xaa,0x8d,0x43,0x08,0x6a,0xfe,0x8e,0x43,0xff,0x0d,0x8d,0x43, 14665 0xbb,0x6c,0x8f,0x43,0xf7,0x17,0x87,0x43,0x5c,0x31,0x8c,0x43,0xb2,0x5e,0x85,0x43,0x09,0x06,0x60,0x38, 14666 0x91,0x43,0x69,0x5d,0x7a,0x43,0x08,0x34,0x1e,0x92,0x43,0x1e,0x5b,0x89,0x43,0x04,0x63,0x7e,0x43,0x5e, 14667 0x01,0x84,0x43,0x59,0x2a,0x87,0x43,0x0d,0xcf,0x8d,0x43,0x09,0x03,0x04,0x06,0x5a,0x18,0x63,0x43,0x82, 14668 0x79,0x8b,0x43,0x08,0x25,0x2c,0x64,0x43,0x82,0x79,0x8b,0x43,0x2a,0x1b,0x65,0x43,0x9d,0xef,0x8a,0x43, 14669 0x2a,0x1b,0x65,0x43,0xc1,0x37,0x8a,0x43,0x08,0x2a,0x1b,0x65,0x43,0x17,0x89,0x89,0x43,0x25,0x2c,0x64, 14670 0x43,0x31,0xff,0x88,0x43,0x5a,0x18,0x63,0x43,0x31,0xff,0x88,0x43,0x08,0xf3,0x16,0x62,0x43,0x31,0xff, 14671 0x88,0x43,0xee,0x27,0x61,0x43,0x17,0x89,0x89,0x43,0xee,0x27,0x61,0x43,0xc1,0x37,0x8a,0x43,0x08,0xee, 14672 0x27,0x61,0x43,0x9d,0xef,0x8a,0x43,0xf3,0x16,0x62,0x43,0x82,0x79,0x8b,0x43,0x5a,0x18,0x63,0x43,0x82, 14673 0x79,0x8b,0x43,0x09,0x06,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x08,0x34,0xee,0x89,0x43,0x82,0x79, 14674 0x8b,0x43,0x85,0x5c,0x8a,0x43,0x9d,0xef,0x8a,0x43,0x85,0x5c,0x8a,0x43,0xc1,0x37,0x8a,0x43,0x08,0x85, 14675 0x5c,0x8a,0x43,0x17,0x89,0x89,0x43,0x34,0xee,0x89,0x43,0x31,0xff,0x88,0x43,0x4f,0x64,0x89,0x43,0x31, 14676 0xff,0x88,0x43,0x08,0x9c,0xe3,0x88,0x43,0x31,0xff,0x88,0x43,0x19,0x6c,0x88,0x43,0x17,0x89,0x89,0x43, 14677 0x19,0x6c,0x88,0x43,0xc1,0x37,0x8a,0x43,0x08,0x19,0x6c,0x88,0x43,0x9d,0xef,0x8a,0x43,0x9c,0xe3,0x88, 14678 0x43,0x82,0x79,0x8b,0x43,0x4f,0x64,0x89,0x43,0x82,0x79,0x8b,0x43,0x09,0x02,0x04,0x06,0x19,0x60,0x86, 14679 0x43,0xec,0xed,0xa3,0x43,0x08,0x35,0xd6,0x85,0x43,0x76,0x43,0xa6,0x43,0x93,0xe1,0x80,0x43,0x57,0x02, 14680 0xac,0x43,0x61,0xd8,0x80,0x43,0x87,0x17,0xae,0x43,0x08,0xa5,0x85,0x80,0x43,0xc3,0xfe,0xaf,0x43,0xce, 14681 0xbc,0x80,0x43,0x83,0x40,0xb1,0x43,0xa5,0x91,0x82,0x43,0x79,0x6e,0xb1,0x43,0x08,0x23,0x26,0x84,0x43, 14682 0x40,0x93,0xb1,0x43,0x30,0xe7,0x84,0x43,0xbe,0x1b,0xb1,0x43,0x11,0x82,0x84,0x43,0xab,0x6b,0xaf,0x43, 14683 0x08,0xb7,0x41,0x84,0x43,0x3b,0x98,0xae,0x43,0xb7,0x41,0x84,0x43,0xc3,0xf2,0xad,0x43,0xa1,0xae,0x83, 14684 0x43,0x83,0x28,0xad,0x43,0x08,0xb2,0x52,0x83,0x43,0x80,0x39,0xac,0x43,0x81,0x49,0x83,0x43,0xf0,0x00, 14685 0xab,0x43,0xe4,0x67,0x85,0x43,0x76,0x4f,0xa8,0x43,0x08,0x9c,0xd7,0x86,0x43,0xd1,0x83,0xa6,0x43,0xec, 14686 0x45,0x87,0x43,0x01,0x75,0xa2,0x43,0x19,0x60,0x86,0x43,0xec,0xed,0xa3,0x43,0x09,0x06,0xd9,0xdc,0x6c, 14687 0x43,0x14,0x25,0xa4,0x43,0x08,0xa2,0xf0,0x6d,0x43,0x9f,0x7a,0xa6,0x43,0x47,0xec,0x77,0x43,0x80,0x39, 14688 0xac,0x43,0xa9,0xfe,0x77,0x43,0xb0,0x4e,0xae,0x43,0x08,0x23,0xa4,0x78,0x43,0xea,0x35,0xb0,0x43,0xd2, 14689 0x35,0x78,0x43,0xab,0x77,0xb1,0x43,0xc1,0x79,0x74,0x43,0xa2,0xa5,0xb1,0x43,0x08,0xc6,0x50,0x71,0x43, 14690 0x68,0xca,0xb1,0x43,0xab,0xce,0x6f,0x43,0xe7,0x52,0xb1,0x43,0xea,0x98,0x70,0x43,0xd4,0xa2,0xaf,0x43, 14691 0x08,0x9d,0x19,0x71,0x43,0x96,0xd8,0xae,0x43,0x9d,0x19,0x71,0x43,0xec,0x29,0xae,0x43,0xca,0x3f,0x72, 14692 0x43,0xab,0x5f,0xad,0x43,0x08,0xa6,0xf7,0x72,0x43,0xa7,0x70,0xac,0x43,0x09,0x0a,0x73,0x43,0x17,0x38, 14693 0xab,0x43,0x44,0xcd,0x6e,0x43,0x9f,0x86,0xa8,0x43,0x08,0xd4,0xed,0x6b,0x43,0xf8,0xba,0xa6,0x43,0x31, 14694 0x11,0x6b,0x43,0x2a,0xac,0xa2,0x43,0xd9,0xdc,0x6c,0x43,0x14,0x25,0xa4,0x43,0x09,0x01,0x05,0x06,0x66, 14695 0x5d,0x7a,0x43,0x74,0xeb,0xc2,0x43,0x08,0x09,0x22,0x77,0x43,0x50,0xbb,0xc7,0x43,0xe9,0xe0,0x7c,0x43, 14696 0xf5,0x86,0xc9,0x43,0x8f,0x94,0x7a,0x43,0xc5,0x95,0xcd,0x43,0x09,0x06,0x08,0x98,0x80,0x43,0x6b,0x19, 14697 0xc3,0x43,0x08,0xb7,0x35,0x82,0x43,0x79,0xf2,0xc7,0x43,0xf1,0xbe,0x7e,0x43,0x1e,0xbe,0xc9,0x43,0x73, 14698 0x7c,0x80,0x43,0xec,0xcc,0xcd,0x43,0x09,0x06,0x28,0xab,0x7d,0x43,0xae,0xde,0xc6,0x43,0x08,0x1e,0xcd, 14699 0x7b,0x43,0x8a,0xa2,0xc9,0x43,0x30,0x89,0x7f,0x43,0x5c,0x94,0xcc,0x43,0x28,0xab,0x7d,0x43,0x42,0x2a, 14700 0xcf,0x43,0x09,0x01,0x05,0x06,0x24,0x14,0xe0,0x42,0xf5,0x77,0x97,0x43,0x08,0xf7,0x1d,0xe7,0x42,0x74, 14701 0x00,0x97,0x43,0x4d,0x93,0xec,0x42,0xdb,0xf5,0x95,0x43,0x29,0x4b,0xed,0x42,0xcd,0x34,0x95,0x43,0x09, 14702 0x06,0x29,0x7b,0xf5,0x42,0x6f,0x1d,0x98,0x43,0x08,0xe4,0xf1,0xfb,0x42,0x61,0x5c,0x97,0x43,0xdb,0x7d, 14703 0x01,0x43,0xb2,0xbe,0x95,0x43,0x55,0x23,0x02,0x43,0xe7,0xaa,0x94,0x43,0x09,0x06,0x98,0xdc,0x03,0x43, 14704 0xbe,0x8b,0x98,0x43,0x08,0x66,0xdf,0x05,0x43,0x47,0xe6,0x97,0x43,0xae,0x87,0x08,0x43,0x98,0x48,0x96, 14705 0x43,0x61,0x08,0x09,0x43,0xd6,0x06,0x95,0x43,0x09,0x06,0x31,0x0b,0x0b,0x43,0x8e,0x82,0x98,0x43,0x08, 14706 0xdb,0xc5,0x0d,0x43,0x80,0xc1,0x97,0x43,0xd6,0xee,0x10,0x43,0xa9,0xec,0x95,0x43,0x79,0xcb,0x11,0x43, 14707 0x55,0x8f,0x94,0x43,0x09,0x06,0xd1,0x2f,0x18,0x43,0xdb,0x01,0x98,0x43,0x08,0xad,0xe7,0x18,0x43,0x38, 14708 0x25,0x97,0x43,0x8a,0x9f,0x19,0x43,0x80,0xb5,0x95,0x43,0xd6,0x1e,0x19,0x43,0xe0,0xd8,0x94,0x43,0x09, 14709 0x06,0x9a,0x5b,0x1d,0x43,0x58,0x8a,0x97,0x43,0x08,0x01,0x5d,0x1e,0x43,0xf1,0x88,0x96,0x43,0x2f,0x83, 14710 0x1f,0x43,0x19,0xb4,0x94,0x43,0x19,0xf0,0x1e,0x43,0x6f,0x05,0x94,0x43,0x09,0x06,0x0b,0x53,0x24,0x43, 14711 0xae,0xdb,0x96,0x43,0x08,0x25,0xd5,0x25,0x43,0x50,0xac,0x95,0x43,0x53,0xfb,0x26,0x43,0x8a,0x7b,0x93, 14712 0x43,0x76,0x43,0x26,0x43,0xb7,0x95,0x92,0x43,0x09,0x06,0x76,0x5b,0x2a,0x43,0x47,0xda,0x95,0x43,0x08, 14713 0xf3,0xef,0x2b,0x43,0x10,0xe2,0x94,0x43,0x6d,0x95,0x2c,0x43,0xae,0xc3,0x92,0x43,0x68,0xa6,0x2b,0x43, 14714 0x47,0xc2,0x91,0x43,0x09,0x06,0x36,0xc1,0x31,0x43,0x2c,0x58,0x94,0x43,0x08,0x8c,0x1e,0x33,0x43,0x31, 14715 0x3b,0x93,0x43,0x79,0x7a,0x33,0x43,0xff,0x25,0x91,0x43,0xd9,0x9d,0x32,0x43,0xc1,0x5b,0x90,0x43,0x09, 14716 0x06,0x25,0x35,0x36,0x43,0x31,0x3b,0x93,0x43,0x08,0x3f,0xb7,0x37,0x43,0xc1,0x67,0x92,0x43,0xe0,0x93, 14717 0x38,0x43,0xae,0xb7,0x90,0x43,0x7e,0x81,0x38,0x43,0x0d,0xdb,0x8f,0x43,0x09,0x06,0xb5,0x85,0x3b,0x43, 14718 0xe4,0xaf,0x91,0x43,0x08,0xcf,0x07,0x3d,0x43,0x9d,0x13,0x91,0x43,0xbc,0x63,0x3d,0x43,0x47,0xb6,0x8f, 14719 0x43,0xe5,0x9a,0x3d,0x43,0x74,0xd0,0x8e,0x43,0x09,0x06,0xae,0xc6,0x42,0x43,0xa4,0xd9,0x8e,0x43,0x08, 14720 0xca,0x48,0x44,0x43,0xfa,0x2a,0x8e,0x43,0xa2,0x11,0x44,0x43,0x9d,0xfb,0x8c,0x43,0x55,0x92,0x44,0x43, 14721 0x0d,0xc3,0x8b,0x43,0x09,0x06,0x39,0x10,0xc3,0x43,0x34,0x36,0x96,0x43,0x08,0x92,0x44,0xc1,0x43,0xe4, 14722 0xc7,0x95,0x43,0x6f,0xf0,0xbf,0x43,0x4b,0xbd,0x94,0x43,0x47,0xb9,0xbf,0x43,0x0b,0xf3,0x93,0x43,0x09, 14723 0x06,0x8f,0x49,0xbe,0x43,0xb7,0xad,0x96,0x43,0x08,0x11,0xb5,0xbc,0x43,0x77,0xe3,0x95,0x43,0x9c,0xf2, 14724 0xba,0x43,0xfa,0x4e,0x94,0x43,0xae,0x96,0xba,0x43,0x31,0x3b,0x93,0x43,0x09,0x06,0xdb,0xb0,0xb9,0x43, 14725 0x10,0xee,0x96,0x43,0x08,0x42,0xa6,0xb8,0x43,0xc8,0x51,0x96,0x43,0x50,0x5b,0xb7,0x43,0x19,0xb4,0x94, 14726 0x43,0xf7,0x1a,0xb7,0x43,0x58,0x72,0x93,0x43,0x09,0x06,0xf2,0x2b,0xb6,0x43,0x10,0xee,0x96,0x43,0x08, 14727 0x9d,0xce,0xb4,0x43,0x04,0x2d,0x96,0x43,0xed,0x30,0xb3,0x43,0x2c,0x58,0x94,0x43,0xce,0xcb,0xb2,0x43, 14728 0xd6,0xfa,0x92,0x43,0x09,0x06,0x5a,0x09,0xb1,0x43,0x19,0xc0,0x96,0x43,0x08,0x6c,0xad,0xb0,0x43,0x77, 14729 0xe3,0x95,0x43,0x7e,0x51,0xb0,0x43,0xc0,0x73,0x94,0x43,0xd8,0x91,0xb0,0x43,0x1e,0x97,0x93,0x43,0x09, 14730 0x06,0x48,0x4d,0xad,0x43,0xbe,0x7f,0x96,0x43,0x08,0x95,0xcc,0xac,0x43,0x58,0x7e,0x95,0x43,0x4d,0x30, 14731 0xac,0x43,0x80,0xa9,0x93,0x43,0xd8,0x79,0xac,0x43,0xd6,0xfa,0x92,0x43,0x09,0x06,0x90,0xd1,0xa9,0x43, 14732 0x14,0xd1,0x95,0x43,0x08,0x83,0x10,0xa9,0x43,0xb7,0xa1,0x94,0x43,0x3b,0x74,0xa8,0x43,0xf1,0x70,0x92, 14733 0x43,0x29,0xd0,0xa8,0x43,0x1e,0x8b,0x91,0x43,0x09,0x06,0x5a,0xcd,0xa6,0x43,0x8a,0x87,0x95,0x43,0x08, 14734 0x1c,0x03,0xa6,0x43,0x23,0x86,0x94,0x43,0x5f,0xb0,0xa5,0x43,0xc1,0x67,0x92,0x43,0xe1,0x27,0xa6,0x43, 14735 0x8a,0x6f,0x91,0x43,0x09,0x06,0xd4,0x5a,0xa3,0x43,0x2c,0x58,0x94,0x43,0x08,0x29,0xac,0xa2,0x43,0x31, 14736 0x3b,0x93,0x43,0x32,0x7e,0xa2,0x43,0xff,0x25,0x91,0x43,0x83,0xec,0xa2,0x43,0x8e,0x52,0x90,0x43,0x09, 14737 0x06,0xf8,0x96,0xa0,0x43,0x1e,0x97,0x93,0x43,0x08,0xeb,0xd5,0x9f,0x43,0x7b,0xba,0x92,0x43,0x99,0x67, 14738 0x9f,0x43,0x9d,0x13,0x91,0x43,0x99,0x67,0x9f,0x43,0xfa,0x36,0x90,0x43,0x09,0x06,0xeb,0xc9,0x9d,0x43, 14739 0xc8,0x39,0x92,0x43,0x08,0xde,0x08,0x9d,0x43,0xb2,0xa6,0x91,0x43,0xe6,0xda,0x9c,0x43,0x2c,0x40,0x90, 14740 0x43,0x52,0xbf,0x9c,0x43,0x5a,0x5a,0x8f,0x43,0x09,0x06,0x37,0x3d,0x9b,0x43,0x85,0x80,0x90,0x43,0x08, 14741 0x2a,0x7c,0x9a,0x43,0xdb,0xd1,0x8f,0x43,0xf0,0xa0,0x9a,0x43,0x7d,0xa2,0x8e,0x43,0x65,0x57,0x9a,0x43, 14742 0xee,0x69,0x8d,0x43,0x09,0x02,0x04,0x06,0x2a,0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x08,0x0d,0x8a,0x31, 14743 0x42,0x9f,0x0e,0x94,0x43,0xf3,0x1f,0x34,0x42,0x3d,0xfc,0x93,0x43,0x63,0xff,0x36,0x42,0xa9,0xe0,0x93, 14744 0x43,0x08,0xb5,0x34,0x5d,0x42,0x0b,0xf3,0x93,0x43,0x6d,0xa4,0x5e,0x42,0x03,0x39,0x98,0x43,0xe7,0x31, 14745 0x5b,0x42,0x93,0x89,0x9d,0x43,0x08,0x02,0x9c,0x58,0x42,0xd4,0x5a,0xa3,0x43,0x38,0x70,0x53,0x42,0x14, 14746 0x49,0xaa,0x43,0xf8,0xed,0x5e,0x42,0x83,0x28,0xad,0x43,0x08,0xea,0x68,0x68,0x42,0x20,0x22,0xaf,0x43, 14747 0x12,0xb8,0x6c,0x42,0xb5,0x49,0xb1,0x43,0x2a,0x4b,0x6d,0x42,0x0d,0x96,0xb3,0x43,0x07,0x2a,0x4b,0x6d, 14748 0x42,0xc6,0x05,0xb5,0x43,0x08,0x87,0x6e,0x6c,0x42,0x68,0xee,0xb7,0x43,0x1c,0x66,0x66,0x42,0x31,0x0e, 14749 0xbb,0x43,0x57,0x11,0x5e,0x42,0x8f,0x49,0xbe,0x43,0x08,0x66,0x96,0x54,0x42,0xb9,0x5c,0xb8,0x43,0x2c, 14750 0x2b,0x3c,0x42,0x68,0xd6,0xb3,0x43,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x07,0x2a,0xf4,0x2e,0x42, 14751 0x61,0xa4,0xa3,0x43,0x08,0x55,0x1a,0x30,0x42,0xf0,0xd0,0xa2,0x43,0xf8,0xf6,0x30,0x42,0xb2,0x06,0xa2, 14752 0x43,0x98,0xd3,0x31,0x42,0xd6,0x4e,0xa1,0x43,0x08,0x1c,0x6f,0x38,0x42,0x2a,0x94,0x9e,0x43,0xc1,0x22, 14753 0x36,0x42,0xf5,0x9b,0x9d,0x43,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x57, 14754 0xa2,0x9b,0x43,0x08,0xab,0x8f,0x35,0x42,0x8a,0xab,0x9b,0x43,0xe9,0x71,0x3a,0x42,0xb2,0xe2,0x9b,0x43, 14755 0xb7,0x74,0x3c,0x42,0x34,0x5a,0x9c,0x43,0x08,0x23,0x7d,0x42,0x42,0x0b,0x2f,0x9e,0x43,0xe5,0x9a,0x3d, 14756 0x42,0x38,0x6d,0xa3,0x43,0x36,0xd9,0x35,0x42,0xf3,0xd7,0xa7,0x43,0x08,0x12,0x61,0x2e,0x42,0xb0,0x42, 14757 0xac,0x43,0x63,0xff,0x36,0x42,0xdd,0x74,0xaf,0x43,0x1e,0xa6,0x45,0x42,0x44,0x82,0xb2,0x43,0x08,0x74, 14758 0x1b,0x4b,0x42,0x79,0x7a,0xb3,0x43,0x10,0x21,0x4f,0x42,0x2a,0x18,0xb5,0x43,0xdb,0x4c,0x54,0x42,0x91, 14759 0x19,0xb6,0x43,0x08,0xee,0x3f,0x65,0x42,0x5f,0x28,0xba,0x43,0xa7,0xaf,0x66,0x42,0xb9,0x50,0xb6,0x43, 14760 0x14,0x58,0x5c,0x42,0xca,0xdc,0xb1,0x43,0x08,0x2c,0x8b,0x4c,0x42,0x4e,0x30,0xac,0x43,0x19,0xcf,0x48, 14761 0x42,0x2a,0xd0,0xa8,0x43,0xbc,0xab,0x49,0x42,0xa9,0x4c,0xa6,0x43,0x08,0x61,0x5f,0x47,0x42,0xfa,0xa2, 14762 0xa2,0x43,0xa7,0xaf,0x66,0x42,0x85,0x98,0x94,0x43,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x07,0x2a, 14763 0xf4,0x2e,0x42,0x04,0x21,0x94,0x43,0x09,0x06,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x08,0xdc,0xe3, 14764 0xf1,0x41,0xe9,0x9e,0x92,0x43,0xd2,0xe7,0x0b,0x42,0xd6,0x06,0x95,0x43,0x2a,0xf4,0x2e,0x42,0x04,0x21, 14765 0x94,0x43,0x07,0x2a,0xf4,0x2e,0x42,0xc3,0x62,0x95,0x43,0x08,0x87,0x17,0x2e,0x42,0xc3,0x62,0x95,0x43, 14766 0xe7,0x3a,0x2d,0x42,0xf5,0x6b,0x95,0x43,0x44,0x5e,0x2c,0x42,0xf5,0x6b,0x95,0x43,0x08,0xd1,0x47,0x1c, 14767 0x42,0x19,0xc0,0x96,0x43,0x66,0xdf,0x05,0x42,0x38,0x19,0x95,0x43,0x12,0x6a,0x00,0x42,0xb2,0xbe,0x95, 14768 0x43,0x08,0xbb,0x6b,0xea,0x41,0xd6,0x12,0x97,0x43,0x2d,0x82,0xfa,0x41,0x61,0x74,0x9b,0x43,0x7e,0x72, 14769 0x06,0x42,0x8a,0xab,0x9b,0x43,0x08,0xc8,0x39,0x12,0x42,0x4e,0xd0,0x9b,0x43,0x53,0xe3,0x22,0x42,0xc3, 14770 0x86,0x9b,0x43,0x2a,0xf4,0x2e,0x42,0x57,0xa2,0x9b,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6a,0x52,0x9d,0x43, 14771 0x08,0x01,0xa5,0x2a,0x42,0xa4,0x2d,0x9d,0x43,0x96,0x9c,0x24,0x42,0x06,0x40,0x9d,0x43,0x8a,0xb7,0x1d, 14772 0x42,0x9a,0x5b,0x9d,0x43,0x08,0x6b,0x16,0x13,0x42,0xcd,0x64,0x9d,0x43,0x42,0xc7,0x0e,0x42,0x9a,0x5b, 14773 0x9d,0x43,0x23,0x26,0x04,0x42,0xcd,0x64,0x9d,0x43,0x08,0xe6,0x91,0xeb,0x41,0x38,0x49,0x9d,0x43,0x73, 14774 0x7b,0xdb,0x41,0xf5,0x83,0x99,0x43,0x7f,0x60,0xe2,0x41,0x0b,0x0b,0x98,0x43,0x08,0x7f,0x60,0xe2,0x41, 14775 0xec,0x99,0x95,0x43,0xe3,0x5a,0xde,0x41,0xbe,0x7f,0x96,0x43,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43, 14776 0x07,0xd0,0xfe,0xea,0x41,0x9f,0x0e,0x94,0x43,0x09,0x06,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x08, 14777 0xd4,0x7e,0x29,0x42,0xab,0x6b,0xaf,0x43,0x4e,0x0c,0x26,0x42,0x44,0x6a,0xae,0x43,0x38,0x79,0x25,0x42, 14778 0xd4,0x96,0xad,0x43,0x08,0x25,0xbd,0x21,0x42,0xe2,0x4b,0xac,0x43,0x49,0x35,0x29,0x42,0x9a,0x97,0xa7, 14779 0x43,0x2a,0xf4,0x2e,0x42,0x61,0xa4,0xa3,0x43,0x07,0x2a,0xf4,0x2e,0x42,0x6d,0xad,0xb0,0x43,0x09,0x06, 14780 0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x08,0x86,0x20,0x80,0x43,0x57,0x41,0xe6,0x43,0x7d,0x4e,0x80, 14781 0x43,0x25,0x38,0xe6,0x43,0xa5,0x85,0x80,0x43,0xf3,0x2e,0xe6,0x43,0x08,0x35,0xca,0x83,0x43,0xd4,0xc9, 14782 0xe5,0x43,0x9c,0xd7,0x86,0x43,0x44,0x91,0xe4,0x43,0xd5,0xca,0x8a,0x43,0x91,0x1c,0xe6,0x43,0x08,0x53, 14783 0x5f,0x8c,0x43,0xf8,0x1d,0xe7,0x43,0x2f,0x17,0x8d,0x43,0x4e,0x7b,0xe8,0x43,0x92,0x29,0x8d,0x43,0x2f, 14784 0x22,0xea,0x43,0x07,0x92,0x29,0x8d,0x43,0x44,0xb5,0xea,0x43,0x08,0xfe,0x0d,0x8d,0x43,0x2a,0x4b,0xed, 14785 0x43,0xe3,0x8b,0x8b,0x43,0x55,0x7d,0xf0,0x43,0xec,0x51,0x89,0x43,0x72,0x0b,0xf4,0x43,0x08,0xcd,0xd4, 14786 0x84,0x43,0x9d,0x55,0xfb,0x43,0xc9,0xe5,0x83,0x43,0x74,0x1e,0xfb,0x43,0x73,0x94,0x84,0x43,0x5a,0x90, 14787 0xf7,0x43,0x08,0xe8,0x62,0x88,0x43,0xfd,0x30,0xee,0x43,0x39,0xc5,0x86,0x43,0xdd,0xbf,0xeb,0x43,0x35, 14788 0xbe,0x81,0x43,0x40,0xde,0xed,0x43,0x08,0x4f,0x34,0x81,0x43,0x36,0x0c,0xee,0x43,0x08,0x98,0x80,0x43, 14789 0xfd,0x30,0xee,0x43,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x40,0xec, 14790 0x43,0x08,0x35,0xbe,0x81,0x43,0x06,0xf7,0xeb,0x43,0x15,0x65,0x83,0x43,0x49,0xa4,0xeb,0x43,0x1e,0x43, 14791 0x85,0x43,0xbe,0x5a,0xeb,0x43,0x08,0xae,0x93,0x8a,0x43,0xfd,0x18,0xea,0x43,0x42,0x97,0x86,0x43,0x5f, 14792 0x67,0xf4,0x43,0xa9,0x98,0x87,0x43,0xd4,0x1d,0xf4,0x43,0x08,0x5c,0x25,0x8a,0x43,0xcf,0x16,0xef,0x43, 14793 0x46,0xaa,0x8d,0x43,0x5a,0x3c,0xe9,0x43,0x19,0x6c,0x88,0x43,0x53,0x5e,0xe7,0x43,0x08,0xc4,0x02,0x85, 14794 0x43,0x96,0x0b,0xe7,0x43,0x85,0x2c,0x82,0x43,0x83,0x67,0xe7,0x43,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7, 14795 0x43,0x07,0x1d,0xe5,0x7f,0x43,0x87,0x4a,0xe6,0x43,0x09,0x06,0xfd,0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43, 14796 0x08,0xfa,0x6c,0x78,0x43,0xd1,0xc2,0xe0,0x43,0x25,0x5c,0x6c,0x43,0x25,0x44,0xe8,0x43,0x1d,0xe5,0x7f, 14797 0x43,0x87,0x4a,0xe6,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x72,0xc3,0xe7,0x43,0x08,0xa6,0x27,0x7b,0x43,0x91, 14798 0x28,0xe8,0x43,0xbc,0xa2,0x77,0x43,0xb0,0x8d,0xe8,0x43,0xc6,0x68,0x75,0x43,0x57,0x4d,0xe8,0x43,0x08, 14799 0xe0,0xd2,0x72,0x43,0xab,0x9e,0xe7,0x43,0x50,0x9a,0x71,0x43,0x2a,0x27,0xe7,0x43,0xea,0x98,0x70,0x43, 14800 0x57,0x35,0xe4,0x43,0x08,0x94,0x3b,0x6f,0x43,0x14,0x7c,0xe2,0x43,0xff,0x13,0x6d,0x43,0x06,0xbb,0xe1, 14801 0x43,0xcf,0xfe,0x6a,0x43,0x06,0xbb,0xe1,0x43,0x08,0x44,0x9d,0x66,0x43,0x77,0x8e,0xe2,0x43,0x3b,0xef, 14802 0x6c,0x43,0x91,0x10,0xe4,0x43,0xfd,0x24,0x6c,0x43,0xb0,0x81,0xe6,0x43,0x08,0x96,0x23,0x6b,0x43,0xee, 14803 0x57,0xe9,0x43,0xca,0x0f,0x6a,0x43,0x5f,0x37,0xec,0x43,0x55,0x71,0x6e,0x43,0x9f,0x01,0xed,0x43,0x08, 14804 0xdb,0xfb,0x75,0x43,0x3b,0xef,0xec,0x43,0x09,0x3a,0x7b,0x43,0xb0,0xa5,0xec,0x43,0x1d,0xe5,0x7f,0x43, 14805 0x91,0x40,0xec,0x43,0x07,0x1d,0xe5,0x7f,0x43,0x91,0x4c,0xee,0x43,0x08,0xa9,0x16,0x7c,0x43,0xb0,0xb1, 14806 0xee,0x43,0x47,0xec,0x77,0x43,0xd9,0xe8,0xee,0x43,0x1e,0x9d,0x73,0x43,0xcf,0x16,0xef,0x43,0x08,0x0e, 14807 0xc9,0x6b,0x43,0xee,0x7b,0xef,0x43,0x7e,0x90,0x6a,0x43,0xfd,0x30,0xee,0x43,0x01,0xfc,0x68,0x43,0x4e, 14808 0x93,0xec,0x43,0x08,0x31,0xf9,0x66,0x43,0x4e,0x87,0xea,0x43,0x31,0x11,0x6b,0x43,0xd4,0xd5,0xe7,0x43, 14809 0xd9,0xc4,0x68,0x43,0xd4,0xc9,0xe5,0x43,0x08,0xe5,0x79,0x67,0x43,0x77,0x9a,0xe4,0x43,0x44,0x9d,0x66, 14810 0x43,0xab,0x86,0xe3,0x43,0x7e,0x78,0x66,0x43,0x0b,0xaa,0xe2,0x43,0x07,0x7e,0x78,0x66,0x43,0x57,0x29, 14811 0xe2,0x43,0x08,0xa7,0xaf,0x66,0x43,0xbe,0x1e,0xe1,0x43,0x87,0x56,0x68,0x43,0x77,0x82,0xe0,0x43,0xfd, 14812 0x24,0x6c,0x43,0xd9,0x94,0xe0,0x43,0x09,0x06,0xc4,0x41,0xbf,0x43,0x85,0xc0,0x72,0x42,0x08,0x73,0xdf, 14813 0xc0,0x43,0xf4,0x76,0x72,0x42,0x97,0x33,0xc2,0x43,0x85,0xc0,0x72,0x42,0xb2,0xb5,0xc3,0x43,0x64,0x56, 14814 0x75,0x42,0x08,0x03,0x24,0xc4,0x43,0x5e,0x7f,0x78,0x42,0xfa,0x51,0xc4,0x43,0x01,0x85,0x7c,0x42,0x5c, 14815 0x64,0xc4,0x43,0xa0,0xb3,0x80,0x42,0x07,0x5c,0x64,0xc4,0x43,0x10,0x93,0x83,0x42,0x08,0xc8,0x48,0xc4, 14816 0x43,0x1c,0x78,0x8a,0x42,0x27,0x6c,0xc3,0x43,0xaf,0xcf,0x94,0x42,0x23,0x7d,0xc2,0x43,0x99,0x9c,0xa4, 14817 0x42,0x08,0x3d,0xe7,0xbf,0x43,0xfb,0xfd,0xb5,0x42,0xb3,0x9d,0xbf,0x43,0x88,0x17,0xae,0x42,0xc4,0x41, 14818 0xbf,0x43,0x69,0x76,0xa3,0x42,0x07,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x08,0x4f,0x8b,0xbf,0x43, 14819 0xed,0x81,0x91,0x42,0xe4,0xa6,0xbf,0x43,0x5d,0x61,0x94,0x42,0xfa,0x39,0xc0,0x43,0x3b,0x49,0x9d,0x42, 14820 0x08,0x2b,0x43,0xc0,0x43,0x28,0xed,0xa9,0x42,0x61,0x3b,0xc1,0x43,0x00,0x9e,0xa5,0x42,0xe4,0xb2,0xc1, 14821 0x43,0x5d,0x91,0x9c,0x42,0x08,0x78,0xce,0xc1,0x43,0xfd,0x36,0x90,0x42,0x22,0x89,0xc4,0x43,0x81,0x72, 14822 0x86,0x42,0xae,0xc6,0xc2,0x43,0xa0,0xb3,0x80,0x42,0x08,0x54,0x86,0xc2,0x43,0x58,0xd1,0x7e,0x42,0x30, 14823 0x32,0xc1,0x43,0xce,0x5e,0x7b,0x42,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x07,0xc4,0x41,0xbf,0x43, 14824 0x85,0xc0,0x72,0x42,0x09,0x06,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x08,0x35,0xfd,0xbb,0x43,0xa4, 14825 0xa1,0x5c,0x42,0x5e,0x34,0xbc,0x43,0x9d,0x2a,0x70,0x42,0x5e,0x40,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08, 14826 0x4c,0x9c,0xbe,0x43,0x0e,0x0a,0x73,0x42,0x08,0xef,0xbe,0x43,0x0e,0x0a,0x73,0x42,0xc4,0x41,0xbf,0x43, 14827 0x85,0xc0,0x72,0x42,0x07,0xc4,0x41,0xbf,0x43,0xe8,0xf1,0x7b,0x42,0x08,0xcd,0x13,0xbf,0x43,0xe8,0xf1, 14828 0x7b,0x42,0xd6,0xe5,0xbe,0x43,0x71,0x3b,0x7c,0x42,0xdf,0xb7,0xbe,0x43,0x71,0x3b,0x7c,0x42,0x08,0x08, 14829 0xe3,0xbc,0x43,0xa4,0x61,0x7d,0x42,0x28,0x3c,0xbb,0x43,0x91,0x45,0x69,0x42,0x28,0x3c,0xbb,0x43,0x58, 14830 0x71,0x6e,0x42,0x08,0xce,0xfb,0xba,0x43,0xd5,0x35,0x78,0x42,0x59,0x45,0xbb,0x43,0x58,0x23,0x82,0x42, 14831 0xa1,0xe1,0xbb,0x43,0xd7,0xbe,0x88,0x42,0x08,0xc9,0x18,0xbc,0x43,0xaf,0x9f,0x8c,0x42,0x1e,0x76,0xbd, 14832 0x43,0x51,0x7c,0x8d,0x42,0xd6,0xe5,0xbe,0x43,0xf4,0x58,0x8e,0x42,0x08,0x9c,0x0a,0xbf,0x43,0x45,0xc7, 14833 0x8e,0x42,0x30,0x26,0xbf,0x43,0x96,0x35,0x8f,0x42,0xc4,0x41,0xbf,0x43,0xac,0xc8,0x8f,0x42,0x07,0xc4, 14834 0x41,0xbf,0x43,0x69,0x76,0xa3,0x42,0x08,0x08,0xef,0xbe,0x43,0xb1,0xd6,0x99,0x42,0xe8,0x89,0xbe,0x43, 14835 0xde,0xc5,0x8d,0x42,0xc0,0x46,0xbc,0x43,0xc2,0x5b,0x90,0x42,0x08,0x9c,0xf2,0xba,0x43,0x86,0x80,0x90, 14836 0x42,0xf2,0x43,0xba,0x43,0xe8,0x73,0x87,0x42,0x8f,0x31,0xba,0x43,0xb6,0xf4,0x7d,0x42,0x07,0x8f,0x31, 14837 0xba,0x43,0x21,0xc6,0x76,0x42,0x08,0xc0,0x3a,0xba,0x43,0x5f,0x48,0x6b,0x42,0xae,0x96,0xba,0x43,0xe3, 14838 0x83,0x61,0x42,0xf6,0x32,0xbb,0x43,0x40,0xa7,0x60,0x42,0x09,0x06,0xea,0x74,0xea,0x43,0x61,0x44,0x93, 14839 0x43,0x08,0x24,0x5c,0xec,0x43,0x31,0x3b,0x93,0x43,0xfb,0x30,0xee,0x43,0x93,0x4d,0x93,0x43,0x0d,0xe1, 14840 0xef,0x43,0x80,0xa9,0x93,0x43,0x08,0x8f,0x58,0xf0,0x43,0xd1,0x17,0x94,0x43,0xb7,0x8f,0xf0,0x43,0x10, 14841 0xe2,0x94,0x43,0xea,0x98,0xf0,0x43,0xa9,0xec,0x95,0x43,0x07,0xea,0x98,0xf0,0x43,0x38,0x25,0x97,0x43, 14842 0x08,0x23,0x74,0xf0,0x43,0x9f,0x32,0x9a,0x43,0x5a,0x60,0xef,0x43,0x53,0xcb,0x9e,0x43,0x2d,0x3a,0xee, 14843 0x43,0xfd,0x91,0xa3,0x43,0x08,0xa2,0xf0,0xed,0x43,0xdd,0x38,0xa5,0x43,0x17,0xa7,0xed,0x43,0xbe,0xdf, 14844 0xa6,0x43,0x5a,0x54,0xed,0x43,0x9f,0x86,0xa8,0x43,0x08,0xfc,0x24,0xec,0x43,0xca,0xc4,0xad,0x43,0x48, 14845 0xa4,0xeb,0x43,0x40,0x6f,0xab,0x43,0x28,0x3f,0xeb,0x43,0x1c,0x0f,0xa8,0x43,0x08,0x1f,0x6d,0xeb,0x43, 14846 0x72,0x48,0xa3,0x43,0x67,0x09,0xec,0x43,0xd1,0x53,0x9e,0x43,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b,0x43, 14847 0x07,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x08,0x7e,0x90,0xea,0x43,0x8a,0x9f,0x99,0x43,0x12,0xac, 14848 0xea,0x43,0xbc,0xa8,0x99,0x43,0xa7,0xc7,0xea,0x43,0xbc,0xa8,0x99,0x43,0x08,0x51,0x76,0xeb,0x43,0x9f, 14849 0x32,0x9a,0x43,0x5e,0x37,0xec,0x43,0x49,0xed,0x9c,0x43,0xb0,0xa5,0xec,0x43,0x2a,0xa0,0xa0,0x43,0x08, 14850 0x09,0xe6,0xec,0x43,0xd1,0x77,0xa4,0x43,0x28,0x4b,0xed,0x43,0x61,0xa4,0xa3,0x43,0xab,0xc2,0xed,0x43, 14851 0x8e,0xb2,0xa0,0x43,0x08,0x70,0xe7,0xed,0x43,0xde,0x08,0x9d,0x43,0x87,0x86,0xf0,0x43,0x2f,0x53,0x97, 14852 0x43,0x87,0x7a,0xee,0x43,0xec,0x99,0x95,0x43,0x08,0xca,0x27,0xee,0x43,0xff,0x3d,0x95,0x43,0x74,0xca, 14853 0xec,0x43,0x55,0x8f,0x94,0x43,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x07,0xea,0x74,0xea,0x43,0x61, 14854 0x44,0x93,0x43,0x09,0x06,0x05,0xd3,0xe5,0x43,0x19,0x9c,0x90,0x43,0x08,0x09,0xc2,0xe6,0x43,0xd1,0xff, 14855 0x8f,0x43,0x4d,0x6f,0xe6,0x43,0x74,0xe8,0x92,0x43,0x3b,0xd7,0xe8,0x43,0xc3,0x56,0x93,0x43,0x08,0x1f, 14856 0x61,0xe9,0x43,0x93,0x4d,0x93,0x43,0x05,0xeb,0xe9,0x43,0x93,0x4d,0x93,0x43,0xea,0x74,0xea,0x43,0x61, 14857 0x44,0x93,0x43,0x07,0xea,0x74,0xea,0x43,0xe7,0xaa,0x94,0x43,0x08,0x24,0x50,0xea,0x43,0xe7,0xaa,0x94, 14858 0x43,0x2d,0x22,0xea,0x43,0xe7,0xaa,0x94,0x43,0x36,0xf4,0xe9,0x43,0xe7,0xaa,0x94,0x43,0x08,0xa2,0xcc, 14859 0xe7,0x43,0xe0,0xd8,0x94,0x43,0xd4,0xc9,0xe5,0x43,0x19,0xa8,0x92,0x43,0xd4,0xc9,0xe5,0x43,0x27,0x69, 14860 0x93,0x43,0x08,0x17,0x77,0xe5,0x43,0xe0,0xd8,0x94,0x43,0x67,0xe5,0xe5,0x43,0x47,0xda,0x95,0x43,0x43, 14861 0x9d,0xe6,0x43,0xe2,0xd3,0x97,0x43,0x08,0x9d,0xdd,0xe6,0x43,0xad,0xe7,0x98,0x43,0x09,0xce,0xe8,0x43, 14862 0xff,0x55,0x99,0x43,0xea,0x74,0xea,0x43,0x8a,0x9f,0x99,0x43,0x07,0xea,0x74,0xea,0x43,0x1e,0xc7,0x9b, 14863 0x43,0x08,0x71,0xcf,0xe9,0x43,0x53,0xb3,0x9a,0x43,0xa7,0xbb,0xe8,0x43,0xdb,0x0d,0x9a,0x43,0xc6,0x14, 14864 0xe7,0x43,0xdb,0x0d,0x9a,0x43,0x08,0x48,0x80,0xe5,0x43,0xdb,0x0d,0x9a,0x43,0x0a,0xb6,0xe4,0x43,0xc3, 14865 0x6e,0x97,0x43,0x76,0x9a,0xe4,0x43,0x74,0xf4,0x94,0x43,0x07,0x76,0x9a,0xe4,0x43,0x79,0xd7,0x93,0x43, 14866 0x08,0xd8,0xac,0xe4,0x43,0x66,0x27,0x92,0x43,0x29,0x1b,0xe5,0x43,0xe0,0xc0,0x90,0x43,0x05,0xd3,0xe5, 14867 0x43,0x19,0x9c,0x90,0x43,0x09,0x06,0x1b,0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x08,0x71,0x0b,0xf4,0x42, 14868 0x00,0x0e,0x8d,0x42,0x8c,0x0f,0x01,0x43,0x3e,0xc0,0x89,0x42,0xf3,0x28,0x06,0x43,0x48,0x9e,0x8b,0x42, 14869 0x08,0x15,0x89,0x09,0x43,0x00,0x0e,0x8d,0x42,0xe0,0x9c,0x0a,0x43,0xc1,0x8b,0x98,0x42,0xa6,0xc1,0x0a, 14870 0x43,0x02,0xa5,0xaa,0x42,0x07,0xa6,0xc1,0x0a,0x43,0xf9,0xf6,0xb0,0x42,0x08,0xa6,0xc1,0x0a,0x43,0x47, 14871 0x8e,0xb4,0x42,0x42,0xaf,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0xe0,0x9c,0x0a,0x43,0xba,0x74,0xbc,0x42,0x08, 14872 0xa1,0xd2,0x09,0x43,0x40,0x47,0xd0,0x42,0x0d,0xab,0x07,0x43,0x91,0xb5,0xd0,0x42,0x3b,0xb9,0x04,0x43, 14873 0xec,0x71,0xba,0x42,0x08,0xe5,0x5b,0x03,0x43,0xe3,0x33,0xa8,0x42,0x63,0xd8,0x00,0x43,0xce,0x70,0x9f, 14874 0x42,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x07,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e,0x42,0x08,0xed, 14875 0x6f,0xed,0x42,0x73,0x24,0x9d,0x42,0xd8,0x0c,0xf5,0x42,0x99,0x6c,0x9c,0x42,0x27,0xab,0xfd,0x42,0xea, 14876 0xda,0x9c,0x42,0x08,0x36,0xca,0x03,0x43,0x2b,0x94,0x9e,0x42,0x68,0xc7,0x01,0x43,0x8f,0xbe,0xa2,0x42, 14877 0xfa,0x06,0x08,0x43,0x73,0xb4,0xb5,0x42,0x08,0x8e,0x2e,0x0a,0x43,0x1f,0x6f,0xb8,0x42,0x9d,0xe3,0x08, 14878 0x43,0xd7,0x1e,0x99,0x42,0x28,0x15,0x05,0x43,0x32,0x3b,0x93,0x42,0x08,0x63,0xf0,0x04,0x43,0x70,0xed, 14879 0x8f,0x42,0x71,0x0b,0xf4,0x42,0x32,0x3b,0x93,0x42,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x07,0x1b, 14880 0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x09,0x06,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42,0x08,0x8e,0x55, 14881 0xc0,0x42,0xb8,0x4d,0x86,0x42,0x60,0xbf,0xd7,0x42,0x3e,0xf0,0x91,0x42,0x63,0xf6,0xe4,0x42,0x70,0xed, 14882 0x8f,0x42,0x08,0x7a,0x89,0xe5,0x42,0xac,0xc8,0x8f,0x42,0xcc,0xf7,0xe5,0x42,0xac,0xc8,0x8f,0x42,0x1b, 14883 0x66,0xe6,0x42,0xe3,0xa3,0x8f,0x42,0x07,0x1b,0x66,0xe6,0x42,0x73,0xf4,0x94,0x42,0x08,0x63,0xf6,0xe4, 14884 0x42,0x3b,0x19,0x95,0x42,0xe6,0x61,0xe3,0x42,0x00,0x3e,0x95,0x42,0xf4,0x16,0xe2,0x42,0xc4,0x62,0x95, 14885 0x42,0x08,0x6e,0x74,0xd6,0x42,0x15,0xd1,0x95,0x42,0x97,0x63,0xca,0x42,0xaf,0xcf,0x94,0x42,0xfb,0x2d, 14886 0xbe,0x42,0x86,0x80,0x90,0x42,0x08,0x97,0x03,0xba,0x42,0xce,0x10,0x8f,0x42,0x5e,0x28,0xba,0x42,0x3e, 14887 0xf0,0x91,0x42,0xf2,0x4f,0xbc,0x42,0x45,0xf7,0x96,0x42,0x08,0x27,0x54,0xbf,0x42,0x73,0x24,0x9d,0x42, 14888 0xa5,0xe8,0xc0,0x42,0x86,0xe0,0xa0,0x42,0xe4,0xca,0xc5,0x42,0xed,0x11,0xaa,0x42,0x08,0x54,0xaa,0xc8, 14889 0x42,0x86,0x40,0xb1,0x42,0x59,0x81,0xc5,0x42,0xa1,0x11,0xc4,0x42,0x3e,0xe7,0xbf,0x42,0xfb,0x8d,0xce, 14890 0x42,0x08,0xb4,0x6d,0xb7,0x42,0x30,0xc2,0xd9,0x42,0x46,0xf5,0xc9,0x42,0xdf,0x53,0xd9,0x42,0x38,0x40, 14891 0xcb,0x42,0x62,0x8f,0xcf,0x42,0x08,0x7d,0xf9,0xcc,0x42,0xec,0xa1,0xc2,0x42,0x07,0x43,0xcd,0x42,0x6c, 14892 0xdd,0xb8,0x42,0x2b,0x8b,0xcc,0x42,0x92,0xf5,0xaf,0x42,0x08,0xf9,0x8d,0xce,0x42,0x41,0x57,0xa7,0x42, 14893 0x5b,0xb8,0xd2,0x42,0xae,0x2f,0xa5,0x42,0x18,0x2f,0xd9,0x42,0x13,0x2a,0xa1,0x42,0x08,0x41,0x7e,0xdd, 14894 0x42,0xe3,0x03,0xa0,0x42,0x2e,0xf2,0xe1,0x42,0x7c,0x02,0x9f,0x42,0x1b,0x66,0xe6,0x42,0xa2,0x4a,0x9e, 14895 0x42,0x07,0x1b,0x66,0xe6,0x42,0xae,0x2f,0xa5,0x42,0x08,0x4d,0x63,0xe4,0x42,0x00,0x9e,0xa5,0x42,0xf4, 14896 0x16,0xe2,0x42,0x15,0x31,0xa6,0x42,0x99,0xca,0xdf,0x42,0x2b,0xc4,0xa6,0x42,0x08,0xc0,0x82,0xc6,0x42, 14897 0xc4,0xc2,0xa5,0x42,0x57,0xe1,0xd5,0x42,0x91,0xb5,0xd0,0x42,0x54,0xda,0xd0,0x42,0x97,0x93,0xd2,0x42, 14898 0x08,0x9c,0x3a,0xc7,0x42,0x17,0x58,0xdc,0x42,0x9c,0x0a,0xbf,0x42,0x6e,0xa4,0xde,0x42,0x90,0x25,0xb8, 14899 0x42,0xdf,0x53,0xd9,0x42,0x08,0x59,0x21,0xb5,0x42,0xf2,0xdf,0xd4,0x42,0x51,0x43,0xb3,0x42,0x91,0xb5, 14900 0xd0,0x42,0xc5,0x29,0xbb,0x42,0x0e,0x1a,0xca,0x42,0x08,0x65,0x36,0xc4,0x42,0xd0,0x07,0xbd,0x42,0x3e, 14901 0xe7,0xbf,0x42,0x37,0x09,0xbe,0x42,0x0c,0xea,0xc1,0x42,0xcd,0xd0,0xaf,0x42,0x08,0x2b,0x5b,0xc4,0x42, 14902 0x18,0x08,0xa3,0x42,0x67,0xa6,0xab,0x42,0x99,0x3c,0x94,0x42,0x5e,0x28,0xba,0x42,0x35,0xe2,0x87,0x42, 14903 0x09,]; 14904 14905 private struct ThePath { 14906 public: 14907 enum Command { 14908 Bounds, // always first, has 4 args (x0, y0, x1, y1) 14909 StrokeMode, 14910 FillMode, 14911 StrokeFillMode, 14912 NormalStroke, 14913 ThinStroke, 14914 MoveTo, 14915 LineTo, 14916 CubicTo, // cubic bezier 14917 EndPath, 14918 } 14919 14920 public: 14921 const(ubyte)[] path; 14922 uint ppos; 14923 14924 public: 14925 this (const(void)[] apath) pure nothrow @trusted @nogc { 14926 path = cast(const(ubyte)[])apath; 14927 } 14928 14929 @property bool empty () const pure nothrow @safe @nogc { pragma(inline, true); return (ppos >= path.length); } 14930 14931 Command getCommand () nothrow @trusted @nogc { 14932 pragma(inline, true); 14933 if (ppos >= cast(uint)path.length) assert(0, "invalid path"); 14934 return cast(Command)(path.ptr[ppos++]); 14935 } 14936 14937 // number of (x,y) pairs for this command 14938 static int argCount (in Command cmd) nothrow @safe @nogc { 14939 version(aliced) pragma(inline, true); 14940 if (cmd == Command.Bounds) return 2; 14941 else if (cmd == Command.MoveTo || cmd == Command.LineTo) return 1; 14942 else if (cmd == Command.CubicTo) return 3; 14943 else return 0; 14944 } 14945 14946 void skipArgs (int argc) nothrow @trusted @nogc { 14947 pragma(inline, true); 14948 ppos += cast(uint)(float.sizeof*2*argc); 14949 } 14950 14951 float getFloat () nothrow @trusted @nogc { 14952 pragma(inline, true); 14953 if (ppos >= cast(uint)path.length || cast(uint)path.length-ppos < float.sizeof) assert(0, "invalid path"); 14954 version(LittleEndian) { 14955 float res = *cast(const(float)*)(&path.ptr[ppos]); 14956 ppos += cast(uint)float.sizeof; 14957 return res; 14958 } else { 14959 static assert(float.sizeof == 4); 14960 uint xp = path.ptr[ppos]|(path.ptr[ppos+1]<<8)|(path.ptr[ppos+2]<<16)|(path.ptr[ppos+3]<<24); 14961 ppos += cast(uint)float.sizeof; 14962 return *cast(const(float)*)(&xp); 14963 } 14964 } 14965 } 14966 14967 // this will add baphomet's background path to the current NanoVega path, so you can fill it. 14968 public void addBaphometBack (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc { 14969 if (nvg is null) return; 14970 14971 auto path = ThePath(baphometPath); 14972 14973 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); } 14974 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); } 14975 14976 bool inPath = false; 14977 while (!path.empty) { 14978 auto cmd = path.getCommand(); 14979 switch (cmd) { 14980 case ThePath.Command.MoveTo: 14981 inPath = true; 14982 immutable float ex = getScaledX(); 14983 immutable float ey = getScaledY(); 14984 nvg.moveTo(ex, ey); 14985 break; 14986 case ThePath.Command.LineTo: 14987 inPath = true; 14988 immutable float ex = getScaledX(); 14989 immutable float ey = getScaledY(); 14990 nvg.lineTo(ex, ey); 14991 break; 14992 case ThePath.Command.CubicTo: // cubic bezier 14993 inPath = true; 14994 immutable float x1 = getScaledX(); 14995 immutable float y1 = getScaledY(); 14996 immutable float x2 = getScaledX(); 14997 immutable float y2 = getScaledY(); 14998 immutable float ex = getScaledX(); 14999 immutable float ey = getScaledY(); 15000 nvg.bezierTo(x1, y1, x2, y2, ex, ey); 15001 break; 15002 case ThePath.Command.EndPath: 15003 if (inPath) return; 15004 break; 15005 default: 15006 path.skipArgs(path.argCount(cmd)); 15007 break; 15008 } 15009 } 15010 } 15011 15012 // this will add baphomet's pupil paths to the current NanoVega path, so you can fill it. 15013 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 { 15014 // pupils starts with "fill-and-stroke" mode 15015 if (nvg is null) return; 15016 15017 auto path = ThePath(baphometPath); 15018 15019 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); } 15020 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); } 15021 15022 bool inPath = false; 15023 bool pupLeft = true; 15024 while (!path.empty) { 15025 auto cmd = path.getCommand(); 15026 switch (cmd) { 15027 case ThePath.Command.StrokeFillMode: inPath = true; break; 15028 case ThePath.Command.MoveTo: 15029 if (!inPath) goto default; 15030 static if (!left) { if (pupLeft) goto default; } 15031 static if (!right) { if (!pupLeft) goto default; } 15032 immutable float ex = getScaledX(); 15033 immutable float ey = getScaledY(); 15034 nvg.moveTo(ex, ey); 15035 break; 15036 case ThePath.Command.LineTo: 15037 if (!inPath) goto default; 15038 static if (!left) { if (pupLeft) goto default; } 15039 static if (!right) { if (!pupLeft) goto default; } 15040 immutable float ex = getScaledX(); 15041 immutable float ey = getScaledY(); 15042 nvg.lineTo(ex, ey); 15043 break; 15044 case ThePath.Command.CubicTo: // cubic bezier 15045 if (!inPath) goto default; 15046 static if (!left) { if (pupLeft) goto default; } 15047 static if (!right) { if (!pupLeft) goto default; } 15048 immutable float x1 = getScaledX(); 15049 immutable float y1 = getScaledY(); 15050 immutable float x2 = getScaledX(); 15051 immutable float y2 = getScaledY(); 15052 immutable float ex = getScaledX(); 15053 immutable float ey = getScaledY(); 15054 nvg.bezierTo(x1, y1, x2, y2, ex, ey); 15055 break; 15056 case ThePath.Command.EndPath: 15057 if (inPath) { 15058 if (pupLeft) pupLeft = false; else return; 15059 } 15060 break; 15061 default: 15062 path.skipArgs(path.argCount(cmd)); 15063 break; 15064 } 15065 } 15066 } 15067 15068 // mode: 'f' to allow fills; 's' to allow strokes; 'w' to allow stroke widths; 'c' to replace fills with strokes 15069 public void renderBaphomet(string mode="fs") (NVGContext nvg, float ofsx=0, float ofsy=0, float scalex=1, float scaley=1) nothrow @trusted @nogc { 15070 template hasChar(char ch, string s) { 15071 static if (s.length == 0) enum hasChar = false; 15072 else static if (s[0] == ch) enum hasChar = true; 15073 else enum hasChar = hasChar!(ch, s[1..$]); 15074 } 15075 enum AllowStroke = hasChar!('s', mode); 15076 enum AllowFill = hasChar!('f', mode); 15077 enum AllowWidth = hasChar!('w', mode); 15078 enum Contour = hasChar!('c', mode); 15079 //static assert(AllowWidth || AllowFill); 15080 15081 if (nvg is null) return; 15082 15083 auto path = ThePath(baphometPath); 15084 15085 float getScaledX () nothrow @trusted @nogc { pragma(inline, true); return (ofsx+path.getFloat()*scalex); } 15086 float getScaledY () nothrow @trusted @nogc { pragma(inline, true); return (ofsy+path.getFloat()*scaley); } 15087 15088 int mode = 0; 15089 int sw = ThePath.Command.NormalStroke; 15090 nvg.beginPath(); 15091 while (!path.empty) { 15092 auto cmd = path.getCommand(); 15093 switch (cmd) { 15094 case ThePath.Command.StrokeMode: mode = ThePath.Command.StrokeMode; break; 15095 case ThePath.Command.FillMode: mode = ThePath.Command.FillMode; break; 15096 case ThePath.Command.StrokeFillMode: mode = ThePath.Command.StrokeFillMode; break; 15097 case ThePath.Command.NormalStroke: sw = ThePath.Command.NormalStroke; break; 15098 case ThePath.Command.ThinStroke: sw = ThePath.Command.ThinStroke; break; 15099 case ThePath.Command.MoveTo: 15100 immutable float ex = getScaledX(); 15101 immutable float ey = getScaledY(); 15102 nvg.moveTo(ex, ey); 15103 break; 15104 case ThePath.Command.LineTo: 15105 immutable float ex = getScaledX(); 15106 immutable float ey = getScaledY(); 15107 nvg.lineTo(ex, ey); 15108 break; 15109 case ThePath.Command.CubicTo: // cubic bezier 15110 immutable float x1 = getScaledX(); 15111 immutable float y1 = getScaledY(); 15112 immutable float x2 = getScaledX(); 15113 immutable float y2 = getScaledY(); 15114 immutable float ex = getScaledX(); 15115 immutable float ey = getScaledY(); 15116 nvg.bezierTo(x1, y1, x2, y2, ex, ey); 15117 break; 15118 case ThePath.Command.EndPath: 15119 if (mode == ThePath.Command.FillMode || mode == ThePath.Command.StrokeFillMode) { 15120 static if (AllowFill || Contour) { 15121 static if (Contour) { 15122 if (mode == ThePath.Command.FillMode) { nvg.strokeWidth = 1; nvg.stroke(); } 15123 } else { 15124 nvg.fill(); 15125 } 15126 } 15127 } 15128 if (mode == ThePath.Command.StrokeMode || mode == ThePath.Command.StrokeFillMode) { 15129 static if (AllowStroke || Contour) { 15130 static if (AllowWidth) { 15131 if (sw == ThePath.Command.NormalStroke) nvg.strokeWidth = 1; 15132 else if (sw == ThePath.Command.ThinStroke) nvg.strokeWidth = 0.5; 15133 else assert(0, "wtf?!"); 15134 } 15135 nvg.stroke(); 15136 } 15137 } 15138 nvg.newPath(); 15139 break; 15140 default: 15141 path.skipArgs(path.argCount(cmd)); 15142 break; 15143 } 15144 } 15145 nvg.newPath(); 15146 }