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