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(int 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") && !fn16.endsWith(".pfb") && !fn16.endsWith(".pfa")  ) {
775             continue;
776         }
777         int weight = FC_WEIGHT_MEDIUM;
778         res = FcPatternGetInteger(fontset.fonts[i], FC_WEIGHT.toStringz, 0, &weight);
779         if(res != FcResultMatch) {
780             //CRLog::debug("no FC_WEIGHT for %s", s);
781             //continue;
782         }
783         switch ( weight ) {
784         case FC_WEIGHT_THIN:          //    0
785             weight = 100;
786             break;
787         case FC_WEIGHT_EXTRALIGHT:    //    40
788         //case FC_WEIGHT_ULTRALIGHT        FC_WEIGHT_EXTRALIGHT
789             weight = 200;
790             break;
791         case FC_WEIGHT_LIGHT:         //    50
792         case FC_WEIGHT_BOOK:          //    75
793         case FC_WEIGHT_REGULAR:       //    80
794         //case FC_WEIGHT_NORMAL:            FC_WEIGHT_REGULAR
795             weight = 400;
796             break;
797         case FC_WEIGHT_MEDIUM:        //    100
798             weight = 500;
799             break;
800         case FC_WEIGHT_DEMIBOLD:      //    180
801         //case FC_WEIGHT_SEMIBOLD:          FC_WEIGHT_DEMIBOLD
802             weight = 600;
803             break;
804         case FC_WEIGHT_BOLD:          //    200
805             weight = 700;
806             break;
807         case FC_WEIGHT_EXTRABOLD:     //    205
808         //case FC_WEIGHT_ULTRABOLD:         FC_WEIGHT_EXTRABOLD
809             weight = 800;
810             break;
811         case FC_WEIGHT_BLACK:         //    210
812         //case FC_WEIGHT_HEAVY:             FC_WEIGHT_BLACK
813             weight = 900;
814             break;
815         case FC_WEIGHT_EXTRABLACK:    //    215
816         //case FC_WEIGHT_ULTRABLACK:        FC_WEIGHT_EXTRABLACK
817             weight = 900;
818             break;
819         default:
820             weight = 400;
821             break;
822         }
823         FcBool scalable = 0;
824         res = FcPatternGetBool(fontset.fonts[i], FC_SCALABLE.toStringz, 0, &scalable);
825         int index = 0;
826         res = FcPatternGetInteger(fontset.fonts[i], FC_INDEX.toStringz, 0, &index);
827         if(res != FcResultMatch) {
828             //CRLog::debug("no FC_INDEX for %s", s);
829             //continue;
830         }
831         res = FcPatternGetString(fontset.fonts[i], FC_FAMILY.toStringz, 0, cast(FcChar8 **)&family);
832         if(res != FcResultMatch) {
833             //CRLog::debug("no FC_FAMILY for %s", s);
834             continue;
835         }
836         res = FcPatternGetString(fontset.fonts[i], FC_STYLE.toStringz, 0, cast(FcChar8 **)&style);
837         if(res != FcResultMatch) {
838             //CRLog::debug("no FC_STYLE for %s", s);
839             style = "".toStringz;
840             //continue;
841         }
842         int slant = FC_SLANT_ROMAN;
843         res = FcPatternGetInteger(fontset.fonts[i], FC_SLANT.toStringz, 0, &slant);
844         if(res != FcResultMatch) {
845             //CRLog::debug("no FC_SLANT for %s", s);
846             //continue;
847         }
848         int spacing = 0;
849         res = FcPatternGetInteger(fontset.fonts[i], FC_SPACING.toStringz, 0, &spacing);
850         if(res != FcResultMatch) {
851             //CRLog::debug("no FC_SPACING for %s", s);
852             //continue;
853         }
854 //                int cr_weight;
855 //                switch(weight) {
856 //                    case FC_WEIGHT_LIGHT: cr_weight = 200; break;
857 //                    case FC_WEIGHT_MEDIUM: cr_weight = 300; break;
858 //                    case FC_WEIGHT_DEMIBOLD: cr_weight = 500; break;
859 //                    case FC_WEIGHT_BOLD: cr_weight = 700; break;
860 //                    case FC_WEIGHT_BLACK: cr_weight = 800; break;
861 //                    default: cr_weight=300; break;
862 //                }
863         FontFamily fontFamily = FontFamily.SansSerif;
864         string face16 = family.fromStringz.toLower.dup;
865         if (spacing == FC_MONO)
866             fontFamily = FontFamily.MonoSpace;
867         else if (face16.indexOf("sans") >= 0)
868             fontFamily = FontFamily.SansSerif;
869         else if (face16.indexOf("serif") >= 0)
870             fontFamily = FontFamily.Serif;
871 
872         //css_ff_inherit,
873         //css_ff_serif,
874         //css_ff_sans_serif,
875         //css_ff_cursive,
876         //css_ff_fantasy,
877         //css_ff_monospace,
878         bool italic = (slant!=FC_SLANT_ROMAN);
879 
880         string face = family.fromStringz.dup;
881         string style16 = style.fromStringz.toLower.dup;
882         if (style16.indexOf("condensed") >= 0)
883             face ~= " Condensed";
884         else if (style16.indexOf("extralight") >= 0)
885             face ~= " Extra Light";
886 
887         if (fontMan.registerFont(fn, fontFamily, face, italic, weight, true))
888             facesFound++;
889 /*
890         LVFontDef def(
891             lString8((const char*)s),
892             -1, // height==-1 for scalable fonts
893             weight,
894             italic,
895             fontFamily,
896             face,
897             index
898         );
899 
900         CRLog::debug("FONTCONFIG: Font family:%s style:%s weight:%d slant:%d spacing:%d file:%s", family, style, weight, slant, spacing, s);
901         if ( _cache.findDuplicate( &def ) ) {
902             CRLog::debug("is duplicate, skipping");
903             continue;
904         }
905         _cache.update( &def, LVFontRef(NULL) );
906 
907         if ( scalable && !def.getItalic() ) {
908             LVFontDef newDef( def );
909             newDef.setItalic(2); // can italicize
910             if ( !_cache.findDuplicate( &newDef ) )
911                 _cache.update( &newDef, LVFontRef(NULL) );
912         }
913 
914         */
915         facesFound++;
916 
917 
918     }
919 
920     FcFontSetDestroy(fontset);
921 
922 
923     long elapsed = currentTimeMillis - startts;
924     Log.i("FontConfig: ", facesFound, " font files registered in ", elapsed, "ms");
925     //CRLog::info("FONTCONFIG: %d fonts registered", facesFound);
926 
927     /+
928     string[] fallback_faces = [
929         "Arial Unicode MS",
930         "AR PL ShanHeiSun Uni",
931         "Liberation Sans"
932         // TODO: more faces
933     ];
934 
935     for ( int i=0; fallback_faces[i]; i++ )
936         if ( SetFallbackFontFace(lString8(fallback_faces[i])) ) {
937             //CRLog::info("Fallback font %s is found", fallback_faces[i]);
938             break;
939         } else {
940             //CRLog::trace("Fallback font %s is not found", fallback_faces[i]);
941         }
942     +/
943 
944     return facesFound > 0;
945 }
946 }