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 199 /// find glyph index for character 200 FT_UInt getCharIndex(dchar code, dchar def_char = 0) { 201 if ( code=='\t' ) 202 code = ' '; 203 FT_UInt ch_glyph_index = FT_Get_Char_Index(_face, code); 204 if (ch_glyph_index == 0) { 205 dchar replacement = getReplacementChar(code); 206 if (replacement) { 207 ch_glyph_index = FT_Get_Char_Index(_face, replacement); 208 if (ch_glyph_index == 0) { 209 replacement = getReplacementChar(replacement); 210 if (replacement) { 211 ch_glyph_index = FT_Get_Char_Index(_face, replacement); 212 } 213 } 214 } 215 if (ch_glyph_index == 0 && def_char) 216 ch_glyph_index = FT_Get_Char_Index( _face, def_char ); 217 } 218 return ch_glyph_index; 219 } 220 221 /// retrieve glyph information, filling glyph struct; returns false if glyph not found 222 bool getGlyphInfo(dchar code, ref Glyph glyph, dchar def_char, bool withImage = true) 223 { 224 //FONT_GUARD 225 int glyph_index = getCharIndex(code, def_char); 226 int flags = FT_LOAD_DEFAULT; 227 const bool _drawMonochrome = _size < FontManager.minAnitialiasedFontSize; 228 SubpixelRenderingMode subpixel = _drawMonochrome ? SubpixelRenderingMode.None : FontManager.subpixelRenderingMode; 229 flags |= (!_drawMonochrome ? (subpixel ? FT_LOAD_TARGET_LCD : (FontManager.instance.hintingMode == HintingMode.Light ? FT_LOAD_TARGET_LIGHT : FT_LOAD_TARGET_NORMAL)) : FT_LOAD_TARGET_MONO); 230 if (withImage) 231 flags |= FT_LOAD_RENDER; 232 if (FontManager.instance.hintingMode == HintingMode.AutoHint || FontManager.instance.hintingMode == HintingMode.Light) 233 flags |= FT_LOAD_FORCE_AUTOHINT; 234 else if (FontManager.instance.hintingMode == HintingMode.Disabled) 235 flags |= FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING; 236 int error = FT_Load_Glyph( 237 _face, /* handle to face object */ 238 glyph_index, /* glyph index */ 239 flags ); /* load flags, see below */ 240 if ( error ) 241 return false; 242 glyph.lastUsage = 1; 243 glyph.blackBoxX = cast(ushort)(_slot.metrics.width >> 6); 244 glyph.blackBoxY = cast(ubyte)(_slot.metrics.height >> 6); 245 glyph.originX = cast(byte)(_slot.metrics.horiBearingX >> 6); 246 glyph.originY = cast(byte)(_slot.metrics.horiBearingY >> 6); 247 glyph.width = cast(ubyte)(myabs(cast(int)(_slot.metrics.horiAdvance)) >> 6); 248 glyph.subpixelMode = subpixel; 249 //glyph.glyphIndex = cast(ushort)code; 250 if (withImage) { 251 FT_Bitmap* bitmap = &_slot.bitmap; 252 ushort w = cast(ushort)(bitmap.width); 253 ubyte h = cast(ubyte)(bitmap.rows); 254 glyph.blackBoxX = w; 255 glyph.blackBoxY = h; 256 int sz = w * cast(int)h; 257 if (sz > 0) { 258 glyph.glyph = new ubyte[sz]; 259 if (_drawMonochrome) { 260 // monochrome bitmap 261 ubyte mask = 0x80; 262 ubyte * ptr = bitmap.buffer; 263 ubyte * dst = glyph.glyph.ptr; 264 foreach(y; 0 .. h) { 265 ubyte * row = ptr; 266 mask = 0x80; 267 foreach(x; 0 .. w) { 268 *dst++ = (*row & mask) ? 0xFF : 00; 269 mask >>= 1; 270 if ( !mask && x != w-1) { 271 mask = 0x80; 272 row++; 273 } 274 } 275 ptr += bitmap.pitch; 276 } 277 278 } else { 279 // antialiased 280 foreach(y; 0 .. h) { 281 foreach(x; 0 .. w) { 282 glyph.glyph[y * w + x] = _gamma256.correct(bitmap.buffer[y * bitmap.pitch + x]); 283 } 284 } 285 } 286 } 287 static if (ENABLE_OPENGL) { 288 glyph.id = nextGlyphId(); 289 } 290 } 291 return true; 292 } 293 294 @property bool isNull() { 295 return (_face is null); 296 } 297 298 void clear() { 299 if (_face !is null) 300 FT_Done_Face(_face); 301 _face = null; 302 } 303 304 } 305 306 /** 307 * Font implementation based on FreeType. 308 */ 309 class FreeTypeFont : Font { 310 private FontFileItem _fontItem; 311 private Collection!(FreeTypeFontFile, true) _files; 312 313 debug static __gshared int _instanceCount; 314 debug @property static int instanceCount() { return _instanceCount; } 315 316 /// need to call create() after construction to initialize font 317 this(FontFileItem item, int size) { 318 _fontItem = item; 319 _size = size; 320 _height = size; 321 debug ++_instanceCount; 322 debug(resalloc) Log.d("Created font, count=", _instanceCount); 323 } 324 325 /// do cleanup 326 ~this() { 327 clear(); 328 debug --_instanceCount; 329 debug(resalloc) Log.d("Destroyed font, count=", _instanceCount); 330 } 331 332 private int _size; 333 private int _height; 334 335 private GlyphCache _glyphCache; 336 337 338 /// cleanup resources 339 override void clear() { 340 _files.clear(); 341 } 342 343 uint getGlyphIndex(dchar code) 344 { 345 return 0; 346 } 347 348 /// find glyph index for character 349 bool findGlyph(dchar code, dchar def_char, ref FT_UInt index, ref FreeTypeFontFile file) { 350 foreach(FreeTypeFontFile f; _files) { 351 index = f.getCharIndex(code, def_char); 352 if (index != 0) { 353 file = f; 354 return true; 355 } 356 } 357 return false; 358 } 359 360 override Glyph * getCharGlyph(dchar ch, bool withImage = true) { 361 if (ch > 0xFFFF) // do not support unicode chars above 0xFFFF - due to cache limitations 362 return null; 363 //long measureStart = std.datetime.Clock.currStdTime; 364 Glyph * found = _glyphCache.find(cast(ushort)ch); 365 //long measureEnd = std.datetime.Clock.currStdTime; 366 //long duration = measureEnd - measureStart; 367 //if (duration > 10000) 368 //if (duration > 10000) 369 // Log.d("ft _glyphCache.find took ", duration / 10, " ns"); 370 if (found !is null) 371 return found; 372 //Log.v("Glyph ", ch, " is not found in cache, getting from font"); 373 FT_UInt index; 374 FreeTypeFontFile file; 375 if (!findGlyph(ch, 0, index, file)) { 376 if (!findGlyph(ch, '?', index, file)) 377 return null; 378 } 379 Glyph * glyph = new Glyph; 380 if (!file.getGlyphInfo(ch, *glyph, 0, withImage)) 381 return null; 382 if (withImage) 383 return _glyphCache.put(ch, glyph); 384 return glyph; 385 } 386 387 /// load font files 388 bool create() { 389 if (!isNull()) 390 clear(); 391 foreach (string filename; _fontItem.filenames) { 392 FreeTypeFontFile file = new FreeTypeFontFile(_fontItem.library, filename); 393 if (file.open(_size, 0)) { 394 _files.add(file); 395 } else { 396 destroy(file); 397 } 398 } 399 return _files.length > 0; 400 } 401 402 /// clear usage flags for all entries 403 override void checkpoint() { 404 _glyphCache.checkpoint(); 405 } 406 407 /// removes entries not used after last call of checkpoint() or cleanup() 408 override void cleanup() { 409 _glyphCache.cleanup(); 410 } 411 412 /// clears glyph cache 413 override void clearGlyphCache() { 414 _glyphCache.clear(); 415 } 416 417 @property override int size() { return _size; } 418 @property override int height() { return _files.length > 0 ? _files[0].height : _size; } 419 @property override int weight() { return _fontItem.def.weight; } 420 @property override int baseline() { return _files.length > 0 ? _files[0].baseline : 0; } 421 @property override bool italic() { return _fontItem.def.italic; } 422 @property override string face() { return _fontItem.def.face; } 423 @property override FontFamily family() { return _fontItem.def.family; } 424 @property override bool isNull() { return _files.length == 0; } 425 } 426 427 private derelict.util.exception.ShouldThrow missingSymFunc( string symName ) { 428 import std.algorithm : equal; 429 static import derelict.util.exception; 430 foreach(s; ["FT_New_Face", "FT_Attach_File", "FT_Set_Pixel_Sizes", 431 "FT_Get_Char_Index", "FT_Load_Glyph", "FT_Done_Face", 432 "FT_Init_FreeType", "FT_Done_FreeType"]) { 433 if (symName.equal(s)) // Symbol is used 434 return derelict.util.exception.ShouldThrow.Yes; 435 } 436 // Don't throw for unused symbol 437 return derelict.util.exception.ShouldThrow.No; 438 } 439 440 /// FreeType based font manager. 441 class FreeTypeFontManager : FontManager { 442 443 private FT_Library _library; 444 private FontFileItem[] _fontFiles; 445 446 private FontFileItem findFileItem(ref FontDef def) { 447 foreach(FontFileItem item; _fontFiles) 448 if (item.def == def) 449 return item; 450 return null; 451 } 452 453 /// override to return list of font faces available 454 override FontFaceProps[] getFaces() { 455 FontFaceProps[] res; 456 for (int i = 0; i < _fontFiles.length; i++) { 457 FontFaceProps item = FontFaceProps(_fontFiles[i].def.face, _fontFiles[i].def.family); 458 bool found = false; 459 for (int j = 0; j < res.length; j++) { 460 if (res[j].face == item.face) { 461 found = true; 462 break; 463 } 464 } 465 if (!found) 466 res ~= item; 467 } 468 return res; 469 } 470 471 472 private static int faceMatch(string requested, string existing) { 473 if (!requested.icmp("Arial")) { 474 if (!existing.icmp("DejaVu Sans")) { 475 return 200; 476 } 477 } 478 if (!requested.icmp("Times New Roman")) { 479 if (!existing.icmp("DejaVu Serif")) { 480 return 200; 481 } 482 } 483 if (!requested.icmp("Courier New")) { 484 if (!existing.icmp("DejaVu Sans Mono")) { 485 return 200; 486 } 487 } 488 return stdFontFacePriority(existing) * 10; 489 } 490 491 private FontFileItem findBestMatch(int weight, bool italic, FontFamily family, string face) { 492 FontFileItem best = null; 493 int bestScore = 0; 494 string[] faces = face ? split(face, ",") : null; 495 foreach(int index, FontFileItem item; _fontFiles) { 496 int score = 0; 497 int bestFaceMatch = 0; 498 if (faces && face.length) { 499 foreach(i; 0 .. faces.length) { 500 string f = faces[i].strip; 501 if (f.icmp(item.def.face) == 0) { 502 score += 3000 - i; 503 break; 504 } 505 int match = faceMatch(f, item.def.face); 506 if (match > bestFaceMatch) 507 bestFaceMatch = match; 508 } 509 } 510 score += bestFaceMatch; 511 if (family == item.def.family) 512 score += 1000; // family match 513 if (italic == item.def.italic) 514 score += 50; // italic match 515 int weightDiff = myabs(weight - item.def.weight); 516 score += 30 - weightDiff / 30; // weight match 517 if (score > bestScore) { 518 bestScore = score; 519 best = item; 520 } 521 } 522 return best; 523 } 524 525 //private FontList _activeFonts; 526 527 private static __gshared FontRef _nullFontRef; 528 529 this() { 530 // load dynaic library 531 try { 532 Log.v("DerelictFT: Loading FreeType library"); 533 if (!DerelictFT) { 534 Log.w("DerelictFT is null. Compiler bug? Applying workaround to fix it."); 535 version(Android) { 536 //DerelictFT = new DerelictFTLoader("libft2.so"); 537 DerelictFT = new DerelictFTLoader; 538 } else { 539 DerelictFT = new DerelictFTLoader; 540 } 541 } 542 DerelictFT.missingSymbolCallback = &missingSymFunc; 543 Log.v("DerelictFT: Missing symbols callback is registered"); 544 DerelictFT.load(); 545 Log.v("DerelictFT: Loaded"); 546 } catch (Exception e) { 547 Log.e("Derelict: cannot load freetype shared library: ", e.msg); 548 throw new Exception("Cannot load freetype library"); 549 } 550 Log.v("Initializing FreeType library"); 551 // init library 552 int error = FT_Init_FreeType(&_library); 553 if (error) { 554 Log.e("Cannot init freetype library, error=", error); 555 throw new Exception("Cannot init freetype library"); 556 } 557 //FT_Library_SetLcdFilter(_library, FT_LCD_FILTER_DEFAULT); 558 } 559 ~this() { 560 debug(FontResources) Log.d("FreeTypeFontManager ~this()"); 561 //_activeFonts.clear(); 562 foreach(ref FontFileItem item; _fontFiles) { 563 destroy(item); 564 item = null; 565 } 566 _fontFiles.length = 0; 567 debug(FontResources) Log.d("Destroyed all fonts. Freeing library."); 568 // uninit library 569 if (_library) 570 FT_Done_FreeType(_library); 571 } 572 573 /// get font instance with specified parameters 574 override ref FontRef getFont(int size, int weight, bool italic, FontFamily family, string face) { 575 FontFileItem f = findBestMatch(weight, italic, family, face); 576 if (f is null) 577 return _nullFontRef; 578 //Log.d("getFont requesteed: ", face, " found: ", f.def.face); 579 return f.get(size); 580 } 581 582 /// clear usage flags for all entries 583 override void checkpoint() { 584 foreach(ref ff; _fontFiles) { 585 ff.checkpoint(); 586 } 587 } 588 589 /// removes entries not used after last call of checkpoint() or cleanup() 590 override void cleanup() { 591 foreach(ref ff; _fontFiles) { 592 ff.cleanup(); 593 } 594 } 595 596 /// clears glyph cache 597 override void clearGlyphCaches() { 598 foreach(ref ff; _fontFiles) { 599 ff.clearGlyphCaches(); 600 } 601 } 602 603 bool registerFont(string filename, bool skipUnknown = false) { 604 import std.path : baseName; 605 FontFamily family = FontFamily.SansSerif; 606 string face = null; 607 bool italic = false; 608 int weight = 0; 609 string name = filename.baseName; 610 switch(name) { 611 case "DroidSans.ttf": face="Droid Sans"; weight = FontWeight.Normal; break; 612 case "DroidSans-Bold.ttf": face="Droid Sans"; weight = FontWeight.Bold; break; 613 case "DroidSansMono.ttf": face="Droid Sans Mono"; weight = FontWeight.Normal; family = FontFamily.MonoSpace; break; 614 case "Roboto-Light.ttf": face="Roboto"; weight = FontWeight.Normal; break; 615 case "Roboto-LightItalic.ttf": face="Roboto"; weight = FontWeight.Normal; italic = true; break; 616 case "Roboto-Bold.ttf": face="Roboto"; weight = FontWeight.Bold; break; 617 case "Roboto-BoldItalic.ttf": face="Roboto"; weight = FontWeight.Bold; italic = true; break; 618 default: 619 if (skipUnknown) 620 return false; 621 } 622 return registerFont(filename, FontFamily.SansSerif, face, italic, weight); 623 } 624 625 /// register freetype font by filename - optinally font properties can be passed if known (e.g. from libfontconfig). 626 bool registerFont(string filename, FontFamily family, string face = null, bool italic = false, int weight = 0, bool dontLoadFile = false) { 627 if (_library is null) 628 return false; 629 //Log.v("FreeTypeFontManager.registerFont ", filename, " ", family, " ", face, " italic=", italic, " weight=", weight); 630 if (!exists(filename) || !isFile(filename)) { 631 Log.d("Font file ", filename, " not found"); 632 return false; 633 } 634 635 if (!dontLoadFile) { 636 FreeTypeFontFile font = new FreeTypeFontFile(_library, filename); 637 if (!font.open(24)) { 638 Log.e("Failed to open font ", filename); 639 destroy(font); 640 return false; 641 } 642 643 if (face == null || weight == 0) { 644 // properties are not set by caller 645 // get properties from loaded font 646 face = font.face; 647 italic = font.italic; 648 weight = font.weight; 649 debug(FontResources)Log.d("Using properties from font file: face=", face, " weight=", weight, " italic=", italic); 650 } 651 destroy(font); 652 } 653 654 FontDef def = FontDef(family, face, italic, weight); 655 FontFileItem item = findFileItem(def); 656 if (item is null) { 657 item = new FontFileItem(_library, def); 658 _fontFiles ~= item; 659 } 660 item.addFile(filename); 661 662 // registered 663 return true; 664 } 665 666 /// returns number of registered fonts 667 @property int registeredFontCount() { 668 return cast(int)_fontFiles.length; 669 } 670 671 } 672 673 private int myabs(int n) { return n >= 0 ? n : -n; } 674 675 676 version(Windows) { 677 } else { 678 679 bool registerFontConfigFonts(FreeTypeFontManager fontMan) { 680 import fontconfig; 681 682 try { 683 DerelictFC.load(); 684 } catch (Exception e) { 685 Log.w("Cannot load FontConfig shared library"); 686 return false; 687 } 688 689 Log.i("Getting list of fonts using FontConfig"); 690 long startts = currentTimeMillis(); 691 692 FcFontSet *fontset; 693 694 FcObjectSet *os = FcObjectSetBuild(FC_FILE.toStringz, FC_WEIGHT.toStringz, FC_FAMILY.toStringz, 695 FC_SLANT.toStringz, FC_SPACING.toStringz, FC_INDEX.toStringz, 696 FC_STYLE.toStringz, null); 697 FcPattern *pat = FcPatternCreate(); 698 //FcBool b = 1; 699 FcPatternAddBool(pat, FC_SCALABLE.toStringz, 1); 700 701 fontset = FcFontList(null, pat, os); 702 703 FcPatternDestroy(pat); 704 FcObjectSetDestroy(os); 705 706 int facesFound = 0; 707 708 // load fonts from file 709 //CRLog::debug("FONTCONFIG: %d font files found", fontset->nfont); 710 foreach(i; 0 .. fontset.nfont) { 711 const (FcChar8) *s = "".toStringz; 712 const (FcChar8) *family = "".toStringz; 713 const (FcChar8) *style = "".toStringz; 714 //FcBool b; 715 FcResult res; 716 //FC_SCALABLE 717 //res = FcPatternGetBool( fontset->fonts[i], FC_OUTLINE, 0, (FcBool*)&b); 718 //if(res != FcResultMatch) 719 // continue; 720 //if ( !b ) 721 // continue; // skip non-scalable fonts 722 res = FcPatternGetString(fontset.fonts[i], FC_FILE.toStringz, 0, cast(FcChar8 **)&s); 723 if (res != FcResultMatch) { 724 continue; 725 } 726 string fn = fromStringz(s).dup; 727 string fn16 = toLower(fn); 728 if (!fn16.endsWith(".ttf") && !fn16.endsWith(".odf") && !fn16.endsWith(".otf") && !fn16.endsWith(".pfb") && !fn16.endsWith(".pfa") ) { 729 continue; 730 } 731 int weight = FC_WEIGHT_MEDIUM; 732 res = FcPatternGetInteger(fontset.fonts[i], FC_WEIGHT.toStringz, 0, &weight); 733 if(res != FcResultMatch) { 734 //CRLog::debug("no FC_WEIGHT for %s", s); 735 //continue; 736 } 737 switch ( weight ) { 738 case FC_WEIGHT_THIN: // 0 739 weight = 100; 740 break; 741 case FC_WEIGHT_EXTRALIGHT: // 40 742 //case FC_WEIGHT_ULTRALIGHT FC_WEIGHT_EXTRALIGHT 743 weight = 200; 744 break; 745 case FC_WEIGHT_LIGHT: // 50 746 case FC_WEIGHT_BOOK: // 75 747 case FC_WEIGHT_REGULAR: // 80 748 //case FC_WEIGHT_NORMAL: FC_WEIGHT_REGULAR 749 weight = 400; 750 break; 751 case FC_WEIGHT_MEDIUM: // 100 752 weight = 500; 753 break; 754 case FC_WEIGHT_DEMIBOLD: // 180 755 //case FC_WEIGHT_SEMIBOLD: FC_WEIGHT_DEMIBOLD 756 weight = 600; 757 break; 758 case FC_WEIGHT_BOLD: // 200 759 weight = 700; 760 break; 761 case FC_WEIGHT_EXTRABOLD: // 205 762 //case FC_WEIGHT_ULTRABOLD: FC_WEIGHT_EXTRABOLD 763 weight = 800; 764 break; 765 case FC_WEIGHT_BLACK: // 210 766 //case FC_WEIGHT_HEAVY: FC_WEIGHT_BLACK 767 weight = 900; 768 break; 769 case FC_WEIGHT_EXTRABLACK: // 215 770 //case FC_WEIGHT_ULTRABLACK: FC_WEIGHT_EXTRABLACK 771 weight = 900; 772 break; 773 default: 774 weight = 400; 775 break; 776 } 777 FcBool scalable = 0; 778 res = FcPatternGetBool(fontset.fonts[i], FC_SCALABLE.toStringz, 0, &scalable); 779 int index = 0; 780 res = FcPatternGetInteger(fontset.fonts[i], FC_INDEX.toStringz, 0, &index); 781 if(res != FcResultMatch) { 782 //CRLog::debug("no FC_INDEX for %s", s); 783 //continue; 784 } 785 res = FcPatternGetString(fontset.fonts[i], FC_FAMILY.toStringz, 0, cast(FcChar8 **)&family); 786 if(res != FcResultMatch) { 787 //CRLog::debug("no FC_FAMILY for %s", s); 788 continue; 789 } 790 res = FcPatternGetString(fontset.fonts[i], FC_STYLE.toStringz, 0, cast(FcChar8 **)&style); 791 if(res != FcResultMatch) { 792 //CRLog::debug("no FC_STYLE for %s", s); 793 style = "".toStringz; 794 //continue; 795 } 796 int slant = FC_SLANT_ROMAN; 797 res = FcPatternGetInteger(fontset.fonts[i], FC_SLANT.toStringz, 0, &slant); 798 if(res != FcResultMatch) { 799 //CRLog::debug("no FC_SLANT for %s", s); 800 //continue; 801 } 802 int spacing = 0; 803 res = FcPatternGetInteger(fontset.fonts[i], FC_SPACING.toStringz, 0, &spacing); 804 if(res != FcResultMatch) { 805 //CRLog::debug("no FC_SPACING for %s", s); 806 //continue; 807 } 808 // int cr_weight; 809 // switch(weight) { 810 // case FC_WEIGHT_LIGHT: cr_weight = 200; break; 811 // case FC_WEIGHT_MEDIUM: cr_weight = 300; break; 812 // case FC_WEIGHT_DEMIBOLD: cr_weight = 500; break; 813 // case FC_WEIGHT_BOLD: cr_weight = 700; break; 814 // case FC_WEIGHT_BLACK: cr_weight = 800; break; 815 // default: cr_weight=300; break; 816 // } 817 FontFamily fontFamily = FontFamily.SansSerif; 818 string face16 = family.fromStringz.toLower.dup; 819 if (spacing == FC_MONO) 820 fontFamily = FontFamily.MonoSpace; 821 else if (face16.indexOf("sans") >= 0) 822 fontFamily = FontFamily.SansSerif; 823 else if (face16.indexOf("serif") >= 0) 824 fontFamily = FontFamily.Serif; 825 826 //css_ff_inherit, 827 //css_ff_serif, 828 //css_ff_sans_serif, 829 //css_ff_cursive, 830 //css_ff_fantasy, 831 //css_ff_monospace, 832 bool italic = (slant!=FC_SLANT_ROMAN); 833 834 string face = family.fromStringz.dup; 835 string style16 = style.fromStringz.toLower.dup; 836 if (style16.indexOf("condensed") >= 0) 837 face ~= " Condensed"; 838 else if (style16.indexOf("extralight") >= 0) 839 face ~= " Extra Light"; 840 841 if (fontMan.registerFont(fn, fontFamily, face, italic, weight, true)) 842 facesFound++; 843 /* 844 LVFontDef def( 845 lString8((const char*)s), 846 -1, // height==-1 for scalable fonts 847 weight, 848 italic, 849 fontFamily, 850 face, 851 index 852 ); 853 854 CRLog::debug("FONTCONFIG: Font family:%s style:%s weight:%d slant:%d spacing:%d file:%s", family, style, weight, slant, spacing, s); 855 if ( _cache.findDuplicate( &def ) ) { 856 CRLog::debug("is duplicate, skipping"); 857 continue; 858 } 859 _cache.update( &def, LVFontRef(NULL) ); 860 861 if ( scalable && !def.getItalic() ) { 862 LVFontDef newDef( def ); 863 newDef.setItalic(2); // can italicize 864 if ( !_cache.findDuplicate( &newDef ) ) 865 _cache.update( &newDef, LVFontRef(NULL) ); 866 } 867 868 */ 869 facesFound++; 870 871 872 } 873 874 FcFontSetDestroy(fontset); 875 876 877 long elapsed = currentTimeMillis - startts; 878 Log.i("FontConfig: ", facesFound, " font files registered in ", elapsed, "ms"); 879 //CRLog::info("FONTCONFIG: %d fonts registered", facesFound); 880 881 /+ 882 string[] fallback_faces = [ 883 "Arial Unicode MS", 884 "AR PL ShanHeiSun Uni", 885 "Liberation Sans" 886 // TODO: more faces 887 ]; 888 889 for ( int i=0; fallback_faces[i]; i++ ) 890 if ( SetFallbackFontFace(lString8(fallback_faces[i])) ) { 891 //CRLog::info("Fallback font %s is found", fallback_faces[i]); 892 break; 893 } else { 894 //CRLog::trace("Fallback font %s is not found", fallback_faces[i]); 895 } 896 +/ 897 898 return facesFound > 0; 899 } 900 }