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