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