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 }