1 /* 2 * DSFML - The Simple and Fast Multimedia Library for D 3 * 4 * Copyright (c) 2013 - 2017 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 /** 26 * $(U Text) is a drawable class that allows one to easily display some text 27 * with a custom style and color on a render target. 28 * 29 * It inherits all the functions from $(TRANSFORMABLE_LINK): position, rotation, 30 * scale, origin. It also adds text-specific properties such as the font to use, 31 * the character size, the font style (bold, italic, underlined), the global 32 * color and the text to display of course. It also provides convenience 33 * functions to calculate the graphical size of the text, or to get the global 34 * position of a given character. 35 * 36 * $(U Text) works in combination with the $(FONT_LINK) class, which loads and 37 * provides the glyphs (visual characters) of a given font. 38 * 39 * The separation of $(FONT_LINK) and $(U Text) allows more flexibility and 40 * better performances: indeed a $(FONT_LINK) is a heavy resource, and any 41 * operation on it is slow (often too slow for real-time applications). On the 42 * other side, a $(U Text) is a lightweight object which can combine the glyphs 43 * data and metrics of a $(FONT_LINK) to display any text on a render target. 44 * 45 * It is important to note that the $(U Text) instance doesn't copy the font 46 * that it uses, it only keeps a reference to it. Thus, a $(FONT_LINK) must not 47 * be destructed while it is used by a $(U Text). 48 * 49 * See also the note on coordinates and undistorted rendering in 50 * $(TRANSFORMABLE_LINK). 51 * 52 * example: 53 * --- 54 * // Declare and load a font 55 * auto font = new Font(); 56 * font.loadFromFile("arial.ttf"); 57 * 58 * // Create a text 59 * auto text = new Text("hello", font); 60 * text.setCharacterSize(30); 61 * text.setStyle(Text.Style.Bold); 62 * text.setColor(Color.Red); 63 * 64 * // Draw it 65 * window.draw(text); 66 * --- 67 * 68 * See_Also: 69 * $(FONT_LINK), $(TRANSFORMABLE_LINK) 70 */ 71 module dsfml.graphics.text; 72 73 import dsfml.graphics.font; 74 import dsfml.graphics.glyph; 75 import dsfml.graphics.color; 76 import dsfml.graphics.rect; 77 import dsfml.graphics.transformable; 78 import dsfml.graphics.drawable; 79 import dsfml.graphics.texture; 80 import dsfml.graphics.vertexarray; 81 import dsfml.graphics.vertex; 82 import dsfml.graphics.rendertarget; 83 import dsfml.graphics.renderstates; 84 import dsfml.graphics.primitivetype; 85 86 import dsfml.system.vector2; 87 88 import std.typecons: Rebindable; 89 90 /** 91 * Graphical text that can be drawn to a render target. 92 */ 93 class Text : Drawable, Transformable 94 { 95 /// Enumeration of the string drawing styles. 96 enum Style 97 { 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 } 107 108 mixin NormalTransformable; 109 110 private 111 { 112 dstring m_string; 113 Rebindable!(const(Font)) m_font; 114 uint m_characterSize; 115 Style m_style; 116 Color m_color; 117 VertexArray m_vertices; 118 FloatRect m_bounds; 119 120 //used for caching the font texture 121 uint lastSizeUsed = 0; 122 Rebindable!(const(Texture)) lastTextureUsed; 123 } 124 125 /** 126 * Default constructor 127 * 128 * Creates an empty text. 129 */ 130 this() 131 { 132 m_string = ""; 133 m_characterSize = 30; 134 m_style = Style.Regular; 135 m_color = Color(255,255,255); 136 m_vertices = new VertexArray(PrimitiveType.Quads,0); 137 m_bounds = FloatRect(); 138 } 139 140 /** 141 * Construct the text from a string, font and size 142 * 143 * Note that if the used font is a bitmap font, it is not scalable, thus not 144 * all requested sizes will be available to use. This needs to be taken into 145 * consideration when setting the character size. If you need to display 146 * text of a certain size, make sure the corresponding bitmap font that 147 * supports that size is used. 148 * 149 * Params: 150 * text = Text assigned to the string 151 * font = Font used to draw the string 152 * characterSize = Base size of characters, in pixels 153 */ 154 this(T)(immutable(T)[] text, const(Font) font, uint characterSize = 30) 155 if (is(T == dchar)||is(T == wchar)||is(T == char)) 156 { 157 import dsfml.system..string; 158 159 m_string = stringConvert!(T, dchar)(text); 160 m_characterSize = characterSize; 161 m_style = Style.Regular; 162 m_color = Color(255,255,255); 163 m_vertices = new VertexArray(PrimitiveType.Quads,0); 164 m_bounds = FloatRect(); 165 m_font = font; 166 updateGeometry(); 167 } 168 169 /// Destructor. 170 ~this() 171 { 172 import dsfml.system.config; 173 mixin(destructorOutput); 174 } 175 176 /** 177 * Get the character size. 178 * 179 * Returns: Size of the characters, in pixels. 180 */ 181 uint getCharacterSize() const 182 { 183 return m_characterSize; 184 } 185 186 /** 187 * Get the global color of the text. 188 * 189 * Returns: Global color of the text. 190 */ 191 Color getColor() const 192 { 193 return m_color; 194 } 195 196 /** 197 * Get thet text's font. 198 * 199 * If the text has no font attached, a NULL pointer is returned. 200 * The returned reference is const, which means that you cannot modify the 201 * font when you get it from this function. 202 * 203 * Returns: Text's font. 204 */ 205 const(Font) getFont() const 206 { 207 if(m_font is null) 208 { 209 return null; 210 } 211 else 212 { 213 return m_font; 214 } 215 } 216 217 /** 218 * Get the global bounding rectangle of the entity. 219 * 220 * The returned rectangle is in global coordinates, which means that it 221 * takes in account the transformations (translation, rotation, scale, ...) 222 * that are applied to the entity. In other words, this function returns the 223 * bounds of the sprite in the global 2D world's coordinate system. 224 * 225 * Returns: Global bounding rectangle of the entity. 226 */ 227 FloatRect getGlobalBounds() 228 { 229 return getTransform().transformRect(getLocalBounds()); 230 } 231 232 /** 233 * Get the local bounding rectangle of the entity. 234 * 235 * The returned rectangle is in local coordinates, which means that it 236 * ignores the transformations (translation, rotation, scale, ...) that are 237 * applied to the entity. In other words, this function returns the bounds 238 * of the entity in the entity's coordinate system. 239 * 240 * Returns: Local bounding rectangle of the entity. 241 */ 242 FloatRect getLocalBounds() const 243 { 244 return m_bounds; 245 } 246 247 //TODO: maybe a getString!dstring or getString!wstring etc template would be appropriate? 248 /** 249 * Get the text's string. 250 * 251 * The returned string is a dstring, a unicode type. 252 */ 253 immutable(T)[] getString(T=char)() const 254 if (is(T == dchar)||is(T == wchar)||is(T == char)) 255 { 256 import dsfml.system..string; 257 return stringConvert!(dchar, T)(m_string); 258 } 259 260 /** 261 * Get the text's style. 262 * 263 * Returns: Text's style. 264 */ 265 Style getStyle() const 266 { 267 return m_style; 268 } 269 270 /** 271 * Set the character size. 272 * 273 * The default size is 30. 274 * 275 * Params: 276 * size = New character size, in pixels. 277 */ 278 void setCharacterSize(uint size) 279 { 280 m_characterSize = size; 281 updateGeometry(); 282 } 283 284 /** 285 * Set the global color of the text. 286 * 287 * By default, the text's color is opaque white. 288 * 289 * Params: 290 * color = New color of the text. 291 */ 292 void setColor(Color color) 293 { 294 m_color = color; 295 updateGeometry(); 296 } 297 298 /** 299 * Set the text's font. 300 * 301 * The font argument refers to a font that must exist as long as the text 302 * uses it. Indeed, the text doesn't store its own copy of the font, but 303 * rather keeps a pointer to the one that you passed to this function. If 304 * the font is destroyed and the text tries to use it, the behaviour is 305 * undefined. 306 * 307 * Params: 308 * font = New font 309 */ 310 void setFont(const(Font) font) 311 { 312 m_font = font; 313 updateGeometry(); 314 } 315 316 /** 317 * Set the text's string. 318 * 319 * A text's string is empty by default. 320 * 321 * Params: 322 * text = New string 323 */ 324 void setString(T)(immutable(T)[] text) 325 if (is(T == dchar)||is(T == wchar)||is(T == char)) 326 { 327 import dsfml.system..string; 328 m_string = stringConvert!(T,dchar)(text); 329 updateGeometry(); 330 } 331 332 /** 333 * Set the text's style. 334 * 335 * You can pass a combination of one or more styles, for example 336 * Style.Bold | Text.Italic. 337 */ 338 void setStyle(Style style) 339 { 340 m_style = style; 341 updateGeometry(); 342 } 343 344 /** 345 * Draw the object to a render target. 346 * 347 * Params: 348 * renderTarget = Render target to draw to 349 * renderStates = Current render states 350 */ 351 void draw(RenderTarget renderTarget, RenderStates renderStates) 352 { 353 import std.stdio; 354 355 if ((m_font !is null) && (m_characterSize>0)) 356 { 357 renderStates.transform *= getTransform(); 358 359 //only call getTexture if the size has changed 360 if(m_characterSize != lastSizeUsed) 361 { 362 //update the size 363 lastSizeUsed = m_characterSize; 364 //grab the new texture 365 lastTextureUsed = m_font.getTexture(m_characterSize); 366 } 367 368 updateGeometry(); 369 370 renderStates.texture = m_font.getTexture(m_characterSize); 371 372 renderTarget.draw(m_vertices, renderStates); 373 } 374 } 375 376 /** 377 * Return the position of the index-th character. 378 * 379 * This function computes the visual position of a character from its index 380 * in the string. The returned position is in global coordinates 381 * (translation, rotation, scale and origin are applied). If index is out of 382 * range, the position of the end of the string is returned. 383 * 384 * Params: 385 * index = Index of the character 386 * 387 * Returns: Position of the character. 388 */ 389 Vector2f findCharacterPos(size_t index) 390 { 391 // Make sure that we have a valid font 392 if(m_font is null) 393 { 394 return Vector2f(0,0); 395 } 396 397 // Adjust the index if it's out of range 398 if(index > m_string.length) 399 { 400 index = m_string.length; 401 } 402 403 bool bold = (m_style & Style.Bold) != 0; 404 405 float hspace = cast(float)(m_font.getGlyph(' ', m_characterSize, bold).advance); 406 float vspace = cast(float)(m_font.getLineSpacing(m_characterSize)); 407 408 Vector2f position; 409 dchar prevChar = 0; 410 for (size_t i = 0; i < index; ++i) 411 { 412 dchar curChar = m_string[i]; 413 414 // Apply the kerning offset 415 position.x += cast(float)(m_font.getKerning(prevChar, curChar, m_characterSize)); 416 prevChar = curChar; 417 418 // Handle special characters 419 switch (curChar) 420 { 421 case ' ' : position.x += hspace; continue; 422 case '\t' : position.x += hspace * 4; continue; 423 case '\n' : position.y += vspace; position.x = 0; continue; 424 case '\v' : position.y += vspace * 4; continue; 425 default: 426 break; 427 } 428 429 // For regular characters, add the advance offset of the glyph 430 position.x += cast(float)(m_font.getGlyph(curChar, m_characterSize, bold).advance); 431 } 432 433 // Transform the position to global coordinates 434 position = getTransform().transformPoint(position); 435 436 return position; 437 } 438 439 private: 440 void updateGeometry() 441 { 442 import std.algorithm; 443 444 // Clear the previous geometry 445 m_vertices.clear(); 446 m_bounds = FloatRect(); 447 448 // No font: nothing to draw 449 if (m_font is null) 450 return; 451 452 // No text: nothing to draw 453 if (m_string.length == 0) 454 return; 455 // Compute values related to the text style 456 bool bold = (m_style & Style.Bold) != 0; 457 bool underlined = (m_style & Style.Underlined) != 0; 458 float italic = (m_style & Style.Italic) ? 0.208f : 0f; // 12 degrees 459 float underlineOffset = m_characterSize * 0.1f; 460 float underlineThickness = m_characterSize * (bold ? 0.1f : 0.07f); 461 462 // Precompute the variables needed by the algorithm 463 float hspace = cast(float)(m_font.getGlyph(' ', m_characterSize, bold).advance); 464 float vspace = cast(float)(m_font.getLineSpacing(m_characterSize)); 465 float x = 0f; 466 float y = cast(float)(m_characterSize); 467 468 // Create one quad for each character 469 float minX = m_characterSize, minY = m_characterSize, maxX = 0, maxY = 0; 470 dchar prevChar = 0; 471 for (size_t i = 0; i < m_string.length; ++i) 472 { 473 dchar curChar = m_string[i]; 474 475 476 // Apply the kerning offset 477 x += cast(float)(m_font.getKerning(prevChar, curChar, m_characterSize)); 478 479 prevChar = curChar; 480 481 // If we're using the underlined style and there's a new line, draw a line 482 if (underlined && (curChar == '\n')) 483 { 484 float top = y + underlineOffset; 485 float bottom = top + underlineThickness; 486 487 m_vertices.append(Vertex(Vector2f(0, top), m_color, Vector2f(1, 1))); 488 m_vertices.append(Vertex(Vector2f(x, top), m_color, Vector2f(1, 1))); 489 m_vertices.append(Vertex(Vector2f(x, bottom), m_color, Vector2f(1, 1))); 490 m_vertices.append(Vertex(Vector2f(0, bottom), m_color, Vector2f(1, 1))); 491 } 492 493 // Handle special characters 494 if ((curChar == ' ') || (curChar == '\t') || (curChar == '\n') || (curChar == '\v')) 495 { 496 // Update the current bounds (min coordinates) 497 minX = min(minX, x); 498 minY = min(minY, y); 499 500 final switch (curChar) 501 { 502 case ' ' : x += hspace; break; 503 case '\t' : x += hspace * 4; break; 504 case '\n' : y += vspace; x = 0; break; 505 case '\v' : y += vspace * 4; break; 506 } 507 508 // Update the current bounds (max coordinates) 509 maxX = max(maxX, x); 510 maxY = max(maxY, y); 511 512 // Next glyph, no need to create a quad for whitespace 513 continue; 514 } 515 516 517 518 // Extract the current glyph's description 519 Glyph glyph = m_font.getGlyph(curChar, m_characterSize, bold); 520 521 float left = glyph.bounds.left; 522 float top = glyph.bounds.top; 523 float right = glyph.bounds.left + glyph.bounds.width; 524 float bottom = glyph.bounds.top + glyph.bounds.height; 525 526 float u1 = cast(float)(glyph.textureRect.left); 527 float v1 = cast(float)(glyph.textureRect.top); 528 float u2 = cast(float)(glyph.textureRect.left + glyph.textureRect.width); 529 float v2 = cast(float)(glyph.textureRect.top + glyph.textureRect.height); 530 531 // Add a quad for the current character 532 m_vertices.append(Vertex(Vector2f(x + left - italic * top, y + top), m_color, Vector2f(u1, v1))); 533 m_vertices.append(Vertex(Vector2f(x + right - italic * top, y + top), m_color, Vector2f(u2, v1))); 534 m_vertices.append(Vertex(Vector2f(x + right - italic * bottom, y + bottom), m_color, Vector2f(u2, v2))); 535 m_vertices.append(Vertex(Vector2f(x + left - italic * bottom, y + bottom), m_color, Vector2f(u1, v2))); 536 537 // Update the current bounds 538 minX = min(minX, x + left - italic * bottom); 539 maxX = max(maxX, x + right - italic * top); 540 minY = min(minY, y + top); 541 maxY = max(maxY, y + bottom); 542 543 // Advance to the next character 544 x += glyph.advance; 545 } 546 547 // If we're using the underlined style, add the last line 548 if (underlined) 549 { 550 float top = y + underlineOffset; 551 float bottom = top + underlineThickness; 552 553 m_vertices.append(Vertex(Vector2f(0, top), m_color, Vector2f(1, 1))); 554 m_vertices.append(Vertex(Vector2f(x, top), m_color, Vector2f(1, 1))); 555 m_vertices.append(Vertex(Vector2f(x, bottom), m_color, Vector2f(1, 1))); 556 m_vertices.append(Vertex(Vector2f(0, bottom), m_color, Vector2f(1, 1))); 557 } 558 559 // Update the bounding rectangle 560 m_bounds.left = minX; 561 m_bounds.top = minY; 562 m_bounds.width = maxX - minX; 563 m_bounds.height = maxY - minY; 564 } 565 } 566 567 unittest 568 { 569 version(DSFML_Unittest_Graphics) 570 { 571 import std.stdio; 572 import dsfml.graphics.rendertexture; 573 574 writeln("Unit test for Text"); 575 576 auto renderTexture = new RenderTexture(); 577 578 renderTexture.create(100,100); 579 580 auto font = new Font(); 581 assert(font.loadFromFile("res/Warenhaus-Standard.ttf")); 582 583 Text text; 584 text = new Text("Sample String", font); 585 586 string temp = text.getString(); 587 588 writeln(temp); 589 590 renderTexture.clear(); 591 592 renderTexture.draw(text); 593 594 renderTexture.display(); 595 596 //grab that texture for usage 597 auto texture = renderTexture.getTexture(); 598 599 texture.copyToImage().saveToFile("Text.png"); 600 601 writeln(); 602 } 603 }