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