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 }