1 /// freetype fonts support 2 module dlangui.graphics.ftfonts; 3 4 import dlangui.graphics.fonts; 5 6 import derelict.freetype.ft; 7 private import dlangui.core.logger; 8 private import std.algorithm; 9 private import std.file; 10 private import std.string; 11 private import std.utf; 12 13 private struct FontDef { 14 immutable FontFamily _family; 15 immutable string _face; 16 immutable bool _italic; 17 immutable int _weight; 18 @property FontFamily family() { return _family; } 19 @property string face() { return _face; } 20 @property bool italic() { return _italic; } 21 @property int weight() { return _weight; } 22 this(FontFamily family, string face, bool italic, int weight) { 23 _family = family; 24 _face = face; 25 _italic = italic; 26 _weight = weight; 27 } 28 const bool opEquals(ref const FontDef v) { 29 return _family == v._family && _italic == v._italic && _weight == v._weight && _face.equal(v._face); 30 } 31 const hash_t toHash() const nothrow @safe { 32 hash_t res = 123; 33 res = res * 31 + cast(hash_t)_italic; 34 res = res * 31 + cast(hash_t)_weight; 35 res = res * 31 + cast(hash_t)_family; 36 res = res * 31 + typeid(_face).getHash(&_face); 37 return res; 38 } 39 } 40 41 private class FontFileItem { 42 private FontList _activeFonts; 43 private FT_Library _library; 44 private FontDef _def; 45 string[] _filenames; 46 @property ref FontDef def() { return _def; } 47 @property string[] filenames() { return _filenames; } 48 @property FT_Library library() { return _library; } 49 void addFile(string fn) { 50 // check for duplicate entry 51 foreach (ref string existing; _filenames) 52 if (fn.equal(existing)) 53 return; 54 _filenames ~= fn; 55 } 56 this(FT_Library library, ref FontDef def) { 57 _library = library; 58 _def = def; 59 } 60 61 private FontRef _nullFontRef; 62 ref FontRef get(int size) { 63 int index = _activeFonts.find(size); 64 if (index >= 0) 65 return _activeFonts.get(index); 66 FreeTypeFont font = new FreeTypeFont(this, size); 67 if (!font.create()) { 68 destroy(font); 69 return _nullFontRef; 70 } 71 return _activeFonts.add(font); 72 } 73 74 } 75 76 private class FreeTypeFontFile { 77 private string _filename; 78 private string _faceName; 79 private FT_Library _library; 80 private FT_Face _face; 81 private FT_GlyphSlot _slot; 82 private FT_Matrix _matrix; /* transformation matrix */ 83 84 @property FT_Library library() { return _library; } 85 86 private int _height; 87 private int _size; 88 private int _baseline; 89 private int _weight; 90 private bool _italic; 91 92 /// filename 93 @property string filename() { return _filename; } 94 // properties as detected after opening of file 95 @property string face() { return _faceName; } 96 @property int height() { return _height; } 97 @property int size() { return _size; } 98 @property int baseline() { return _baseline; } 99 @property int weight() { return _weight; } 100 @property bool italic() { return _italic; } 101 102 //private static int _instanceCount; 103 this(FT_Library library, string filename) { 104 _library = library; 105 _filename = filename; 106 _matrix.xx = 0x10000; 107 _matrix.yy = 0x10000; 108 _matrix.xy = 0; 109 _matrix.yx = 0; 110 //Log.d("Created FreeTypeFontFile, count=", ++_instanceCount); 111 } 112 113 ~this() { 114 clear(); 115 //Log.d("Destroyed FreeTypeFontFile, count=", --_instanceCount); 116 } 117 118 private static string familyName(FT_Face face) 119 { 120 string faceName = fromStringz(face.family_name); 121 string styleName = fromStringz(face.style_name); 122 if (faceName.equal("Arial") && styleName.equal("Narrow")) 123 faceName ~= " Narrow"; 124 else if (styleName.equal("Condensed")) 125 faceName ~= " Condensed"; 126 return faceName; 127 } 128 129 /// open face with specified size 130 bool open(int size, int index = 0) { 131 int error = FT_New_Face( _library, _filename.toStringz, index, &_face); /* create face object */ 132 if (error) 133 return false; 134 if ( _filename.endsWith(".pfb") || _filename.endsWith(".pfa") ) { 135 string kernFile = _filename[0 .. $ - 4]; 136 if (exists(kernFile ~ ".afm")) { 137 kernFile ~= ".afm"; 138 } else if (exists(kernFile ~ ".pfm" )) { 139 kernFile ~= ".pfm"; 140 } else { 141 kernFile.clear(); 142 } 143 if (kernFile.length > 0) 144 error = FT_Attach_File(_face, kernFile.toStringz); 145 } 146 Log.d("Font file opened successfully"); 147 _slot = _face.glyph; 148 _faceName = familyName(_face); 149 error = FT_Set_Pixel_Sizes( 150 _face, /* handle to face object */ 151 0, /* pixel_width */ 152 size ); /* pixel_height */ 153 if (error) { 154 clear(); 155 return false; 156 } 157 _height = cast(int)(_face.size.metrics.height >> 6); 158 _size = size; 159 _baseline = _height + cast(int)(_face.size.metrics.descender >> 6); 160 _weight = _face.style_flags & FT_STYLE_FLAG_BOLD ? FontWeight.Bold : FontWeight.Normal; 161 _italic = _face.style_flags & FT_STYLE_FLAG_ITALIC ? true : false; 162 Log.d("Opened font face=", _faceName, " height=", _height, " size=", size, " weight=", weight, " italic=", italic); 163 return true; // successfully opened 164 } 165 166 /// find some suitable replacement for important characters missing in font 167 static dchar getReplacementChar(dchar code) { 168 switch (code) { 169 case UNICODE_SOFT_HYPHEN_CODE: 170 return '-'; 171 case 0x0401: // CYRILLIC CAPITAL LETTER IO 172 return 0x0415; //CYRILLIC CAPITAL LETTER IE 173 case 0x0451: // CYRILLIC SMALL LETTER IO 174 return 0x0435; // CYRILLIC SMALL LETTER IE 175 case UNICODE_NO_BREAK_SPACE: 176 return ' '; 177 case 0x2010: 178 case 0x2011: 179 case 0x2012: 180 case 0x2013: 181 case 0x2014: 182 case 0x2015: 183 return '-'; 184 case 0x2018: 185 case 0x2019: 186 case 0x201a: 187 case 0x201b: 188 return '\''; 189 case 0x201c: 190 case 0x201d: 191 case 0x201e: 192 case 0x201f: 193 case 0x00ab: 194 case 0x00bb: 195 return '\"'; 196 case 0x2039: 197 return '<'; 198 case 0x203A: 199 return '>'; 200 case 0x2044: 201 return '/'; 202 case 0x2022: // css_lst_disc: 203 return '*'; 204 case 0x26AA: // css_lst_disc: 205 case 0x25E6: // css_lst_disc: 206 case 0x25CF: // css_lst_disc: 207 return 'o'; 208 case 0x25CB: // css_lst_circle: 209 return '*'; 210 case 0x25A0: // css_lst_square: 211 return '-'; 212 default: 213 return 0; 214 } 215 } 216 217 /// find glyph index for character 218 FT_UInt getCharIndex(dchar code, dchar def_char = 0) { 219 if ( code=='\t' ) 220 code = ' '; 221 FT_UInt ch_glyph_index = FT_Get_Char_Index(_face, code); 222 if (ch_glyph_index == 0) { 223 dchar replacement = getReplacementChar(code); 224 if (replacement) 225 ch_glyph_index = FT_Get_Char_Index(_face, replacement); 226 if (ch_glyph_index == 0 && def_char) 227 ch_glyph_index = FT_Get_Char_Index( _face, def_char ); 228 } 229 return ch_glyph_index; 230 } 231 232 /// retrieve glyph information, filling glyph struct; returns false if glyph not found 233 bool getGlyphInfo(dchar code, ref Glyph glyph, dchar def_char, bool withImage = true) 234 { 235 //FONT_GUARD 236 int glyph_index = getCharIndex(code, def_char); 237 int flags = FT_LOAD_DEFAULT; 238 const bool _drawMonochrome = false; 239 flags |= (!_drawMonochrome ? FT_LOAD_TARGET_NORMAL : FT_LOAD_TARGET_MONO); 240 if (withImage) 241 flags |= FT_LOAD_RENDER; 242 //if (_hintingMode == HINTING_MODE_AUTOHINT) 243 // flags |= FT_LOAD_FORCE_AUTOHINT; 244 //else if (_hintingMode == HINTING_MODE_DISABLED) 245 // flags |= FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING; 246 int error = FT_Load_Glyph( 247 _face, /* handle to face object */ 248 glyph_index, /* glyph index */ 249 flags ); /* load flags, see below */ 250 if ( error ) 251 return false; 252 glyph.lastUsage = 1; 253 glyph.blackBoxX = cast(ubyte)(_slot.metrics.width >> 6); 254 glyph.blackBoxY = cast(ubyte)(_slot.metrics.height >> 6); 255 glyph.originX = cast(byte)(_slot.metrics.horiBearingX >> 6); 256 glyph.originY = cast(byte)(_slot.metrics.horiBearingY >> 6); 257 glyph.width = cast(ubyte)(myabs(cast(int)(_slot.metrics.horiAdvance)) >> 6); 258 glyph.glyphIndex = cast(ushort)code; 259 if (withImage) { 260 FT_Bitmap* bitmap = &_slot.bitmap; 261 ubyte w = cast(ubyte)(bitmap.width); 262 ubyte h = cast(ubyte)(bitmap.rows); 263 glyph.blackBoxX = w; 264 glyph.blackBoxY = h; 265 int sz = w * cast(int)h; 266 if (sz > 0) { 267 glyph.glyph = new ubyte[sz]; 268 for (int i = 0; i < sz; i++) 269 glyph.glyph[i] = bitmap.buffer[i]; 270 } 271 version (USE_OPENGL) { 272 glyph.id = nextGlyphId(); 273 } 274 } 275 return true; 276 } 277 278 @property bool isNull() { 279 return (_face is null); 280 } 281 282 void clear() { 283 if (_face !is null) 284 FT_Done_Face(_face); 285 _face = null; 286 } 287 288 } 289 290 /** 291 * Font implementation based on Win32 API system fonts. 292 */ 293 class FreeTypeFont : Font { 294 private FontFileItem _fontItem; 295 private FreeTypeFontFile[] _files; 296 297 static int _instanceCount; 298 /// need to call create() after construction to initialize font 299 this(FontFileItem item, int size) { 300 _fontItem = item; 301 _size = size; 302 _height = size; 303 Log.d("Created font, count=", ++_instanceCount); 304 } 305 306 /// do cleanup 307 ~this() { 308 clear(); 309 Log.d("Destroyed font, count=", --_instanceCount); 310 } 311 312 private int _size; 313 private int _height; 314 315 private GlyphCache _glyphCache; 316 317 318 /// cleanup resources 319 override void clear() { 320 foreach(ref FreeTypeFontFile file; _files) { 321 destroy(file); 322 file = null; 323 } 324 _files.clear(); 325 } 326 327 uint getGlyphIndex(dchar code) 328 { 329 return 0; 330 } 331 332 /// find glyph index for character 333 bool findGlyph(dchar code, dchar def_char, ref FT_UInt index, ref FreeTypeFontFile file) { 334 foreach(FreeTypeFontFile f; _files) { 335 index = f.getCharIndex(code, def_char); 336 if (index != 0) { 337 file = f; 338 return true; 339 } 340 } 341 return false; 342 } 343 344 private Glyph tmpGlyphInfo; 345 override Glyph * getCharGlyph(dchar ch, bool withImage = true) { 346 if (ch > 0xFFFF) // do not support unicode chars above 0xFFFF - due to cache limitations 347 return null; 348 long measureStart = std.datetime.Clock.currStdTime; 349 Glyph * found = _glyphCache.find(cast(ushort)ch); 350 long measureEnd = std.datetime.Clock.currStdTime; 351 long duration = measureEnd - measureStart; 352 //if (duration > 10000) 353 if (duration > 10000) 354 Log.d("ft _glyphCache.find took ", duration / 10, " ns"); 355 if (found !is null) 356 return found; 357 Log.v("Glyph ", ch, " is not found in cache, getting from font"); 358 FT_UInt index; 359 FreeTypeFontFile file; 360 if (!findGlyph(ch, 0, index, file)) { 361 if (!findGlyph(ch, '?', index, file)) 362 return null; 363 } 364 if (!file.getGlyphInfo(ch, tmpGlyphInfo, 0, withImage)) 365 return null; 366 if (withImage) 367 return _glyphCache.put(cast(ushort)ch, &tmpGlyphInfo); 368 return &tmpGlyphInfo; 369 } 370 371 // draw text string to buffer 372 override void drawText(DrawBuf buf, int x, int y, const dchar[] text, uint color) { 373 int[] widths; 374 int bl = baseline; 375 int xx = 0; 376 for (int i = 0; i < text.length; i++) { 377 Glyph * glyph = getCharGlyph(text[i], true); 378 if (glyph is null) 379 continue; 380 if ( glyph.blackBoxX && glyph.blackBoxY ) { 381 int x0 = x + xx + glyph.originX; 382 int y0 = y + bl - glyph.originY; 383 if (x0 > buf.width) 384 break; // outside right bound 385 Rect rc = Rect(x0, y0, x0 + glyph.blackBoxX, y0 + glyph.blackBoxY); 386 if (buf.applyClipping(rc)) 387 buf.drawGlyph( x0, 388 y0, 389 glyph, 390 color); 391 } 392 xx += glyph.width; 393 } 394 } 395 396 override int measureText(const dchar[] text, ref int[] widths, int maxWidth) { 397 if (text.length == 0) 398 return 0; 399 const dchar * pstr = text.ptr; 400 uint len = cast(uint)text.length; 401 int x = 0; 402 int charsMeasured = 0; 403 int * pwidths = widths.ptr; 404 for (int i = 0; i < len; i++) { 405 //auto measureStart = std.datetime.Clock.currAppTick; 406 Glyph * glyph = getCharGlyph(pstr[i], true); // TODO: what is better 407 //auto measureEnd = std.datetime.Clock.currAppTick; 408 //auto duration = measureEnd - measureStart; 409 //if (duration.length > 10) 410 // Log.d("ft measureText took ", duration.length, " ticks"); 411 if (glyph is null) { 412 // if no glyph, use previous width - treat as zero width 413 pwidths[i] = i > 0 ? pwidths[i-1] : 0; 414 continue; 415 } 416 int w = x + glyph.width; // using advance 417 int w2 = x + glyph.originX + glyph.blackBoxX; // using black box 418 if (w < w2) // choose bigger value 419 w = w2; 420 pwidths[i] = w; 421 x += glyph.width; 422 charsMeasured = i + 1; 423 if (x > maxWidth) 424 break; 425 } 426 return charsMeasured; 427 } 428 429 bool create() { 430 if (!isNull()) 431 clear(); 432 foreach (string filename; _fontItem.filenames) { 433 FreeTypeFontFile file = new FreeTypeFontFile(_fontItem.library, filename); 434 if (file.open(_size, 0)) { 435 _files ~= file; 436 } 437 } 438 return _files.length > 0; 439 } 440 441 // clear usage flags for all entries 442 override void checkpoint() { 443 _glyphCache.checkpoint(); 444 } 445 446 // removes entries not used after last call of checkpoint() or cleanup() 447 override void cleanup() { 448 _glyphCache.cleanup(); 449 } 450 451 @property override int size() { return _size; } 452 @property override int height() { return _files.length > 0 ? _files[0].height : _size; } 453 @property override int weight() { return _fontItem.def.weight; } 454 @property override int baseline() { return _files.length > 0 ? _files[0].baseline : 0; } 455 @property override bool italic() { return _fontItem.def.italic; } 456 @property override string face() { return _fontItem.def.face; } 457 @property override FontFamily family() { return _fontItem.def.family; } 458 @property override bool isNull() { return _files.length == 0; } 459 } 460 461 462 /// FreeType based font manager. 463 class FreeTypeFontManager : FontManager { 464 465 private FT_Library _library; 466 private FontFileItem[] _fontFiles; 467 468 private FontFileItem findFileItem(ref FontDef def) { 469 foreach(FontFileItem item; _fontFiles) 470 if (item.def == def) 471 return item; 472 return null; 473 } 474 475 private FontFileItem findBestMatch(int weight, bool italic, FontFamily family, string face) { 476 FontFileItem best = null; 477 int bestScore = 0; 478 foreach(FontFileItem item; _fontFiles) { 479 int score = 0; 480 if (face is null || face.equal(item.def.face)) 481 score += 200; // face match 482 if (family == item.def.family) 483 score += 100; // family match 484 if (italic == item.def.italic) 485 score += 50; // italic match 486 int weightDiff = myabs(weight - item.def.weight); 487 score += 30 - weightDiff / 30; // weight match 488 if (score > bestScore) { 489 bestScore = score; 490 best = item; 491 } 492 } 493 return best; 494 } 495 496 //private FontList _activeFonts; 497 498 private static FontRef _nullFontRef; 499 500 this() { 501 // load dynaic library 502 DerelictFT.load(); 503 // init library 504 int error = FT_Init_FreeType(&_library); 505 if (error) { 506 Log.e("Cannot init freetype library, error=", error); 507 throw new Exception("Cannot init freetype library"); 508 } 509 } 510 ~this() { 511 Log.d("FreeTypeFontManager ~this()"); 512 //_activeFonts.clear(); 513 foreach(ref FontFileItem item; _fontFiles) { 514 destroy(item); 515 item = null; 516 } 517 _fontFiles.length = 0; 518 Log.d("Destroyed all fonts. Freeing library."); 519 // uninit library 520 if (_library) 521 FT_Done_FreeType(_library); 522 } 523 524 /// get font instance with specified parameters 525 override ref FontRef getFont(int size, int weight, bool italic, FontFamily family, string face) { 526 FontFileItem f = findBestMatch(weight, italic, family, face); 527 if (f is null) 528 return _nullFontRef; 529 return f.get(size); 530 } 531 532 /// clear usage flags for all entries 533 override void checkpoint() { 534 } 535 536 /// removes entries not used after last call of checkpoint() or cleanup() 537 override void cleanup() { 538 } 539 540 /// register freetype font by filename - optinally font properties can be passed if known (e.g. from libfontconfig). 541 bool registerFont(string filename, FontFamily family = FontFamily.SansSerif, string face = null, bool italic = false, int weight = 0) { 542 if (_library is null) 543 return false; 544 Log.d("FreeTypeFontManager.registerFont ", filename, " ", family, " ", face, " italic=", italic, " weight=", weight); 545 if (!exists(filename) || !isFile(filename)) 546 return false; 547 548 FreeTypeFontFile font = new FreeTypeFontFile(_library, filename); 549 if (!font.open(24)) { 550 Log.e("Failed to open font ", filename); 551 destroy(font); 552 return false; 553 } 554 555 if (face == null || weight == 0) { 556 // properties are not set by caller 557 // get properties from loaded font 558 face = font.face; 559 italic = font.italic; 560 weight = font.weight; 561 Log.d("Using properties from font file: face=", face, " weight=", weight, " italic=", italic); 562 } 563 564 FontDef def = FontDef(family, face, italic, weight); 565 FontFileItem item = findFileItem(def); 566 if (item is null) { 567 item = new FontFileItem(_library, def); 568 _fontFiles ~= item; 569 } 570 item.addFile(filename); 571 572 // registered 573 return true; 574 } 575 576 } 577 578 private int myabs(int n) { return n >= 0 ? n : -n; }