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