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