1 /* 2 Copyright (c) 2017-2018 Timur Gafarov 3 4 Boost Software License - Version 1.0 - August 17th, 2003 5 Permission is hereby granted, free of charge, to any person or organization 6 obtaining a copy of the software and accompanying documentation covered by 7 this license (the "Software") to use, reproduce, display, distribute, 8 execute, and transmit the Software, and to prepare derivative works of the 9 Software, and to permit third-parties to whom the Software is furnished to 10 do so, all subject to the following: 11 12 The copyright notices in the Software and this entire statement, including 13 the above license grant, this restriction and the following disclaimer, 14 must be included in all copies of the Software, in whole or in part, and 15 all derivative works of the Software, unless such copies or derivative 16 works are solely in the form of machine-executable object code generated by 17 a source language processor. 18 19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 DEALINGS IN THE SOFTWARE. 26 */ 27 28 module dagon.ui.ftfont; 29 30 import std.stdio; 31 32 import std.string; 33 import std.ascii; 34 import std.utf; 35 import std.file; 36 37 import dlib.core.memory; 38 import dlib.core.stream; 39 import dlib.container.dict; 40 import dlib.text.utf8; 41 import dlib.math.vector; 42 import dlib.image.color; 43 44 import dagon.core.ownership; 45 import dagon.ui.font; 46 import dagon.graphics.rc; 47 import dagon.graphics.shaderloader; 48 49 version(NoFreetype) 50 { 51 pragma(msg, "Warning: Dagon is compiled without Freetype support, dagon.ui.ftfont is not available"); 52 } 53 else 54 { 55 version = EnableFreetype; 56 } 57 58 version(EnableFreetype): 59 60 import dagon.core.libs; 61 62 struct Glyph 63 { 64 bool valid; 65 GLuint textureId = 0; 66 FT_Glyph ftGlyph = null; 67 int width = 0; 68 int height = 0; 69 FT_Pos advanceX = 0; 70 } 71 72 int nextPowerOfTwo(int a) 73 { 74 int rval = 1; 75 while(rval < a) 76 rval <<= 1; 77 return rval; 78 } 79 80 final class FreeTypeFont: Font 81 { 82 FT_Face ftFace; 83 FT_Library ftLibrary; 84 Dict!(Glyph, dchar) glyphs; 85 86 Vector2f[4] vertices; 87 Vector2f[4] texcoords; 88 uint[3][2] indices; 89 90 GLuint vao = 0; 91 GLuint vbo = 0; 92 GLuint tbo = 0; 93 GLuint eao = 0; 94 95 bool canRender = false; 96 97 GLuint shaderProgram; 98 99 GLint modelViewMatrixLoc; 100 GLint projectionMatrixLoc; 101 102 GLint glyphPositionLoc; 103 GLint glyphScaleLoc; 104 GLint glyphTexcoordScaleLoc; 105 106 GLint glyphTextureLoc; 107 GLint glyphColorLoc; 108 109 string vs = import("Text.vs"); 110 string fs = import("Text.fs"); 111 112 this(uint height, Owner o) 113 { 114 super(o); 115 this.height = height; 116 117 if (FT_Init_FreeType(&ftLibrary)) 118 throw new Exception("FT_Init_FreeType failed"); 119 120 vertices[0] = Vector2f(0, 1); 121 vertices[1] = Vector2f(0, 0); 122 vertices[2] = Vector2f(1, 0); 123 vertices[3] = Vector2f(1, 1); 124 125 texcoords[0] = Vector2f(0, 1); 126 texcoords[1] = Vector2f(0, 0); 127 texcoords[2] = Vector2f(1, 0); 128 texcoords[3] = Vector2f(1, 1); 129 130 indices[0][0] = 0; 131 indices[0][1] = 1; 132 indices[0][2] = 2; 133 134 indices[1][0] = 0; 135 indices[1][1] = 2; 136 indices[1][2] = 3; 137 } 138 139 void createFromFile(string filename) 140 { 141 if (!exists(filename)) 142 throw new Exception("Cannot find font file " ~ filename); 143 144 if (FT_New_Face(ftLibrary, toStringz(filename), 0, &ftFace)) 145 throw new Exception("FT_New_Face failed (there is probably a problem with your font file)"); 146 147 FT_Set_Char_Size(ftFace, cast(int)height << 6, cast(int)height << 6, 96, 96); 148 glyphs = New!(Dict!(Glyph, dchar)); 149 } 150 151 void createFromMemory(ubyte[] buffer) 152 { 153 if (FT_New_Memory_Face(ftLibrary, buffer.ptr, cast(uint)buffer.length, 0, &ftFace)) 154 throw new Exception("FT_New_Face failed (there is probably a problem with your font file)"); 155 156 FT_Set_Char_Size(ftFace, cast(int)height << 6, cast(int)height << 6, 96, 96); 157 glyphs = New!(Dict!(Glyph, dchar)); 158 } 159 160 void prepareVAO() 161 { 162 if (canRender) 163 return; 164 165 glGenBuffers(1, &vbo); 166 glBindBuffer(GL_ARRAY_BUFFER, vbo); 167 glBufferData(GL_ARRAY_BUFFER, vertices.length * float.sizeof * 2, vertices.ptr, GL_STATIC_DRAW); 168 169 glGenBuffers(1, &tbo); 170 glBindBuffer(GL_ARRAY_BUFFER, tbo); 171 glBufferData(GL_ARRAY_BUFFER, texcoords.length * float.sizeof * 2, texcoords.ptr, GL_STATIC_DRAW); 172 173 glGenBuffers(1, &eao); 174 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eao); 175 glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.length * uint.sizeof * 3, indices.ptr, GL_STATIC_DRAW); 176 177 glGenVertexArrays(1, &vao); 178 glBindVertexArray(vao); 179 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eao); 180 181 glEnableVertexAttribArray(0); 182 glBindBuffer(GL_ARRAY_BUFFER, vbo); 183 glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, null); 184 185 glEnableVertexAttribArray(1); 186 glBindBuffer(GL_ARRAY_BUFFER, tbo); 187 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, null); 188 189 //glBindBuffer(GL_ARRAY_BUFFER, 0); 190 //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); 191 192 glBindVertexArray(0); 193 194 GLuint vert = compileShader(vs, ShaderStage.vertex); 195 GLuint frag = compileShader(fs, ShaderStage.fragment); 196 if (vert != 0 && frag != 0) 197 shaderProgram = linkShaders(vert, frag); 198 199 if (shaderProgram != 0) 200 { 201 modelViewMatrixLoc = glGetUniformLocation(shaderProgram, "modelViewMatrix"); 202 projectionMatrixLoc = glGetUniformLocation(shaderProgram, "projectionMatrix"); 203 204 glyphPositionLoc = glGetUniformLocation(shaderProgram, "glyphPosition"); 205 glyphScaleLoc = glGetUniformLocation(shaderProgram, "glyphScale"); 206 glyphTexcoordScaleLoc = glGetUniformLocation(shaderProgram, "glyphTexcoordScale"); 207 glyphTextureLoc = glGetUniformLocation(shaderProgram, "glyphTexture"); 208 glyphColorLoc = glGetUniformLocation(shaderProgram, "glyphColor"); 209 } 210 211 canRender = true; 212 } 213 214 void preloadASCII() 215 { 216 enum ASCII_CHARS = 128; 217 foreach(i; 0..ASCII_CHARS) 218 { 219 GLuint tex; 220 glGenTextures(1, &tex); 221 loadGlyph(i, tex); 222 } 223 } 224 225 ~this() 226 { 227 if (canRender) 228 { 229 glDeleteVertexArrays(1, &vao); 230 glDeleteBuffers(1, &vbo); 231 glDeleteBuffers(1, &tbo); 232 glDeleteBuffers(1, &eao); 233 } 234 235 foreach(i, glyph; glyphs) 236 glDeleteTextures(1, &glyph.textureId); 237 Delete(glyphs); 238 } 239 240 uint loadGlyph(dchar code, GLuint texId) 241 { 242 FT_Glyph glyph; 243 244 uint charIndex = FT_Get_Char_Index(ftFace, code); 245 246 if (charIndex == 0) 247 { 248 //TODO: if character wasn't found in font file 249 } 250 251 auto res = FT_Load_Glyph(ftFace, charIndex, FT_LOAD_DEFAULT); 252 253 if (res) 254 throw new Exception(format("FT_Load_Glyph failed with code %s", res)); 255 256 if (FT_Get_Glyph(ftFace.glyph, &glyph)) 257 throw new Exception("FT_Get_Glyph failed"); 258 259 FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, null, 1); 260 FT_BitmapGlyph bitmapGlyph = cast(FT_BitmapGlyph)glyph; 261 262 FT_Bitmap bitmap = bitmapGlyph.bitmap; 263 264 int width = nextPowerOfTwo(bitmap.width); 265 int height = nextPowerOfTwo(bitmap.rows); 266 267 GLubyte[] img = New!(GLubyte[])(2 * width * height); 268 269 foreach(j; 0..height) 270 foreach(i; 0..width) 271 { 272 img[2 * (i + j * width)] = 255; 273 img[2 * (i + j * width) + 1] = 274 (i >= bitmap.width || j >= bitmap.rows)? 275 0 : bitmap.buffer[i + bitmap.width * j]; 276 } 277 278 glBindTexture(GL_TEXTURE_2D, texId); 279 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 280 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 281 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 282 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 283 284 glTexImage2D(GL_TEXTURE_2D, 285 0, GL_RG8, width, height, 286 0, GL_RG, GL_UNSIGNED_BYTE, img.ptr); 287 288 Delete(img); 289 290 Glyph g = Glyph(true, texId, glyph, width, height, ftFace.glyph.advance.x); 291 glyphs[code] = g; 292 293 return charIndex; 294 } 295 296 dchar loadChar(dchar code) 297 { 298 GLuint tex; 299 glGenTextures(1, &tex); 300 loadGlyph(code, tex); 301 return code; 302 } 303 304 float renderGlyph(dchar code, float shift) 305 { 306 Glyph glyph; 307 if (code in glyphs) 308 glyph = glyphs[code]; 309 else 310 glyph = glyphs[loadChar(code)]; 311 312 //if (!glyph.valid) 313 // return 0.0f; 314 315 FT_BitmapGlyph bitmapGlyph = cast(FT_BitmapGlyph)(glyph.ftGlyph); 316 FT_Bitmap bitmap = bitmapGlyph.bitmap; 317 318 glBindTexture(GL_TEXTURE_2D, glyph.textureId); 319 glUniform1i(glyphTextureLoc, 0); 320 321 float chWidth = cast(float)bitmap.width; 322 float chHeight = cast(float)bitmap.rows; 323 float texWidth = cast(float)glyph.width; 324 float texHeight = cast(float)glyph.height; 325 326 float x = 0.5f / texWidth + chWidth / texWidth; 327 float y = 0.5f / texHeight + chHeight / texHeight; 328 329 Vector2f glyphPosition = Vector2f(shift + bitmapGlyph.left, -bitmapGlyph.top); //-(bitmapGlyph.top - bitmap.rows)) 330 Vector2f glyphScale = Vector2f(bitmap.width, bitmap.rows); 331 Vector2f glyphTexcoordScale = Vector2f(x, y); 332 333 glUniform2fv(glyphPositionLoc, 1, glyphPosition.arrayof.ptr); 334 glUniform2fv(glyphScaleLoc, 1, glyphScale.arrayof.ptr); 335 glUniform2fv(glyphTexcoordScaleLoc, 1, glyphTexcoordScale.arrayof.ptr); 336 337 glBindVertexArray(vao); 338 glDrawElements(GL_TRIANGLES, cast(uint)indices.length * 3, GL_UNSIGNED_INT, cast(void*)0); 339 glBindVertexArray(0); 340 341 shift = glyph.advanceX >> 6; 342 343 glBindTexture(GL_TEXTURE_2D, 0); 344 345 return shift; 346 } 347 348 int glyphAdvance(dchar code) 349 { 350 Glyph glyph; 351 if (code in glyphs) 352 glyph = glyphs[code]; 353 else 354 glyph = glyphs[loadChar(code)]; 355 return cast(int)(glyph.advanceX >> 6); 356 } 357 358 override void render(RenderingContext* rc, Color4f color, string str) 359 { 360 if (!canRender) 361 return; 362 363 glUseProgram(shaderProgram); 364 365 glUniformMatrix4fv(modelViewMatrixLoc, 1, GL_FALSE, rc.modelViewMatrix.arrayof.ptr); 366 glUniformMatrix4fv(projectionMatrixLoc, 1, GL_FALSE, rc.projectionMatrix.arrayof.ptr); 367 368 glUniform4fv(glyphColorLoc, 1, color.arrayof.ptr); 369 370 float shift = 0.0f; 371 UTF8Decoder dec = UTF8Decoder(str); 372 int ch; 373 do 374 { 375 ch = dec.decodeNext(); 376 if (ch == 0 || ch == UTF8_END || ch == UTF8_ERROR) break; 377 dchar code = ch; 378 if (code.isASCII) 379 { 380 if (code.isPrintable) 381 shift += renderGlyph(code, shift); 382 } 383 else 384 shift += renderGlyph(code, shift); 385 } while(ch != UTF8_END && ch != UTF8_ERROR); 386 387 glUseProgram(0); 388 } 389 390 override float width(string str) 391 { 392 float width = 0.0f; 393 UTF8Decoder dec = UTF8Decoder(str); 394 int ch; 395 do 396 { 397 ch = dec.decodeNext(); 398 if (ch == 0 || ch == UTF8_END || ch == UTF8_ERROR) break; 399 dchar code = ch; 400 if (code.isASCII) 401 { 402 if (code.isPrintable) 403 width += glyphAdvance(code); 404 } 405 else 406 width += glyphAdvance(code); 407 } while(ch != UTF8_END && ch != UTF8_ERROR); 408 409 return width; 410 } 411 }