1 /*
2 Copyright (c) 2017-2020 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         //glBindBuffer(GL_ARRAY_BUFFER, 0);
205         //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
206 
207         glBindVertexArray(0);
208 
209         vs = Shader.load("data/__internal/shaders/Glyph/Glyph.vert.glsl");
210         fs = Shader.load("data/__internal/shaders/Glyph/Glyph.frag.glsl");
211 
212         GLuint vert = compileShader(vs, ShaderStage.vertex);
213         GLuint frag = compileShader(fs, ShaderStage.fragment);
214         if (vert != 0 && frag != 0)
215             shaderProgram = linkShaders(vert, frag);
216 
217         debug writeln("GlyphShader: program ", shaderProgram);
218 
219         if (shaderProgram != 0)
220         {
221             modelViewMatrixLoc = glGetUniformLocation(shaderProgram, "modelViewMatrix");
222             projectionMatrixLoc = glGetUniformLocation(shaderProgram, "projectionMatrix");
223 
224             glyphPositionLoc = glGetUniformLocation(shaderProgram, "glyphPosition");
225             glyphScaleLoc = glGetUniformLocation(shaderProgram, "glyphScale");
226             glyphTexcoordScaleLoc = glGetUniformLocation(shaderProgram, "glyphTexcoordScale");
227             glyphTextureLoc = glGetUniformLocation(shaderProgram, "glyphTexture");
228             glyphColorLoc = glGetUniformLocation(shaderProgram, "glyphColor");
229         }
230 
231         canRender = true;
232     }
233 
234     void preloadASCII()
235     {
236         enum ASCII_CHARS = 128;
237         foreach(i; 0..ASCII_CHARS)
238         {
239             GLuint tex;
240             glGenTextures(1, &tex);
241             loadGlyph(i, tex);
242         }
243     }
244 
245     ~this()
246     {
247         vs.free();
248         fs.free();
249 
250         if (canRender)
251         {
252             glDeleteVertexArrays(1, &vao);
253             glDeleteBuffers(1, &vbo);
254             glDeleteBuffers(1, &tbo);
255             glDeleteBuffers(1, &eao);
256         }
257 
258         foreach(i, glyph; glyphs)
259             glDeleteTextures(1, &glyph.textureId);
260         Delete(glyphs);
261     }
262 
263     uint loadGlyph(dchar code, GLuint texId)
264     {
265         FT_Glyph glyph;
266 
267         uint charIndex = FT_Get_Char_Index(ftFace, code);
268 
269         if (charIndex == 0)
270         {
271             //TODO: if character wasn't found in font file
272         }
273 
274         auto res = FT_Load_Glyph(ftFace, charIndex, FT_LOAD_DEFAULT);
275 
276         if (res)
277             throw new Exception(format("FT_Load_Glyph failed with code %s", res));
278 
279         if (FT_Get_Glyph(ftFace.glyph, &glyph))
280             throw new Exception("FT_Get_Glyph failed");
281 
282         FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, null, 1);
283         FT_BitmapGlyph bitmapGlyph = cast(FT_BitmapGlyph)glyph;
284 
285         FT_Bitmap bitmap = bitmapGlyph.bitmap;
286 
287         int width = nextPowerOfTwo(bitmap.width);
288         int height = nextPowerOfTwo(bitmap.rows);
289 
290         GLubyte[] img = New!(GLubyte[])(2 * width * height);
291 
292         foreach(j; 0..height)
293         foreach(i; 0..width)
294         {
295             img[2 * (i + j * width)] = 255;
296             img[2 * (i + j * width) + 1] =
297                 (i >= bitmap.width || j >= bitmap.rows)?
298                  0 : bitmap.buffer[i + bitmap.width * j];
299         }
300 
301         glBindTexture(GL_TEXTURE_2D, texId);
302         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
303         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
304         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
305         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
306 
307         glTexImage2D(GL_TEXTURE_2D,
308             0, GL_RG8, width, height,
309             0, GL_RG, GL_UNSIGNED_BYTE, img.ptr);
310 
311         Delete(img);
312 
313         Glyph g = Glyph(true, texId, glyph, width, height, ftFace.glyph.advance.x);
314         glyphs[code] = g;
315 
316         return charIndex;
317     }
318 
319     dchar loadChar(dchar code)
320     {
321         GLuint tex;
322         glGenTextures(1, &tex);
323         loadGlyph(code, tex);
324         return code;
325     }
326 
327     float renderGlyph(dchar code, float shift)
328     {
329         Glyph glyph;
330         if (code in glyphs)
331             glyph = glyphs[code];
332         else
333             glyph = glyphs[loadChar(code)];
334 
335         //if (!glyph.valid)
336         //    return 0.0f;
337 
338         FT_BitmapGlyph bitmapGlyph = cast(FT_BitmapGlyph)(glyph.ftGlyph);
339         FT_Bitmap bitmap = bitmapGlyph.bitmap;
340 
341         glBindTexture(GL_TEXTURE_2D, glyph.textureId);
342 
343         float chWidth = cast(float)bitmap.width;
344         float chHeight = cast(float)bitmap.rows;
345         float texWidth = cast(float)glyph.width;
346         float texHeight = cast(float)glyph.height;
347 
348         float x = 0.5f / texWidth + chWidth / texWidth;
349         float y = 0.5f / texHeight + chHeight / texHeight;
350 
351         Vector2f glyphPosition = Vector2f(shift + bitmapGlyph.left, -bitmapGlyph.top);
352         Vector2f glyphScale = Vector2f(bitmap.width, bitmap.rows);
353         Vector2f glyphTexcoordScale = Vector2f(x, y);
354 
355         glUniform2fv(glyphPositionLoc, 1, glyphPosition.arrayof.ptr);
356         glUniform2fv(glyphScaleLoc, 1, glyphScale.arrayof.ptr);
357         glUniform2fv(glyphTexcoordScaleLoc, 1, glyphTexcoordScale.arrayof.ptr);
358 
359         glBindVertexArray(vao);
360         glDrawElements(GL_TRIANGLES, cast(uint)indices.length * 3, GL_UNSIGNED_INT, cast(void*)0);
361         glBindVertexArray(0);
362 
363         shift = glyph.advanceX >> 6;
364 
365         glBindTexture(GL_TEXTURE_2D, 0);
366 
367         return shift;
368     }
369 
370     int glyphAdvance(dchar code)
371     {
372         Glyph glyph;
373         if (code in glyphs)
374             glyph = glyphs[code];
375         else
376             glyph = glyphs[loadChar(code)];
377         return cast(int)(glyph.advanceX >> 6);
378     }
379 
380     override void render(GraphicsState* state, Color4f color, string str)
381     {
382         if (!canRender)
383             return;
384 
385         glUseProgram(shaderProgram);
386 
387         glUniformMatrix4fv(modelViewMatrixLoc, 1, GL_FALSE, state.modelViewMatrix.arrayof.ptr);
388         glUniformMatrix4fv(projectionMatrixLoc, 1, GL_FALSE, state.projectionMatrix.arrayof.ptr);
389         glUniform4fv(glyphColorLoc, 1, color.arrayof.ptr);
390         glUniform1i(glyphTextureLoc, 0);
391 
392         float shift = 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                     shift += renderGlyph(code, shift);
404             }
405             else
406                 shift += renderGlyph(code, shift);
407         } while(ch != UTF8_END && ch != UTF8_ERROR);
408 
409         glUseProgram(0);
410     }
411 
412     override float width(string str)
413     {
414         float width = 0.0f;
415         UTF8Decoder dec = UTF8Decoder(str);
416         int ch;
417         do
418         {
419             ch = dec.decodeNext();
420             if (ch == 0 || ch == UTF8_END || ch == UTF8_ERROR) break;
421             dchar code = ch;
422             if (code.isASCII)
423             {
424                 if (code.isPrintable)
425                     width += glyphAdvance(code);
426             }
427             else
428                 width += glyphAdvance(code);
429         } while(ch != UTF8_END && ch != UTF8_ERROR);
430 
431         return width;
432     }
433 }
434 
435 class FontAsset: Asset
436 {
437     FreeTypeFont font;
438     ubyte[] buffer;
439 
440     this(uint height, Owner o)
441     {
442         super(o);
443         font = New!FreeTypeFont(height, this);
444     }
445 
446     ~this()
447     {
448         release();
449     }
450 
451     override bool loadThreadSafePart(string filename, InputStream istrm, ReadOnlyFileSystem fs, AssetManager mngr)
452     {
453         FileStat s;
454         fs.stat(filename, s);
455         buffer = New!(ubyte[])(cast(size_t)s.sizeInBytes);
456         istrm.fillArray(buffer);
457         font.createFromMemory(buffer);
458         return true;
459     }
460 
461     override bool loadThreadUnsafePart()
462     {
463         font.prepareVAO();
464         font.preloadASCII();
465         return true;
466     }
467 
468     override void release()
469     {
470         if (buffer.length)
471             Delete(buffer);
472     }
473 }
474 
475 FontAsset addFontAsset(Scene scene, string filename, uint height, bool preload = false)
476 {
477     FontAsset font;
478     if (scene.assetManager.assetExists(filename))
479         font = cast(FontAsset)scene.assetManager.getAsset(filename);
480     else
481     {
482         font = New!FontAsset(height, scene.assetManager);
483         scene.addAsset(font, filename, preload);
484     }
485     return font;
486 }