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], see also _textSizeBuffer description)
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     /*************************************************************************
261      * Buffer to reuse while measuring strings to avoid GC
262      *
263      * This array store character widths cumulatively.
264      * For example, after measure of monospaced 10-pixel-width font line
265      * "abc def" _textSizeBuffer should contain something like:
266      * [10, 20, 30, 40, 50, 60, 70]
267      ************************************************************************/
268     protected int[] _textSizeBuffer;
269 
270     /*************************************************************************
271      * Measure text string as single line, returns width and height
272      *
273      * Params:
274      *          text = text string to measure
275      *          maxWidth = maximum width - measure is stopping if max width is reached
276      *          tabSize = tabulation size, in number of spaces
277      *          tabOffset = when string is drawn not from left position, use to move tab stops left/right
278      *          textFlags = TextFlag bit set - to control underline, hotkey label processing, etc...
279      ************************************************************************/
280     Point textSize(const dchar[] text, int maxWidth = MAX_WIDTH_UNSPECIFIED, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) {
281         if (_textSizeBuffer.length < text.length + 1)
282             _textSizeBuffer.length = text.length + 1;
283         int charsMeasured = measureText(text, _textSizeBuffer, maxWidth, tabSize, tabOffset, textFlags);
284         if (charsMeasured < 1)
285             return Point(0,0);
286         return Point(_textSizeBuffer[charsMeasured - 1], height);
287     }
288 
289     /*****************************************************************************************
290      * Draw text string to buffer.
291      *
292      * Params:
293      *      buf =   graphics buffer to draw text to
294      *      x =     x coordinate to draw first character at
295      *      y =     y coordinate to draw first character at
296      *      text =  text string to draw
297      *      color =  color for drawing of glyphs
298      *      tabSize = tabulation size, in number of spaces
299      *      tabOffset = when string is drawn not from left position, use to move tab stops left/right
300      *      textFlags = set of TextFlag bit fields
301      ****************************************************************************************/
302     void drawText(DrawBuf buf, int x, int y, const dchar[] text, uint color, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) {
303         if (text.length == 0)
304             return; // nothing to draw - empty text
305         if (_textSizeBuffer.length < text.length)
306             _textSizeBuffer.length = text.length;
307         int charsMeasured = measureText(text, _textSizeBuffer, MAX_WIDTH_UNSPECIFIED, tabSize, tabOffset, textFlags);
308         Rect clip = buf.clipRect; //clipOrFullRect;
309         if (clip.empty)
310             return; // not visible - clipped out
311         if (y + height < clip.top || y >= clip.bottom)
312             return; // not visible - fully above or below clipping rectangle
313         int _baseline = baseline;
314         bool underline = (textFlags & TextFlag.Underline) != 0;
315         int underlineHeight = 1;
316         int underlineY = y + _baseline + underlineHeight * 2;
317         foreach(int i; 0 .. charsMeasured) {
318             dchar ch = text[i];
319             if (ch == '&' && (textFlags & (TextFlag.UnderlineHotKeys | TextFlag.HotKeys | TextFlag.UnderlineHotKeysWhenAltPressed))) {
320                 if (textFlags & (TextFlag.UnderlineHotKeys | TextFlag.UnderlineHotKeysWhenAltPressed))
321                     underline = true; // turn ON underline for hot key
322                 continue; // skip '&' in hot key when measuring
323             }
324             int xx = (i > 0) ? _textSizeBuffer[i - 1] : 0;
325             if (x + xx > clip.right)
326                 break;
327             if (x + xx + 255 < clip.left)
328                 continue; // far at left of clipping region
329 
330             if (underline) {
331                 int xx2 = _textSizeBuffer[i];
332                 // draw underline
333                 if (xx2 > xx)
334                     buf.fillRect(Rect(x + xx, underlineY, x + xx2, underlineY + underlineHeight), color);
335                 // turn off underline after hot key
336                 if (!(textFlags & TextFlag.Underline))
337                     underline = false;
338             }
339 
340             if (ch == ' ' || ch == '\t')
341                 continue;
342             Glyph * glyph = getCharGlyph(ch);
343             if (glyph is null)
344                 continue;
345             if ( glyph.blackBoxX && glyph.blackBoxY ) {
346                 int gx = x + xx + glyph.originX;
347                 if (gx + glyph.correctedBlackBoxX < clip.left)
348                     continue;
349                 buf.drawGlyph( gx,
350                                y + _baseline - glyph.originY,
351                               glyph,
352                               color);
353             }
354         }
355     }
356 
357     /*****************************************************************************************
358     * Draw text string to buffer.
359     *
360     * Params:
361     *      buf =   graphics buffer to draw text to
362     *      x =     x coordinate to draw first character at
363     *      y =     y coordinate to draw first character at
364     *      text =  text string to draw
365     *      charProps =  array of character properties, charProps[i] are properties for character text[i]
366     *      tabSize = tabulation size, in number of spaces
367     *      tabOffset = when string is drawn not from left position, use to move tab stops left/right
368     *      textFlags = set of TextFlag bit fields
369     ****************************************************************************************/
370     void drawColoredText(DrawBuf buf, int x, int y, const dchar[] text, const CustomCharProps[] charProps, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) {
371         if (text.length == 0)
372             return; // nothing to draw - empty text
373         if (_textSizeBuffer.length < text.length)
374             _textSizeBuffer.length = text.length;
375         int charsMeasured = measureText(text, _textSizeBuffer, MAX_WIDTH_UNSPECIFIED, tabSize, tabOffset, textFlags);
376         Rect clip = buf.clipRect; //clipOrFullRect;
377         if (clip.empty)
378             return; // not visible - clipped out
379         if (y + height < clip.top || y >= clip.bottom)
380             return; // not visible - fully above or below clipping rectangle
381         int _baseline = baseline;
382         uint customizedTextFlags = (charProps.length ? charProps[0].textFlags : 0) | textFlags;
383         bool underline = (customizedTextFlags & TextFlag.Underline) != 0;
384         int underlineHeight = 1;
385         int underlineY = y + _baseline + underlineHeight * 2;
386         foreach(int i; 0 .. charsMeasured) {
387             dchar ch = text[i];
388             uint color = i < charProps.length ? charProps[i].color : charProps[$ - 1].color;
389             customizedTextFlags = (i < charProps.length ? charProps[i].textFlags : charProps[$ - 1].textFlags) | textFlags;
390             underline = (customizedTextFlags & TextFlag.Underline) != 0;
391             // turn off underline after hot key
392             if (ch == '&' && (textFlags & (TextFlag.UnderlineHotKeys | TextFlag.HotKeys | TextFlag.UnderlineHotKeysWhenAltPressed))) {
393                 if (textFlags & (TextFlag.UnderlineHotKeys | TextFlag.UnderlineHotKeysWhenAltPressed))
394                     underline = true; // turn ON underline for hot key
395                 continue; // skip '&' in hot key when measuring
396             }
397             int xx = (i > 0) ? _textSizeBuffer[i - 1] : 0;
398             if (x + xx > clip.right)
399                 break;
400             if (x + xx + 255 < clip.left)
401                 continue; // far at left of clipping region
402 
403             if (underline) {
404                 int xx2 = _textSizeBuffer[i];
405                 // draw underline
406                 if (xx2 > xx)
407                     buf.fillRect(Rect(x + xx, underlineY, x + xx2, underlineY + underlineHeight), color);
408                 // turn off underline after hot key
409                 if (!(customizedTextFlags & TextFlag.Underline))
410                     underline = false;
411             }
412 
413             if (ch == ' ' || ch == '\t')
414                 continue;
415             Glyph * glyph = getCharGlyph(ch);
416             if (glyph is null)
417                 continue;
418             if ( glyph.blackBoxX && glyph.blackBoxY ) {
419                 int gx = x + xx + glyph.originX;
420                 if (gx + glyph.correctedBlackBoxX < clip.left)
421                     continue;
422                 buf.drawGlyph( gx,
423                                y + _baseline - glyph.originY,
424                               glyph,
425                               color);
426             }
427         }
428     }
429 
430     /// measure multiline text with line splitting, returns width and height in pixels
431     Point measureMultilineText(const dchar[] text, int maxLines = 0, int maxWidth = 0, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) {
432         SimpleTextFormatter fmt;
433         FontRef fnt = FontRef(this);
434         return fmt.format(text, fnt, maxLines, maxWidth, tabSize, tabOffset, textFlags);
435     }
436 
437     /// draws multiline text with line splitting
438     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) {
439         SimpleTextFormatter fmt;
440         FontRef fnt = FontRef(this);
441         fmt.format(text, fnt, maxLines, maxWidth, tabSize, tabOffset, textFlags);
442         fmt.draw(buf, x, y, fnt, color);
443     }
444 
445 
446     /// get character glyph information
447     abstract Glyph * getCharGlyph(dchar ch, bool withImage = true);
448 
449     /// clear usage flags for all entries
450     abstract void checkpoint();
451     /// removes entries not used after last call of checkpoint() or cleanup()
452     abstract void cleanup();
453     /// clears glyph cache
454     abstract void clearGlyphCache();
455 
456     void clear() {}
457 
458     ~this() { clear(); }
459 }
460 alias FontRef = Ref!Font;
461 
462 /// helper to split text into several lines and draw it
463 struct SimpleTextFormatter {
464     dstring[] _lines;
465     int[] _linesWidths;
466     int _maxLineWidth;
467     int _tabSize;
468     int _tabOffset;
469     uint _textFlags;
470     /// split text into lines and measure it; returns size in pixels
471     Point format(const dchar[] text, FontRef fnt, int maxLines = 0, int maxWidth = 0, int tabSize = 4, int tabOffset = 0, uint textFlags = 0) {
472         _tabSize = tabSize;
473         _tabOffset = tabOffset;
474         _textFlags = textFlags;
475         Point sz;
476         _lines.length = 0;
477         _linesWidths.length = 0;
478         int lineHeight = fnt.height;
479         if (text.length == 0) {
480             sz.y = lineHeight;
481             return sz;
482         }
483         int[] widths;
484         int charsMeasured = fnt.measureText(text, widths, MAX_WIDTH_UNSPECIFIED, _tabSize, _tabOffset, _textFlags);
485         int lineStart = 0;
486         int lineStartX = 0;
487         int lastWordEnd = 0;
488         int lastWordEndX = 0;
489         dchar prevChar = 0;
490         foreach(int i; 0 .. charsMeasured + 1) {
491             dchar ch = i < charsMeasured ? text[i] : 0;
492             if (ch == '\n' || i == charsMeasured) {
493                 // split by EOL char or at end of text
494                 dstring line = cast(dstring)text[lineStart .. i];
495                 int lineEndX = (i == lineStart) ? lineStartX : widths[i - 1];
496                 int lineWidth = lineEndX - lineStartX;
497                 sz.y += lineHeight;
498                 if (sz.x < lineWidth)
499                     sz.x = lineWidth;
500                 _lines ~= line;
501                 _linesWidths ~= lineWidth;
502                 if (i == charsMeasured) // end of text reached
503                     break;
504 
505                 // check max lines constraint
506                 if (maxLines && _lines.length >= maxLines) // max lines reached
507                     break;
508 
509                 lineStart = i + 1;
510                 lineStartX = widths[i];
511             } else {
512                 // split by width
513                 int x = widths[i];
514                 if (ch == '\t' || ch == ' ') {
515                     // track last word end
516                     if (prevChar != '\t' && prevChar != ' ' && prevChar != 0) {
517                         lastWordEnd = i;
518                         lastWordEndX = widths[i];
519                     }
520                     prevChar = ch;
521                     continue;
522                 }
523                 if (maxWidth > 0 && maxWidth != MAX_WIDTH_UNSPECIFIED && x > maxWidth && x - lineStartX > maxWidth && i > lineStart) {
524                     // need splitting
525                     int lineEnd = i;
526                     int lineEndX = widths[i - 1];
527                     if (lastWordEnd > lineStart && lastWordEndX - lineStartX >= maxWidth / 3) {
528                         // split on word bound
529                         lineEnd = lastWordEnd;
530                         lineEndX = widths[lastWordEnd - 1];
531                     }
532                     // add line
533                     dstring line = cast(dstring)text[lineStart .. lineEnd]; //lastWordEnd];
534                     int lineWidth = lineEndX - lineStartX;
535                     sz.y += lineHeight;
536                     if (sz.x < lineWidth)
537                         sz.x = lineWidth;
538                     _lines ~= line;
539                     _linesWidths ~= lineWidth;
540 
541                     // check max lines constraint
542                     if (maxLines && _lines.length >= maxLines) // max lines reached
543                         break;
544 
545                     // find next line start
546                     lineStart = lineEnd;
547                     while(lineStart < text.length && (text[lineStart] == ' ' || text[lineStart] == '\t'))
548                         lineStart++;
549                     if (lineStart >= text.length)
550                         break;
551                     lineStartX = widths[lineStart - 1];
552                 }
553             }
554             prevChar = ch;
555         }
556         _maxLineWidth = sz.x;
557         return sz;
558     }
559     /// draw formatted text
560     void draw(DrawBuf buf, int x, int y, FontRef fnt, uint color) {
561         int lineHeight = fnt.height;
562         foreach(line; _lines) {
563             fnt.drawText(buf, x, y, line, color, _tabSize, _tabOffset, _textFlags);
564             y += lineHeight;
565         }
566     }
567 
568     /// draw horizontaly aligned formatted text
569     void draw(DrawBuf buf, int x, int y, FontRef fnt, uint color, ubyte alignment) {
570         int lineHeight = fnt.height;
571         dstring line;
572         int lineWidth;
573         for (int i = 0 ; i < _lines.length ; i++) {
574             line = _lines[i];
575             lineWidth = _linesWidths[i];
576             if ((alignment & Align.HCenter) == Align.HCenter) {
577                 fnt.drawText(buf, x + (_maxLineWidth - lineWidth) / 2, y, line, color, _tabSize, _tabOffset, _textFlags);
578             }
579             else if (alignment & Align.Left) {
580                 fnt.drawText(buf, x, y, line, color, _tabSize, _tabOffset, _textFlags);
581             }
582             else if (alignment & Align.Right) {
583                 fnt.drawText(buf, x + _maxLineWidth - lineWidth, y, line, color, _tabSize, _tabOffset, _textFlags);
584             }
585             y += lineHeight;
586         }
587     }
588 }
589 
590 
591 /// font instance collection - utility class, for font manager implementations
592 struct FontList {
593     FontRef[] _list;
594     uint _len;
595     ~this() {
596         clear();
597     }
598 
599     @property uint length() {
600         return _len;
601     }
602 
603     void clear() {
604         foreach(i; 0 .. _len) {
605             _list[i].clear();
606             _list[i] = null;
607         }
608         _len = 0;
609     }
610     // returns item by index
611     ref FontRef get(int index) {
612         return _list[index];
613     }
614     // find by a set of parameters - returns index of found item, -1 if not found
615     int find(int size, int weight, bool italic, FontFamily family, string face) {
616         foreach(int i; 0 .. _len) {
617             Font item = _list[i].get;
618             if (item.family != family)
619                 continue;
620             if (item.size != size)
621                 continue;
622             if (item.italic != italic || item.weight != weight)
623                 continue;
624             if (!equal(item.face, face))
625                 continue;
626             return i;
627         }
628         return -1;
629     }
630     // find by size only - returns index of found item, -1 if not found
631     int find(int size) {
632         foreach(int i; 0 .. _len) {
633             Font item = _list[i].get;
634             if (item.size != size)
635                 continue;
636             return i;
637         }
638         return -1;
639     }
640     ref FontRef add(Font item) {
641         //Log.d("FontList.add() enter");
642         if (_len >= _list.length) {
643             _list.length = _len < 16 ? 16 : _list.length * 2;
644         }
645         _list[_len++] = item;
646         //Log.d("FontList.add() exit");
647         return _list[_len - 1];
648     }
649     // remove unused items - with reference == 1
650     void cleanup() {
651         foreach(i; 0 .. _len)
652             if (_list[i].refCount <= 1)
653                 _list[i].clear();
654         uint dst = 0;
655         foreach(i; 0 .. _len) {
656             if (!_list[i].isNull)
657                 if (i != dst)
658                     _list[dst++] = _list[i];
659         }
660         _len = dst;
661         foreach(i; 0 .. _len)
662             _list[i].cleanup();
663     }
664     void checkpoint() {
665         foreach(i; 0 .. _len)
666             _list[i].checkpoint();
667     }
668     /// clears glyph cache
669     void clearGlyphCache() {
670         foreach(i; 0 .. _len)
671             _list[i].clearGlyphCache();
672     }
673 }
674 
675 /// 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)
676 const int DEF_MIN_ANTIALIASED_FONT_SIZE = 0; // 0 means always use antialiasing
677 
678 /// Hinting mode (currently supported for FreeType only)
679 enum HintingMode : int {
680     /// based on information from font (using bytecode interpreter)
681     Normal, // 0
682     /// force autohinting algorithm even if font contains hint data
683     AutoHint, // 1
684     /// disable hinting completely
685     Disabled, // 2
686     /// light autohint (similar to Mac)
687     Light // 3
688 }
689 
690 /// font face properties item
691 struct FontFaceProps {
692     /// font face name
693     string face;
694     /// font family
695     FontFamily family;
696 }
697 
698 /// Access points to fonts.
699 class FontManager {
700     protected static __gshared FontManager _instance;
701     protected static __gshared int _minAnitialiasedFontSize = DEF_MIN_ANTIALIASED_FONT_SIZE;
702     protected static __gshared HintingMode _hintingMode = HintingMode.Normal;
703     protected static __gshared SubpixelRenderingMode _subpixelRenderingMode = SubpixelRenderingMode.None;
704 
705     /// sets new font manager singleton instance
706     static @property void instance(FontManager manager) {
707         if (_instance !is null) {
708             destroy(_instance);
709             _instance = null;
710         }
711         _instance = manager;
712     }
713 
714     /// returns font manager singleton instance
715     static @property FontManager instance() {
716         return _instance;
717     }
718 
719     /// get font instance best matched specified parameters
720     abstract ref FontRef getFont(int size, int weight, bool italic, FontFamily family, string face);
721 
722     /// override to return list of font faces available
723     FontFaceProps[] getFaces() {
724         return null;
725     }
726 
727     /// clear usage flags for all entries -- for cleanup of unused fonts
728     abstract void checkpoint();
729 
730     /// removes entries not used after last call of checkpoint() or cleanup()
731     abstract void cleanup();
732 
733     /// get min font size for antialiased fonts (0 means antialiasing always on, some big value = always off)
734     static @property int minAnitialiasedFontSize() {
735         return _minAnitialiasedFontSize;
736     }
737 
738     /// 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)
739     static @property void minAnitialiasedFontSize(int size) {
740         if (_minAnitialiasedFontSize != size) {
741             _minAnitialiasedFontSize = size;
742             if (_instance)
743                 _instance.clearGlyphCaches();
744         }
745     }
746 
747     /// get current hinting mode (Normal, AutoHint, Disabled)
748     static @property HintingMode hintingMode() {
749         return _hintingMode;
750     }
751 
752     /// set hinting mode (Normal, AutoHint, Disabled)
753     static @property void hintingMode(HintingMode mode) {
754         if (_hintingMode != mode) {
755             _hintingMode = mode;
756             if (_instance)
757                 _instance.clearGlyphCaches();
758         }
759     }
760 
761     /// get current subpixel rendering mode for fonts (aka ClearType)
762     static @property SubpixelRenderingMode subpixelRenderingMode() {
763         return _subpixelRenderingMode;
764     }
765 
766     /// set subpixel rendering mode for fonts (aka ClearType)
767     static @property void subpixelRenderingMode(SubpixelRenderingMode mode) {
768         _subpixelRenderingMode = mode;
769     }
770 
771     private static __gshared double _fontGamma = 1.0;
772     /// get font gamma (1.0 is neutral, < 1.0 makes glyphs lighter, >1.0 makes glyphs bolder)
773     static @property double fontGamma() { return _fontGamma; }
774     /// set font gamma (1.0 is neutral, < 1.0 makes glyphs lighter, >1.0 makes glyphs bolder)
775     static @property void fontGamma(double v) {
776         double gamma = clamp(v, 0.1, 4);
777         if (_fontGamma != gamma) {
778             _fontGamma = gamma;
779             _gamma65.gamma = gamma;
780             _gamma256.gamma = gamma;
781             if (_instance)
782                 _instance.clearGlyphCaches();
783         }
784     }
785 
786     void clearGlyphCaches() {
787         // override to clear glyph caches
788     }
789 
790     ~this() {
791         Log.d("Destroying font manager");
792     }
793 }
794 
795 
796 /***************************************
797  * Glyph image cache
798  *
799  *
800  * Recently used glyphs are marked with glyph.lastUsage = 1
801  *
802  * checkpoint() call clears usage marks
803  *
804  * cleanup() removes all items not accessed since last checkpoint()
805  *
806  ***************************************/
807 struct GlyphCache
808 {
809     alias glyph_ptr = Glyph*;
810     private glyph_ptr[][1024] _glyphs;
811 
812     /// try to find glyph for character in cache, returns null if not found
813     glyph_ptr find(dchar ch) {
814         ch = ch & 0xF_FFFF;
815         //if (_array is null)
816         //    _array = new Glyph[0x10000];
817         uint p = ch >> 8;
818         glyph_ptr[] row = _glyphs[p];
819         if (row is null)
820             return null;
821         uint i = ch & 0xFF;
822         glyph_ptr res = row[i];
823         if (!res)
824             return null;
825         res.lastUsage = 1;
826         return res;
827     }
828 
829     /// put character glyph to cache
830     glyph_ptr put(dchar ch, glyph_ptr glyph) {
831         ch = ch & 0xF_FFFF;
832         uint p = ch >> 8;
833         uint i = ch & 0xFF;
834         if (_glyphs[p] is null)
835             _glyphs[p] = new glyph_ptr[256];
836         _glyphs[p][i] = glyph;
837         glyph.lastUsage = 1;
838         return glyph;
839     }
840 
841     /// removes entries not used after last call of checkpoint() or cleanup()
842     void cleanup() {
843         foreach(part; _glyphs) {
844             if (part !is null)
845             foreach(ref item; part) {
846                 if (item && !item.lastUsage) {
847                     static if (ENABLE_OPENGL) {
848                         // notify about destroyed glyphs
849                         if (_glyphDestroyCallback !is null) {
850                             _glyphDestroyCallback(item.id);
851                         }
852                     }
853                     destroy(item);
854                     item = null;
855                 }
856             }
857         }
858     }
859 
860     /// clear usage flags for all entries
861     void checkpoint() {
862         foreach(part; _glyphs) {
863             if (part !is null)
864             foreach(item; part) {
865                 if (item)
866                     item.lastUsage = 0;
867             }
868         }
869     }
870 
871     /// removes all entries (when built with USE_OPENGL version, notify OpenGL cache about removed glyphs)
872     void clear() {
873         foreach(part; _glyphs) {
874             if (part !is null)
875             foreach(ref item; part) {
876                 if (item) {
877                     static if (ENABLE_OPENGL) {
878                         // notify about destroyed glyphs
879                         if (_glyphDestroyCallback !is null) {
880                             _glyphDestroyCallback(item.id);
881                         }
882                     }
883                     destroy(item);
884                     item = null;
885                 }
886             }
887         }
888     }
889     /// on destroy, destroy all items (when built with USE_OPENGL version, notify OpenGL cache about removed glyphs)
890     ~this() {
891         clear();
892     }
893 }
894 
895 
896 // support of font glyph Gamma correction
897 // table to correct gamma and translate to output range 0..255
898 // maxv is 65 for win32 fonts, 256 for freetype
899 import std.math;
900 //---------------------------------
901 class glyph_gamma_table(int maxv = 65)
902 {
903     this(double gammaValue = 1.0)
904     {
905         gamma(gammaValue);
906     }
907     @property double gamma() { return _gamma; }
908     @property void gamma(double g) {
909         _gamma = g;
910         foreach(int i; 0 .. maxv)
911         {
912             double v = (maxv - 1.0 - i) / maxv;
913             v = pow(v, g);
914             int n = 255 - cast(int)round(v * 255);
915             ubyte n_clamp = cast(ubyte)clamp(n, 0, 255);
916             _map[i] = n_clamp;
917         }
918     }
919     /// correct byte value from source range to 0..255 applying gamma
920     ubyte correct(ubyte src) {
921         if (src >= maxv) src = maxv - 1;
922         return _map[src];
923     }
924 private:
925     ubyte[maxv] _map;
926     double _gamma = 1.0;
927 }
928 
929 /// find some suitable replacement for important characters missing in font
930 dchar getReplacementChar(dchar code) {
931     switch (code) {
932         case UNICODE_SOFT_HYPHEN_CODE:
933             return '-';
934         case 0x0401: // CYRILLIC CAPITAL LETTER IO
935             return 0x0415; //CYRILLIC CAPITAL LETTER IE
936         case 0x0451: // CYRILLIC SMALL LETTER IO
937             return 0x0435; // CYRILLIC SMALL LETTER IE
938         case UNICODE_NO_BREAK_SPACE:
939             return ' ';
940         case 0x2010:
941         case 0x2011:
942         case 0x2012:
943         case 0x2013:
944         case 0x2014:
945         case 0x2015:
946             return '-';
947         case 0x2018:
948         case 0x2019:
949         case 0x201a:
950         case 0x201b:
951             return '\'';
952         case 0x201c:
953         case 0x201d:
954         case 0x201e:
955         case 0x201f:
956         case 0x00ab:
957         case 0x00bb:
958             return '\"';
959         case 0x2039:
960             return '<';
961         case 0x203A:
962         case '‣':
963         case '►':
964             return '>';
965         case 0x2044:
966             return '/';
967         case 0x2022: // css_lst_disc:
968             return '*';
969         case 0x26AA: // css_lst_disc:
970         case 0x25E6: // css_lst_disc:
971         case 0x25CF: // css_lst_disc:
972             return 'o';
973         case 0x25CB: // css_lst_circle:
974             return '*';
975         case 0x25A0: // css_lst_square:
976             return '-';
977         case '↑': //
978             return '▲';
979         case '↓': //
980             return '▼';
981         case '▲': //
982             return '^';
983         case '▼': //
984             return 'v';
985         default:
986             return 0;
987     }
988 }
989 
990 __gshared glyph_gamma_table!65 _gamma65;
991 __gshared glyph_gamma_table!256 _gamma256;