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