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