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