1 // Written in the D programming language.
2 
3 /**
4 This module contains base fonts access interface and common implementation.
5 
6 Font - base class for fonts.
7 
8 FontManager - base class for font managers - provides access to available fonts.
9 
10 
11 Actual implementation is:
12 
13 dlangui.graphics.ftfonts - FreeType based font manager.
14 
15 dlangui.platforms.windows.w32fonts - Win32 API based font manager.
16 
17 
18 To enable OpenGL support, build with version(USE_OPENGL);
19 
20 See_Also: dlangui.graphics.drawbuf, DrawBuf, drawbuf, drawbuf.html
21 
22 
23 
24 Synopsis:
25 
26 ----
27 import dlangui.graphics.fonts;
28 
29 // find suitable font of size 25, normal, preferrable Arial, or, if not available, any SansSerif font
30 FontRef font = FontManager.instance.getFont(25, FontWeight.Normal, false, FontFamily.SansSerif, "Arial");
31 
32 dstring sampleText = "Sample text to draw"d;
33 // measure text string width and height (one line)
34 Point sz = font.textSize(sampleText);
35 // draw red text at center of DrawBuf buf
36 font.drawText(buf, buf.width / 2 - sz.x/2, buf.height / 2 - sz.y / 2, sampleText, 0xFF0000);
37 
38 ----
39 
40 Copyright: Vadim Lopatin, 2014
41 License:   Boost License 1.0
42 Authors:   Vadim Lopatin, coolreader.org@gmail.com
43 */
44 module dlangui.graphics.fonts;
45 
46 public import dlangui.core.config;
47 public import dlangui.graphics.drawbuf;
48 public import dlangui.core.types;
49 public import dlangui.core.logger;
50 private import dlangui.widgets.styles;
51 import std.algorithm;
52 
53 /// font families enum
54 enum FontFamily : ubyte {
55     /// Unknown / not set / does not matter
56     Unspecified,
57     /// Sans Serif font, e.g. Arial
58     SansSerif,
59     /// Serif font, e.g. Times New Roman
60     Serif,
61     /// Fantasy font
62     Fantasy,
63     /// Cursive font
64     Cursive,
65     /// Monospace font (fixed pitch font), e.g. Courier New
66     MonoSpace
67 }
68 
69 /// font weight constants (0..1000)
70 enum FontWeight : int {
71     /// normal font weight
72     Normal = 400,
73     /// bold font
74     Bold = 800
75 }
76 
77 immutable dchar UNICODE_SOFT_HYPHEN_CODE = 0x00ad;
78 immutable dchar UNICODE_ZERO_WIDTH_SPACE = 0x200b;
79 immutable dchar UNICODE_NO_BREAK_SPACE = 0x00a0;
80 immutable dchar UNICODE_HYPHEN = 0x2010;
81 immutable dchar UNICODE_NB_HYPHEN = 0x2011;
82 
83 /// custom character properties - for char-by-char drawing of text string with different character color and style
84 struct CustomCharProps {
85     uint color;
86     uint textFlags;
87 
88     this(uint color, bool underline = false, bool strikeThrough = false) {
89         this.color = color;
90         this.textFlags = 0;
91         if (underline)
92             this.textFlags |= TextFlag.Underline;
93         if (strikeThrough)
94             this.textFlags |= TextFlag.StrikeThrough;
95     }
96 }
97 
98 static if (ENABLE_OPENGL) {
99 
100     private __gshared void function(uint id) _glyphDestroyCallback;
101     /**
102      * get glyph destroy callback (to cleanup OpenGL caches)
103      * 
104      * Used for resource management. Usually you don't have to call it manually.
105      */
106     @property void function(uint id) glyphDestroyCallback() { return _glyphDestroyCallback; }
107     /**
108      * Set glyph destroy callback (to cleanup OpenGL caches)
109      * This callback is used to tell OpenGL glyph cache that glyph is not more used - to let OpenGL glyph cache delete texture if all glyphs in it are no longer used.
110      * 
111      * Used for resource management. Usually you don't have to call it manually.
112      */
113     @property void glyphDestroyCallback(void function(uint id) callback) { _glyphDestroyCallback = callback; }
114 
115     private __gshared uint _nextGlyphId;
116     /**
117      * ID generator for glyphs
118      * 
119      * Generates next glyph ID. Unique IDs are being used to control OpenGL glyph cache items lifetime.
120      * 
121      * Used for resource management. Usually you don't have to call it manually.
122      */
123     uint nextGlyphId() { return _nextGlyphId++; }
124 }
125 
126 /// constant for measureText maxWidth paramenter - to tell that all characters of text string should be measured.
127 immutable int MAX_WIDTH_UNSPECIFIED = int.max;
128 
129 /** Instance of font with specific size, weight, face, etc.
130  *
131  * Allows to measure text string and draw it on DrawBuf
132  *
133  * Use FontManager.instance.getFont() to retrieve font instance.
134  */
135 class Font : RefCountedObject {
136     /// returns font size (as requested from font engine)
137     abstract @property int size();
138     /// returns actual font height including interline space
139     abstract @property int height();
140     /// returns font weight
141     abstract @property int weight();
142     /// returns baseline offset
143     abstract @property int baseline();
144     /// returns true if font is italic
145     abstract @property bool italic();
146     /// returns font face name
147     abstract @property string face();
148     /// returns font family
149     abstract @property FontFamily family();
150     /// returns true if font object is not yet initialized / loaded
151     abstract @property bool isNull();
152 
153     /// return true if antialiasing is enabled, false if not enabled
154     @property bool antialiased() {
155         return size >= FontManager.instance.minAnitialiasedFontSize;
156     }
157 
158 
159     private int _fixedFontDetection = -1;
160 
161     /// returns true if font has fixed pitch (all characters have equal width)
162     @property bool isFixed() {
163         if (_fixedFontDetection < 0) {
164             if (charWidth('i') == charWidth(' ') && charWidth('M') == charWidth('i'))
165                 _fixedFontDetection = 1;
166             else
167                 _fixedFontDetection = 0;
168         }
169         return _fixedFontDetection == 1;
170     }
171 
172     protected int _spaceWidth = -1;
173 
174     /// returns true if font is fixed
175     @property int spaceWidth() {
176         if (_spaceWidth < 0) {
177             _spaceWidth = charWidth(' ');
178             if (_spaceWidth <= 0)
179                 _spaceWidth = charWidth('0');
180             if (_spaceWidth <= 0)
181                 _spaceWidth = size;
182         }
183         return _spaceWidth;
184     }
185 
186     /// returns character width
187     int charWidth(dchar ch) {
188         Glyph * g = getCharGlyph(ch);
189         return !g ? 0 : g.width;
190     }
191 
192     /*******************************************************************************************
193      * Measure text string, return accumulated widths[] (distance to end of n-th character), returns number of measured chars.
194      *
195      * Supports Tab character processing and processing of menu item labels like '&File'.
196      *
197      * Params:
198      *          text = text string to measure
199      *          widths = output buffer to put measured widths (widths[i] will be set to cumulative widths text[0..i])
200      *          maxWidth = maximum width to measure - measure is stopping if max width is reached (pass MAX_WIDTH_UNSPECIFIED to measure all characters)
201      *          tabSize = tabulation size, in number of spaces
202      *          tabOffset = when string is drawn not from left position, use to move tab stops left/right
203      *          textFlags = TextFlag bit set - to control underline, hotkey label processing, etc...
204      * Returns:
205      *          number of characters measured (may be less than text.length if maxWidth is reached)
206      ******************************************************************************************/
207     int measureText(const dchar[] text, ref int[] widths, int maxWidth = MAX_WIDTH_UNSPECIFIED, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) {
208         if (text.length == 0)
209             return 0;
210         const dchar * pstr = text.ptr;
211         uint len = cast(uint)text.length;
212         if (widths.length < len)
213             widths.length = len + 1;
214         int x = 0;
215         int charsMeasured = 0;
216         int * pwidths = widths.ptr;
217         int tabWidth = spaceWidth * tabSize; // width of full tab in pixels
218         tabOffset = tabOffset % tabWidth;
219         if (tabOffset < 0)
220             tabOffset += tabWidth;
221         foreach(int i; 0 .. len) {
222             //auto measureStart = std.datetime.Clock.currAppTick;
223             dchar ch = pstr[i];
224             if (ch == '\t') {
225                 // measure tab
226                 int tabPosition = (x + tabWidth - tabOffset) / tabWidth * tabWidth + tabOffset;
227                 while (tabPosition < x + spaceWidth)
228                     tabPosition += tabWidth;
229                 pwidths[i] = tabPosition;
230                 charsMeasured = i + 1;
231                 x = tabPosition;
232                 continue;
233             } else if (ch == '&' && (textFlags & (TextFlag.UnderlineHotKeys | TextFlag.HotKeys | TextFlag.UnderlineHotKeysWhenAltPressed))) {
234                 pwidths[i] = x;
235                 continue; // skip '&' in hot key when measuring
236             }
237             Glyph * glyph = getCharGlyph(pstr[i], true); // TODO: what is better
238             //auto measureEnd = std.datetime.Clock.currAppTick;
239             //auto duration = measureEnd - measureStart;
240             //if (duration.length > 10)
241             //    Log.d("ft measureText took ", duration.length, " ticks");
242             if (glyph is null) {
243                 // if no glyph, use previous width - treat as zero width
244                 pwidths[i] = x;
245                 continue;
246             }
247             int w = x + glyph.width; // using advance
248             int w2 = x + glyph.originX + glyph.correctedBlackBoxX; // using black box
249             if (w < w2) // choose bigger value
250                 w = w2;
251             pwidths[i] = w;
252             x += glyph.width;
253             charsMeasured = i + 1;
254             if (x > maxWidth)
255                 break;
256         }
257         return charsMeasured;
258     }
259 
260     protected int[] _textSizeBuffer; // buffer to reuse while measuring strings - to avoid GC
261 
262     /*************************************************************************
263      * Measure text string as single line, returns width and height
264      * 
265      * Params:
266      *          text = text string to measure
267      *          maxWidth = maximum width - measure is stopping if max width is reached
268      *          tabSize = tabulation size, in number of spaces
269      *          tabOffset = when string is drawn not from left position, use to move tab stops left/right
270      *          textFlags = TextFlag bit set - to control underline, hotkey label processing, etc...
271      ************************************************************************/
272     Point textSize(const dchar[] text, int maxWidth = MAX_WIDTH_UNSPECIFIED, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) {
273         if (_textSizeBuffer.length < text.length + 1)
274             _textSizeBuffer.length = text.length + 1;
275         int charsMeasured = measureText(text, _textSizeBuffer, maxWidth, tabSize, tabOffset, textFlags);
276         if (charsMeasured < 1)
277             return Point(0,0);
278         return Point(_textSizeBuffer[charsMeasured - 1], height);
279     }
280 
281     /*****************************************************************************************
282      * Draw text string to buffer.
283      *
284      * Params:
285      *      buf =   graphics buffer to draw text to
286      *      x =     x coordinate to draw first character at
287      *      y =     y coordinate to draw first character at
288      *      text =  text string to draw
289      *      color =  color for drawing of glyphs
290      *      tabSize = tabulation size, in number of spaces
291      *      tabOffset = when string is drawn not from left position, use to move tab stops left/right
292      *      textFlags = set of TextFlag bit fields
293      ****************************************************************************************/
294     void drawText(DrawBuf buf, int x, int y, const dchar[] text, uint color, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) {
295         if (text.length == 0)
296             return; // nothing to draw - empty text
297         if (_textSizeBuffer.length < text.length)
298             _textSizeBuffer.length = text.length;
299         int charsMeasured = measureText(text, _textSizeBuffer, MAX_WIDTH_UNSPECIFIED, tabSize, tabOffset, textFlags);
300         Rect clip = buf.clipRect; //clipOrFullRect;
301         if (clip.empty)
302             return; // not visible - clipped out
303         if (y + height < clip.top || y >= clip.bottom)
304             return; // not visible - fully above or below clipping rectangle
305         int _baseline = baseline;
306         bool underline = (textFlags & TextFlag.Underline) != 0;
307         int underlineHeight = 1;
308         int underlineY = y + _baseline + underlineHeight * 2;
309         foreach(int i; 0 .. charsMeasured) {
310             dchar ch = text[i];
311             if (ch == '&' && (textFlags & (TextFlag.UnderlineHotKeys | TextFlag.HotKeys | TextFlag.UnderlineHotKeysWhenAltPressed))) {
312                 if (textFlags & (TextFlag.UnderlineHotKeys | TextFlag.UnderlineHotKeysWhenAltPressed))
313                     underline = true; // turn ON underline for hot key
314                 continue; // skip '&' in hot key when measuring
315             }
316             int xx = (i > 0) ? _textSizeBuffer[i - 1] : 0;
317             if (x + xx > clip.right)
318                 break;
319             if (x + xx + 255 < clip.left)
320                 continue; // far at left of clipping region
321 
322             if (underline) {
323                 int xx2 = _textSizeBuffer[i];
324                 // draw underline
325                 if (xx2 > xx)
326                     buf.fillRect(Rect(x + xx, underlineY, x + xx2, underlineY + underlineHeight), color);
327                 // turn off underline after hot key
328                 if (!(textFlags & TextFlag.Underline))
329                     underline = false; 
330             }
331 
332             if (ch == ' ' || ch == '\t')
333                 continue;
334             Glyph * glyph = getCharGlyph(ch);
335             if (glyph is null)
336                 continue;
337             if ( glyph.blackBoxX && glyph.blackBoxY ) {
338                 int gx = x + xx + glyph.originX;
339                 if (gx + glyph.correctedBlackBoxX < clip.left)
340                     continue;
341                 buf.drawGlyph( gx,
342                                y + _baseline - glyph.originY,
343                               glyph,
344                               color);
345             }
346         }
347     }
348 
349     /*****************************************************************************************
350     * Draw text string to buffer.
351     *
352     * Params:
353     *      buf =   graphics buffer to draw text to
354     *      x =     x coordinate to draw first character at
355     *      y =     y coordinate to draw first character at
356     *      text =  text string to draw
357     *      charProps =  array of character properties, charProps[i] are properties for character text[i]
358     *      tabSize = tabulation size, in number of spaces
359     *      tabOffset = when string is drawn not from left position, use to move tab stops left/right
360     *      textFlags = set of TextFlag bit fields
361     ****************************************************************************************/
362     void drawColoredText(DrawBuf buf, int x, int y, const dchar[] text, const CustomCharProps[] charProps, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) {
363         if (text.length == 0)
364             return; // nothing to draw - empty text
365         if (_textSizeBuffer.length < text.length)
366             _textSizeBuffer.length = text.length;
367         int charsMeasured = measureText(text, _textSizeBuffer, MAX_WIDTH_UNSPECIFIED, tabSize, tabOffset, textFlags);
368         Rect clip = buf.clipRect; //clipOrFullRect;
369         if (clip.empty)
370             return; // not visible - clipped out
371         if (y + height < clip.top || y >= clip.bottom)
372             return; // not visible - fully above or below clipping rectangle
373         int _baseline = baseline;
374         uint customizedTextFlags = (charProps.length ? charProps[0].textFlags : 0) | textFlags;
375         bool underline = (customizedTextFlags & TextFlag.Underline) != 0;
376         int underlineHeight = 1;
377         int underlineY = y + _baseline + underlineHeight * 2;
378         foreach(int i; 0 .. charsMeasured) {
379             dchar ch = text[i];
380             uint color = i < charProps.length ? charProps[i].color : charProps[$ - 1].color;
381             customizedTextFlags = (i < charProps.length ? charProps[i].textFlags : charProps[$ - 1].textFlags) | textFlags;
382             underline = (customizedTextFlags & TextFlag.Underline) != 0;
383             // turn off underline after hot key
384             if (ch == '&' && (textFlags & (TextFlag.UnderlineHotKeys | TextFlag.HotKeys | TextFlag.UnderlineHotKeysWhenAltPressed))) {
385                 if (textFlags & (TextFlag.UnderlineHotKeys | TextFlag.UnderlineHotKeysWhenAltPressed))
386                     underline = true; // turn ON underline for hot key
387                 continue; // skip '&' in hot key when measuring
388             }
389             int xx = (i > 0) ? _textSizeBuffer[i - 1] : 0;
390             if (x + xx > clip.right)
391                 break;
392             if (x + xx + 255 < clip.left)
393                 continue; // far at left of clipping region
394 
395             if (underline) {
396                 int xx2 = _textSizeBuffer[i];
397                 // draw underline
398                 if (xx2 > xx)
399                     buf.fillRect(Rect(x + xx, underlineY, x + xx2, underlineY + underlineHeight), color);
400                 // turn off underline after hot key
401                 if (!(customizedTextFlags & TextFlag.Underline))
402                     underline = false;
403             }
404 
405             if (ch == ' ' || ch == '\t')
406                 continue;
407             Glyph * glyph = getCharGlyph(ch);
408             if (glyph is null)
409                 continue;
410             if ( glyph.blackBoxX && glyph.blackBoxY ) {
411                 int gx = x + xx + glyph.originX;
412                 if (gx + glyph.correctedBlackBoxX < clip.left)
413                     continue;
414                 buf.drawGlyph( gx,
415                                y + _baseline - glyph.originY,
416                               glyph,
417                               color);
418             }
419         }
420     }
421 
422     /// measure multiline text with line splitting, returns width and height in pixels
423     Point measureMultilineText(const dchar[] text, int maxLines = 0, int maxWidth = 0, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) {
424         SimpleTextFormatter fmt;
425         FontRef fnt = FontRef(this);
426         return fmt.format(text, fnt, maxLines, maxWidth, tabSize, tabOffset, textFlags);
427     }
428 
429     /// draws multiline text with line splitting
430     void drawMultilineText(DrawBuf buf, int x, int y, const dchar[] text, uint color, int maxLines = 0, int maxWidth = 0, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) {
431         SimpleTextFormatter fmt;
432         FontRef fnt = FontRef(this);
433         fmt.format(text, fnt, maxLines, maxWidth, tabSize, tabOffset, textFlags);
434         fmt.draw(buf, x, y, fnt, color);
435     }
436 
437 
438     /// get character glyph information
439     abstract Glyph * getCharGlyph(dchar ch, bool withImage = true);
440 
441     /// clear usage flags for all entries
442     abstract void checkpoint();
443     /// removes entries not used after last call of checkpoint() or cleanup()
444     abstract void cleanup();
445     /// clears glyph cache
446     abstract void clearGlyphCache();
447 
448     void clear() {}
449 
450     ~this() { clear(); }
451 }
452 alias FontRef = Ref!Font;
453 
454 /// helper to split text into several lines and draw it
455 struct SimpleTextFormatter {
456     dstring[] _lines;
457     int _tabSize;
458     int _tabOffset;
459     uint _textFlags;
460     /// split text into lines and measure it; returns size in pixels
461     Point format(const dchar[] text, FontRef fnt, int maxLines = 0, int maxWidth = 0, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) {
462         _tabSize = tabSize;
463         _tabOffset = tabOffset;
464         _textFlags = textFlags;
465         Point sz;
466         _lines.length = 0;
467         int lineHeight = fnt.height;
468         if (text.length == 0) {
469             sz.y = lineHeight;
470             return sz;
471         }
472         int[] widths;
473         int charsMeasured = fnt.measureText(text, widths, MAX_WIDTH_UNSPECIFIED, _tabSize, _tabOffset, _textFlags);
474         int lineStart = 0;
475         int lineStartX = 0;
476         int lastWordEnd = 0;
477         int lastWordEndX = 0;
478         dchar prevChar = 0;
479         foreach(int i; 0 .. charsMeasured + 1) {
480             dchar ch = i < charsMeasured ? text[i] : 0;
481             if (ch == '\n' || i == charsMeasured) {
482                 // split by EOL char or at end of text
483                 dstring line = cast(dstring)text[lineStart .. i];
484                 int lineEndX = (i == lineStart) ? lineStartX : widths[i - 1];
485                 int lineWidth = lineEndX - lineStartX;
486                 sz.y += lineHeight;
487                 if (sz.x < lineWidth)
488                     sz.x = lineWidth;
489                 _lines ~= line;
490                 if (i == charsMeasured) // end of text reached
491                     break;
492 
493                 // check max lines constraint
494                 if (maxLines && _lines.length >= maxLines) // max lines reached
495                     break;
496 
497                 lineStart = i + 1;
498                 lineStartX = widths[i];
499             } else {
500                 // split by width
501                 int x = widths[i];
502                 if (ch == '\t' || ch == ' ') {
503                     // track last word end
504                     if (prevChar != '\t' && prevChar != ' ' && prevChar != 0) {
505                         lastWordEnd = i;
506                         lastWordEndX = widths[i]; 
507                     }
508                     prevChar = ch;
509                     continue;
510                 }
511                 if (maxWidth > 0 && maxWidth != MAX_WIDTH_UNSPECIFIED && x > maxWidth && x - lineStartX > maxWidth && i > lineStart) {
512                     // need splitting
513                     int lineEnd = i;
514                     int lineEndX = widths[i - 1];
515                     if (lastWordEnd > lineStart && lastWordEndX - lineStartX >= maxWidth / 3) {
516                         // split on word bound
517                         lineEnd = lastWordEnd;
518                         lineEndX = widths[lastWordEnd - 1];
519                     }
520                     // add line
521                     dstring line = cast(dstring)text[lineStart .. lineEnd]; //lastWordEnd];
522                     int lineWidth = lineEndX - lineStartX;
523                     sz.y += lineHeight;
524                     if (sz.x < lineWidth)
525                         sz.x = lineWidth;
526                     _lines ~= line;
527 
528                     // check max lines constraint
529                     if (maxLines && _lines.length >= maxLines) // max lines reached
530                         break;
531 
532                     // find next line start
533                     lineStart = lineEnd;
534                     while(lineStart < text.length && (text[lineStart] == ' ' || text[lineStart] == '\t'))
535                         lineStart++;
536                     if (lineStart >= text.length)
537                         break;
538                     lineStartX = widths[lineStart - 1];
539                 }
540             }
541             prevChar = ch;
542         }
543         return sz;
544     }
545     /// draw formatted text
546     void draw(DrawBuf buf, int x, int y, FontRef fnt, uint color) {
547         int lineHeight = fnt.height;
548         foreach(line; _lines) {
549             fnt.drawText(buf, x, y, line, color, _tabSize, _tabOffset, _textFlags);
550             y += lineHeight;
551         }
552     }
553 }
554 
555 
556 /// font instance collection - utility class, for font manager implementations
557 struct FontList {
558     FontRef[] _list;
559     uint _len;
560     ~this() {
561         clear();
562     }
563     
564     @property uint length() {
565         return _len;
566     }
567     
568     void clear() {
569         foreach(i; 0 .. _len) {
570             _list[i].clear();
571             _list[i] = null;
572         }
573         _len = 0;
574     }
575     // returns item by index
576     ref FontRef get(int index) {
577         return _list[index];
578     }
579     // find by a set of parameters - returns index of found item, -1 if not found
580     int find(int size, int weight, bool italic, FontFamily family, string face) {
581         foreach(int i; 0 .. _len) {
582             Font item = _list[i].get;
583             if (item.family != family)
584                 continue;
585             if (item.size != size)
586                 continue;
587             if (item.italic != italic || item.weight != weight)
588                 continue;
589             if (!equal(item.face, face))
590                 continue;
591             return i;
592         }
593         return -1;
594     }
595     // find by size only - returns index of found item, -1 if not found
596     int find(int size) {
597         foreach(int i; 0 .. _len) {
598             Font item = _list[i].get;
599             if (item.size != size)
600                 continue;
601             return i;
602         }
603         return -1;
604     }
605     ref FontRef add(Font item) {
606         //Log.d("FontList.add() enter");
607         if (_len >= _list.length) {
608             _list.length = _len < 16 ? 16 : _list.length * 2;
609         }
610         _list[_len++] = item;
611         //Log.d("FontList.add() exit");
612         return _list[_len - 1];
613     }
614     // remove unused items - with reference == 1
615     void cleanup() {
616         foreach(i; 0 .. _len)
617             if (_list[i].refCount <= 1)
618                 _list[i].clear();
619         uint dst = 0;
620         foreach(i; 0 .. _len) {
621             if (!_list[i].isNull)
622                 if (i != dst)
623                     _list[dst++] = _list[i];
624         }
625         _len = dst;
626         foreach(i; 0 .. _len)
627             _list[i].cleanup();
628     }
629     void checkpoint() {
630         foreach(i; 0 .. _len)
631             _list[i].checkpoint();
632     }
633     /// clears glyph cache
634     void clearGlyphCache() {
635         foreach(i; 0 .. _len)
636             _list[i].clearGlyphCache();
637     }
638 }
639 
640 /// default min font size for antialiased fonts (e.g. if 16 is set, for 16+ sizes antialiasing will be used, for sizes <=15 - antialiasing will be off)
641 const int DEF_MIN_ANTIALIASED_FONT_SIZE = 0; // 0 means always use antialiasing
642 
643 /// Hinting mode (currently supported for FreeType only)
644 enum HintingMode : int {
645     /// based on information from font (using bytecode interpreter)
646     Normal, // 0
647     /// force autohinting algorithm even if font contains hint data
648     AutoHint, // 1
649     /// disable hinting completely
650     Disabled, // 2
651     /// light autohint (similar to Mac)
652     Light // 3
653 }
654 
655 /// font face properties item
656 struct FontFaceProps {
657     /// font face name
658     string face;
659     /// font family
660     FontFamily family;
661 }
662 
663 /// Access points to fonts.
664 class FontManager {
665     protected static __gshared FontManager _instance;
666     protected static __gshared int _minAnitialiasedFontSize = DEF_MIN_ANTIALIASED_FONT_SIZE;
667     protected static __gshared HintingMode _hintingMode = HintingMode.Normal;
668     protected static __gshared SubpixelRenderingMode _subpixelRenderingMode = SubpixelRenderingMode.None;
669 
670     /// sets new font manager singleton instance
671     static @property void instance(FontManager manager) {
672         if (_instance !is null) {
673             destroy(_instance);
674             _instance = null;
675         }
676         _instance = manager;
677     }
678 
679     /// returns font manager singleton instance
680     static @property FontManager instance() {
681         return _instance;
682     }
683 
684     /// get font instance best matched specified parameters
685     abstract ref FontRef getFont(int size, int weight, bool italic, FontFamily family, string face);
686 
687     /// override to return list of font faces available
688     FontFaceProps[] getFaces() {
689         return null;
690     }
691 
692     /// clear usage flags for all entries -- for cleanup of unused fonts
693     abstract void checkpoint();
694 
695     /// removes entries not used after last call of checkpoint() or cleanup()
696     abstract void cleanup();
697 
698     /// get min font size for antialiased fonts (0 means antialiasing always on, some big value = always off)
699     static @property int minAnitialiasedFontSize() {
700         return _minAnitialiasedFontSize;
701     }
702 
703     /// set new min font size for antialiased fonts - fonts with size >= specified value will be antialiased (0 means antialiasing always on, some big value = always off)
704     static @property void minAnitialiasedFontSize(int size) {
705         if (_minAnitialiasedFontSize != size) {
706             _minAnitialiasedFontSize = size;
707             if (_instance)
708                 _instance.clearGlyphCaches();
709         }
710     }
711 
712     /// get current hinting mode (Normal, AutoHint, Disabled)
713     static @property HintingMode hintingMode() {
714         return _hintingMode;
715     }
716 
717     /// set hinting mode (Normal, AutoHint, Disabled)
718     static @property void hintingMode(HintingMode mode) {
719         if (_hintingMode != mode) {
720             _hintingMode = mode;
721             if (_instance)
722                 _instance.clearGlyphCaches();
723         }
724     }
725 
726     /// get current subpixel rendering mode for fonts (aka ClearType)
727     static @property SubpixelRenderingMode subpixelRenderingMode() {
728         return _subpixelRenderingMode;
729     }
730 
731     /// set subpixel rendering mode for fonts (aka ClearType)
732     static @property void subpixelRenderingMode(SubpixelRenderingMode mode) {
733         _subpixelRenderingMode = mode;
734     }
735 
736     private static __gshared double _fontGamma = 1.0;
737     /// get font gamma (1.0 is neutral, < 1.0 makes glyphs lighter, >1.0 makes glyphs bolder)
738     static @property double fontGamma() { return _fontGamma; }
739     /// set font gamma (1.0 is neutral, < 1.0 makes glyphs lighter, >1.0 makes glyphs bolder)
740     static @property void fontGamma(double v) {
741         double gamma = clamp(v, 0.1, 4);
742         if (_fontGamma != gamma) {
743             _fontGamma = gamma;
744             _gamma65.gamma = gamma;
745             _gamma256.gamma = gamma;
746             if (_instance)
747                 _instance.clearGlyphCaches();
748         }
749     }
750 
751     void clearGlyphCaches() {
752         // override to clear glyph caches
753     }
754 
755     ~this() {
756         Log.d("Destroying font manager");
757     }
758 }
759 
760 
761 /***************************************
762  * Glyph image cache
763  *
764  *
765  * Recently used glyphs are marked with glyph.lastUsage = 1
766  * 
767  * checkpoint() call clears usage marks
768  *
769  * cleanup() removes all items not accessed since last checkpoint()
770  *
771  ***************************************/
772 struct GlyphCache
773 {
774     alias glyph_ptr = Glyph*;
775     private glyph_ptr[][1024] _glyphs;
776     
777     /// try to find glyph for character in cache, returns null if not found
778     glyph_ptr find(dchar ch) {
779         ch = ch & 0xF_FFFF;
780         //if (_array is null)
781         //    _array = new Glyph[0x10000];
782         uint p = ch >> 8;
783         glyph_ptr[] row = _glyphs[p];
784         if (row is null)
785             return null;
786         uint i = ch & 0xFF;
787         glyph_ptr res = row[i];
788         if (!res)
789             return null;
790         res.lastUsage = 1;
791         return res;
792     }
793     
794     /// put character glyph to cache
795     glyph_ptr put(dchar ch, glyph_ptr glyph) {
796         ch = ch & 0xF_FFFF;
797         uint p = ch >> 8;
798         uint i = ch & 0xFF;
799         if (_glyphs[p] is null)
800             _glyphs[p] = new glyph_ptr[256];
801         _glyphs[p][i] = glyph;
802         glyph.lastUsage = 1;
803         return glyph;
804     }
805     
806     /// removes entries not used after last call of checkpoint() or cleanup()
807     void cleanup() {
808         foreach(part; _glyphs) {
809             if (part !is null)
810             foreach(ref item; part) {
811                 if (item && !item.lastUsage) {
812                     static if (ENABLE_OPENGL) {
813                         // notify about destroyed glyphs
814                         if (_glyphDestroyCallback !is null) {
815                             _glyphDestroyCallback(item.id);
816                         }
817                     }
818                     destroy(item);
819                     item = null;
820                 }
821             }
822         }
823     }
824     
825     /// clear usage flags for all entries
826     void checkpoint() {
827         foreach(part; _glyphs) {
828             if (part !is null)
829             foreach(item; part) {
830                 if (item)
831                     item.lastUsage = 0;
832             }
833         }
834     }
835     
836     /// removes all entries (when built with USE_OPENGL version, notify OpenGL cache about removed glyphs)
837     void clear() {
838         foreach(part; _glyphs) {
839             if (part !is null)
840             foreach(ref item; part) {
841                 if (item) {
842                     static if (ENABLE_OPENGL) {
843                         // notify about destroyed glyphs
844                         if (_glyphDestroyCallback !is null) {
845                             _glyphDestroyCallback(item.id);
846                         }
847                     }
848                     destroy(item);
849                     item = null;
850                 }
851             }
852         }
853     }
854     /// on destroy, destroy all items (when built with USE_OPENGL version, notify OpenGL cache about removed glyphs)
855     ~this() {
856         clear();
857     }
858 }
859 
860 
861 // support of font glyph Gamma correction
862 // table to correct gamma and translate to output range 0..255
863 // maxv is 65 for win32 fonts, 256 for freetype
864 import std.math;
865 //---------------------------------
866 class glyph_gamma_table(int maxv = 65)
867 {
868     this(double gammaValue = 1.0)
869     {
870         gamma(gammaValue);
871     }
872     @property double gamma() { return _gamma; }
873     @property void gamma(double g) {
874         _gamma = g;
875         foreach(int i; 0 .. maxv)
876         {
877             double v = (maxv - 1.0 - i) / maxv;
878             v = pow(v, g);
879             int n = 255 - cast(int)round(v * 255);
880             ubyte n_clamp = cast(ubyte)clamp(n, 0, 255);
881             _map[i] = n_clamp;
882         }
883     }
884     /// correct byte value from source range to 0..255 applying gamma
885     ubyte correct(ubyte src) {
886         if (src >= maxv) src = maxv - 1;
887         return _map[src];
888     }
889 private:
890     ubyte[maxv] _map;
891     double _gamma = 1.0;
892 }
893 
894 __gshared glyph_gamma_table!65 _gamma65;
895 __gshared glyph_gamma_table!256 _gamma256;