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