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.libs;
45 import dagon.core.ownership;
46 import dagon.ui.font;
47 import dagon.graphics.rc;
48 
49 struct Glyph
50 {
51     bool valid;
52     GLuint textureId = 0;
53     FT_Glyph ftGlyph = null;
54     int width = 0;
55     int height = 0;
56     FT_Pos advanceX = 0;
57 }
58 
59 int nextPowerOfTwo(int a)
60 {
61     int rval = 1;
62     while(rval < a)
63         rval <<= 1;
64     return rval;
65 }
66 
67 final class FreeTypeFont: Font
68 {
69     FT_Face ftFace;
70     FT_Library ftLibrary;
71     Dict!(Glyph, dchar) glyphs;
72 
73     Vector2f[4] vertices;
74     Vector2f[4] texcoords;
75     uint[3][2] indices;
76 
77     GLuint vao = 0;
78     GLuint vbo = 0;
79     GLuint tbo = 0;
80     GLuint eao = 0;
81 
82     bool canRender = false;
83 
84     GLuint shaderProgram;
85     GLuint vertexShader;
86     GLuint fragmentShader;
87 
88     GLint modelViewMatrixLoc;
89     GLint projectionMatrixLoc;
90 
91     GLint glyphPositionLoc;
92     GLint glyphScaleLoc;
93     GLint glyphTexcoordScaleLoc;
94 
95     GLint glyphTextureLoc;
96     GLint glyphColorLoc;
97 
98     string vs = import("Text.vs");
99     string fs = import("Text.fs");
100 
101     this(uint height, Owner o)
102     {
103         super(o);
104         this.height = height;
105 
106         if (FT_Init_FreeType(&ftLibrary))
107             throw new Exception("FT_Init_FreeType failed");
108 
109         vertices[0] = Vector2f(0, 1);
110         vertices[1] = Vector2f(0, 0);
111         vertices[2] = Vector2f(1, 0);
112         vertices[3] = Vector2f(1, 1);
113 
114         texcoords[0] = Vector2f(0, 1);
115         texcoords[1] = Vector2f(0, 0);
116         texcoords[2] = Vector2f(1, 0);
117         texcoords[3] = Vector2f(1, 1);
118 
119         indices[0][0] = 0;
120         indices[0][1] = 1;
121         indices[0][2] = 2;
122 
123         indices[1][0] = 0;
124         indices[1][1] = 2;
125         indices[1][2] = 3;
126     }
127 
128     void createFromFile(string filename)
129     {
130         if (!exists(filename))
131             throw new Exception("Cannot find font file " ~ filename);
132 
133         if (FT_New_Face(ftLibrary, toStringz(filename), 0, &ftFace))
134             throw new Exception("FT_New_Face failed (there is probably a problem with your font file)");
135 
136         FT_Set_Char_Size(ftFace, cast(int)height << 6, cast(int)height << 6, 96, 96);
137         glyphs = New!(Dict!(Glyph, dchar));
138     }
139 
140     void createFromMemory(ubyte[] buffer)
141     {
142         if (FT_New_Memory_Face(ftLibrary, buffer.ptr, cast(uint)buffer.length, 0, &ftFace))
143             throw new Exception("FT_New_Face failed (there is probably a problem with your font file)");
144 
145         FT_Set_Char_Size(ftFace, cast(int)height << 6, cast(int)height << 6, 96, 96);
146         glyphs = New!(Dict!(Glyph, dchar));
147     }
148 
149     void prepareVAO()
150     {
151         if (canRender)
152             return;
153 
154         glGenBuffers(1, &vbo);
155         glBindBuffer(GL_ARRAY_BUFFER, vbo);
156         glBufferData(GL_ARRAY_BUFFER, vertices.length * float.sizeof * 2, vertices.ptr, GL_STATIC_DRAW);
157 
158         glGenBuffers(1, &tbo);
159         glBindBuffer(GL_ARRAY_BUFFER, tbo);
160         glBufferData(GL_ARRAY_BUFFER, texcoords.length * float.sizeof * 2, texcoords.ptr, GL_STATIC_DRAW);
161 
162         glGenBuffers(1, &eao);
163         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eao);
164         glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.length * uint.sizeof * 3, indices.ptr, GL_STATIC_DRAW);
165 
166         glGenVertexArrays(1, &vao);
167         glBindVertexArray(vao);
168         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eao);
169 
170         glEnableVertexAttribArray(0);
171         glBindBuffer(GL_ARRAY_BUFFER, vbo);
172         glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, null);
173 
174         glEnableVertexAttribArray(1);
175         glBindBuffer(GL_ARRAY_BUFFER, tbo);
176         glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, null);
177 
178         //glBindBuffer(GL_ARRAY_BUFFER, 0);
179         //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
180 
181         glBindVertexArray(0);
182 
183         const(char*)pvs = vs.ptr;
184         const(char*)pfs = fs.ptr;
185 
186         char[1000] infobuffer = 0;
187         int infobufferlen = 0;
188 
189         vertexShader = glCreateShader(GL_VERTEX_SHADER);
190         glShaderSource(vertexShader, 1, &pvs, null);
191         glCompileShader(vertexShader);
192         GLint success = 0;
193         glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
194         if (!success)
195         {
196             GLint logSize = 0;
197             glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &logSize);
198             glGetShaderInfoLog(vertexShader, 999, &logSize, infobuffer.ptr);
199             writeln("Error in vertex shader:");
200             writeln(infobuffer[0..logSize]);
201         }
202 
203         fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
204         glShaderSource(fragmentShader, 1, &pfs, null);
205         glCompileShader(fragmentShader);
206         success = 0;
207         glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
208         if (!success)
209         {
210             GLint logSize = 0;
211             glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &logSize);
212             glGetShaderInfoLog(fragmentShader, 999, &logSize, infobuffer.ptr);
213             writeln("Error in fragment shader:");
214             writeln(infobuffer[0..logSize]);
215         }
216 
217         shaderProgram = glCreateProgram();
218         glAttachShader(shaderProgram, vertexShader);
219         glAttachShader(shaderProgram, fragmentShader);
220         glLinkProgram(shaderProgram);
221 
222         modelViewMatrixLoc = glGetUniformLocation(shaderProgram, "modelViewMatrix");
223         projectionMatrixLoc = glGetUniformLocation(shaderProgram, "projectionMatrix");
224 
225         glyphPositionLoc = glGetUniformLocation(shaderProgram, "glyphPosition");
226         glyphScaleLoc = glGetUniformLocation(shaderProgram, "glyphScale");
227         glyphTexcoordScaleLoc = glGetUniformLocation(shaderProgram, "glyphTexcoordScale");
228         glyphTextureLoc = glGetUniformLocation(shaderProgram, "glyphTexture");
229         glyphColorLoc = glGetUniformLocation(shaderProgram, "glyphColor");
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         if (canRender)
248         {
249             glDeleteVertexArrays(1, &vao);
250             glDeleteBuffers(1, &vbo);
251             glDeleteBuffers(1, &tbo);
252             glDeleteBuffers(1, &eao);
253         }
254 
255         foreach(i, glyph; glyphs)
256             glDeleteTextures(1, &glyph.textureId);
257         Delete(glyphs);
258     }
259 
260     uint loadGlyph(dchar code, GLuint texId)
261     {
262         FT_Glyph glyph;
263 
264         uint charIndex = FT_Get_Char_Index(ftFace, code);
265 
266         if (charIndex == 0)
267         {
268             //TODO: if character wasn't found in font file
269         }
270 
271         auto res = FT_Load_Glyph(ftFace, charIndex, FT_LOAD_DEFAULT);
272 
273         if (res)
274             throw new Exception(format("FT_Load_Glyph failed with code %s", res));
275 
276         if (FT_Get_Glyph(ftFace.glyph, &glyph))
277             throw new Exception("FT_Get_Glyph failed");
278 
279         FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, null, 1);
280         FT_BitmapGlyph bitmapGlyph = cast(FT_BitmapGlyph)glyph;
281 
282         FT_Bitmap bitmap = bitmapGlyph.bitmap;
283 
284         int width = nextPowerOfTwo(bitmap.width);
285         int height = nextPowerOfTwo(bitmap.rows);
286 
287         GLubyte[] img = New!(GLubyte[])(2 * width * height);
288 
289         foreach(j; 0..height)
290         foreach(i; 0..width)
291         {
292             img[2 * (i + j * width)] = 255;
293             img[2 * (i + j * width) + 1] =
294                 (i >= bitmap.width || j >= bitmap.rows)?
295                  0 : bitmap.buffer[i + bitmap.width * j];
296         }
297 
298         glBindTexture(GL_TEXTURE_2D, texId);
299         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
300         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
301         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
302         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
303 
304         glTexImage2D(GL_TEXTURE_2D,
305             0, GL_RG8, width, height,
306             0, GL_RG, GL_UNSIGNED_BYTE, img.ptr);
307 
308         Delete(img);
309 
310         Glyph g = Glyph(true, texId, glyph, width, height, ftFace.glyph.advance.x);
311         glyphs[code] = g;
312 
313         return charIndex;
314     }
315 
316     dchar loadChar(dchar code)
317     {
318         GLuint tex;
319         glGenTextures(1, &tex);
320         loadGlyph(code, tex);
321         return code;
322     }
323 
324     float renderGlyph(dchar code, float shift)
325     {
326         Glyph glyph;
327         if (code in glyphs)
328             glyph = glyphs[code];
329         else
330             glyph = glyphs[loadChar(code)];
331 
332         //if (!glyph.valid)
333         //    return 0.0f;
334 
335         FT_BitmapGlyph bitmapGlyph = cast(FT_BitmapGlyph)(glyph.ftGlyph);
336         FT_Bitmap bitmap = bitmapGlyph.bitmap;
337 
338         glBindTexture(GL_TEXTURE_2D, glyph.textureId);
339         glUniform1i(glyphTextureLoc, 0);
340 
341         float chWidth = cast(float)bitmap.width;
342         float chHeight = cast(float)bitmap.rows;
343         float texWidth = cast(float)glyph.width;
344         float texHeight = cast(float)glyph.height;
345 
346         float x = 0.5f / texWidth + chWidth / texWidth;
347         float y = 0.5f / texHeight + chHeight / texHeight;
348 
349         Vector2f glyphPosition = Vector2f(shift + bitmapGlyph.left, -bitmapGlyph.top); //-(bitmapGlyph.top - bitmap.rows))
350         Vector2f glyphScale = Vector2f(bitmap.width, bitmap.rows);
351         Vector2f glyphTexcoordScale = Vector2f(x, y);
352 
353         glUniform2fv(glyphPositionLoc, 1, glyphPosition.arrayof.ptr);
354         glUniform2fv(glyphScaleLoc, 1, glyphScale.arrayof.ptr);
355         glUniform2fv(glyphTexcoordScaleLoc, 1, glyphTexcoordScale.arrayof.ptr);
356 
357         glBindVertexArray(vao);
358         glDrawElements(GL_TRIANGLES, cast(uint)indices.length * 3, GL_UNSIGNED_INT, cast(void*)0);
359         glBindVertexArray(0);
360 
361         shift = glyph.advanceX >> 6;
362 
363         glBindTexture(GL_TEXTURE_2D, 0);
364 
365         return shift;
366     }
367 
368     int glyphAdvance(dchar code)
369     {
370         Glyph glyph;
371         if (code in glyphs)
372             glyph = glyphs[code];
373         else
374             glyph = glyphs[loadChar(code)];
375         return cast(int)(glyph.advanceX >> 6);
376     }
377 
378     override void render(RenderingContext* rc, Color4f color, string str)
379     {
380         if (!canRender)
381             return;
382 
383         glUseProgram(shaderProgram);
384 
385         glUniformMatrix4fv(modelViewMatrixLoc, 1, GL_FALSE, rc.modelViewMatrix.arrayof.ptr);
386         glUniformMatrix4fv(projectionMatrixLoc, 1, GL_FALSE, rc.projectionMatrix.arrayof.ptr);
387 
388         glUniform4fv(glyphColorLoc, 1, color.arrayof.ptr);
389 
390         float shift = 0.0f;
391         UTF8Decoder dec = UTF8Decoder(str);
392         int ch;
393         do
394         {
395             ch = dec.decodeNext();
396             if (ch == 0 || ch == UTF8_END || ch == UTF8_ERROR) break;
397             dchar code = ch;
398             if (code.isASCII)
399             {
400                 if (code.isPrintable)
401                     shift += renderGlyph(code, shift);
402             }
403             else
404                 shift += renderGlyph(code, shift);
405         } while(ch != UTF8_END && ch != UTF8_ERROR);
406 
407         glUseProgram(0);
408     }
409 
410     override float width(string str)
411     {
412         float width = 0.0f;
413         UTF8Decoder dec = UTF8Decoder(str);
414         int ch;
415         do
416         {
417             ch = dec.decodeNext();
418             if (ch == 0 || ch == UTF8_END || ch == UTF8_ERROR) break;
419             dchar code = ch;
420             if (code.isASCII)
421             {
422                 if (code.isPrintable)
423                     width += glyphAdvance(code);
424             }
425             else
426                 width += glyphAdvance(code);
427         } while(ch != UTF8_END && ch != UTF8_ERROR);
428 
429         return width;
430     }
431 }