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     FT_UInt getCharIndex(dchar code, dchar def_char = 0) {
205         if ( code=='\t' )
206             code = ' ';
207         FT_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(FT_UInt prevCharIndex, FT_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 FT_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         FT_UInt index1;
396         FreeTypeFontFile file1;
397         if (!findGlyph(prevChar, 0, index1, file1))
398             return 0;
399         FT_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         FT_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 }