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() const 485 { 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() const 503 { 504 return localBounds; 505 } 506 507 @property 508 { 509 /** 510 * The text's style. 511 * 512 * You can pass a combination of one or more styles, for example 513 * Style.Bold | Text.Italic. 514 */ 515 Style style(Style newStyle) 516 { 517 if(m_style != newStyle) 518 { 519 m_style = newStyle; 520 m_geometryNeedUpdate = true; 521 } 522 523 return m_style; 524 } 525 526 /// ditto 527 Style style() const 528 { 529 return m_style; 530 } 531 } 532 533 /** 534 * Set the text's style. 535 * 536 * You can pass a combination of one or more styles, for example 537 * Style.Bold | Text.Italic. 538 * 539 * Params: 540 * newStyle = New style 541 * 542 * //deprecated: Use the 'style' property instead. 543 */ 544 //deprecated("Use the 'style' property instead.") 545 void setStyle(Style newStyle) 546 { 547 style = newStyle; 548 } 549 550 /** 551 * Get the text's style. 552 * 553 * Returns: Text's style. 554 * 555 * //deprecated: Use the 'style' property instead. 556 */ 557 //deprecated("Use the 'style' property instead.") 558 Style getStyle() const 559 { 560 return style; 561 } 562 563 @property 564 { 565 /** 566 * The text's string. 567 * 568 * A text's string is empty by default. 569 */ 570 571 572 /*const(dchar)[] string(const(dchar)[] str) 573 { 574 // Because of the conversion, assume the text is new 575 stringCopy(str); 576 m_geometryNeedUpdate = true; 577 return m_string; 578 } 579 /// ditto 580 const(dchar)[] string() const 581 { 582 return m_string; 583 }*/ 584 585 const(T)[] string(T)(const(T)[] text) 586 if (is(T == dchar)||is(T == wchar)||is(T == char)) 587 { 588 // Because of the conversion, assume the text is new 589 stringCopy(text); 590 m_geometryNeedUpdate = true; 591 592 return string!T(); 593 } 594 595 const(T)[] string(T=char)() const 596 if(is(T == dchar)||is(T==wchar)||is(T==char)) 597 { 598 import std.utf: toUTF8, toUTF16, toUTF32; 599 600 static if(is(T == char)){ 601 return toUTF8(m_string); 602 } else static if(is( T == wchar)){ 603 return toUTF16(m_string); 604 } else static if(is(T == dchar)){ 605 return toUTF32(m_string); 606 } 607 } 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 import std.stdio; 947 import nudsfml.graphics.rendertexture; 948 949 writeln("Unit test for Text"); 950 951 auto renderTexture = new RenderTexture(); 952 953 renderTexture.create(400,200); 954 955 auto font = new Font(); 956 assert(font.loadFromFile("data/FiraMono-Regular.ttf")); 957 958 Text regular = new Text("Regular", font, 20); 959 Text bold = new Text("Bold", font, 20); 960 Text italic = new Text("Italic", font, 20); 961 Text boldItalic = new Text("Bold Italic", font, 20); 962 Text strikeThrough = new Text("Strike Through", font, 20); 963 Text italicStrikeThrough = new Text("Italic Strike Through", font, 20); 964 Text boldStrikeThrough = new Text("Bold Strike Through", font, 20); 965 Text boldItalicStrikeThrough = new Text("Bold Italic Strike Through", font, 20); 966 Text outlined = new Text("Outlined", font, 20); 967 Text outlinedBoldItalicStrikeThrough = new Text("Outlined Bold Italic Strike Through", font, 20); 968 969 bold.style = Text.Style.Bold; 970 bold.position = Vector2f(0,20); 971 972 italic.style = Text.Style.Italic; 973 italic.position = Vector2f(0,40); 974 975 boldItalic.style = Text.Style.Bold | Text.Style.Italic; 976 boldItalic.position = Vector2f(0,60); 977 978 strikeThrough.style = Text.Style.StrikeThrough; 979 strikeThrough.position = Vector2f(0,80); 980 981 italicStrikeThrough.style = Text.Style.Italic | Text.Style.StrikeThrough; 982 italicStrikeThrough.position = Vector2f(0,100); 983 984 boldStrikeThrough.style = Text.Style.Bold | Text.Style.StrikeThrough; 985 boldStrikeThrough.position = Vector2f(0,120); 986 987 boldItalicStrikeThrough.style = Text.Style.Bold | Text.Style.Italic | Text.Style.StrikeThrough; 988 boldItalicStrikeThrough.position = Vector2f(0,140); 989 990 outlined.outlineColor = Color.Red; 991 outlined.outlineThickness = 0.5f; 992 outlined.position = Vector2f(0,160); 993 994 outlinedBoldItalicStrikeThrough.style = Text.Style.Bold | Text.Style.Italic | Text.Style.StrikeThrough; 995 outlinedBoldItalicStrikeThrough.outlineColor = Color.Red; 996 outlinedBoldItalicStrikeThrough.outlineThickness = 0.5f; 997 outlinedBoldItalicStrikeThrough.position = Vector2f(0,180); 998 999 writeln(regular..string); 1000 writeln(bold..string); 1001 bold..string = bold..string; 1002 writeln(italic..string); 1003 writeln(boldItalic..string); 1004 writeln(strikeThrough..string); 1005 writeln(italicStrikeThrough..string); 1006 writeln(boldStrikeThrough..string); 1007 writeln(boldItalicStrikeThrough..string); 1008 writeln(outlined..string); 1009 writeln(outlinedBoldItalicStrikeThrough..string); 1010 1011 1012 renderTexture.clear(); 1013 1014 renderTexture.draw(regular); 1015 renderTexture.draw(bold); 1016 renderTexture.draw(italic); 1017 renderTexture.draw(boldItalic); 1018 renderTexture.draw(strikeThrough); 1019 renderTexture.draw(italicStrikeThrough); 1020 renderTexture.draw(boldStrikeThrough); 1021 renderTexture.draw(boldItalicStrikeThrough); 1022 renderTexture.draw(outlined); 1023 renderTexture.draw(outlinedBoldItalicStrikeThrough); 1024 1025 renderTexture.display(); 1026 1027 //grab that texture for usage 1028 auto texture = renderTexture.getTexture(); 1029 1030 writeln( texture.copyToImage().saveToFile("Text.png")) ; 1031 1032 auto fontTexture = font.getTexture(20); 1033 writeln(fontTexture.copyToImage().saveToFile("Font.png")); 1034 1035 writeln(); 1036 1037 }