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