1 /* 2 * DSFML - The Simple and Fast Multimedia Library for D 3 * 4 * Copyright (c) 2013 - 2018 Jeremy DeHaan (dehaan.jeremiah@gmail.com) 5 * 6 * This software is provided 'as-is', without any express or implied warranty. 7 * In no event will the authors be held liable for any damages arising from the 8 * use of this software. 9 * 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 * 14 * 1. The origin of this software must not be misrepresented; you must not claim 15 * that you wrote the original software. If you use this software in a product, 16 * an acknowledgment in the product documentation would be appreciated but is 17 * not required. 18 * 19 * 2. Altered source versions must be plainly marked as such, and must not be 20 * misrepresented as being the original software. 21 * 22 * 3. This notice may not be removed or altered from any source distribution 23 * 24 * 25 * DSFML is based on SFML (Copyright Laurent Gomila) 26 */ 27 28 /** 29 * $(U Text) is a drawable class that allows one to easily display some text 30 * with a custom style and color on a render target. 31 * 32 * It inherits all the functions from $(TRANSFORMABLE_LINK): position, rotation, 33 * scale, origin. It also adds text-specific properties such as the font to use, 34 * the character size, the font style (bold, italic, underlined), the global 35 * color and the text to display of course. It also provides convenience 36 * functions to calculate the graphical size of the text, or to get the global 37 * position of a given character. 38 * 39 * $(U Text) works in combination with the $(FONT_LINK) class, which loads and 40 * provides the glyphs (visual characters) of a given font. 41 * 42 * The separation of $(FONT_LINK) and $(U Text) allows more flexibility and 43 * better performances: indeed a $(FONT_LINK) is a heavy resource, and any 44 * operation on it is slow (often too slow for real-time applications). On the 45 * other side, a $(U Text) is a lightweight object which can combine the glyphs 46 * data and metrics of a $(FONT_LINK) to display any text on a render target. 47 * 48 * It is important to note that the $(U Text) instance doesn't copy the font 49 * that it uses, it only keeps a reference to it. Thus, a $(FONT_LINK) must not 50 * be destructed while it is used by a $(U Text). 51 * 52 * See also the note on coordinates and undistorted rendering in 53 * $(TRANSFORMABLE_LINK). 54 * 55 * example: 56 * --- 57 * // Declare and load a font 58 * auto font = new Font(); 59 * font.loadFromFile("arial.ttf"); 60 * 61 * // Create a text 62 * auto text = new Text("hello", font); 63 * text.setCharacterSize(30); 64 * text.setStyle(Text.Style.Bold); 65 * text.setColor(Color.Red); 66 * 67 * // Draw it 68 * window.draw(text); 69 * --- 70 * 71 * See_Also: 72 * $(FONT_LINK), $(TRANSFORMABLE_LINK) 73 */ 74 module nudsfml.graphics.text; 75 76 import nudsfml.graphics.font; 77 import nudsfml.graphics.glyph; 78 import nudsfml.graphics.color; 79 import nudsfml.graphics.rect; 80 import nudsfml.graphics.transformable; 81 import nudsfml.graphics.drawable; 82 import nudsfml.graphics.texture; 83 import nudsfml.graphics.vertexarray; 84 import nudsfml.graphics.vertex; 85 import nudsfml.graphics.rendertarget; 86 import nudsfml.graphics.renderstates; 87 import nudsfml.graphics.primitivetype; 88 89 import nudsfml.system.vector2; 90 91 /** 92 * Graphical text that can be drawn to a render target. 93 */ 94 class Text : Drawable, Transformable 95 { 96 /// Enumeration of the string drawing styles. 97 enum Style { 98 /// Regular characters, no style 99 Regular = 0, 100 /// Bold characters 101 Bold = 1 << 0, 102 /// Italic characters 103 Italic = 1 << 1, 104 /// Underlined characters 105 Underlined = 1 << 2, 106 /// Strike through characters 107 StrikeThrough = 1 << 3 108 } 109 110 mixin NormalTransformable; 111 112 private 113 { 114 dchar[] m_string; 115 Font m_font; 116 uint m_characterSize; 117 Style m_style; 118 Color m_fillColor; 119 Color m_outlineColor; 120 float m_outlineThickness; 121 VertexArray m_vertices; 122 VertexArray m_outlineVertices; 123 FloatRect m_bounds; 124 bool m_geometryNeedUpdate; 125 126 //helper function to copy input string into character buffer 127 void stringCopy(T)(const(T)[] str) 128 if (is(T == dchar)||is(T == wchar)||is(T == char)) { 129 import std.utf: byDchar; 130 131 //make a conservative estimate on how much room we'll need 132 m_string.reserve(dchar.sizeof * str.length); 133 m_string.length = 0; 134 135 foreach(dchar c; str.byDchar()) 136 m_string~=c; 137 } 138 } 139 140 /** 141 * Default constructor 142 * 143 * Creates an empty text. 144 */ 145 this() { 146 m_characterSize = 30; 147 m_style = Style.Regular; 148 m_fillColor = Color(255,255,255); 149 m_outlineColor = Color(0,0,0); 150 m_outlineThickness = 0; 151 m_vertices = new VertexArray(PrimitiveType.Triangles); 152 m_outlineVertices = new VertexArray(PrimitiveType.Triangles); 153 m_bounds = FloatRect(); 154 m_geometryNeedUpdate = false; 155 } 156 157 /** 158 * Construct the text from a string, font and size 159 * 160 * Note that if the used font is a bitmap font, it is not scalable, thus not 161 * all requested sizes will be available to use. This needs to be taken into 162 * consideration when setting the character size. If you need to display 163 * text of a certain size, make sure the corresponding bitmap font that 164 * supports that size is used. 165 * 166 * Params: 167 * text = Text assigned to the string 168 * font = Font used to draw the string 169 * characterSize = Base size of characters, in pixels 170 * 171 * //deprecated: Use the constructor that takes a 'const(dchar)[]' instead. 172 */ 173 //deprecated("Use the constructor that takes a 'const(dchar)[]' instead.") 174 this(T)(const(T)[] text, Font font, uint characterSize = 30) 175 if (is(T == dchar)||is(T == wchar)||is(T == char)) 176 { 177 stringCopy(text); 178 m_font = font; 179 m_characterSize = characterSize; 180 m_style = Style.Regular; 181 m_fillColor = Color(255,255,255); 182 m_outlineColor = Color(0,0,0); 183 m_outlineThickness = 0; 184 m_vertices = new VertexArray(PrimitiveType.Triangles); 185 m_outlineVertices = new VertexArray(PrimitiveType.Triangles); 186 m_bounds = FloatRect(); 187 m_geometryNeedUpdate = true; 188 } 189 190 /** 191 * Construct the text from a string, font and size 192 * 193 * Note that if the used font is a bitmap font, it is not scalable, thus not 194 * all requested sizes will be available to use. This needs to be taken into 195 * consideration when setting the character size. If you need to display 196 * text of a certain size, make sure the corresponding bitmap font that 197 * supports that size is used. 198 * 199 * Params: 200 * text = Text assigned to the string 201 * font = Font used to draw the string 202 * characterSize = Base size of characters, in pixels 203 */ 204 this(T)(const(dchar)[] text, Font font, uint characterSize = 30) 205 { 206 stringCopy(text); 207 m_font = font; 208 m_characterSize = characterSize; 209 m_style = Style.Regular; 210 m_fillColor = Color(255,255,255); 211 m_outlineColor = Color(0,0,0); 212 m_outlineThickness = 0; 213 m_vertices = new VertexArray(PrimitiveType.Triangles); 214 m_outlineVertices = new VertexArray(PrimitiveType.Triangles); 215 m_bounds = FloatRect(); 216 m_geometryNeedUpdate = true; 217 } 218 219 /// Destructor. 220 ~this() 221 { 222 //import nudsfml.system.config; 223 //mixin(destructorOutput); 224 } 225 226 @property 227 { 228 /** 229 * The character size in pixels. 230 * 231 * The default size is 30. 232 * 233 * Note that if the used font is a bitmap font, it is not scalable, thus 234 * not all requested sizes will be available to use. This needs to be 235 * taken into consideration when setting the character size. If you need 236 * to display text of a certain size, make sure the corresponding bitmap 237 * font that supports that size is used. 238 */ 239 uint characterSize(uint size) { 240 if(m_characterSize != size) { 241 m_characterSize = size; 242 m_geometryNeedUpdate = true; 243 } 244 return m_characterSize; 245 } 246 247 /// ditto 248 uint characterSize() const { 249 return m_characterSize; 250 } 251 } 252 253 /** 254 * Set the character size. 255 * 256 * The default size is 30. 257 * 258 * Note that if the used font is a bitmap font, it is not scalable, thus 259 * not all requested sizes will be available to use. This needs to be 260 * taken into consideration when setting the character size. If you need 261 * to display text of a certain size, make sure the corresponding bitmap 262 * font that supports that size is used. 263 * 264 * Params: 265 * size = New character size, in pixels. 266 * 267 * //deprecated: Use the 'characterSize' property instead. 268 */ 269 //deprecated("Use the 'characterSize' property instead.") 270 void setCharacterSize(uint size){ 271 characterSize = size; 272 } 273 274 /** 275 * Get the character size. 276 * 277 * Returns: Size of the characters, in pixels. 278 * 279 * //deprecated: Use the 'characterSize' property instead. 280 */ 281 //deprecated("Use the 'characterSize' property instead.") 282 uint getCharacterSize() const { 283 return characterSize; 284 } 285 286 /** 287 * Set the fill color of the text. 288 * 289 * By default, the text's color is opaque white. 290 * 291 * Params: 292 * color = New color of the text. 293 * 294 * //deprecated: Use the 'fillColor' or 'outlineColor' properties instead. 295 */ 296 //deprecated("Use the 'fillColor' or 'outlineColor' properties instead.") 297 void setColor(Color color) { 298 fillColor = color; 299 } 300 301 /** 302 * Get the fill color of the text. 303 * 304 * Returns: Fill color of the text. 305 * 306 * //deprecated: Use the 'fillColor' or 'outlineColor' properties instead. 307 */ 308 //deprecated("Use the 'fillColor' or 'outlineColor' properties instead.") 309 Color getColor() const { 310 return fillColor; 311 } 312 313 @property 314 { 315 /** 316 * The fill color of the text. 317 * 318 * By default, the text's fill color is opaque white. Setting the fill 319 * color to a transparent color with an outline will cause the outline to 320 * be displayed in the fill area of the text. 321 */ 322 Color fillColor(Color color) { 323 if(m_fillColor != color) { 324 m_fillColor = color; 325 326 // Change vertex colors directly, no need to update whole geometry 327 // (if geometry is updated anyway, we can skip this step) 328 if(!m_geometryNeedUpdate) { 329 for(int i = 0; i < m_vertices.getVertexCount(); ++i) { 330 m_vertices[i].color = m_fillColor; 331 } 332 } 333 } 334 335 return m_fillColor; 336 } 337 338 /// ditto 339 Color fillColor() const { 340 return m_fillColor; 341 } 342 } 343 344 @property 345 { 346 /** 347 * The outline color of the text. 348 * 349 * By default, the text's outline color is opaque black. 350 */ 351 Color outlineColor(Color color) { 352 if(m_outlineColor != color){ 353 m_outlineColor = color; 354 355 // Change vertex colors directly, no need to update whole geometry 356 // (if geometry is updated anyway, we can skip this step) 357 if(!m_geometryNeedUpdate) { 358 for(int i = 0; i < m_outlineVertices.getVertexCount(); ++i) { 359 m_outlineVertices[i].color = m_outlineColor; 360 } 361 } 362 } 363 364 return m_outlineColor; 365 } 366 367 /// ditto 368 Color outlineColor() const { 369 return m_outlineColor; 370 } 371 } 372 373 @property 374 { 375 /** 376 * The outline color of the text. 377 * 378 * By default, the text's outline color is opaque black. 379 */ 380 float outlineThickness(float thickness) { 381 if(m_outlineThickness != thickness) { 382 m_outlineThickness = thickness; 383 m_geometryNeedUpdate = true; 384 } 385 386 return m_outlineThickness; 387 } 388 389 /// ditto 390 float outlineThickness() const { 391 return m_outlineThickness; 392 } 393 } 394 395 @property { 396 /** 397 * The text's font. 398 */ 399 const(Font) font(Font newFont) { 400 if (m_font !is newFont){ 401 m_font = newFont; 402 m_geometryNeedUpdate = true; 403 } 404 405 return m_font; 406 } 407 408 /// ditto 409 const(Font) font() const{ 410 return m_font; 411 } 412 } 413 414 /** 415 * Set the text's font. 416 * 417 * Params: 418 * newFont = New font 419 * 420 * //deprecated: Use the 'font' property instead. 421 */ 422 //deprecated("Use the 'font' property instead.") 423 void setFont(Font newFont) 424 { 425 font = newFont; 426 } 427 428 /** 429 * Get thet text's font. 430 * 431 * Returns: Text's font. 432 * 433 * //deprecated: Use the 'font' property instead. 434 */ 435 //deprecated("Use the 'font' property instead.") 436 const(Font) getFont() const 437 { 438 return font; 439 } 440 441 /** 442 * Get the global bounding rectangle of the entity. 443 * 444 * The returned rectangle is in global coordinates, which means that it 445 * takes in account the transformations (translation, rotation, scale, ...) 446 * that are applied to the entity. In other words, this function returns the 447 * bounds of the sprite in the global 2D world's coordinate system. 448 * 449 * Returns: Global bounding rectangle of the entity. 450 */ 451 @property FloatRect globalBounds() 452 { 453 return getTransform().transformRect(localBounds); 454 } 455 456 /** 457 * Get the global bounding rectangle of the entity. 458 * 459 * The returned rectangle is in global coordinates, which means that it 460 * takes in account the transformations (translation, rotation, scale, ...) 461 * that are applied to the entity. In other words, this function returns the 462 * bounds of the sprite in the global 2D world's coordinate system. 463 * 464 * Returns: Global bounding rectangle of the entity. 465 * 466 * //deprecated: Use the 'globalBounds' property instead. 467 */ 468 //deprecated("Use the 'globalBounds' property instead.") 469 FloatRect getGlobalBounds() 470 { 471 return globalBounds; 472 } 473 474 /** 475 * Get the local bounding rectangle of the entity. 476 * 477 * The returned rectangle is in local coordinates, which means that it 478 * ignores the transformations (translation, rotation, scale, ...) that are 479 * applied to the entity. In other words, this function returns the bounds 480 * of the entity in the entity's coordinate system. 481 * 482 * Returns: Local bounding rectangle of the entity. 483 */ 484 @property FloatRect localBounds() { 485 ensureGeometryUpdate(); 486 return m_bounds; 487 } 488 489 /** 490 * Get the local bounding rectangle of the entity. 491 * 492 * The returned rectangle is in local coordinates, which means that it 493 * ignores the transformations (translation, rotation, scale, ...) that are 494 * applied to the entity. In other words, this function returns the bounds 495 * of the entity in the entity's coordinate system. 496 * 497 * Returns: Local bounding rectangle of the entity. 498 * 499 * //deprecated: Use the 'globalBounds' property instead. 500 */ 501 //deprecated("Use the 'localBounds' property instead.") 502 FloatRect getLocalBounds(){ 503 return localBounds; 504 } 505 506 @property 507 { 508 /** 509 * The text's style. 510 * 511 * You can pass a combination of one or more styles, for example 512 * Style.Bold | Text.Italic. 513 */ 514 Style style(Style newStyle) 515 { 516 if(m_style != newStyle) 517 { 518 m_style = newStyle; 519 m_geometryNeedUpdate = true; 520 } 521 522 return m_style; 523 } 524 525 /// ditto 526 Style style() const 527 { 528 return m_style; 529 } 530 } 531 532 /** 533 * Set the text's style. 534 * 535 * You can pass a combination of one or more styles, for example 536 * Style.Bold | Text.Italic. 537 * 538 * Params: 539 * newStyle = New style 540 * 541 * //deprecated: Use the 'style' property instead. 542 */ 543 //deprecated("Use the 'style' property instead.") 544 void setStyle(Style newStyle) 545 { 546 style = newStyle; 547 } 548 549 /** 550 * Get the text's style. 551 * 552 * Returns: Text's style. 553 * 554 * //deprecated: Use the 'style' property instead. 555 */ 556 //deprecated("Use the 'style' property instead.") 557 Style getStyle() const 558 { 559 return style; 560 } 561 562 @property 563 { 564 /** 565 * The text's string. 566 * 567 * A text's string is empty by default. 568 */ 569 570 571 /*const(dchar)[] string(const(dchar)[] str) 572 { 573 // Because of the conversion, assume the text is new 574 stringCopy(str); 575 m_geometryNeedUpdate = true; 576 return m_string; 577 } 578 /// ditto 579 const(dchar)[] string() const 580 { 581 return m_string; 582 }*/ 583 584 const(T)[] string(T)(const(T)[] text) 585 if (is(T == dchar)||is(T == wchar)||is(T == char)) 586 { 587 // Because of the conversion, assume the text is new 588 stringCopy(text); 589 m_geometryNeedUpdate = true; 590 591 return string!T(); 592 } 593 594 const(T)[] string(T=char)() const 595 if(is(T == dchar)||is(T==wchar)||is(T==char)) 596 { 597 import std.utf: toUTF8, toUTF16, toUTF32; 598 599 static if(is(T == char)){ 600 return toUTF8(m_string); 601 } else static if(is( T == wchar)){ 602 return toUTF16(m_string); 603 } else static if(is(T == dchar)){ 604 return toUTF32(m_string); 605 } 606 } 607 608 } 609 610 /** 611 * Set the text's string. 612 * 613 * A text's string is empty by default. 614 * 615 * Params: 616 * text = New string 617 * 618 * //deprecated: Use the 'string' property instead. 619 */ 620 //deprecated("Use the 'string' property instead.") 621 void setString(T)(const(T)[] text) 622 if (is(T == dchar)||is(T == wchar)||is(T == char)) 623 { 624 // Because of the conversion, assume the text is new 625 stringCopy(text); 626 m_geometryNeedUpdate = true; 627 } 628 629 /** 630 * Get a copy of the text's string. 631 * 632 * Returns: a copy of the text's string. 633 * 634 * //deprecated: Use the 'string' property instead. 635 */ 636 //deprecated("Use the 'string' property instead.") 637 const(T)[] getString(T=char)() const 638 if (is(T == dchar)||is(T == wchar)||is(T == char)) 639 { 640 import std.utf: toUTF8, toUTF16, toUTF32; 641 642 static if(is(T == char)) 643 return toUTF8(m_string); 644 else static if(is(T == wchar)) 645 return toUTF16(m_string); 646 else static if(is(T == dchar)) 647 return toUTF32(m_string); 648 } 649 650 /** 651 * Draw the object to a render target. 652 * 653 * Params: 654 * renderTarget = Render target to draw to 655 * renderStates = Current render states 656 */ 657 void draw(RenderTarget renderTarget, RenderStates renderStates) 658 { 659 if (m_font !is null) 660 { 661 ensureGeometryUpdate(); 662 663 renderStates.transform *= getTransform(); 664 renderStates.texture = m_font.getTexture(m_characterSize); 665 666 // Only draw the outline if there is something to draw 667 if (m_outlineThickness != 0) 668 renderTarget.draw(m_outlineVertices, renderStates); 669 670 renderTarget.draw(m_vertices, renderStates); 671 } 672 } 673 674 /** 675 * Return the position of the index-th character. 676 * 677 * This function computes the visual position of a character from its index 678 * in the string. The returned position is in global coordinates 679 * (translation, rotation, scale and origin are applied). If index is out of 680 * range, the position of the end of the string is returned. 681 * 682 * Params: 683 * index = Index of the character 684 * 685 * Returns: Position of the character. 686 */ 687 Vector2f findCharacterPos(size_t index) 688 { 689 // Make sure that we have a valid font 690 if(m_font is null) 691 { 692 return Vector2f(0,0); 693 } 694 695 // Adjust the index if it's out of range 696 if(index > m_string.length) 697 { 698 index = m_string.length; 699 } 700 701 // Precompute the variables needed by the algorithm 702 bool bold = (m_style & Style.Bold) != 0; 703 float hspace = cast(float)(m_font.getGlyph(' ', m_characterSize, bold).advance); 704 float vspace = cast(float)(m_font.getLineSpacing(m_characterSize)); 705 706 // Compute the position 707 Vector2f position; 708 dchar prevChar = 0; 709 for (size_t i = 0; i < index; ++i) 710 { 711 dchar curChar = m_string[i]; 712 713 // Apply the kerning offset 714 position.x += cast(float)(m_font.getKerning(prevChar, curChar, m_characterSize)); 715 prevChar = curChar; 716 717 // Handle special characters 718 switch (curChar) 719 { 720 case ' ' : position.x += hspace; continue; 721 case '\t' : position.x += hspace * 4; continue; 722 case '\n' : position.y += vspace; position.x = 0; continue; 723 case '\v' : position.y += vspace * 4; continue; 724 default : break; 725 } 726 727 // For regular characters, add the advance offset of the glyph 728 position.x += cast(float)(m_font.getGlyph(curChar, m_characterSize, bold).advance); 729 } 730 731 // Transform the position to global coordinates 732 position = getTransform().transformPoint(position); 733 734 return position; 735 } 736 737 private: 738 void ensureGeometryUpdate() 739 { 740 import std.math: floor; 741 import std.algorithm: max, min; 742 743 // Add an underline or strikethrough line to the vertex array 744 static void addLine(VertexArray vertices, float lineLength, 745 float lineTop, ref const(Color) color, float offset, 746 float thickness, float outlineThickness = 0) 747 { 748 float top = floor(lineTop + offset - (thickness / 2) + 0.5f); 749 float bottom = top + floor(thickness + 0.5f); 750 751 vertices.append(Vertex(Vector2f(-outlineThickness, top - outlineThickness), color, Vector2f(1, 1))); 752 vertices.append(Vertex(Vector2f(lineLength + outlineThickness, top - outlineThickness), color, Vector2f(1, 1))); 753 vertices.append(Vertex(Vector2f(-outlineThickness, bottom + outlineThickness), color, Vector2f(1, 1))); 754 vertices.append(Vertex(Vector2f(-outlineThickness, bottom + outlineThickness), color, Vector2f(1, 1))); 755 vertices.append(Vertex(Vector2f(lineLength + outlineThickness, top - outlineThickness), color, Vector2f(1, 1))); 756 vertices.append(Vertex(Vector2f(lineLength + outlineThickness, bottom + outlineThickness), color, Vector2f(1, 1))); 757 } 758 759 // Add a glyph quad to the vertex array 760 static void addGlyphQuad(VertexArray vertices, Vector2f position, 761 ref const(Color) color, ref const(Glyph) glyph, 762 float italic, float outlineThickness = 0) 763 { 764 float left = glyph.bounds.left; 765 float top = glyph.bounds.top; 766 float right = glyph.bounds.left + glyph.bounds.width; 767 float bottom = glyph.bounds.top + glyph.bounds.height; 768 769 float u1 = glyph.textureRect.left; 770 float v1 = glyph.textureRect.top; 771 float u2 = glyph.textureRect.left + glyph.textureRect.width; 772 float v2 = glyph.textureRect.top + glyph.textureRect.height; 773 774 vertices.append(Vertex(Vector2f(position.x + left - italic * top - outlineThickness, position.y + top - outlineThickness), color, Vector2f(u1, v1))); 775 vertices.append(Vertex(Vector2f(position.x + right - italic * top - outlineThickness, position.y + top - outlineThickness), color, Vector2f(u2, v1))); 776 vertices.append(Vertex(Vector2f(position.x + left - italic * bottom - outlineThickness, position.y + bottom - outlineThickness), color, Vector2f(u1, v2))); 777 vertices.append(Vertex(Vector2f(position.x + left - italic * bottom - outlineThickness, position.y + bottom - outlineThickness), color, Vector2f(u1, v2))); 778 vertices.append(Vertex(Vector2f(position.x + right - italic * top - outlineThickness, position.y + top - outlineThickness), color, Vector2f(u2, v1))); 779 vertices.append(Vertex(Vector2f(position.x + right - italic * bottom - outlineThickness, position.y + bottom - outlineThickness), color, Vector2f(u2, v2))); 780 } 781 782 // Do nothing, if geometry has not changed 783 if (!m_geometryNeedUpdate) 784 return; 785 786 // Mark geometry as updated 787 m_geometryNeedUpdate = false; 788 789 // Clear the previous geometry 790 m_vertices.clear(); 791 m_outlineVertices.clear(); 792 m_bounds = FloatRect(); 793 794 // No font or text: nothing to draw 795 if (!m_font || m_string.length == 0) 796 return; 797 798 // Compute values related to the text style 799 bool bold = (m_style & Style.Bold) != 0; 800 bool underlined = (m_style & Style.Underlined) != 0; 801 bool strikeThrough = (m_style & Style.StrikeThrough) != 0; 802 float italic = (m_style & Style.Italic) ? 0.208f : 0.0f; // 12 degrees 803 float underlineOffset = m_font.getUnderlinePosition(m_characterSize); 804 float underlineThickness = m_font.getUnderlineThickness(m_characterSize); 805 806 // Compute the location of the strike through dynamically 807 // We use the center point of the lowercase 'x' glyph as the reference 808 // We reuse the underline thickness as the thickness of the strike through as well 809 FloatRect xBounds = m_font.getGlyph('x', m_characterSize, bold).bounds; 810 float strikeThroughOffset = xBounds.top + xBounds.height / 2.0f; 811 812 // Precompute the variables needed by the algorithm 813 float hspace = m_font.getGlyph(' ', m_characterSize, bold).advance; 814 float vspace = m_font.getLineSpacing(m_characterSize); 815 float x = 0.0f; 816 float y = cast(float)m_characterSize; 817 818 // Create one quad for each character 819 float minX = cast(float)m_characterSize; 820 float minY = cast(float)m_characterSize; 821 float maxX = 0.0f; 822 float maxY = 0.0f; 823 dchar prevChar = '\0'; 824 for (size_t i = 0; i < m_string.length; ++i) 825 { 826 dchar curChar = m_string[i]; 827 828 // Apply the kerning offset 829 x += m_font.getKerning(prevChar, curChar, m_characterSize); 830 prevChar = curChar; 831 832 // If we're using the underlined style and there's a new line, draw a line 833 if (underlined && (curChar == '\n')) 834 { 835 addLine(m_vertices, x, y, m_fillColor, underlineOffset, underlineThickness); 836 837 if (m_outlineThickness != 0) 838 addLine(m_outlineVertices, x, y, m_outlineColor, underlineOffset, underlineThickness, m_outlineThickness); 839 } 840 841 // If we're using the strike through style and there's a new line, draw a line across all characters 842 if (strikeThrough && (curChar == '\n')) 843 { 844 addLine(m_vertices, x, y, m_fillColor, strikeThroughOffset, underlineThickness); 845 846 if (m_outlineThickness != 0) 847 addLine(m_outlineVertices, x, y, m_outlineColor, strikeThroughOffset, underlineThickness, m_outlineThickness); 848 } 849 850 // Handle special characters 851 if ((curChar == ' ') || (curChar == '\t') || (curChar == '\n')) 852 { 853 // Update the current bounds (min coordinates) 854 minX = min(minX, x); 855 minY = min(minY, y); 856 857 switch (curChar) 858 { 859 case ' ': x += hspace; break; 860 case '\t': x += hspace * 4; break; 861 case '\n': y += vspace; x = 0; break; 862 default : break; 863 } 864 865 // Update the current bounds (max coordinates) 866 maxX = max(maxX, x); 867 maxY = max(maxY, y); 868 869 // Next glyph, no need to create a quad for whitespace 870 continue; 871 } 872 873 // Apply the outline 874 if (m_outlineThickness != 0) 875 { 876 Glyph glyph = m_font.getGlyph(curChar, m_characterSize, bold, m_outlineThickness); 877 878 float left = glyph.bounds.left; 879 float top = glyph.bounds.top; 880 float right = glyph.bounds.left + glyph.bounds.width; 881 float bottom = glyph.bounds.top + glyph.bounds.height; 882 883 // Add the outline glyph to the vertices 884 addGlyphQuad(m_outlineVertices, Vector2f(x, y), m_outlineColor, glyph, italic, m_outlineThickness); 885 886 // Update the current bounds with the outlined glyph bounds 887 minX = min(minX, x + left - italic * bottom - m_outlineThickness); 888 maxX = max(maxX, x + right - italic * top - m_outlineThickness); 889 minY = min(minY, y + top - m_outlineThickness); 890 maxY = max(maxY, y + bottom - m_outlineThickness); 891 } 892 893 // Extract the current glyph's description 894 const Glyph glyph = m_font.getGlyph(curChar, m_characterSize, bold); 895 896 // Add the glyph to the vertices 897 addGlyphQuad(m_vertices, Vector2f(x, y), m_fillColor, glyph, italic); 898 899 // Update the current bounds with the non outlined glyph bounds 900 if (m_outlineThickness == 0) 901 { 902 float left = glyph.bounds.left; 903 float top = glyph.bounds.top; 904 float right = glyph.bounds.left + glyph.bounds.width; 905 float bottom = glyph.bounds.top + glyph.bounds.height; 906 907 minX = min(minX, x + left - italic * bottom); 908 maxX = max(maxX, x + right - italic * top); 909 minY = min(minY, y + top); 910 maxY = max(maxY, y + bottom); 911 } 912 913 // Advance to the next character 914 x += glyph.advance; 915 } 916 917 // If we're using the underlined style, add the last line 918 if (underlined && (x > 0)) 919 { 920 addLine(m_vertices, x, y, m_fillColor, underlineOffset, underlineThickness); 921 922 if (m_outlineThickness != 0) 923 addLine(m_outlineVertices, x, y, m_outlineColor, underlineOffset, underlineThickness, m_outlineThickness); 924 } 925 926 // If we're using the strike through style, add the last line across all characters 927 if (strikeThrough && (x > 0)) 928 { 929 addLine(m_vertices, x, y, m_fillColor, strikeThroughOffset, underlineThickness); 930 931 if (m_outlineThickness != 0) 932 addLine(m_outlineVertices, x, y, m_outlineColor, strikeThroughOffset, underlineThickness, m_outlineThickness); 933 } 934 935 // Update the bounding rectangle 936 m_bounds.left = minX; 937 m_bounds.top = minY; 938 m_bounds.width = maxX - minX; 939 m_bounds.height = maxY - minY; 940 } 941 } 942 943 unittest 944 { 945 import std.stdio; 946 import nudsfml.graphics.rendertexture; 947 948 writeln("Unit test for Text"); 949 950 auto renderTexture = new RenderTexture(); 951 952 renderTexture.create(400,200); 953 954 auto font = new Font(); 955 assert(font.loadFromFile("data/FiraMono-Regular.ttf")); 956 957 Text regular = new Text("Regular", font, 20); 958 Text bold = new Text("Bold", font, 20); 959 Text italic = new Text("Italic", font, 20); 960 Text boldItalic = new Text("Bold Italic", font, 20); 961 Text strikeThrough = new Text("Strike Through", font, 20); 962 Text italicStrikeThrough = new Text("Italic Strike Through", font, 20); 963 Text boldStrikeThrough = new Text("Bold Strike Through", font, 20); 964 Text boldItalicStrikeThrough = new Text("Bold Italic Strike Through", font, 20); 965 Text outlined = new Text("Outlined", font, 20); 966 Text outlinedBoldItalicStrikeThrough = new Text("Outlined Bold Italic Strike Through", font, 20); 967 968 bold.style = Text.Style.Bold; 969 bold.position = Vector2f(0,20); 970 971 italic.style = Text.Style.Italic; 972 italic.position = Vector2f(0,40); 973 974 boldItalic.style = Text.Style.Bold | Text.Style.Italic; 975 boldItalic.position = Vector2f(0,60); 976 977 strikeThrough.style = Text.Style.StrikeThrough; 978 strikeThrough.position = Vector2f(0,80); 979 980 italicStrikeThrough.style = Text.Style.Italic | Text.Style.StrikeThrough; 981 italicStrikeThrough.position = Vector2f(0,100); 982 983 boldStrikeThrough.style = Text.Style.Bold | Text.Style.StrikeThrough; 984 boldStrikeThrough.position = Vector2f(0,120); 985 986 boldItalicStrikeThrough.style = Text.Style.Bold | Text.Style.Italic | Text.Style.StrikeThrough; 987 boldItalicStrikeThrough.position = Vector2f(0,140); 988 989 outlined.outlineColor = Color.Red; 990 outlined.outlineThickness = 0.5f; 991 outlined.position = Vector2f(0,160); 992 993 outlinedBoldItalicStrikeThrough.style = Text.Style.Bold | Text.Style.Italic | Text.Style.StrikeThrough; 994 outlinedBoldItalicStrikeThrough.outlineColor = Color.Red; 995 outlinedBoldItalicStrikeThrough.outlineThickness = 0.5f; 996 outlinedBoldItalicStrikeThrough.position = Vector2f(0,180); 997 998 writeln(regular..string); 999 writeln(bold..string); 1000 bold..string = bold..string; 1001 writeln(italic..string); 1002 writeln(boldItalic..string); 1003 writeln(strikeThrough..string); 1004 writeln(italicStrikeThrough..string); 1005 writeln(boldStrikeThrough..string); 1006 writeln(boldItalicStrikeThrough..string); 1007 writeln(outlined..string); 1008 writeln(outlinedBoldItalicStrikeThrough..string); 1009 1010 1011 renderTexture.clear(); 1012 1013 renderTexture.draw(regular); 1014 renderTexture.draw(bold); 1015 renderTexture.draw(italic); 1016 renderTexture.draw(boldItalic); 1017 renderTexture.draw(strikeThrough); 1018 renderTexture.draw(italicStrikeThrough); 1019 renderTexture.draw(boldStrikeThrough); 1020 renderTexture.draw(boldItalicStrikeThrough); 1021 renderTexture.draw(outlined); 1022 renderTexture.draw(outlinedBoldItalicStrikeThrough); 1023 1024 renderTexture.display(); 1025 1026 //grab that texture for usage 1027 auto texture = renderTexture.getTexture(); 1028 1029 writeln( texture.copyToImage().saveToFile("Text.png")) ; 1030 1031 auto fontTexture = font.getTexture(20); 1032 writeln(fontTexture.copyToImage().saveToFile("Font.png")); 1033 1034 writeln(); 1035 1036 }