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