1 module armos.graphics.font;
2 
3 import derelict.freetype.ft;
4 import armos.graphics;
5 import armos.math;
6 import armos.types;
7 
8 /++
9     読み込まれる文字を表します.
10 +/
11 struct Glyph{
12     size_t index;
13     uint glyph;
14     int height;
15     int width;
16     int bearingX, bearingY;
17     int xmin, xmax, ymin, ymax;
18     int advance;
19     float tW,tH;
20     float t1,t2,v1,v2;
21 
22     void setupInfo(in size_t index, in uint glyph, in FT_Face face){
23         this.index = index;
24         this.glyph = glyph;
25         height     = cast(int)face.glyph.metrics.height >>6;
26         width      = cast(int)face.glyph.metrics.width >>6;
27         bearingX   = cast(int)face.glyph.metrics.horiBearingX >>6;
28         bearingY   = cast(int)face.glyph.metrics.horiBearingY >>6;
29         xmin       = face.glyph.bitmap_left;
30         xmax       = xmin + width;
31         ymin       = -face.glyph.bitmap_top;
32         ymax       = ymin + height;
33         advance    = cast(int)face.glyph.metrics.horiAdvance >>6;
34         tW         = width;
35         tH         = height;
36     }
37 } 
38 
39 /++
40 Fontを読み込み,描画を行います.
41 +/
42 class Font{
43     private alias This = typeof(this);
44     public{
45         this(){
46             DerelictFT.load();
47             if(!_librariesInitialized){
48                 FT_Error error = FT_Init_FreeType( &_library );
49                 if(!error){
50                     _librariesInitialized = true;
51                 }
52             }
53             _mesh = new Mesh;
54             _mesh.primitiveMode = PrimitiveMode.Triangles;
55         }
56 
57         ~this(){
58             FT_Done_Face(_face);
59         }
60 
61         /++
62         +/
63         This load(
64             in string fontPath, in int fontSize, 
65             in bool isAntiAliased=false,
66             in bool isFullCharacterSet=false,
67             in bool makeContours=false,
68             in float simplifyAmt=0.3f,
69             in int dpi=0
70         ){
71             int fontId = 0;
72             _fontSize = fontSize;
73             FT_Error error = FT_New_Face( _library, fontPath.ptr, fontId, &_face );
74 
75             if(dpi!=0) _dpi = dpi;
76             
77             FT_Set_Char_Size( _face, fontSize << 6, fontSize << 6, _dpi, _dpi);
78             _fontUnitScale = (cast(float)fontSize * _dpi) / (72 * _face.units_per_EM);
79             _lineHeight = _face.height * _fontUnitScale;
80             _ascenderHeight = _face.ascender * _fontUnitScale;
81             _descenderHeight = _face.descender * _fontUnitScale;
82             
83             _glyphBBox.set(
84                     _face.bbox.xMin * _fontUnitScale,
85                     _face.bbox.yMin * _fontUnitScale,
86                     (_face.bbox.xMax - _face.bbox.xMin) * _fontUnitScale,
87                     (_face.bbox.yMax - _face.bbox.yMin) * _fontUnitScale
88                     );
89             
90             _useKerning = FT_HAS_KERNING( _face );
91             _numCharacters = (isFullCharacterSet ? 256 : 128) - NUM_CHARACTER_TO_START;
92             loadEachCharacters(isAntiAliased);
93             if(isAntiAliased && _fontSize>20){
94             	_textureAtlas.minMagFilter(TextureMinFilter.Linear,TextureMagFilter.Linear);
95             }else{
96             	_textureAtlas.minMagFilter(TextureMinFilter.Nearest,TextureMagFilter.Nearest);
97             }
98             return this;
99         }
100 
101         /++
102         +/
103         This drawString(in string str, in int x, in int y){
104             if (str.length == 0) return this;
105 
106             _mesh.vertices   = new Vector4f[](str.length*4);
107             _mesh.texCoords0 = new Vector4f[](str.length*4);
108             _mesh.indices    = new int[](str.length*6);
109 
110             float xShift = x;
111             float yShift = y + _lineHeight;
112             int previousCharacter = 0;
113             foreach (size_t i, c; str) {
114                 switch (c) {
115                     case '\n':
116                         yShift += _lineHeight;
117                         xShift = x;
118                         previousCharacter = 0;
119                         break;
120                     case '\t':
121                         xShift += toCharacter(' ').advance * _letterSpace * 4;
122                         previousCharacter = c;
123                         break;
124                     case ' ':
125                         xShift += toCharacter(' ').advance * _letterSpace;
126                         previousCharacter = c;
127                         break;
128                     default:
129                         auto glyph = _characters[_glyphIndexMap[c]];
130                         if(previousCharacter>0){
131                             xShift += kerning(c,previousCharacter);
132                         }
133                         _mesh.setCharacterToStringMesh(glyph, i, xShift, yShift);
134                         xShift += glyph.advance*_letterSpace;
135                         previousCharacter = c;
136                 }
137             }
138             _mesh.drawWireFrame;
139             currentMaterial.texture("tex0", _textureAtlas);
140             _mesh.drawFill;
141             return this;
142         }
143 
144         //TODO
145         /++
146         +/
147         // void drawStringAsShapes(string drawnString, int x, int y)const{}
148     }//public
149 
150     private{
151         static immutable  NUM_CHARACTER_TO_START = 32;
152         static FT_Library _library;
153         static bool _librariesInitialized = false;
154         static int _dpi = 96;
155         Glyph[] _characters;
156         size_t[uint] _glyphIndexMap;
157         FT_Face _face;
158         int _fontSize;
159         float _fontUnitScale;
160         float _lineHeight = 0.0;
161         float _ascenderHeight = 0.0;
162         float _descenderHeight = 0.0;
163         float _letterSpace = 1f;
164         bool _useKerning;
165         int _numCharacters = 0;
166         Rectangle _glyphBBox;
167         Mesh _mesh;
168         Texture _textureAtlas;
169 
170         float kerning(in uint c, in uint previousCharacter){
171             if(FT_HAS_KERNING( _face )){
172                 FT_Vector v;
173                 FT_Get_Kerning(_face, FT_Get_Char_Index(_face, c), FT_Get_Char_Index(_face, previousCharacter), 1, &v);
174                 return v.x * _fontUnitScale;
175             }else{
176                 return 0;
177             }
178         }
179 
180         Glyph toCharacter(char c){
181             return _characters[_glyphIndexMap[c]];
182         }
183 
184         void loadEachCharacters(in bool isAntiAliased){
185             int border = 4;
186             long areaSum = 0;
187             _characters = new Glyph[](_numCharacters);
188             Bitmap!(char)[] expandedData = new Bitmap!(char)[](_numCharacters);
189 
190             foreach (size_t i, ref character; _characters) {
191                 uint glyph = cast(uint)(i+NUM_CHARACTER_TO_START);
192                 _glyphIndexMap[glyph] = i;
193                 if (glyph == 0xA4) glyph = 0x20AC; // hack to load the euro sign, all codes in 8859-15 match with utf-32 except for this one
194                 FT_Error error = FT_Load_Glyph( _face, FT_Get_Char_Index( _face, glyph ), isAntiAliased ?  FT_LOAD_FORCE_AUTOHINT : FT_LOAD_DEFAULT );
195 
196                 if (isAntiAliased){
197                     FT_Render_Glyph(_face.glyph, FT_RENDER_MODE_NORMAL);
198                 }else{
199                     FT_Render_Glyph(_face.glyph, FT_RENDER_MODE_MONO);
200                 }
201 
202                 FT_Bitmap* bitmap= &_face.glyph.bitmap;
203 
204                 auto width  = bitmap.width;
205                 auto height = bitmap.rows;
206 
207                 character.setupInfo(i, glyph, _face);
208 
209                 areaSum += cast(long)( (character.tW+border*2)*(character.tH+border*2) );
210 
211                 if(width==0 || height==0) continue;
212                 expandedData[i].allocate(width, height, ColorFormat.RGBA);
213                 expandedData[i].setAllPixels(0, 0);
214                 expandedData[i].setAllPixels(1, 0);
215                 expandedData[i].setAllPixels(2, 0);
216                 expandedData[i].setAllPixels(3, 0);
217 
218                 if (isAntiAliased){
219                     // Optimizable
220                     for (int y = 0; y < bitmap.rows; ++y) {
221                         for (int x = 0; x < bitmap.width; ++x) {
222                             auto index = x + y * bitmap.pitch;
223                             auto c = bitmap.buffer[index];
224                             expandedData[i].pixel(x, y, 0, 255);
225                             expandedData[i].pixel(x, y, 1, 255);
226                             expandedData[i].pixel(x, y, 2, 255);
227                             expandedData[i].pixel(x, y, 3, c);
228                         }
229                     }
230                 } else {
231                     auto src =  bitmap.buffer;
232                     for(typeof(height) j=0; j < height; j++) {
233                         char b=0;
234                         auto bptr =  src;
235                         for(typeof(width) k=0; k < width; k++){
236 
237                             if (k%8==0){
238                                 b = (*bptr++);
239                             }
240 
241                             expandedData[i].pixel(k, j, 0, b&0x80 ? 255 : 0);
242                             expandedData[i].pixel(k, j, 1, b&0x80 ? 255 : 0);
243                             expandedData[i].pixel(k, j, 2, b&0x80 ? 255 : 0);
244                             expandedData[i].pixel(k, j, 3, b&0x80 ? 255 : 0);
245                             b <<= 1;
246                         }
247                         src += bitmap.pitch;
248                     }
249                 }
250             }
251             _textureAtlas = _characters.packInTexture(areaSum, border, expandedData);
252         }
253     }//private
254 }
255 
256 private void setCharacterToStringMesh(Mesh mesh, in Glyph glyph, in size_t index, in float x, in float y){
257     mesh.vertices[index*4]   = Vector4f(glyph.xmin+x, glyph.ymin+y, 0, 1);
258     mesh.vertices[index*4+1] = Vector4f(glyph.xmax+x, glyph.ymin+y, 0, 1);
259     mesh.vertices[index*4+2] = Vector4f(glyph.xmax+x, glyph.ymax+y, 0, 1);
260     mesh.vertices[index*4+3] = Vector4f(glyph.xmin+x, glyph.ymax+y, 0, 1);
261 
262     mesh.texCoords0[index*4]   = Vector4f(glyph.t1, glyph.v1, 0, 0);
263     mesh.texCoords0[index*4+1] = Vector4f(glyph.t2, glyph.v1, 0, 0);
264     mesh.texCoords0[index*4+2] = Vector4f(glyph.t2, glyph.v2, 0, 0);
265     mesh.texCoords0[index*4+3] = Vector4f(glyph.t1, glyph.v2, 0, 0);
266 
267     import std.conv;
268     mesh.indices[index*6]   = (index*4).to!(Mesh.IndexType);
269     mesh.indices[index*6+1] = (index*4+1).to!(Mesh.IndexType);
270     mesh.indices[index*6+2] = (index*4+2).to!(Mesh.IndexType);
271     mesh.indices[index*6+3] = (index*4+2).to!(Mesh.IndexType);
272     mesh.indices[index*6+4] = (index*4+3).to!(Mesh.IndexType);
273     mesh.indices[index*6+5] = (index*4).to!(Mesh.IndexType);
274 }
275 
276 private Texture packInTexture(ref Glyph[] characters, in long areaSum, in int border, Bitmap!(char)[] expandedData){
277     import std.algorithm;
278     bool compareCharacters(in Glyph c1, in Glyph c2){
279         if(c1.tH == c2.tH) return c1.tW > c2.tW;
280         else return c1.tH > c2.tH;
281     }
282     auto sortedCharacter = characters.dup;
283     sort!(compareCharacters)(sortedCharacter);
284 
285     Vector2i atlasSize = calcAtlasSize(areaSum, sortedCharacter, border);
286 
287     Bitmap!char atlasPixelsLuminanceAlpha;
288     atlasPixelsLuminanceAlpha.allocate(atlasSize[0], atlasSize[1], ColorFormat.RGBA);
289     atlasPixelsLuminanceAlpha.setAllPixels(0,0);
290     atlasPixelsLuminanceAlpha.setAllPixels(1,0);
291     atlasPixelsLuminanceAlpha.setAllPixels(2,0);
292     atlasPixelsLuminanceAlpha.setAllPixels(3,0);
293 
294 
295     int x=0;
296     int y=0;
297     int maxRowHeight = cast(int)sortedCharacter[0].tH + border*2;
298     for(int i = 0; i < cast(int)sortedCharacter.length; i++){
299         Bitmap!(char)* charBitmap = &expandedData[cast(int)sortedCharacter[i].index];
300 
301         if(x+cast(int)sortedCharacter[i].tW + border*2 > atlasSize[0]){
302             x = 0;
303             y += maxRowHeight;
304             maxRowHeight = cast(int)sortedCharacter[i].tH + border*2;
305         }
306 
307         characters[sortedCharacter[i].index].t1 = cast( float )(x + border)/cast( float )(atlasSize[0]);
308         characters[sortedCharacter[i].index].v1 = cast( float )(y + border)/cast( float )(atlasSize[1]);
309         characters[sortedCharacter[i].index].t2 = cast(float)(characters[sortedCharacter[i].index].tW + x + border)/cast(float)(atlasSize[0]);
310         characters[sortedCharacter[i].index].v2 = cast(float)(characters[sortedCharacter[i].index].tH + y + border)/cast(float)(atlasSize[1]);
311         charBitmap.pasteInto(atlasPixelsLuminanceAlpha,x+border,y+border);
312         x+= cast(int)sortedCharacter[i].tW + border*2;
313     }
314     return (new Texture).allocate(atlasPixelsLuminanceAlpha);
315 }
316 
317 private Vector2i calcAtlasSize(long areaSum, in Glyph[] sortedCharacter, int border){
318     import std.math;
319     bool packed = false;
320     float alpha = log(cast(float)areaSum)*1.44269;
321     int w;
322     int h;
323     while(!packed){
324         w = cast(int)pow(2,floor((alpha/2.0) + 0.5)); 
325         h = w;
326         int x=0;
327         int y=0;
328         int maxRowHeight = cast(int)sortedCharacter[0].tH + border*2;
329         packed = true;
330         foreach (ref c; sortedCharacter) {
331             import std.conv:to;
332             if(x+c.tW + border*2 > w){
333                 x = 0;
334                 y += maxRowHeight;
335                 maxRowHeight = c.tH.to!int + border*2;
336                 if(y + maxRowHeight > h){
337                     alpha++;
338                     packed = false;
339                     break;
340                 }
341             }
342             x += c.tW.to!int + border*2;
343         }
344     }
345     return Vector2i(w, h);
346 }