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