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