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 }