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