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;