1 /* 2 ******************************************************************************************* 3 * Dgame (a D game framework) - Copyright (c) Randy Schütt 4 * 5 * This software is provided 'as-is', without any express or implied warranty. 6 * In no event will the authors be held liable for any damages arising from 7 * the use of this software. 8 * 9 * Permission is granted to anyone to use this software for any purpose, 10 * including commercial applications, and to alter it and redistribute it 11 * freely, subject to the following restrictions: 12 * 13 * 1. The origin of this software must not be misrepresented; you must not claim 14 * that you wrote the original software. If you use this software in a product, 15 * an acknowledgment in the product documentation would be appreciated but is 16 * not required. 17 * 18 * 2. Altered source versions must be plainly marked as such, and must not be 19 * misrepresented as being the original software. 20 * 21 * 3. This notice may not be removed or altered from any source distribution. 22 ******************************************************************************************* 23 */ 24 module Dgame.Graphics.Text; 25 26 private { 27 import std..string : format, toStringz; 28 29 import derelict.opengl3.gl; 30 import derelict.sdl2.sdl; // because of SDL_Surface and SDL_FreeSurface 31 import derelict.sdl2.ttf; 32 33 import Dgame.Internal.Log; 34 import Dgame.Graphics.Drawable; 35 import Dgame.Graphics.Transformable; 36 import Dgame.Graphics.Color; 37 import Dgame.Graphics.Font; 38 import Dgame.Graphics.Texture; 39 import Dgame.Graphics.Shape; 40 import Dgame.Graphics.Blend; 41 import Dgame.Math.Vector2; 42 import Dgame.Math.Rect; 43 import Dgame.System.VertexRenderer; 44 } 45 46 private Font*[] _FontFinalizer; 47 48 static ~this() { 49 debug Log.info("Text: Finalize Font"); 50 51 for (size_t i = 0; i < _FontFinalizer.length; ++i) { 52 if (_FontFinalizer[i] !is null) { 53 debug Log.info(" -> Finalize font i = %d, ptr = %x", i, _FontFinalizer[i].ptr); 54 _FontFinalizer[i].free(); 55 } 56 } 57 58 _FontFinalizer = null; 59 60 debug Log.info("Font finalized"); 61 } 62 63 /** 64 * Text defines a graphical 2D text, that can be drawn on screen. 65 * - The default foreground color is <code>Color.Black</code> 66 * - The default background color is <code>Color.White</code> 67 * 68 * Author: rschuett 69 */ 70 class Text : Transformable, Drawable, Blendable { 71 protected: 72 string _text; 73 bool _needUpdate; 74 75 Color _fg = Color.Black; 76 Color _bg = Color.White; 77 78 Font _font; 79 Texture _tex; 80 Blend _blend; 81 82 private: 83 void _storePixel(SDL_Surface* rhs, Texture.Format fmt) in { 84 assert(this._tex !is null, "No Texture!"); 85 assert(rhs !is null, "No Surface!"); 86 } body { 87 this._tex.loadFromMemory(rhs.pixels, 88 cast(ushort) rhs.w, cast(ushort) rhs.h, 89 rhs.format.BitsPerPixel, fmt); 90 } 91 92 void _update() in { 93 assert(this._tex !is null, "No Texture!"); 94 } body { 95 this._needUpdate = false; 96 97 SDL_Surface* srfc; 98 scope(exit) SDL_FreeSurface(srfc); 99 100 immutable char* cstr = toStringz(this._text); 101 102 SDL_Color fg = void; 103 SDL_Color bg = void; 104 105 this._fg.transferTo(&fg); 106 this._bg.transferTo(&bg); 107 108 const Font.Mode fmode = this._font.getMode(); 109 110 final switch (fmode) { 111 case Font.Mode.Solid: 112 srfc = TTF_RenderUTF8_Solid(this._font.ptr, cstr, fg); 113 break; 114 case Font.Mode.Shaded: 115 srfc = TTF_RenderUTF8_Shaded(this._font.ptr, cstr, fg, bg); 116 break; 117 case Font.Mode.Blended: 118 srfc = TTF_RenderUTF8_Blended(this._font.ptr, cstr, fg); 119 break; 120 } 121 122 enforce(srfc !is null, "Surface is null."); 123 124 if (fmode != Font.Mode.Blended) { 125 /// Adapt PixelFormat 126 SDL_PixelFormat fmt; 127 fmt.BitsPerPixel = 24; 128 129 SDL_Surface* opt = SDL_ConvertSurface(srfc, &fmt, 0); 130 scope(exit) SDL_FreeSurface(opt); 131 132 enforce(opt !is null, "Optimized is null."); 133 enforce(opt.pixels !is null, "Optimized pixels is null."); 134 135 Texture.Format t_fmt = Texture.Format.None; 136 if (opt.format.Rmask != 0x000000ff) 137 t_fmt = opt.format.BitsPerPixel == 24 ? Texture.Format.BGR : Texture.Format.BGRA; 138 139 this._storePixel(opt, t_fmt); 140 } else { 141 Texture.Format t_fmt = Texture.Format.None; 142 if (srfc.format.Rmask != 0x000000ff) 143 t_fmt = srfc.format.BitsPerPixel == 24 ? Texture.Format.BGR : Texture.Format.BGRA; 144 145 this._storePixel(srfc, t_fmt); 146 } 147 } 148 149 protected: 150 void _render() in { 151 assert(this._tex !is null, "No valid Texture."); 152 } body { 153 glPushMatrix(); 154 scope(exit) glPopMatrix(); 155 156 super._applyTranslation(); 157 158 if (this._needUpdate) 159 this._update(); 160 161 if (!glIsEnabled(GL_TEXTURE_2D)) 162 glEnable(GL_TEXTURE_2D); 163 164 glPushAttrib(GL_CURRENT_BIT | GL_COLOR_BUFFER_BIT); 165 scope(exit) glPopAttrib(); 166 167 float dx = 0f; 168 float dy = 0f; 169 float dw = this._tex.width; 170 float dh = this._tex.height; 171 172 float[12] vertices = [ 173 dx, dy, 0f, 174 dx + dw, dy, 0f, 175 dx + dw, dy + dh, 0f, 176 dx, dy + dh, 0f 177 ]; 178 179 float[8] texCoords = [0f, 0f, 1f, 0f, 1f, 1f, 0f, 1f]; 180 181 VertexRenderer.pointTo(Target.Vertex, &vertices[0]); 182 VertexRenderer.pointTo(Target.TexCoords, &texCoords[0]); 183 184 scope(exit) { 185 VertexRenderer.disableAllStates(); 186 this._tex.unbind(); 187 } 188 189 this._tex.bind(); 190 191 if (this._blend !is null) 192 this._blend.applyBlending(); 193 194 VertexRenderer.drawArrays(Shape.Type.TriangleFan, vertices.length); 195 } 196 197 public: 198 /** 199 * CTor 200 */ 201 this(ref Font font, string text = null) { 202 this.replaceFont(font); 203 204 this._text = text; 205 this._needUpdate = true; 206 this._tex = new Texture(); 207 } 208 209 /** 210 * CTor: Rvalue version 211 */ 212 this(Font font, string text = null) { 213 this(font, text); 214 } 215 216 /** 217 * Calculate, store and return the center point. 218 */ 219 override ref const(Vector2s) calculateCenter() pure nothrow { 220 if (this._tex is null) 221 return super.getCenter(); 222 223 super.setCenter(this._tex.width / 2, this._tex.height / 2); 224 225 return super.getCenter(); 226 } 227 228 /** 229 * Check whether the bounding box of this Text collide 230 * with the bounding box of another Text 231 */ 232 bool collideWith(const Text rhs) const { 233 return this.collideWith(rhs.getBoundingBox()); 234 } 235 236 /** 237 * Check whether the bounding box of this Sprite collide 238 * with the given Rect 239 */ 240 bool collideWith(ref const FloatRect rect) const { 241 return this.getBoundingBox().intersects(rect); 242 } 243 244 /** 245 * Rvalue version 246 */ 247 bool collideWith(const FloatRect rect) const { 248 return this.collideWith(rect); 249 } 250 251 final: 252 /** 253 * Set (or reset) the current Blend instance. 254 */ 255 void setBlend(Blend blend) pure nothrow { 256 this._blend = blend; 257 } 258 259 /** 260 * Returns the current Blend instance, or null. 261 */ 262 inout(Blend) getBlend() inout pure nothrow { 263 return this._blend; 264 } 265 266 /** 267 * Returns the bounding box, the area which will be drawn on the screen. 268 */ 269 FloatRect getBoundingBox() const pure nothrow in { 270 assert(this._tex !is null); 271 } body { 272 return FloatRect(super.getPosition(), this._tex.width, this._tex.height); 273 } 274 275 /** 276 * Returns the width of the Text Texture 277 */ 278 @property 279 ushort width() const pure nothrow { 280 return this._tex !is null ? this._tex.width : 0; 281 } 282 283 /** 284 * Returns the height of the Text Texture 285 */ 286 @property 287 ushort height() const pure nothrow { 288 return this._tex !is null ? this._tex.height : 0; 289 } 290 291 /** 292 * Replace the current Font. 293 */ 294 void replaceFont(ref Font font) { 295 this._font = font; 296 _FontFinalizer ~= &this._font; 297 298 this._needUpdate = true; 299 } 300 301 /** 302 * Rvalue version 303 */ 304 void replaceFont(Font font) { 305 this.replaceFont(font); 306 } 307 308 /** 309 * Get the image containing the rendered characters. 310 */ 311 inout(Texture) getTexture() inout pure nothrow { 312 return this._tex; 313 } 314 315 /** 316 * Returns the current Font object. 317 */ 318 ref inout(Font) getFont() inout pure nothrow { 319 return this._font; 320 } 321 322 /** 323 * Activate an update. 324 * The current image will be updated. 325 * In most cases, this happens automatically, 326 * but sometimes it is usefull. 327 */ 328 void forceUpdate() pure nothrow { 329 this._needUpdate = true; 330 } 331 332 /** 333 * Format a given string and draw it then on the image 334 */ 335 void format(Args...)(string text, Args args) { 336 string formated = .format(text, args); 337 338 if (formated != this._text) { 339 this._text = formated; 340 this._needUpdate = true; 341 } 342 } 343 344 /** 345 * Replace the current string. 346 * 347 * Examples: 348 * --- 349 * Font fnt = new Font("samples/font/arial.ttf", 12); 350 * Text t = new Text(font); 351 * t("My new string"); 352 * --- 353 */ 354 void opCall(string text) pure nothrow { 355 if (text != this._text) { 356 this._text = text; 357 this._needUpdate = true; 358 } 359 } 360 361 /** 362 * Replace the current string. 363 * 364 * Examples: 365 * --- 366 * Font fnt = new Font("samples/font/arial.ttf", 12); 367 * Text t1 = new Text(font); 368 * Text t2 = new Text(font); 369 * t1("My new string"); 370 * t2(t1); // now both t's draw 'My new string' on screen. 371 * --- 372 */ 373 void opCall(ref const Text t) pure nothrow { 374 return this.opCall(t.getString()); 375 } 376 377 /** 378 * Concatenate the current string with another. 379 * 380 * Examples: 381 * --- 382 * Font fnt = new Font("samples/font/arial.ttf", 12); 383 * Text t = new Text(font); 384 * t("My new string"); 385 * t ~= "is great!"; // t draws now 'My new string is great' on screen. 386 * --- 387 * The example above is the same as if you do: 388 * --- 389 * t += "is great!"; 390 * --- 391 * Both operators (~ and +) are allowed. 392 */ 393 ref Text opBinary(string op)(string text) pure nothrow 394 if (op == "~" || op == "+") 395 { 396 this._text ~= text; 397 this._needUpdate = true; 398 399 return this; 400 } 401 402 /** 403 * Concatenate the current string with another. 404 */ 405 ref Text opBinary(string op)(ref const Text t) pure nothrow 406 if (op == "~" || op == "+") 407 { 408 return this.opBinary!(op)(t.getString()); 409 } 410 411 /** 412 * Returns the current string. 413 */ 414 string getString() const pure nothrow { 415 return this._text; 416 } 417 418 /** 419 * Set the (foreground) color. 420 */ 421 void setColor(ref const Color col) { 422 this._needUpdate = true; 423 this._fg = col; 424 } 425 426 /** 427 * Rvalue version 428 */ 429 void setColor(const Color col) { 430 this.setColor(col); 431 } 432 433 /** 434 * Returns the (foreground) color. 435 */ 436 ref const(Color) getColor() const pure nothrow { 437 return this._fg; 438 } 439 440 /** 441 * Set the background color. 442 * Only needed if your Font.Mode is not Font.Mode.Solid. 443 */ 444 void setBackgroundColor(ref const Color col) { 445 this._needUpdate = true; 446 this._bg = col; 447 } 448 449 /** 450 * Rvalue version 451 */ 452 void setBackgroundColor(const Color col) { 453 this.setBackgroundColor(col); 454 } 455 456 /** 457 * Returns the background color. 458 */ 459 ref const(Color) getBackgroundColor() const pure nothrow { 460 return this._bg; 461 } 462 }