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 };