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