1 2 module ws.gl.font; 3 4 import 5 file = std.file, 6 std.process, 7 std.traits, 8 9 derelict.freetype.ft, 10 derelict.util.exception, 11 12 ws.io, 13 ws.string, 14 ws.gl.gl,ws.gl.batch; 15 16 17 version(Posix) 18 import std.path; 19 20 21 class Font { 22 23 const string name; 24 const long size; 25 26 protected { 27 this(string name, long size){ 28 this.name = name; 29 this.size = size; 30 } 31 32 /*static ~this(){ 33 FT_Done_FreeType(ftLib); 34 }*/ 35 36 Glyph[dchar] glyphs; 37 bool reference; 38 FT_Face ftFace; 39 40 static bool initialized = false; 41 42 static FT_Library ftLib; 43 static Font[string] fonts; 44 45 static string[] searchPaths; 46 static string[] searchExtensions = ["", ".otf", ".ttf"]; 47 } 48 49 static Font load(string name, int size = 12){ 50 if(!initialized){ 51 DerelictFT.missingSymbolCallback = (name){ 52 if(name == "FT_Gzip_Uncompress") 53 return ShouldThrow.No; 54 return ShouldThrow.Yes; 55 }; 56 DerelictFT.load(); 57 searchPaths ~= "fonts/"; 58 version(Windows) 59 searchPaths ~= "C:/Windows/Fonts/"; 60 version(Posix){ 61 searchPaths ~= "/usr/share/fonts/TTF/"; 62 searchPaths ~= "~/.fonts/".expandTilde; 63 } 64 FT_Error r = FT_Init_FreeType(&ftLib); 65 if(r) 66 throw new Exception("Failed to initialize FreeType 2 [" ~ tostring(r) ~ "]"); 67 initialized = true; 68 } 69 70 if(tostring(name, "::", size) in fonts) 71 return fonts[name ~ "::" ~ tostring(size)]; 72 string wholeName; 73 bool found = false; 74 foreach(folder; searchPaths){ 75 foreach(string ext; searchExtensions){ 76 if(file.exists(folder ~ name ~ ext)){ 77 wholeName = folder ~ name ~ ext; 78 found = true; 79 } 80 } 81 } 82 if(!found){ 83 writeln("Failed to find font file \"" ~ name ~ "\""); 84 if(name == "sans"){ 85 throw new Exception("Failed to find fallback font \"sans\", no usable font available"); 86 } else { 87 Font f = load("sans", size); 88 f.reference = true; 89 fonts[name ~ "::" ~ tostring(size)] = f; 90 return f; 91 } 92 } 93 FT_Face face; 94 FT_Error r = FT_New_Face(ftLib, wholeName.toStringz(), 0, &face); 95 if(r) 96 throw new Exception("Failed to load font \"" ~ name ~ "\": " ~ tostring(r)); 97 else{ 98 auto f = new Font(name, size); 99 FT_Set_Char_Size(face, size << 6, size << 6, 96, 96); 100 f.ftFace = face; 101 f.reference = false; 102 fonts[name ~ "::" ~ tostring(size)] = f; 103 return f; 104 } 105 } 106 107 static class Glyph { 108 uint tex; 109 Batch vao; 110 long advance; 111 private this(){} 112 } 113 114 Glyph opIndex(dchar c){ 115 if(c in glyphs) 116 return glyphs[c]; 117 auto g = new Glyph; 118 if(c == ' '){ 119 g.tex = 0; 120 g.advance = cast(long)(size*0.9); 121 } 122 FT_Error e = FT_Load_Glyph(ftFace, FT_Get_Char_Index(ftFace, c), FT_LOAD_FORCE_AUTOHINT); 123 if(e) 124 throw new Exception("FT_Load_Glyph failed (" ~ tostring(e) ~ ")"); 125 FT_Glyph glyph; 126 e = FT_Get_Glyph(ftFace.glyph, &glyph); 127 if(e) 128 throw new Exception("FT_Get_Glyph failed (" ~ tostring(e) ~ ")"); 129 FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, null, 1); 130 FT_Bitmap bitmap = (cast(FT_BitmapGlyph)glyph).bitmap; 131 int width = bitmap.width; 132 int height = bitmap.rows; 133 auto expandedData = new GLubyte[width * height * 4]; 134 for(int y=0; y < height; y++){ 135 for(int x=0; x < width; x++){ 136 auto pixel = &expandedData[(x + y*width)*4]; 137 pixel[0] = pixel[1] = pixel[2] = 138 (x>=width || y>=bitmap.rows || bitmap.buffer[x + y*width]) ? 255 : 0; 139 pixel[3] = (x>=width || y>=bitmap.rows) ? 0 : bitmap.buffer[x + y*width]; 140 } 141 } 142 glGenTextures(1, &g.tex); 143 glBindTexture(GL_TEXTURE_2D, g.tex); 144 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 145 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 146 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 147 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 148 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 149 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, expandedData.ptr); 150 long x = ftFace.glyph.metrics.horiBearingX >> 6; 151 long y = ftFace.glyph.metrics.horiBearingY >> 6; 152 g.advance = ftFace.glyph.metrics.horiAdvance >> 6; 153 g.vao = new Batch; 154 g.vao.begin(4, GL_TRIANGLE_FAN); 155 g.vao.addPoint([x, y, 0], [0, 0]); 156 g.vao.addPoint([x, y-height, 0], [0, 1]); 157 g.vao.addPoint([x+width, y-height, 0], [1, 1]); 158 g.vao.addPoint([x+width, y, 0], [1, 0]); 159 g.vao.finish(); 160 glyphs[c] = g; 161 return g; 162 } 163 164 }; 165