1 /*
2 Copyright (c) 2017-2022 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.ext.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.ownership;
39 import dlib.core.stream;
40 import dlib.filesystem.filesystem;
41 import dlib.filesystem.stdfs;
42 import dlib.container.dict;
43 import dlib.text.utf8;
44 import dlib.math.vector;
45 import dlib.image.color;
46 import dlib.text.str;
47 
48 import dagon.core.application;
49 import dagon.ui.font;
50 import dagon.graphics.shaderloader;
51 import dagon.graphics.state;
52 import dagon.graphics.shader;
53 import dagon.resource.asset;
54 import dagon.resource.scene;
55 
56 import dagon.core.bindings;
57 public import bindbc.freetype;
58 
59 __gshared FTSupport freetypeSupport;
60 
61 void initFreetype()
62 {
63     freetypeSupport = loadFreeType();
64     if (freetypeSupport != ftSupport)
65     {
66         if (freetypeSupport == FTSupport.badLibrary)
67             writeln("Warning: failed to load some Freetype functions. It seems that you have an old version of Freetype. Dagon will try to use it, but it is recommended to install Freetype 2.8.1 or higher");
68         else
69             writeln("Error: Freetype library is not found. Please, install Freetype 2.8.1");
70     }
71 }
72 
73 static this()
74 {
75     initFreetype();
76 }
77 
78 struct Glyph
79 {
80     bool valid;
81     GLuint textureId = 0;
82     FT_Glyph ftGlyph = null;
83     int width = 0;
84     int height = 0;
85     FT_Pos advanceX = 0;
86 }
87 
88 int nextPowerOfTwo(int a)
89 {
90     int rval = 1;
91     while(rval < a)
92         rval <<= 1;
93     return rval;
94 }
95 
96 final class FreeTypeFont: Font
97 {
98     FT_Face ftFace;
99     FT_Library ftLibrary;
100     Dict!(Glyph, dchar) glyphs;
101 
102     Vector2f[4] vertices;
103     Vector2f[4] texcoords;
104     uint[3][2] indices;
105 
106     GLuint vao = 0;
107     GLuint vbo = 0;
108     GLuint tbo = 0;
109     GLuint eao = 0;
110 
111     bool canRender = false;
112 
113     GLuint shaderProgram;
114 
115     GLint modelViewMatrixLoc;
116     GLint projectionMatrixLoc;
117 
118     GLint glyphPositionLoc;
119     GLint glyphScaleLoc;
120     GLint glyphTexcoordScaleLoc;
121 
122     GLint glyphTextureLoc;
123     GLint glyphColorLoc;
124 
125     String vs, fs;
126 
127     this(uint height, Owner o)
128     {
129         super(o);
130         this.height = height;
131 
132         if (FT_Init_FreeType(&ftLibrary))
133             throw new Exception("FT_Init_FreeType failed");
134 
135         vertices[0] = Vector2f(0, 1);
136         vertices[1] = Vector2f(0, 0);
137         vertices[2] = Vector2f(1, 0);
138         vertices[3] = Vector2f(1, 1);
139 
140         texcoords[0] = Vector2f(0, 1);
141         texcoords[1] = Vector2f(0, 0);
142         texcoords[2] = Vector2f(1, 0);
143         texcoords[3] = Vector2f(1, 1);
144 
145         indices[0][0] = 0;
146         indices[0][1] = 1;
147         indices[0][2] = 2;
148 
149         indices[1][0] = 0;
150         indices[1][1] = 2;
151         indices[1][2] = 3;
152     }
153 
154     void createFromFile(string filename)
155     {
156         if (!exists(filename))
157             throw new Exception("Cannot find font file " ~ filename);
158 
159         if (FT_New_Face(ftLibrary, toStringz(filename), 0, &ftFace))
160             throw new Exception("FT_New_Face failed (there is probably a problem with your font file)");
161 
162         FT_Set_Char_Size(ftFace, cast(int)height << 6, cast(int)height << 6, 96, 96);
163         glyphs = New!(Dict!(Glyph, dchar));
164     }
165 
166     void createFromMemory(ubyte[] buffer)
167     {
168         if (FT_New_Memory_Face(ftLibrary, buffer.ptr, cast(uint)buffer.length, 0, &ftFace))
169             throw new Exception("FT_New_Face failed (there is probably a problem with your font file)");
170 
171         FT_Set_Char_Size(ftFace, cast(int)height << 6, cast(int)height << 6, 96, 96);
172         glyphs = New!(Dict!(Glyph, dchar));
173     }
174 
175     void prepareVAO()
176     {
177         if (canRender)
178             return;
179 
180         glGenBuffers(1, &vbo);
181         glBindBuffer(GL_ARRAY_BUFFER, vbo);
182         glBufferData(GL_ARRAY_BUFFER, vertices.length * float.sizeof * 2, vertices.ptr, GL_STATIC_DRAW);
183 
184         glGenBuffers(1, &tbo);
185         glBindBuffer(GL_ARRAY_BUFFER, tbo);
186         glBufferData(GL_ARRAY_BUFFER, texcoords.length * float.sizeof * 2, texcoords.ptr, GL_STATIC_DRAW);
187 
188         glGenBuffers(1, &eao);
189         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eao);
190         glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.length * uint.sizeof * 3, indices.ptr, GL_STATIC_DRAW);
191 
192         glGenVertexArrays(1, &vao);
193         glBindVertexArray(vao);
194         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eao);
195 
196         glEnableVertexAttribArray(0);
197         glBindBuffer(GL_ARRAY_BUFFER, vbo);
198         glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, null);
199 
200         glEnableVertexAttribArray(1);
201         glBindBuffer(GL_ARRAY_BUFFER, tbo);
202         glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, null);
203 
204         glBindVertexArray(0);
205 
206         vs = Shader.load("data/__internal/shaders/Glyph/Glyph.vert.glsl");
207         fs = Shader.load("data/__internal/shaders/Glyph/Glyph.frag.glsl");
208 
209         GLuint vert = compileShader(vs, ShaderStage.vertex);
210         GLuint frag = compileShader(fs, ShaderStage.fragment);
211         if (vert != 0 && frag != 0)
212             shaderProgram = linkShaders(vert, frag);
213 
214         if (shaderProgram != 0)
215         {
216             modelViewMatrixLoc = glGetUniformLocation(shaderProgram, "modelViewMatrix");
217             projectionMatrixLoc = glGetUniformLocation(shaderProgram, "projectionMatrix");
218 
219             glyphPositionLoc = glGetUniformLocation(shaderProgram, "glyphPosition");
220             glyphScaleLoc = glGetUniformLocation(shaderProgram, "glyphScale");
221             glyphTexcoordScaleLoc = glGetUniformLocation(shaderProgram, "glyphTexcoordScale");
222             glyphTextureLoc = glGetUniformLocation(shaderProgram, "glyphTexture");
223             glyphColorLoc = glGetUniformLocation(shaderProgram, "glyphColor");
224         }
225 
226         canRender = true;
227     }
228 
229     void preloadASCII()
230     {
231         enum ASCII_CHARS = 128;
232         foreach(i; 0..ASCII_CHARS)
233         {
234             GLuint tex;
235             glGenTextures(1, &tex);
236             loadGlyph(i, tex);
237         }
238     }
239 
240     ~this()
241     {
242         vs.free();
243         fs.free();
244 
245         if (canRender)
246         {
247             glDeleteVertexArrays(1, &vao);
248             glDeleteBuffers(1, &vbo);
249             glDeleteBuffers(1, &tbo);
250             glDeleteBuffers(1, &eao);
251         }
252 
253         foreach(i, glyph; glyphs)
254             glDeleteTextures(1, &glyph.textureId);
255         Delete(glyphs);
256     }
257 
258     uint loadGlyph(dchar code, GLuint texId)
259     {
260         FT_Glyph glyph;
261 
262         uint charIndex = FT_Get_Char_Index(ftFace, code);
263 
264         if (charIndex == 0)
265         {
266             //TODO: if character wasn't found in font file
267         }
268 
269         auto res = FT_Load_Glyph(ftFace, charIndex, FT_LOAD_DEFAULT);
270 
271         if (res)
272             throw new Exception(format("FT_Load_Glyph failed with code %s", res));
273 
274         if (FT_Get_Glyph(ftFace.glyph, &glyph))
275             throw new Exception("FT_Get_Glyph failed");
276 
277         FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, null, 1);
278         FT_BitmapGlyph bitmapGlyph = cast(FT_BitmapGlyph)glyph;
279 
280         FT_Bitmap bitmap = bitmapGlyph.bitmap;
281 
282         int width = nextPowerOfTwo(bitmap.width);
283         int height = nextPowerOfTwo(bitmap.rows);
284 
285         GLubyte[] img = New!(GLubyte[])(2 * width * height);
286 
287         foreach(j; 0..height)
288         foreach(i; 0..width)
289         {
290             img[2 * (i + j * width)] = 255;
291             img[2 * (i + j * width) + 1] =
292                 (i >= bitmap.width || j >= bitmap.rows)?
293                  0 : bitmap.buffer[i + bitmap.width * j];
294         }
295 
296         glBindTexture(GL_TEXTURE_2D, texId);
297         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
298         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
299         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
300         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
301 
302         glTexImage2D(GL_TEXTURE_2D,
303             0, GL_RG8, width, height,
304             0, GL_RG, GL_UNSIGNED_BYTE, img.ptr);
305 
306         Delete(img);
307 
308         Glyph g = Glyph(true, texId, glyph, width, height, ftFace.glyph.advance.x);
309         glyphs[code] = g;
310 
311         return charIndex;
312     }
313 
314     dchar loadChar(dchar code)
315     {
316         GLuint tex;
317         glGenTextures(1, &tex);
318         loadGlyph(code, tex);
319         return code;
320     }
321 
322     float renderGlyph(dchar code, float shift)
323     {
324         Glyph glyph;
325         if (code in glyphs)
326             glyph = glyphs[code];
327         else
328             glyph = glyphs[loadChar(code)];
329 
330         FT_BitmapGlyph bitmapGlyph = cast(FT_BitmapGlyph)(glyph.ftGlyph);
331         FT_Bitmap bitmap = bitmapGlyph.bitmap;
332 
333         glBindTexture(GL_TEXTURE_2D, glyph.textureId);
334 
335         float chWidth = cast(float)bitmap.width;
336         float chHeight = cast(float)bitmap.rows;
337         float texWidth = cast(float)glyph.width;
338         float texHeight = cast(float)glyph.height;
339 
340         float x = 0.5f / texWidth + chWidth / texWidth;
341         float y = 0.5f / texHeight + chHeight / texHeight;
342 
343         Vector2f glyphPosition = Vector2f(shift + bitmapGlyph.left, -bitmapGlyph.top);
344         Vector2f glyphScale = Vector2f(bitmap.width, bitmap.rows);
345         Vector2f glyphTexcoordScale = Vector2f(x, y);
346 
347         glUniform2fv(glyphPositionLoc, 1, glyphPosition.arrayof.ptr);
348         glUniform2fv(glyphScaleLoc, 1, glyphScale.arrayof.ptr);
349         glUniform2fv(glyphTexcoordScaleLoc, 1, glyphTexcoordScale.arrayof.ptr);
350 
351         glBindVertexArray(vao);
352         glDrawElements(GL_TRIANGLES, cast(uint)indices.length * 3, GL_UNSIGNED_INT, cast(void*)0);
353         glBindVertexArray(0);
354 
355         shift = glyph.advanceX >> 6;
356 
357         glBindTexture(GL_TEXTURE_2D, 0);
358 
359         return shift;
360     }
361 
362     int glyphAdvance(dchar code)
363     {
364         Glyph glyph;
365         if (code in glyphs)
366             glyph = glyphs[code];
367         else
368             glyph = glyphs[loadChar(code)];
369         return cast(int)(glyph.advanceX >> 6);
370     }
371 
372     override void render(GraphicsState* state, Color4f color, string str)
373     {
374         if (!canRender)
375             return;
376 
377         glUseProgram(shaderProgram);
378 
379         glUniformMatrix4fv(modelViewMatrixLoc, 1, GL_FALSE, state.modelViewMatrix.arrayof.ptr);
380         glUniformMatrix4fv(projectionMatrixLoc, 1, GL_FALSE, state.projectionMatrix.arrayof.ptr);
381         glUniform4fv(glyphColorLoc, 1, color.arrayof.ptr);
382         glUniform1i(glyphTextureLoc, 0);
383 
384         float shift = 0.0f;
385         UTF8Decoder dec = UTF8Decoder(str);
386         int ch;
387         do
388         {
389             ch = dec.decodeNext();
390             if (ch == 0 || ch == UTF8_END || ch == UTF8_ERROR) break;
391             dchar code = ch;
392             if (code.isASCII)
393             {
394                 if (code.isPrintable)
395                     shift += renderGlyph(code, shift);
396             }
397             else
398                 shift += renderGlyph(code, shift);
399         } while(ch != UTF8_END && ch != UTF8_ERROR);
400 
401         glUseProgram(0);
402     }
403 
404     override float width(string str)
405     {
406         float width = 0.0f;
407         UTF8Decoder dec = UTF8Decoder(str);
408         int ch;
409         do
410         {
411             ch = dec.decodeNext();
412             if (ch == 0 || ch == UTF8_END || ch == UTF8_ERROR) break;
413             dchar code = ch;
414             if (code.isASCII)
415             {
416                 if (code.isPrintable)
417                     width += glyphAdvance(code);
418             }
419             else
420                 width += glyphAdvance(code);
421         } while(ch != UTF8_END && ch != UTF8_ERROR);
422 
423         return width;
424     }
425 }
426 
427 class FontAsset: Asset
428 {
429     FreeTypeFont font;
430     ubyte[] buffer;
431 
432     this(uint height, Owner o)
433     {
434         super(o);
435         font = New!FreeTypeFont(height, this);
436     }
437 
438     ~this()
439     {
440         release();
441     }
442 
443     override bool loadThreadSafePart(string filename, InputStream istrm, ReadOnlyFileSystem fs, AssetManager mngr)
444     {
445         FileStat s;
446         fs.stat(filename, s);
447         buffer = New!(ubyte[])(cast(size_t)s.sizeInBytes);
448         istrm.fillArray(buffer);
449         font.createFromMemory(buffer);
450         return true;
451     }
452 
453     override bool loadThreadUnsafePart()
454     {
455         font.prepareVAO();
456         font.preloadASCII();
457         return true;
458     }
459 
460     override void release()
461     {
462         if (buffer.length)
463             Delete(buffer);
464     }
465 }
466 
467 FontAsset addFontAsset(Scene scene, string filename, uint height, bool preload = false)
468 {
469     FontAsset font;
470     if (scene.assetManager.assetExists(filename))
471         font = cast(FontAsset)scene.assetManager.getAsset(filename);
472     else
473     {
474         font = New!FontAsset(height, scene.assetManager);
475         scene.addAsset(font, filename, preload);
476     }
477     return font;
478 }