1 /* 2 Copyright © 2020, Luna Nielsen 3 Distributed under the 2-Clause BSD License, see LICENSE file. 4 5 Authors: Luna Nielsen 6 */ 7 module engine.render.texture.font; 8 import bindbc.freetype; 9 import std.exception; 10 import std.conv : text; 11 import std.string; 12 import std.format; 13 import engine; 14 15 private { 16 FT_Library lib; 17 Shader fontShader; 18 Camera2D fontCamera; 19 20 // VAO and VP can be reused 21 GLuint vao; 22 GLint vp; 23 24 /// How many entries in a Font batch 25 enum EntryCount = 10_000; 26 27 // Various variables that make it easier to reference sizes 28 enum VecSize = 2; 29 enum UVSize = 2; 30 enum ColorSize = 4; 31 enum VertsCount = 6; 32 enum DataLength = VecSize+UVSize+ColorSize; 33 enum DataSize = DataLength*VertsCount; 34 35 vec2 transformVerts(vec2 position, mat4 matrix) { 36 return vec2(matrix*vec4(position.x, position.y, 0, 1)); 37 } 38 39 vec2 transformVertsR(vec2 position, mat4 matrix) { 40 return vec2(vec4(position.x, position.y, 0, 1)*matrix); 41 } 42 } 43 44 /** 45 The game's public font 46 */ 47 Font GameFont; 48 49 /** 50 Initialize the font subsystem 51 */ 52 void initFontSystem() { 53 int err = FT_Init_FreeType(&lib); 54 if (err != FT_Err_Ok) { 55 AppLog.fatal("Font System", "FreeType returned error %s", err); 56 } 57 58 // Set up batching 59 glGenVertexArrays(1, &vao); 60 61 fontCamera = new Camera2D(); 62 fontShader = new Shader(import("shaders/font.vert"), import("shaders/font.frag")); 63 vp = fontShader.getUniformLocation("vp"); 64 65 // The game's font 66 GameFont = new Font(cast(ubyte[])import("fonts/KosugiMaru.ttf"), 24); 67 } 68 69 /** 70 A font 71 */ 72 class Font { 73 private: 74 // 75 // Glyph managment 76 // 77 struct Glyph { 78 vec4i area; 79 vec2 advance; 80 vec2 bearing; 81 } 82 83 struct GlyphIndex { 84 dchar c; 85 int size; 86 } 87 88 vec2 metrics; 89 90 ubyte[] faceMemory; 91 92 FT_Face fontFace; 93 TexturePacker fontPacker; 94 Texture fontTexture; 95 Glyph[GlyphIndex] glyphs; 96 int size; 97 98 // Generates glyph for the specified character 99 bool genGlyphFor(dchar c) { 100 101 // Find the character's index in the font 102 uint index = FT_Get_Char_Index(fontFace, c); 103 104 // Load it if possible, otherwise tell the outside code 105 FT_Load_Glyph(fontFace, index, FT_LOAD_RENDER); 106 if (fontFace.glyph is null) return false; 107 108 // Get width/height of character 109 immutable(uint) width = fontFace.glyph.bitmap.width; 110 immutable(uint) height = fontFace.glyph.bitmap.rows; 111 112 // Get length of pixel data, then get it in to a slice 113 size_t dataLength = fontFace.glyph.bitmap.pitch*fontFace.glyph.bitmap.rows; 114 ubyte[] pixData = fontFace.glyph.bitmap.buffer[0..dataLength]; 115 116 // Find space in the texture and pack the glyph in there 117 vec4i area = fontPacker.packTexture(vec2i(width, height)); 118 fontTexture.setDataRegion(pixData, area.x, area.y, width, height); 119 120 // Add glyph to listing 121 glyphs[GlyphIndex(c, size)] = Glyph( 122 // Area (in texture) 123 area, 124 125 // Advance 126 vec2(fontFace.glyph.advance.x >> 6, fontFace.glyph.advance.y >> 6), 127 128 // Bearing 129 vec2(fontFace.glyph.bitmap_left, fontFace.glyph.bitmap_top) 130 ); 131 return true; 132 } 133 134 // 135 // Font Batching 136 // 137 float[DataSize*EntryCount] data; 138 size_t dataOffset; 139 size_t tris; 140 141 GLuint buffer; 142 143 void addVertexData(vec2 position, vec2 uvs, vec4 color) { 144 data[dataOffset..dataOffset+DataLength] = [position.x, position.y, uvs.x, uvs.y, color.x, color.y, color.z, color.w]; 145 dataOffset += DataLength; 146 } 147 148 void init_(int canvasSize) { 149 150 // Select unicode so we can render i18n text 151 FT_Select_Charmap(fontFace, FT_ENCODING_UNICODE); 152 153 // Create the texture 154 fontTexture = new Texture(canvasSize, canvasSize, GL_RED, 1); 155 fontPacker = new TexturePacker(vec2i(canvasSize, canvasSize)); 156 157 glBindVertexArray(vao); 158 glGenBuffers(1, &buffer); 159 glBindBuffer(GL_ARRAY_BUFFER, buffer); 160 glBufferData(GL_ARRAY_BUFFER, float.sizeof*data.length, data.ptr, GL_DYNAMIC_DRAW); 161 } 162 163 public: 164 165 /** 166 Gets the font metrics 167 */ 168 vec2 getMetrics() { 169 return metrics; 170 } 171 172 /** 173 Destroys the font 174 */ 175 ~this() { 176 destroy(fontTexture); 177 FT_Done_Face(fontFace); 178 glDeleteBuffers(1, &buffer); 179 180 // Destroy face memory if we loaded it from memory 181 if (faceMemory !is null) { 182 destroy(faceMemory); 183 } 184 } 185 186 /** 187 Constructs a new font 188 189 canvasSize specifies how big the texture for the font will be. 190 */ 191 this(string file, int size, int canvasSize = 4096) { 192 int err = FT_New_Face(lib, file.toStringz, 0, &fontFace); 193 194 enforce(err != FT_Err_Unknown_File_Format, "Unknown file format for %s".format(file)); 195 enforce(err == FT_Err_Ok, "Error %s while loading font file".format(err)); 196 197 // Change size of text 198 this.changeSize(size); 199 200 // Initializes the texture 201 this.init_(canvasSize); 202 } 203 204 /** 205 Constructs a new font 206 207 canvasSize specifies how big the texture for the font will be. 208 */ 209 this(ubyte[] memFace, int size, int canvasSize = 4096) { 210 211 // Copy data from memFace 212 import std.algorithm.mutation : copy; 213 faceMemory = new ubyte[memFace.length]; 214 copy(memFace, faceMemory); 215 216 AppLog.info("FontLoader", "Loading font from memory with size %s", memFace.length); 217 int err = FT_New_Memory_Face(lib, faceMemory.ptr, faceMemory.length, 0, &fontFace); 218 219 enforce(err != FT_Err_Unknown_File_Format, "Unknown file format"); 220 enforce(err == FT_Err_Ok, "Error %s while loading font file".format(err)); 221 222 // Change size of text 223 this.changeSize(size); 224 225 // Initializes the texture 226 this.init_(canvasSize); 227 } 228 229 230 // 231 // Glyph managment 232 // 233 234 /** 235 Changes size of font 236 */ 237 final void changeSize(int size) { 238 239 // Don't try to change size when it's the same 240 if (size == this.size) return; 241 242 // Set the size of the font 243 FT_Set_Pixel_Sizes(fontFace, 0, size); 244 metrics = vec2(size, fontFace.size.metrics.height >> 6); 245 this.size = size; 246 } 247 248 /** 249 Gets the advance of a glyph 250 */ 251 vec2 advance(dchar glyph) { 252 253 // Newline is special 254 if (glyph == '\n') return vec2(0); 255 256 auto idx = GlyphIndex(glyph, size); 257 258 // Make sure glyph is present 259 if (idx !in glyphs) enforce(genGlyphFor(glyph), "Could not find glyph for character %s".format(glyph)); 260 261 // Return the advance of the glyphs 262 return glyphs[idx].advance; 263 } 264 265 /** 266 Measure size (width, height) of rectangle needed to contain the specified text 267 */ 268 vec2 measure(dstring text) { 269 270 int lines = 1; 271 float curLineLen = 0; 272 vec2 size = vec2(0); 273 274 foreach(dchar c; text) { 275 if (c == '\n') { 276 lines++; 277 curLineLen = 0; 278 continue; 279 } 280 281 auto idx = GlyphIndex(c, this.size); 282 283 // Try to generate glyph if not present 284 if (idx !in glyphs) { 285 genGlyphFor(c); 286 287 // At this point if the glyph does not exist, skip it 288 if (idx !in glyphs) continue; 289 } 290 291 // Bitshift the X advance to make it be in pixels. 292 curLineLen += glyphs[idx].advance.x; 293 294 // Update the bounding box if the length extends 295 if (curLineLen > size.x) size.x = curLineLen; 296 } 297 size.y = metrics.y*lines; 298 return size; 299 } 300 301 302 // 303 // Font Batching 304 // 305 306 /** 307 Basic string draw function 308 */ 309 void draw(dstring text, vec2 position, vec4 color=vec4(1)) { 310 vec2 next = position; 311 size_t line; 312 313 foreach(dchar c; text) { 314 315 // Skip newline 316 if (c == '\n') { 317 line++; 318 next.x = position.x; 319 next.y += metrics.y; 320 continue; 321 } 322 323 auto idx = GlyphIndex(c, size); 324 325 // Load character if neccesary 326 if (idx !in glyphs) { 327 genGlyphFor(c); 328 329 // At this point if the glyph does not exist, skip it 330 if (idx !in glyphs) continue; 331 } 332 333 draw(c, next, vec2(0), 0, color); 334 next.x += glyphs[idx].advance.x; 335 } 336 } 337 338 /** 339 draws a character 340 */ 341 void draw(dchar c, vec2 position, vec2 origin=vec2(0), float rotation=0, vec4 color=vec4(1)) { 342 343 auto idx = GlyphIndex(c, size); 344 345 // Load character if neccesary 346 if (idx !in glyphs) { 347 genGlyphFor(c); 348 349 // At this point if the glyph does not exist, skip it 350 if (idx !in glyphs) return; 351 } 352 353 // Flush if neccesary 354 if (dataOffset == DataSize*EntryCount) flush(); 355 vec4 area = glyphs[idx].area; 356 vec2 bearing = glyphs[idx].bearing; 357 358 vec2 pos = vec2( 359 position.x + bearing.x, 360 (position.y - bearing.y)+metrics.y 361 ); 362 363 mat4 transform = 364 mat4.translation(-origin.x, -origin.y, 0) * 365 mat4.translation(pos.x, pos.y, 0) * 366 mat4.translation(origin.x, origin.y, 0) * 367 mat4.zrotation(rotation) * 368 mat4.translation(-origin.x, -origin.y, 0) * 369 mat4.scaling(area.z, area.w, 0); 370 371 372 vec4 uvArea = vec4( 373 (area.x)+0.25, 374 (area.y)+0.25, 375 (area.x+area.z)-0.25, 376 (area.y+area.w)-0.25, 377 ); 378 379 // Triangle 1 380 addVertexData(vec2(0, 1).transformVerts(transform), vec2(uvArea.x, uvArea.w), color); 381 addVertexData(vec2(1, 0).transformVerts(transform), vec2(uvArea.z, uvArea.y), color); 382 addVertexData(vec2(0, 0).transformVerts(transform), vec2(uvArea.x, uvArea.y), color); 383 384 // Triangle 2 385 addVertexData(vec2(0, 1).transformVerts(transform), vec2(uvArea.x, uvArea.w), color); 386 addVertexData(vec2(1, 1).transformVerts(transform), vec2(uvArea.z, uvArea.w), color); 387 addVertexData(vec2(1, 0).transformVerts(transform), vec2(uvArea.z, uvArea.y), color); 388 389 tris += 2; 390 } 391 392 /** 393 Flush font rendering 394 */ 395 void flush() { 396 397 // Disable depth testing for the batcher 398 glDisable(GL_DEPTH_TEST); 399 // Bind VAO 400 glBindVertexArray(vao); 401 402 // Bind just in case some shennanigans happen 403 glBindBuffer(GL_ARRAY_BUFFER, buffer); 404 405 // Update with this draw round's data 406 glBufferSubData(GL_ARRAY_BUFFER, 0, dataOffset*float.sizeof, data.ptr); 407 408 // Bind the texture 409 fontTexture.bind(); 410 411 // Use our sprite batcher shader 412 fontShader.use(); 413 fontShader.setUniform(vp, fontCamera.matrix); 414 415 // Vertex Buffer 416 glEnableVertexAttribArray(0); 417 glVertexAttribPointer( 418 0, 419 VecSize, 420 GL_FLOAT, 421 GL_FALSE, 422 DataLength*GLfloat.sizeof, 423 null, 424 ); 425 426 // UV Buffer 427 glEnableVertexAttribArray(1); 428 glVertexAttribPointer( 429 1, 430 UVSize, 431 GL_FLOAT, 432 GL_FALSE, 433 DataLength*GLfloat.sizeof, 434 cast(GLvoid*)(UVSize*GLfloat.sizeof), 435 ); 436 437 // UV Buffer 438 glEnableVertexAttribArray(2); 439 glVertexAttribPointer( 440 2, 441 ColorSize, 442 GL_FLOAT, 443 GL_FALSE, 444 DataLength*GLfloat.sizeof, 445 cast(GLvoid*)((VecSize+UVSize)*GLfloat.sizeof), 446 ); 447 448 // Draw the triangles 449 glDrawArrays(GL_TRIANGLES, 0, cast(int)(tris*3)); 450 451 // Reset the batcher's state 452 glDisableVertexAttribArray(0); 453 glDisableVertexAttribArray(1); 454 glDisableVertexAttribArray(2); 455 dataOffset = 0; 456 tris = 0; 457 458 // Re-enable depth test Clear depth buffer 459 glEnable(GL_DEPTH_TEST); 460 } 461 462 }