1 
2 module ws.gl.font;
3 
4 import
5     file = std.file,
6     std.process,
7     std.traits,
8     std.conv,
9     std.algorithm,
10     std.math,
11 
12     derelict.freetype.ft,
13     derelict.util.exception,
14 
15     ws.bindings.fontconfig,
16     ws.log,
17     ws.string,
18     ws.gl.gl,
19     ws.gl.context,
20     ws.gl.batch;
21 
22 
23 version(Posix)
24     import std.path;
25 
26 
27 
28 private __gshared FT_Library ftLib;
29 
30 
31 shared static this(){
32     DerelictFT.missingSymbolCallback = (name){
33         //if(name == "FT_Gzip_Uncompress")
34             return ShouldThrow.No;
35         //return ShouldThrow.Yes;
36     };
37     DerelictFT.load();
38     FT_Error r = FT_Init_FreeType(&ftLib);
39     if(r)
40         throw new Exception("Failed to initialize FreeType 2 [" ~ tostring(r) ~ "]");
41     FT_Library_SetLcdFilter(ftLib, FT_LCD_FILTER_DEFAULT);
42 }
43 
44 shared static ~this(){
45     FT_Done_FreeType(ftLib);
46 }
47 
48 struct SelectedFont {
49     string path;
50     int index;
51 }
52 
53 
54 byte applyGamma(ubyte b){
55     return cast(ubyte)(((b/255.0)^^0.45)*255);
56 }
57 
58 version(Posix){
59     SelectedFont findFont(string name, int size){
60         FcPattern *match;
61         FcResult result;
62         char *file;
63         int index;
64         name ~= "-" ~ size.to!string;
65         auto pat = FcNameParse(name.toStringz);
66         FcConfigSubstitute(null, pat, FcMatchPattern);
67         FcDefaultSubstitute(pat);
68         match = FcFontMatch(null, pat, &result);
69 
70         FcPatternGetString(match, "file", 0, cast(FcChar8 **) &file);
71         FcPatternGetInteger(match, "index", 0, &index);
72 
73         SelectedFont selected;
74         selected.path = file.to!string;
75         selected.index = index;
76 
77         FcPatternDestroy(match);
78         FcPatternDestroy(pat);
79 
80         return selected;
81     }
82 }
83 
84 version(Windows){
85     import ws.wm.win32.api;
86     auto winDir(){
87         WCHAR[MAX_PATH] winDir;
88         auto winDirLength = GetWindowsDirectory(winDir.ptr, MAX_PATH);
89         return winDir[0..winDirLength].to!string;
90     }
91     auto iterateRegistryKey(string key){
92         struct Iterator {
93             int opApply(int delegate(string, void[]) dg){
94                 int delegateResult = 0;
95                 enum fontRegistryPath = "Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts";
96                 HKEY hKey;
97                 LONG result;
98                 result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, fontRegistryPath, 0, KEY_READ, &hKey);
99                 if(result != ERROR_SUCCESS){
100                     return delegateResult;
101                 }
102                 DWORD maxKeySize, maxDataSize;
103                 result = RegQueryInfoKey(hKey, null, null, null, null, null, null, null, &maxKeySize, &maxDataSize, null, null);
104                 if(result != ERROR_SUCCESS){
105                     return delegateResult;
106                 }
107                 DWORD valueIndex = 0;
108                 auto valueName = new BYTE[maxKeySize];
109                 auto value = new BYTE[maxDataSize];
110                 DWORD keySize, valueSize, valueType;
111                 do {
112                     valueSize = maxDataSize;
113                     keySize = maxKeySize;
114                     result = RegEnumValue(hKey, valueIndex, cast(WCHAR*)valueName.ptr, &keySize, null, &valueType, value.ptr, &valueSize);
115                     valueIndex++;
116                     if(result != ERROR_SUCCESS || valueType != REG_SZ) {
117                         continue;
118                     }
119                     auto keyNice = (cast(wchar*)(valueName[0..keySize])).to!string;
120                     auto valueNice = value[0..valueSize];
121                     delegateResult = dg(keyNice, valueNice);
122                     if(delegateResult)
123                         break;
124                 }while(result != ERROR_NO_MORE_ITEMS);
125                 RegCloseKey(hKey);
126                 return delegateResult;
127             }
128         }
129         return Iterator();
130     }
131 
132     SelectedFont findFont(string name, int size){
133         foreach(key, valueBytes; iterateRegistryKey("Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts")){
134             auto value = (cast(wchar*)valueBytes.ptr).to!string;
135             if(name == key || (name ~ " (TrueType)") == key){
136                 return SelectedFont(winDir ~ "\\Fonts\\" ~ value, 0);
137             }
138         }
139         return SelectedFont("", 0);
140     }
141 }
142 
143 
144 version(Windows){
145     enum FALLBACK_FONT = "Tahoma";
146 }
147 version(Posix){
148     enum FALLBACK_FONT = "sans";
149 }
150 
151 
152 class Font {
153 
154     const string name;
155     const int size;
156     const int height;
157     const int verticalOffset;
158     const int em;
159     GlContext context;
160 
161     this(GlContext context, string name, int size){
162         this.context = context;
163         if(name.split("-")[$-1].isNumeric)
164             size = name.split("-")[$-1].to!int;
165         this.name = name;
166         this.size = size;
167 
168         bool found = false;
169         auto selected = findFont(name, size);
170         if(!selected.path.length){
171             Log.warning("Could not find font \"%s\"".format(name));
172             selected = findFont(FALLBACK_FONT, size);
173             if(!selected.path.length)
174                 throw new Exception("Failed to find fallback font \"%s\", no usable font available".format(FALLBACK_FONT));
175         }
176         FT_Face face;
177         FT_Error r = FT_New_Face(ftLib, selected.path.toStringz(), selected.index, &face);
178         if(r)
179             throw new Exception("Failed to load font \"" ~ name ~ "\": " ~ tostring(r));
180         else {
181             FT_Set_Char_Size(face, size*64, size*64, 96, 96);
182             ftFace = face;
183             auto scale = face.size.metrics.x_scale;
184             height = cast(int)FT_MulFix(face.height, scale)/64;
185             em = cast(int)FT_MulFix(face.units_per_EM, scale)/64;
186             verticalOffset = -cast(int)FT_MulFix(face.descender, scale)/64;
187         }
188     }
189 
190     protected {
191         Glyph[dchar] glyphs;
192         FT_Face ftFace;
193     }
194 
195     static class Glyph {
196         uint tex;
197         Batch vao;
198         long advance;
199         private this(){}
200     }
201 
202     Glyph opIndex(dchar c){
203         if(c in glyphs)
204             return glyphs[c];
205         auto g = new Glyph;
206         FT_Error e = FT_Load_Glyph(ftFace, FT_Get_Char_Index(ftFace, c), FT_LOAD_FORCE_AUTOHINT);
207         if(e)
208             throw new Exception("FT_Load_Glyph failed (" ~ tostring(e) ~ ")");
209         FT_Glyph glyph;
210         e = FT_Get_Glyph(ftFace.glyph, &glyph);
211         if(e)
212             throw new Exception("FT_Get_Glyph failed (" ~ tostring(e) ~ ")");
213         FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_LCD, null, 1);
214         FT_Bitmap bitmap = (cast(FT_BitmapGlyph)glyph).bitmap;
215         int width = bitmap.width/3;
216         int pad = bitmap.pitch-bitmap.width;
217         int height = bitmap.rows;
218         auto textureData = new GLubyte[width * height * 4];
219         for(int y=0; y < height; y++){
220             for(int x=0; x < width; x++){
221                 auto pixel = &textureData[(x + y*width)*4];
222                 pixel[0..3] = bitmap.buffer[(x + y*width)*3+y*pad .. (x + y*width)*3+y*pad+3];
223                 pixel[3] = 1;
224                 /+
225                 pixel[0..3] = bitmap.buffer[(x + y*width)*3+y*pad .. (x + y*width)*3+y*pad+3];
226                 pixel[3] = pixel[0..3].maxElement;
227                 foreach(ref b; pixel[0..4])
228                     b = b.applyGamma;
229                 //pixel[3] = 255;
230                 +/
231             }
232         }
233         context.genTextures(1, &g.tex);
234         context.bindTexture(GL_TEXTURE_2D, g.tex);
235         context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
236         context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
237         context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
238         context.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
239         context.pixelStorei(GL_UNPACK_ALIGNMENT, 1);
240         context.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, textureData.ptr);
241         long x = ftFace.glyph.metrics.horiBearingX/64;
242         long y = ftFace.glyph.metrics.horiBearingY/64 + verticalOffset;
243         g.advance = ftFace.glyph.metrics.horiAdvance/64;
244         g.vao = new Batch(context, gl.triangleFan, Batch.vert3 ~ Batch.tex2, [
245             x, y, 0, 0, 0,
246             x, y-height, 0, 0, 1,
247             x+width, y-height, 0, 1, 1,
248             x+width, y, 0, 1, 0
249         ]);
250         glyphs[c] = g;
251         return g;
252     }
253 
254 };