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 if (symbolName == "FT_Get_PS_Font_Value") 54 return ShouldThrow.No; 55 else 56 return ShouldThrow.Yes; 57 }); 58 DerelictFT.load(); 59 if (FT_Init_FreeType(&ftLibrary) != 0) 60 throw new Exception("Cannot initialize FreeType"); 61 } 62 63 64 65 66 this(string filepath, int size, CharSet set) { 67 auto error = FT_New_Face( ftLibrary, 68 &filepath.ptr[0], 69 0, 70 &face ); 71 if ( error == FT_Err_Unknown_File_Format ) 72 { 73 throw new Exception("Unknown file format."); 74 } 75 else if ( error ) 76 { 77 writeln(error); 78 throw new Exception("Could not read the file " ~ cast(string)filepath ~ " ."); 79 80 } 81 82 error = FT_Set_Pixel_Sizes(face, 0, size); 83 if (error != 0) 84 throw new Exception("Could not set font size(is the font fixed sized?)."); 85 86 87 recti[] rects = new recti[set.length]; 88 int rectsIndex = 0; 89 foreach(dchar chr; set) { 90 auto glyph_index = FT_Get_Char_Index( face, chr ); 91 if (glyph_index == 0) { 92 throw new Exception("Could not find \'" ~to!string(chr)~ "\' glyph index."); 93 } 94 95 error = FT_Load_Glyph( face, glyph_index, FT_LOAD_DEFAULT ); 96 if ( error != 0) 97 throw new Exception("Could not load glyph \'" ~to!string(chr)~ "\'."); 98 99 /* convert to an anti-aliased bitmap */ 100 error = FT_Render_Glyph( face.glyph, FT_RENDER_MODE_NORMAL ); 101 if ( error ) 102 throw new Exception("Could not render glyph \'" ~to!string(chr)~ "\'."); 103 104 FT_Bitmap bitmap = face.glyph.bitmap; 105 106 auto height = bitmap.rows; 107 auto width = bitmap.width; 108 rects[rectsIndex] = recti(width, height); 109 rectsIndex++; 110 } 111 112 113 auto atlasSize = MaxRectsBinPack.FindOptimalSize(10, rects, 1); 114 if (atlasSize.x == 0) { 115 throw new Exception("Could not find atlas size."); 116 } 117 auto bin = new MaxRectsBinPack(atlasSize.x, atlasSize.y, 1); 118 rects = bin.InsertArray(rects); 119 120 ubyte[] atlas = new ubyte[(atlasSize.x*atlasSize.y*2)]; 121 122 rectsIndex = 0; 123 foreach(dchar chr; set) { 124 auto glyph_index = FT_Get_Char_Index( face, chr ); 125 if (glyph_index == 0) { 126 throw new Exception("Could not find \'" ~to!string(chr)~ "\' glyph index."); 127 } 128 129 error = FT_Load_Glyph( face, glyph_index, FT_LOAD_DEFAULT ); 130 if ( error != 0) 131 throw new Exception("Could no load glyph \'" ~to!string(chr)~ "\'."); 132 133 /* convert to an anti-aliased bitmap */ 134 error = FT_Render_Glyph( face.glyph, FT_RENDER_MODE_NORMAL ); 135 if ( error ) 136 throw new Exception("Could no render glyph \'" ~to!string(chr)~ "\'."); 137 138 auto glyph = face.glyph; 139 FT_Bitmap bitmap = glyph.bitmap; 140 141 142 143 auto pixelSize = bitmap.width/bitmap.pitch; 144 if (pixelSize < 0) { 145 pixelSize = -pixelSize; 146 } 147 auto height = bitmap.rows; 148 auto width = bitmap.width; 149 150 auto r = rects[rectsIndex]; 151 152 FT_Glyph_Metrics metrics = glyph.metrics; 153 154 Map[chr] = LetterInfo(r,chr, 155 ((cast(double)metrics.horiBearingX) / 64) / cast(double)size , 156 ( ((cast(double)metrics.horiBearingY) / 64) - cast(double)r.Dy()) / cast(double)size , 157 ((cast(double)metrics.horiAdvance) / 64) / cast(double)size , 158 cast(double)r.Dx() / cast(double)size, 159 cast(double)r.Dy() / cast(double)size); 160 161 auto buffer = bitmap.buffer; 162 for (int x =0;x<width;x++) { 163 for(int y=0;y<height;y++) { 164 auto pos = ((r.min.x+x)+(r.min.y+y)*atlasSize.x)*2; 165 if (pos >= atlas.length) 166 throw new Exception("DAFUQ"); 167 atlas[pos] = 0xff; 168 atlas[pos+1] = buffer[x+(height-y-1)*width]; 169 } 170 } 171 rectsIndex++; 172 } 173 Atlas = new Texture(atlas, atlasSize.x, atlasSize.y); 174 } 175 }