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