1 /*
2 Copyright (c) 2014-2015 Timur Gafarov
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 
6 Permission is hereby granted, free of charge, to any person or organization
7 obtaining a copy of the software and accompanying documentation covered by
8 this license (the "Software") to use, reproduce, display, distribute,
9 execute, and transmit the Software, and to prepare derivative works of the
10 Software, and to permit third-parties to whom the Software is furnished to
11 do so, all subject to the following:
12 
13 The copyright notices in the Software and this entire statement, including
14 the above license grant, this restriction and the following disclaimer,
15 must be included in all copies of the Software, in whole or in part, and
16 all derivative works of the Software, unless such copies or derivative
17 works are solely in the form of machine-executable object code generated by
18 a source language processor.
19 
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 DEALINGS IN THE SOFTWARE.
27 */
28 
29 module dgl.ui.ftfont;
30 
31 import std.stdio;
32 
33 import std.string;
34 import std.ascii;
35 import std.range;
36 import std.file;
37 
38 import dlib.core.memory;
39 import dlib.container.bst;
40 
41 import derelict.opengl.gl;
42 import derelict.freetype.ft;
43 
44 import dgl.ui.font;
45 
46 struct Glyph
47 {
48     GLuint textureId = 0;
49     FT_Glyph ftGlyph = null;
50     int width = 0;
51     int height = 0;
52     FT_Pos advanceX = 0;
53 }
54 
55 int nextPowerOfTwo(int a)
56 {
57     int rval = 1;
58     while(rval < a)
59         rval <<= 1;
60     return rval;
61 }
62 
63 class CharStorage(T): BST!(T)
64 {
65     this()
66     {
67         super();
68     }
69 
70     void opIndexAssign(T v, dchar k)
71     {
72         insert(k, v);
73     }
74 
75     T opIndex(dchar k)
76     {
77         auto node = find(k);
78         if (node is null)
79             return value.init;
80         else
81             return node.value;
82     }
83 
84     T* opBinaryRight(string op)(dchar k) if (op == "in")
85     {
86         auto node = find(k);
87         if (node !is null)
88             return &node.value;
89         else
90             return null;
91     }
92 
93     size_t length()
94     {
95         uint len = 1;
96         foreach(i, glyph; this)
97             len++;
98         return len;
99     }
100 }
101 
102 final class FreeTypeFont: Font
103 {
104     FT_Face ftFace;
105     FT_Library ftLibrary;
106 
107     CharStorage!Glyph glyphs;
108 
109     this(string filename, uint height)
110     {
111         enum ASCII_CHARS = 128;
112         this.height = height;
113 
114         if (FT_Init_FreeType(&ftLibrary))
115             throw new Exception("FT_Init_FreeType failed");
116 
117         if (!exists(filename))
118             throw new Exception("Cannot find font file " ~ filename);
119 
120         if (FT_New_Face(ftLibrary, toStringz(filename), 0, &ftFace))
121             throw new Exception("FT_New_Face failed (there is probably a problem with your font file)");
122 
123         FT_Set_Char_Size(ftFace, height << 6, height << 6, 96, 96);
124 
125         glyphs = New!(CharStorage!Glyph)();
126 
127         foreach(i; 0..ASCII_CHARS)
128         {
129             GLuint tex;
130             glGenTextures(1, &tex);
131             loadGlyph(i, tex);
132         }
133     }
134 
135     ~this()
136     {
137         //writefln("Deleting %s glyph(s) in FTFont...", glyphs.length);
138         foreach(i, glyph; glyphs)
139             glDeleteTextures(1, &glyph.textureId);
140         Delete(glyphs);
141     }
142 
143     void free()
144     {
145         Delete(this);
146     }
147 
148     uint loadGlyph(dchar code, GLuint texId)
149     {
150         uint charIndex = FT_Get_Char_Index(ftFace, code);
151 
152         if (charIndex == 0)
153         {
154             //TODO: if character wasn't found in font file
155         }
156 
157         if (FT_Load_Glyph(ftFace, charIndex, FT_LOAD_DEFAULT))
158             throw new Exception("FT_Load_Glyph failed");
159 
160         FT_Glyph glyph;
161         if (FT_Get_Glyph(ftFace.glyph, &glyph))
162             throw new Exception("FT_Get_Glyph failed");
163 
164         FT_Glyph_To_Bitmap(&glyph, FT_Render_Mode.FT_RENDER_MODE_NORMAL, null, 1);
165         FT_BitmapGlyph bitmapGlyph = cast(FT_BitmapGlyph)glyph;
166 
167         FT_Bitmap bitmap = bitmapGlyph.bitmap;
168 
169         int width = nextPowerOfTwo(bitmap.width);
170         int height = nextPowerOfTwo(bitmap.rows);
171 
172         GLubyte[] img = New!(GLubyte[])(2 * width * height);
173 
174         foreach(j; 0..height)
175         foreach(i; 0..width)
176         {
177             img[2 * (i + j * width)] = 255;
178             img[2 * (i + j * width) + 1] =
179                 (i >= bitmap.width || j >= bitmap.rows)?
180                  0 : bitmap.buffer[i + bitmap.width * j];
181         }
182 
183         glBindTexture(GL_TEXTURE_2D, texId);
184         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
185         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
186         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
187         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
188 
189         glTexImage2D(GL_TEXTURE_2D,
190             0, GL_RGBA, width, height,
191             0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, img.ptr);
192 
193         Delete(img);
194 
195         Glyph g = Glyph(texId, glyph, width, height, ftFace.glyph.advance.x);
196         glyphs[code] = g;
197 
198         return charIndex;
199     }
200 
201     dchar loadChar(dchar code)
202     {
203         GLuint tex;
204         glGenTextures(1, &tex);
205         loadGlyph(code, tex);
206         return code;
207     }
208 
209     float renderGlyph(dchar code)
210     {
211         Glyph glyph;
212         if (code in glyphs)
213             glyph = glyphs[code];
214         else
215             glyph = glyphs[loadChar(code)];
216 
217         FT_BitmapGlyph bitmapGlyph = cast(FT_BitmapGlyph)(glyph.ftGlyph);
218         FT_Bitmap bitmap = bitmapGlyph.bitmap;
219 
220         glBindTexture(GL_TEXTURE_2D, glyph.textureId);
221 
222         glPushMatrix();
223         glTranslatef(bitmapGlyph.left, 0, 0);
224 
225         float chWidth = cast(float)bitmap.width;
226         float chHeight = cast(float)bitmap.rows;
227         float texWidth = cast(float)glyph.width;
228         float texHeight = cast(float)glyph.height;
229 
230         glTranslatef(0, bitmapGlyph.top - bitmap.rows, 0);
231         float x = 0.5f / texWidth + chWidth / texWidth;
232         float y = 0.5f / texHeight + chHeight / texHeight;
233         glBegin(GL_QUADS);
234             glTexCoord2f(0, 0); glVertex2f(0, bitmap.rows);
235             glTexCoord2f(0, y); glVertex2f(0, 0);
236             glTexCoord2f(x, y); glVertex2f(bitmap.width, 0);
237             glTexCoord2f(x, 0); glVertex2f(bitmap.width, bitmap.rows);
238         glEnd();
239         glPopMatrix();
240         float shift = glyph.advanceX >> 6;
241         glTranslatef(shift, 0, 0);
242 
243         glBindTexture(GL_TEXTURE_2D, 0);
244 
245         return shift;
246     }
247 
248     int glyphAdvance(dchar code)
249     {
250         Glyph glyph;
251         if (code in glyphs)
252             glyph = glyphs[code];
253         else
254             glyph = glyphs[loadChar(code)];
255         return cast(int)(glyph.advanceX >> 6);
256     }
257 
258     override void draw(string str)
259     {
260         foreach(ch; stride(str, 1))
261         {
262             dchar code = ch;
263             if (code.isASCII)
264             {
265                 if (code.isPrintable)
266                     renderGlyph(code);
267             }
268             else
269                 renderGlyph(code);
270         }
271     }
272 
273     override float textWidth(string str)
274     {
275         float width = 0.0f;
276         foreach(ch; stride(str, 1))
277         {
278             dchar code = ch;
279             if (code.isASCII)
280             {
281                 if (code.isPrintable)
282                     width += glyphAdvance(code);
283             }
284             else
285                 width += glyphAdvance(code);
286         }
287 
288         return width;
289     }
290 }