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 }