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