1 // Written in the D programming language. 2 3 /** 4 This file contains FontManager implementation based on FreeType library. 5 6 Copyright: Vadim Lopatin, 2014 7 License: Boost License 1.0 8 Authors: Vadim Lopatin, coolreader.org@gmail.com 9 */ 10 module dlangui.graphics.ftfonts; 11 12 import dlangui.core.config; 13 static if (ENABLE_FREETYPE): 14 15 import dlangui.graphics.fonts; 16 17 import derelict.freetype.ft; 18 import dlangui.core.logger; 19 import dlangui.core.collections; 20 import std.algorithm; 21 import std.file; 22 import std.string; 23 import std.utf; 24 25 __gshared int[string] STD_FONT_FACES; 26 27 int stdFontFacePriority(string face) { 28 if (auto p = (face in STD_FONT_FACES)) 29 return *p; 30 return 0; 31 } 32 33 /// define debug=FontResources for logging of font file resources creation/freeing 34 //debug = FontResources; 35 36 private struct FontDef { 37 immutable FontFamily family; 38 immutable string face; 39 immutable bool italic; 40 immutable int weight; 41 42 this(FontFamily family, string face, bool italic, int weight) { 43 this.family = family; 44 this.face = face; 45 this.italic = italic; 46 this.weight = weight; 47 } 48 bool opEquals(ref const FontDef v) const { 49 return family == v.family && italic == v.italic && weight == v.weight && face.equal(v.face); 50 } 51 hash_t toHash() const nothrow @safe { 52 hash_t res = 123; 53 res = res * 31 + cast(hash_t)italic; 54 res = res * 31 + cast(hash_t)weight; 55 res = res * 31 + cast(hash_t)family; 56 res = res * 31 + typeid(face).getHash(&face); 57 return res; 58 } 59 } 60 61 private class FontFileItem { 62 private FontList _activeFonts; 63 private FT_Library _library; 64 private FontDef _def; 65 string[] _filenames; 66 @property ref FontDef def() { return _def; } 67 @property string[] filenames() { return _filenames; } 68 @property FT_Library library() { return _library; } 69 void addFile(string fn) { 70 // check for duplicate entry 71 foreach (ref string existing; _filenames) 72 if (fn.equal(existing)) 73 return; 74 _filenames ~= fn; 75 } 76 this(FT_Library library, ref FontDef def) { 77 _library = library; 78 _def = def; 79 } 80 81 private FontRef _nullFontRef; 82 ref FontRef get(int size) { 83 int index = _activeFonts.find(size); 84 if (index >= 0) 85 return _activeFonts.get(index); 86 FreeTypeFont font = new FreeTypeFont(this, size); 87 if (!font.create()) { 88 destroy(font); 89 return _nullFontRef; 90 } 91 return _activeFonts.add(font); 92 } 93 94 void clearGlyphCaches() { 95 _activeFonts.clearGlyphCache(); 96 } 97 void checkpoint() { 98 _activeFonts.checkpoint(); 99 } 100 void cleanup() { 101 _activeFonts.cleanup(); 102 } 103 } 104 105 class FreeTypeFontFile { 106 private string _filename; 107 private string _faceName; 108 private FT_Library _library; 109 private FT_Face _face; 110 private FT_GlyphSlot _slot; 111 private FT_Matrix _matrix; /* transformation matrix */ 112 113 @property FT_Library library() { return _library; } 114 115 private int _height; 116 private int _size; 117 private int _baseline; 118 private int _weight; 119 private bool _italic; 120 121 /// filename 122 @property string filename() { return _filename; } 123 // properties as detected after opening of file 124 @property string face() { return _faceName; } 125 @property int height() { return _height; } 126 @property int size() { return _size; } 127 @property int baseline() { return _baseline; } 128 @property int weight() { return _weight; } 129 @property bool italic() { return _italic; } 130 131 debug private static __gshared int _instanceCount; 132 debug @property static int instanceCount() { return _instanceCount; } 133 this(FT_Library library, string filename) { 134 _library = library; 135 _filename = filename; 136 _matrix.xx = 0x10000; 137 _matrix.yy = 0x10000; 138 _matrix.xy = 0; 139 _matrix.yx = 0; 140 debug ++_instanceCount; 141 debug(FontResources) Log.d("Created FreeTypeFontFile, count=", _instanceCount); 142 } 143 144 ~this() { 145 clear(); 146 debug --_instanceCount; 147 debug(FontResources) Log.d("Destroyed FreeTypeFontFile, count=", _instanceCount); 148 } 149 150 private static string familyName(FT_Face face) 151 { 152 string faceName = fromStringz(face.family_name).dup; 153 string styleName = fromStringz(face.style_name).dup; 154 if (faceName.equal("Arial") && styleName.equal("Narrow")) 155 faceName ~= " Narrow"; 156 else if (styleName.equal("Condensed")) 157 faceName ~= " Condensed"; 158 return faceName; 159 } 160 161 /// open face with specified size 162 bool open(int size, int index = 0) { 163 int error = FT_New_Face( _library, _filename.toStringz, index, &_face); /* create face object */ 164 if (error) 165 return false; 166 if ( _filename.endsWith(".pfb") || _filename.endsWith(".pfa") ) { 167 string kernFile = _filename[0 .. $ - 4]; 168 if (exists(kernFile ~ ".afm")) { 169 kernFile ~= ".afm"; 170 } else if (exists(kernFile ~ ".pfm" )) { 171 kernFile ~= ".pfm"; 172 } else { 173 kernFile.destroy(); 174 } 175 if (kernFile.length > 0) 176 error = FT_Attach_File(_face, kernFile.toStringz); 177 } 178 debug(FontResources) Log.d("Font file opened successfully"); 179 _slot = _face.glyph; 180 _faceName = familyName(_face); 181 error = FT_Set_Pixel_Sizes( 182 _face, /* handle to face object */ 183 0, /* pixel_width */ 184 size ); /* pixel_height */ 185 if (error) { 186 clear(); 187 return false; 188 } 189 _height = cast(int)(_face.size.metrics.height >> 6); 190 _size = size; 191 _baseline = _height + cast(int)(_face.size.metrics.descender >> 6); 192 _weight = _face.style_flags & FT_STYLE_FLAG_BOLD ? FontWeight.Bold : FontWeight.Normal; 193 _italic = _face.style_flags & FT_STYLE_FLAG_ITALIC ? true : false; 194 debug(FontResources) Log.d("Opened font face=", _faceName, " height=", _height, " size=", size, " weight=", weight, " italic=", italic); 195 return true; // successfully opened 196 } 197 198 /// find some suitable replacement for important characters missing in font 199 static dchar getReplacementChar(dchar code) { 200 switch (code) { 201 case UNICODE_SOFT_HYPHEN_CODE: 202 return '-'; 203 case 0x0401: // CYRILLIC CAPITAL LETTER IO 204 return 0x0415; //CYRILLIC CAPITAL LETTER IE 205 case 0x0451: // CYRILLIC SMALL LETTER IO 206 return 0x0435; // CYRILLIC SMALL LETTER IE 207 case UNICODE_NO_BREAK_SPACE: 208 return ' '; 209 case 0x2010: 210 case 0x2011: 211 case 0x2012: 212 case 0x2013: 213 case 0x2014: 214 case 0x2015: 215 return '-'; 216 case 0x2018: 217 case 0x2019: 218 case 0x201a: 219 case 0x201b: 220 return '\''; 221 case 0x201c: 222 case 0x201d: 223 case 0x201e: 224 case 0x201f: 225 case 0x00ab: 226 case 0x00bb: 227 return '\"'; 228 case 0x2039: 229 return '<'; 230 case 0x203A: 231 case '‣': 232 case '►': 233 return '>'; 234 case 0x2044: 235 return '/'; 236 case 0x2022: // css_lst_disc: 237 return '*'; 238 case 0x26AA: // css_lst_disc: 239 case 0x25E6: // css_lst_disc: 240 case 0x25CF: // css_lst_disc: 241 return 'o'; 242 case 0x25CB: // css_lst_circle: 243 return '*'; 244 case 0x25A0: // css_lst_square: 245 return '-'; 246 default: 247 return 0; 248 } 249 } 250 251 /// find glyph index for character 252 FT_UInt getCharIndex(dchar code, dchar def_char = 0) { 253 if ( code=='\t' ) 254 code = ' '; 255 FT_UInt ch_glyph_index = FT_Get_Char_Index(_face, code); 256 if (ch_glyph_index == 0) { 257 dchar replacement = getReplacementChar(code); 258 if (replacement) 259 ch_glyph_index = FT_Get_Char_Index(_face, replacement); 260 if (ch_glyph_index == 0 && def_char) 261 ch_glyph_index = FT_Get_Char_Index( _face, def_char ); 262 } 263 return ch_glyph_index; 264 } 265 266 /// retrieve glyph information, filling glyph struct; returns false if glyph not found 267 bool getGlyphInfo(dchar code, ref Glyph glyph, dchar def_char, bool withImage = true) 268 { 269 //FONT_GUARD 270 int glyph_index = getCharIndex(code, def_char); 271 int flags = FT_LOAD_DEFAULT; 272 const bool _drawMonochrome = _size < FontManager.minAnitialiasedFontSize; 273 SubpixelRenderingMode subpixel = _drawMonochrome ? SubpixelRenderingMode.None : FontManager.subpixelRenderingMode; 274 flags |= (!_drawMonochrome ? (subpixel ? FT_LOAD_TARGET_LCD : (FontManager.instance.hintingMode == HintingMode.Light ? FT_LOAD_TARGET_LIGHT : FT_LOAD_TARGET_NORMAL)) : FT_LOAD_TARGET_MONO); 275 if (withImage) 276 flags |= FT_LOAD_RENDER; 277 if (FontManager.instance.hintingMode == HintingMode.AutoHint || FontManager.instance.hintingMode == HintingMode.Light) 278 flags |= FT_LOAD_FORCE_AUTOHINT; 279 else if (FontManager.instance.hintingMode == HintingMode.Disabled) 280 flags |= FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING; 281 int error = FT_Load_Glyph( 282 _face, /* handle to face object */ 283 glyph_index, /* glyph index */ 284 flags ); /* load flags, see below */ 285 if ( error ) 286 return false; 287 glyph.lastUsage = 1; 288 glyph.blackBoxX = cast(ushort)(_slot.metrics.width >> 6); 289 glyph.blackBoxY = cast(ubyte)(_slot.metrics.height >> 6); 290 glyph.originX = cast(byte)(_slot.metrics.horiBearingX >> 6); 291 glyph.originY = cast(byte)(_slot.metrics.horiBearingY >> 6); 292 glyph.width = cast(ubyte)(myabs(cast(int)(_slot.metrics.horiAdvance)) >> 6); 293 glyph.subpixelMode = subpixel; 294 //glyph.glyphIndex = cast(ushort)code; 295 if (withImage) { 296 FT_Bitmap* bitmap = &_slot.bitmap; 297 ushort w = cast(ushort)(bitmap.width); 298 ubyte h = cast(ubyte)(bitmap.rows); 299 glyph.blackBoxX = w; 300 glyph.blackBoxY = h; 301 int sz = w * cast(int)h; 302 if (sz > 0) { 303 glyph.glyph = new ubyte[sz]; 304 if (_drawMonochrome) { 305 // monochrome bitmap 306 ubyte mask = 0x80; 307 ubyte * ptr = bitmap.buffer; 308 ubyte * dst = glyph.glyph.ptr; 309 foreach(y; 0 .. h) { 310 ubyte * row = ptr; 311 mask = 0x80; 312 foreach(x; 0 .. w) { 313 *dst++ = (*row & mask) ? 0xFF : 00; 314 mask >>= 1; 315 if ( !mask && x != w-1) { 316 mask = 0x80; 317 row++; 318 } 319 } 320 ptr += bitmap.pitch; 321 } 322 323 } else { 324 // antialiased 325 foreach(y; 0 .. h) { 326 foreach(x; 0 .. w) { 327 glyph.glyph[y * w + x] = _gamma256.correct(bitmap.buffer[y * bitmap.pitch + x]); 328 } 329 } 330 } 331 } 332 static if (ENABLE_OPENGL) { 333 glyph.id = nextGlyphId(); 334 } 335 } 336 return true; 337 } 338 339 @property bool isNull() { 340 return (_face is null); 341 } 342 343 void clear() { 344 if (_face !is null) 345 FT_Done_Face(_face); 346 _face = null; 347 } 348 349 } 350 351 /** 352 * Font implementation based on FreeType. 353 */ 354 class FreeTypeFont : Font { 355 private FontFileItem _fontItem; 356 private Collection!(FreeTypeFontFile, true) _files; 357 358 debug static __gshared int _instanceCount; 359 debug @property static int instanceCount() { return _instanceCount; } 360 361 /// need to call create() after construction to initialize font 362 this(FontFileItem item, int size) { 363 _fontItem = item; 364 _size = size; 365 _height = size; 366 debug ++_instanceCount; 367 debug(resalloc) Log.d("Created font, count=", _instanceCount); 368 } 369 370 /// do cleanup 371 ~this() { 372 clear(); 373 debug --_instanceCount; 374 debug(resalloc) Log.d("Destroyed font, count=", _instanceCount); 375 } 376 377 private int _size; 378 private int _height; 379 380 private GlyphCache _glyphCache; 381 382 383 /// cleanup resources 384 override void clear() { 385 _files.clear(); 386 } 387 388 uint getGlyphIndex(dchar code) 389 { 390 return 0; 391 } 392 393 /// find glyph index for character 394 bool findGlyph(dchar code, dchar def_char, ref FT_UInt index, ref FreeTypeFontFile file) { 395 foreach(FreeTypeFontFile f; _files) { 396 index = f.getCharIndex(code, def_char); 397 if (index != 0) { 398 file = f; 399 return true; 400 } 401 } 402 return false; 403 } 404 405 override Glyph * getCharGlyph(dchar ch, bool withImage = true) { 406 if (ch > 0xFFFF) // do not support unicode chars above 0xFFFF - due to cache limitations 407 return null; 408 //long measureStart = std.datetime.Clock.currStdTime; 409 Glyph * found = _glyphCache.find(cast(ushort)ch); 410 //long measureEnd = std.datetime.Clock.currStdTime; 411 //long duration = measureEnd - measureStart; 412 //if (duration > 10000) 413 //if (duration > 10000) 414 // Log.d("ft _glyphCache.find took ", duration / 10, " ns"); 415 if (found !is null) 416 return found; 417 //Log.v("Glyph ", ch, " is not found in cache, getting from font"); 418 FT_UInt index; 419 FreeTypeFontFile file; 420 if (!findGlyph(ch, 0, index, file)) { 421 if (!findGlyph(ch, '?', index, file)) 422 return null; 423 } 424 Glyph * glyph = new Glyph; 425 if (!file.getGlyphInfo(ch, *glyph, 0, withImage)) 426 return null; 427 if (withImage) 428 return _glyphCache.put(ch, glyph); 429 return glyph; 430 } 431 432 /// load font files 433 bool create() { 434 if (!isNull()) 435 clear(); 436 foreach (string filename; _fontItem.filenames) { 437 FreeTypeFontFile file = new FreeTypeFontFile(_fontItem.library, filename); 438 if (file.open(_size, 0)) { 439 _files.add(file); 440 } else { 441 destroy(file); 442 } 443 } 444 return _files.length > 0; 445 } 446 447 /// clear usage flags for all entries 448 override void checkpoint() { 449 _glyphCache.checkpoint(); 450 } 451 452 /// removes entries not used after last call of checkpoint() or cleanup() 453 override void cleanup() { 454 _glyphCache.cleanup(); 455 } 456 457 /// clears glyph cache 458 override void clearGlyphCache() { 459 _glyphCache.clear(); 460 } 461 462 @property override int size() { return _size; } 463 @property override int height() { return _files.length > 0 ? _files[0].height : _size; } 464 @property override int weight() { return _fontItem.def.weight; } 465 @property override int baseline() { return _files.length > 0 ? _files[0].baseline : 0; } 466 @property override bool italic() { return _fontItem.def.italic; } 467 @property override string face() { return _fontItem.def.face; } 468 @property override FontFamily family() { return _fontItem.def.family; } 469 @property override bool isNull() { return _files.length == 0; } 470 } 471 472 private derelict.util.exception.ShouldThrow missingSymFunc( string symName ) { 473 import std.algorithm : equal; 474 static import derelict.util.exception; 475 foreach(s; ["FT_New_Face", "FT_Attach_File", "FT_Set_Pixel_Sizes", 476 "FT_Get_Char_Index", "FT_Load_Glyph", "FT_Done_Face", 477 "FT_Init_FreeType", "FT_Done_FreeType"]) { 478 if (symName.equal(s)) // Symbol is used 479 return derelict.util.exception.ShouldThrow.Yes; 480 } 481 // Don't throw for unused symbol 482 return derelict.util.exception.ShouldThrow.No; 483 } 484 485 /// FreeType based font manager. 486 class FreeTypeFontManager : FontManager { 487 488 private FT_Library _library; 489 private FontFileItem[] _fontFiles; 490 491 private FontFileItem findFileItem(ref FontDef def) { 492 foreach(FontFileItem item; _fontFiles) 493 if (item.def == def) 494 return item; 495 return null; 496 } 497 498 /// override to return list of font faces available 499 override FontFaceProps[] getFaces() { 500 FontFaceProps[] res; 501 for (int i = 0; i < _fontFiles.length; i++) { 502 FontFaceProps item = FontFaceProps(_fontFiles[i].def.face, _fontFiles[i].def.family); 503 bool found = false; 504 for (int j = 0; j < res.length; j++) { 505 if (res[j].face == item.face) { 506 found = true; 507 break; 508 } 509 } 510 if (!found) 511 res ~= item; 512 } 513 return res; 514 } 515 516 517 private static int faceMatch(string requested, string existing) { 518 if (!requested.icmp("Arial")) { 519 if (!existing.icmp("DejaVu Sans")) { 520 return 200; 521 } 522 } 523 if (!requested.icmp("Times New Roman")) { 524 if (!existing.icmp("DejaVu Serif")) { 525 return 200; 526 } 527 } 528 if (!requested.icmp("Courier New")) { 529 if (!existing.icmp("DejaVu Sans Mono")) { 530 return 200; 531 } 532 } 533 return stdFontFacePriority(existing) * 10; 534 } 535 536 private FontFileItem findBestMatch(int weight, bool italic, FontFamily family, string face) { 537 FontFileItem best = null; 538 int bestScore = 0; 539 string[] faces = face ? split(face, ",") : null; 540 foreach(int index, FontFileItem item; _fontFiles) { 541 int score = 0; 542 int bestFaceMatch = 0; 543 if (faces && face.length) { 544 foreach(i; 0 .. faces.length) { 545 string f = faces[i].strip; 546 if (f.icmp(item.def.face) == 0) { 547 score += 3000 - i; 548 break; 549 } 550 int match = faceMatch(f, item.def.face); 551 if (match > bestFaceMatch) 552 bestFaceMatch = match; 553 } 554 } 555 score += bestFaceMatch; 556 if (family == item.def.family) 557 score += 1000; // family match 558 if (italic == item.def.italic) 559 score += 50; // italic match 560 int weightDiff = myabs(weight - item.def.weight); 561 score += 30 - weightDiff / 30; // weight match 562 if (score > bestScore) { 563 bestScore = score; 564 best = item; 565 } 566 } 567 return best; 568 } 569 570 //private FontList _activeFonts; 571 572 private static __gshared FontRef _nullFontRef; 573 574 this() { 575 // load dynaic library 576 try { 577 Log.v("DerelictFT: Loading FreeType library"); 578 if (!DerelictFT) { 579 Log.w("DerelictFT is null. Compiler bug? Applying workaround to fix it."); 580 version(Android) { 581 //DerelictFT = new DerelictFTLoader("libft2.so"); 582 DerelictFT = new DerelictFTLoader; 583 } else { 584 DerelictFT = new DerelictFTLoader; 585 } 586 } 587 DerelictFT.missingSymbolCallback = &missingSymFunc; 588 Log.v("DerelictFT: Missing symbols callback is registered"); 589 DerelictFT.load(); 590 Log.v("DerelictFT: Loaded"); 591 } catch (Exception e) { 592 Log.e("Derelict: cannot load freetype shared library: ", e.msg); 593 throw new Exception("Cannot load freetype library"); 594 } 595 Log.v("Initializing FreeType library"); 596 // init library 597 int error = FT_Init_FreeType(&_library); 598 if (error) { 599 Log.e("Cannot init freetype library, error=", error); 600 throw new Exception("Cannot init freetype library"); 601 } 602 //FT_Library_SetLcdFilter(_library, FT_LCD_FILTER_DEFAULT); 603 } 604 ~this() { 605 debug(FontResources) Log.d("FreeTypeFontManager ~this()"); 606 //_activeFonts.clear(); 607 foreach(ref FontFileItem item; _fontFiles) { 608 destroy(item); 609 item = null; 610 } 611 _fontFiles.length = 0; 612 debug(FontResources) Log.d("Destroyed all fonts. Freeing library."); 613 // uninit library 614 if (_library) 615 FT_Done_FreeType(_library); 616 } 617 618 /// get font instance with specified parameters 619 override ref FontRef getFont(int size, int weight, bool italic, FontFamily family, string face) { 620 FontFileItem f = findBestMatch(weight, italic, family, face); 621 if (f is null) 622 return _nullFontRef; 623 //Log.d("getFont requesteed: ", face, " found: ", f.def.face); 624 return f.get(size); 625 } 626 627 /// clear usage flags for all entries 628 override void checkpoint() { 629 foreach(ref ff; _fontFiles) { 630 ff.checkpoint(); 631 } 632 } 633 634 /// removes entries not used after last call of checkpoint() or cleanup() 635 override void cleanup() { 636 foreach(ref ff; _fontFiles) { 637 ff.cleanup(); 638 } 639 } 640 641 /// clears glyph cache 642 override void clearGlyphCaches() { 643 foreach(ref ff; _fontFiles) { 644 ff.clearGlyphCaches(); 645 } 646 } 647 648 bool registerFont(string filename, bool skipUnknown = false) { 649 import std.path : baseName; 650 FontFamily family = FontFamily.SansSerif; 651 string face = null; 652 bool italic = false; 653 int weight = 0; 654 string name = filename.baseName; 655 switch(name) { 656 case "DroidSans.ttf": face="Droid Sans"; weight = FontWeight.Normal; break; 657 case "DroidSans-Bold.ttf": face="Droid Sans"; weight = FontWeight.Bold; break; 658 case "DroidSansMono.ttf": face="Droid Sans Mono"; weight = FontWeight.Normal; family = FontFamily.MonoSpace; break; 659 case "Roboto-Light.ttf": face="Roboto"; weight = FontWeight.Normal; break; 660 case "Roboto-LightItalic.ttf": face="Roboto"; weight = FontWeight.Normal; italic = true; break; 661 case "Roboto-Bold.ttf": face="Roboto"; weight = FontWeight.Bold; break; 662 case "Roboto-BoldItalic.ttf": face="Roboto"; weight = FontWeight.Bold; italic = true; break; 663 default: 664 if (skipUnknown) 665 return false; 666 } 667 return registerFont(filename, FontFamily.SansSerif, face, italic, weight); 668 } 669 670 /// register freetype font by filename - optinally font properties can be passed if known (e.g. from libfontconfig). 671 bool registerFont(string filename, FontFamily family, string face = null, bool italic = false, int weight = 0, bool dontLoadFile = false) { 672 if (_library is null) 673 return false; 674 //Log.v("FreeTypeFontManager.registerFont ", filename, " ", family, " ", face, " italic=", italic, " weight=", weight); 675 if (!exists(filename) || !isFile(filename)) { 676 Log.d("Font file ", filename, " not found"); 677 return false; 678 } 679 680 if (!dontLoadFile) { 681 FreeTypeFontFile font = new FreeTypeFontFile(_library, filename); 682 if (!font.open(24)) { 683 Log.e("Failed to open font ", filename); 684 destroy(font); 685 return false; 686 } 687 688 if (face == null || weight == 0) { 689 // properties are not set by caller 690 // get properties from loaded font 691 face = font.face; 692 italic = font.italic; 693 weight = font.weight; 694 debug(FontResources)Log.d("Using properties from font file: face=", face, " weight=", weight, " italic=", italic); 695 } 696 destroy(font); 697 } 698 699 FontDef def = FontDef(family, face, italic, weight); 700 FontFileItem item = findFileItem(def); 701 if (item is null) { 702 item = new FontFileItem(_library, def); 703 _fontFiles ~= item; 704 } 705 item.addFile(filename); 706 707 // registered 708 return true; 709 } 710 711 /// returns number of registered fonts 712 @property int registeredFontCount() { 713 return cast(int)_fontFiles.length; 714 } 715 716 } 717 718 private int myabs(int n) { return n >= 0 ? n : -n; } 719 720 721 version(Windows) { 722 } else { 723 724 bool registerFontConfigFonts(FreeTypeFontManager fontMan) { 725 import fontconfig; 726 727 try { 728 DerelictFC.load(); 729 } catch (Exception e) { 730 Log.w("Cannot load FontConfig shared library"); 731 return false; 732 } 733 734 Log.i("Getting list of fonts using FontConfig"); 735 long startts = currentTimeMillis(); 736 737 FcFontSet *fontset; 738 739 FcObjectSet *os = FcObjectSetBuild(FC_FILE.toStringz, FC_WEIGHT.toStringz, FC_FAMILY.toStringz, 740 FC_SLANT.toStringz, FC_SPACING.toStringz, FC_INDEX.toStringz, 741 FC_STYLE.toStringz, null); 742 FcPattern *pat = FcPatternCreate(); 743 //FcBool b = 1; 744 FcPatternAddBool(pat, FC_SCALABLE.toStringz, 1); 745 746 fontset = FcFontList(null, pat, os); 747 748 FcPatternDestroy(pat); 749 FcObjectSetDestroy(os); 750 751 int facesFound = 0; 752 753 // load fonts from file 754 //CRLog::debug("FONTCONFIG: %d font files found", fontset->nfont); 755 foreach(i; 0 .. fontset.nfont) { 756 const (FcChar8) *s = "".toStringz; 757 const (FcChar8) *family = "".toStringz; 758 const (FcChar8) *style = "".toStringz; 759 //FcBool b; 760 FcResult res; 761 //FC_SCALABLE 762 //res = FcPatternGetBool( fontset->fonts[i], FC_OUTLINE, 0, (FcBool*)&b); 763 //if(res != FcResultMatch) 764 // continue; 765 //if ( !b ) 766 // continue; // skip non-scalable fonts 767 res = FcPatternGetString(fontset.fonts[i], FC_FILE.toStringz, 0, cast(FcChar8 **)&s); 768 if (res != FcResultMatch) { 769 continue; 770 } 771 string fn = fromStringz(s).dup; 772 string fn16 = toLower(fn); 773 if (!fn16.endsWith(".ttf") && !fn16.endsWith(".odf") && !fn16.endsWith(".otf") && !fn16.endsWith(".pfb") && !fn16.endsWith(".pfa") ) { 774 continue; 775 } 776 int weight = FC_WEIGHT_MEDIUM; 777 res = FcPatternGetInteger(fontset.fonts[i], FC_WEIGHT.toStringz, 0, &weight); 778 if(res != FcResultMatch) { 779 //CRLog::debug("no FC_WEIGHT for %s", s); 780 //continue; 781 } 782 switch ( weight ) { 783 case FC_WEIGHT_THIN: // 0 784 weight = 100; 785 break; 786 case FC_WEIGHT_EXTRALIGHT: // 40 787 //case FC_WEIGHT_ULTRALIGHT FC_WEIGHT_EXTRALIGHT 788 weight = 200; 789 break; 790 case FC_WEIGHT_LIGHT: // 50 791 case FC_WEIGHT_BOOK: // 75 792 case FC_WEIGHT_REGULAR: // 80 793 //case FC_WEIGHT_NORMAL: FC_WEIGHT_REGULAR 794 weight = 400; 795 break; 796 case FC_WEIGHT_MEDIUM: // 100 797 weight = 500; 798 break; 799 case FC_WEIGHT_DEMIBOLD: // 180 800 //case FC_WEIGHT_SEMIBOLD: FC_WEIGHT_DEMIBOLD 801 weight = 600; 802 break; 803 case FC_WEIGHT_BOLD: // 200 804 weight = 700; 805 break; 806 case FC_WEIGHT_EXTRABOLD: // 205 807 //case FC_WEIGHT_ULTRABOLD: FC_WEIGHT_EXTRABOLD 808 weight = 800; 809 break; 810 case FC_WEIGHT_BLACK: // 210 811 //case FC_WEIGHT_HEAVY: FC_WEIGHT_BLACK 812 weight = 900; 813 break; 814 case FC_WEIGHT_EXTRABLACK: // 215 815 //case FC_WEIGHT_ULTRABLACK: FC_WEIGHT_EXTRABLACK 816 weight = 900; 817 break; 818 default: 819 weight = 400; 820 break; 821 } 822 FcBool scalable = 0; 823 res = FcPatternGetBool(fontset.fonts[i], FC_SCALABLE.toStringz, 0, &scalable); 824 int index = 0; 825 res = FcPatternGetInteger(fontset.fonts[i], FC_INDEX.toStringz, 0, &index); 826 if(res != FcResultMatch) { 827 //CRLog::debug("no FC_INDEX for %s", s); 828 //continue; 829 } 830 res = FcPatternGetString(fontset.fonts[i], FC_FAMILY.toStringz, 0, cast(FcChar8 **)&family); 831 if(res != FcResultMatch) { 832 //CRLog::debug("no FC_FAMILY for %s", s); 833 continue; 834 } 835 res = FcPatternGetString(fontset.fonts[i], FC_STYLE.toStringz, 0, cast(FcChar8 **)&style); 836 if(res != FcResultMatch) { 837 //CRLog::debug("no FC_STYLE for %s", s); 838 style = "".toStringz; 839 //continue; 840 } 841 int slant = FC_SLANT_ROMAN; 842 res = FcPatternGetInteger(fontset.fonts[i], FC_SLANT.toStringz, 0, &slant); 843 if(res != FcResultMatch) { 844 //CRLog::debug("no FC_SLANT for %s", s); 845 //continue; 846 } 847 int spacing = 0; 848 res = FcPatternGetInteger(fontset.fonts[i], FC_SPACING.toStringz, 0, &spacing); 849 if(res != FcResultMatch) { 850 //CRLog::debug("no FC_SPACING for %s", s); 851 //continue; 852 } 853 // int cr_weight; 854 // switch(weight) { 855 // case FC_WEIGHT_LIGHT: cr_weight = 200; break; 856 // case FC_WEIGHT_MEDIUM: cr_weight = 300; break; 857 // case FC_WEIGHT_DEMIBOLD: cr_weight = 500; break; 858 // case FC_WEIGHT_BOLD: cr_weight = 700; break; 859 // case FC_WEIGHT_BLACK: cr_weight = 800; break; 860 // default: cr_weight=300; break; 861 // } 862 FontFamily fontFamily = FontFamily.SansSerif; 863 string face16 = family.fromStringz.toLower.dup; 864 if (spacing == FC_MONO) 865 fontFamily = FontFamily.MonoSpace; 866 else if (face16.indexOf("sans") >= 0) 867 fontFamily = FontFamily.SansSerif; 868 else if (face16.indexOf("serif") >= 0) 869 fontFamily = FontFamily.Serif; 870 871 //css_ff_inherit, 872 //css_ff_serif, 873 //css_ff_sans_serif, 874 //css_ff_cursive, 875 //css_ff_fantasy, 876 //css_ff_monospace, 877 bool italic = (slant!=FC_SLANT_ROMAN); 878 879 string face = family.fromStringz.dup; 880 string style16 = style.fromStringz.toLower.dup; 881 if (style16.indexOf("condensed") >= 0) 882 face ~= " Condensed"; 883 else if (style16.indexOf("extralight") >= 0) 884 face ~= " Extra Light"; 885 886 if (fontMan.registerFont(fn, fontFamily, face, italic, weight, true)) 887 facesFound++; 888 /* 889 LVFontDef def( 890 lString8((const char*)s), 891 -1, // height==-1 for scalable fonts 892 weight, 893 italic, 894 fontFamily, 895 face, 896 index 897 ); 898 899 CRLog::debug("FONTCONFIG: Font family:%s style:%s weight:%d slant:%d spacing:%d file:%s", family, style, weight, slant, spacing, s); 900 if ( _cache.findDuplicate( &def ) ) { 901 CRLog::debug("is duplicate, skipping"); 902 continue; 903 } 904 _cache.update( &def, LVFontRef(NULL) ); 905 906 if ( scalable && !def.getItalic() ) { 907 LVFontDef newDef( def ); 908 newDef.setItalic(2); // can italicize 909 if ( !_cache.findDuplicate( &newDef ) ) 910 _cache.update( &newDef, LVFontRef(NULL) ); 911 } 912 913 */ 914 facesFound++; 915 916 917 } 918 919 FcFontSetDestroy(fontset); 920 921 922 long elapsed = currentTimeMillis - startts; 923 Log.i("FontConfig: ", facesFound, " font files registered in ", elapsed, "ms"); 924 //CRLog::info("FONTCONFIG: %d fonts registered", facesFound); 925 926 /+ 927 string[] fallback_faces = [ 928 "Arial Unicode MS", 929 "AR PL ShanHeiSun Uni", 930 "Liberation Sans" 931 // TODO: more faces 932 ]; 933 934 for ( int i=0; fallback_faces[i]; i++ ) 935 if ( SetFallbackFontFace(lString8(fallback_faces[i])) ) { 936 //CRLog::info("Fallback font %s is found", fallback_faces[i]); 937 break; 938 } else { 939 //CRLog::trace("Fallback font %s is not found", fallback_faces[i]); 940 } 941 +/ 942 943 return facesFound > 0; 944 } 945 }