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 }