1 module Engine.Font; 2 3 import derelict.freetype.ft; 4 import derelict.util.exception; 5 import std.stdio; 6 import std.conv; 7 import Engine.Texture; 8 import Engine.Atlas; 9 import Engine.math; 10 11 alias dchar[] CharSet; 12 13 14 struct LetterInfo { 15 public recti AtlasRect; 16 public dchar Charecter; 17 public double XOffset; 18 public double YOffset; 19 public double XAdvance; 20 public double RelativeWidth; 21 public double RelativeHeight; 22 } 23 24 class Font { 25 extern(C) package static FT_Library ftLibrary; 26 27 package FT_Face face; 28 Texture Atlas; 29 LetterInfo[dchar] Map; 30 31 static CharSet ASCII = CreateCharSet('!', '~'); 32 33 static CharSet CreateCharSet(dchar min, dchar max) { 34 auto arr = new dchar[max-min+1]; 35 for(auto i=0;min<=max;min++,i++) { 36 arr[i] = min; 37 } 38 return arr; 39 } 40 41 static string CreateSet(dchar min, dchar max) { 42 string s = "[to!dchar(" ~ to!string(to!int(min)); 43 min++; 44 for(;min<=max;min++) { 45 s ~= "),to!dchar(" ~ to!string(to!int(min)); 46 } 47 s ~= ")]"; 48 return s; 49 } 50 51 static this() { 52 DerelictFT.missingSymbolCallback( (string symbolName){ 53 switch (symbolName) { 54 case "FT_Get_PS_Font_Value": 55 case "FT_Get_CID_Registry_Ordering_Supplement" : 56 case "FT_Get_CID_From_Glyph_Index" : 57 case "FT_Get_CID_Is_Internally_CID_Keyed" : 58 case "FT_Stream_OpenBzip2": 59 case "FT_Gzip_Uncompress": 60 case "FT_Property_Set": 61 case "FT_Property_Get": 62 case "FT_Outline_EmboldenXY": 63 return ShouldThrow.No; 64 default: 65 return ShouldThrow.Yes; 66 } 67 }); 68 DerelictFT.load(); 69 if (FT_Init_FreeType(&ftLibrary) != 0) 70 throw new Exception("Cannot initialize FreeType"); 71 } 72 73 74 75 76 this(string filepath, int size, CharSet set) { 77 auto error = FT_New_Face( ftLibrary, 78 &filepath.ptr[0], 79 0, 80 &face ); 81 if ( error == FT_Err_Unknown_File_Format ) 82 { 83 throw new Exception("Unknown file format."); 84 } 85 else if ( error ) 86 { 87 writeln(error); 88 throw new Exception("Could not read the file " ~ cast(string)filepath ~ " ."); 89 90 } 91 92 error = FT_Set_Pixel_Sizes(face, 0, size); 93 if (error != 0) 94 throw new Exception("Could not set font size(is the font fixed sized?)."); 95 96 97 recti[] rects = new recti[set.length]; 98 int rectsIndex = 0; 99 foreach(dchar chr; set) { 100 auto glyph_index = FT_Get_Char_Index( face, chr ); 101 if (glyph_index == 0) { 102 throw new Exception("Could not find \'" ~to!string(chr)~ "\' glyph index."); 103 } 104 105 error = FT_Load_Glyph( face, glyph_index, FT_LOAD_DEFAULT ); 106 if ( error != 0) 107 throw new Exception("Could not load glyph \'" ~to!string(chr)~ "\'."); 108 109 /* convert to an anti-aliased bitmap */ 110 error = FT_Render_Glyph( face.glyph, FT_RENDER_MODE_NORMAL ); 111 if ( error ) 112 throw new Exception("Could not render glyph \'" ~to!string(chr)~ "\'."); 113 114 FT_Bitmap bitmap = face.glyph.bitmap; 115 116 auto height = bitmap.rows; 117 auto width = bitmap.width; 118 rects[rectsIndex] = recti(width, height); 119 rectsIndex++; 120 } 121 122 123 auto atlasSize = MaxRectsBinPack.FindOptimalSize(10, rects, 1); 124 if (atlasSize.x == 0) { 125 throw new Exception("Could not find atlas size."); 126 } 127 auto bin = new MaxRectsBinPack(atlasSize.x, atlasSize.y, 1); 128 rects = bin.InsertArray(rects); 129 130 ubyte[] atlas = new ubyte[(atlasSize.x*atlasSize.y*2)]; 131 132 rectsIndex = 0; 133 foreach(dchar chr; set) { 134 auto glyph_index = FT_Get_Char_Index( face, chr ); 135 if (glyph_index == 0) { 136 throw new Exception("Could not find \'" ~to!string(chr)~ "\' glyph index."); 137 } 138 139 error = FT_Load_Glyph( face, glyph_index, FT_LOAD_DEFAULT ); 140 if ( error != 0) 141 throw new Exception("Could no load glyph \'" ~to!string(chr)~ "\'."); 142 143 /* convert to an anti-aliased bitmap */ 144 error = FT_Render_Glyph( face.glyph, FT_RENDER_MODE_NORMAL ); 145 if ( error ) 146 throw new Exception("Could no render glyph \'" ~to!string(chr)~ "\'."); 147 148 auto glyph = face.glyph; 149 FT_Bitmap bitmap = glyph.bitmap; 150 151 152 153 auto pixelSize = bitmap.width/bitmap.pitch; 154 if (pixelSize < 0) { 155 pixelSize = -pixelSize; 156 } 157 auto height = bitmap.rows; 158 auto width = bitmap.width; 159 160 auto r = rects[rectsIndex]; 161 162 FT_Glyph_Metrics metrics = glyph.metrics; 163 164 Map[chr] = LetterInfo(r,chr, 165 ((cast(double)metrics.horiBearingX) / 64) / cast(double)size , 166 ( ((cast(double)metrics.horiBearingY) / 64) - cast(double)r.Dy()) / cast(double)size , 167 ((cast(double)metrics.horiAdvance) / 64) / cast(double)size , 168 cast(double)r.Dx() / cast(double)size, 169 cast(double)r.Dy() / cast(double)size); 170 171 auto buffer = bitmap.buffer; 172 for (int x =0;x<width;x++) { 173 for(int y=0;y<height;y++) { 174 auto pos = ((r.min.x+x)+(r.min.y+y)*atlasSize.x)*2; 175 if (pos >= atlas.length) 176 throw new Exception("DAFUQ"); 177 atlas[pos] = 0xff; 178 atlas[pos+1] = buffer[x+(height-y-1)*width]; 179 } 180 } 181 rectsIndex++; 182 } 183 Atlas = new Texture(atlas, atlasSize.x, atlasSize.y); 184 } 185 }