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