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