diff --git a/src/dlangui/core/types.d b/src/dlangui/core/types.d index 8991fe9c..2e41e65b 100644 --- a/src/dlangui/core/types.d +++ b/src/dlangui/core/types.d @@ -348,11 +348,17 @@ enum SubpixelRenderingMode : ubyte { align(1) struct Glyph { + static if (ENABLE_OPENGL) { + /// 0: unique id of glyph (for drawing in hardware accelerated scenes) + uint id; + } + /// 0: width of glyph black box ushort blackBoxX; @property ushort correctedBlackBoxX() { return subpixelMode ? blackBoxX / 3 : blackBoxX; } + /// 2: height of glyph black box ubyte blackBoxY; /// 3: X origin for glyph @@ -361,17 +367,15 @@ struct Glyph byte originY; /// 5: full width of glyph - ubyte width; - /// 6: subpixel rendering mode - if !=SubpixelRenderingMode.None, glyph data contains 3 bytes per pixel instead of 1 + ubyte widthPixels; + /// 6: full width of glyph scaled * 64 + ushort widthScaled; + /// 8: subpixel rendering mode - if !=SubpixelRenderingMode.None, glyph data contains 3 bytes per pixel instead of 1 SubpixelRenderingMode subpixelMode; - /// 7: usage flag, to handle cleanup of unused glyphs + /// 9: usage flag, to handle cleanup of unused glyphs ubyte lastUsage; - static if (ENABLE_OPENGL) { - /// 8: unique id of glyph (for drawing in hardware accelerated scenes) - uint id; - } - ///< 12: glyph data, arbitrary size (blackBoxX * blackBoxY) + ///< 10: glyph data, arbitrary size (blackBoxX * blackBoxY) ubyte[] glyph; } diff --git a/src/dlangui/graphics/fonts.d b/src/dlangui/graphics/fonts.d index 1176a334..f5f50568 100644 --- a/src/dlangui/graphics/fonts.d +++ b/src/dlangui/graphics/fonts.d @@ -186,7 +186,22 @@ class Font : RefCountedObject { /// returns character width int charWidth(dchar ch) { Glyph * g = getCharGlyph(ch); - return !g ? 0 : g.width; + return !g ? 0 : g.widthPixels; + } + + protected bool _allowKerning; + /// override to enable kerning support + @property bool allowKerning() { + return false; + } + /// override to enable kerning support + @property void allowKerning(bool allow) { + _allowKerning = allow; + } + + /// override to implement kerning offset calculation + int getKerningOffset(dchar prevChar, dchar currentChar) { + return 0; } /******************************************************************************************* @@ -211,6 +226,7 @@ class Font : RefCountedObject { uint len = cast(uint)text.length; if (widths.length < len) widths.length = len + 1; + bool useKerning = allowKerning; int x = 0; int charsMeasured = 0; int * pwidths = widths.ptr; @@ -218,6 +234,7 @@ class Font : RefCountedObject { tabOffset = tabOffset % tabWidth; if (tabOffset < 0) tabOffset += tabWidth; + dchar prevChar = 0; foreach(int i; 0 .. len) { //auto measureStart = std.datetime.Clock.currAppTick; dchar ch = pstr[i]; @@ -229,9 +246,11 @@ class Font : RefCountedObject { pwidths[i] = tabPosition; charsMeasured = i + 1; x = tabPosition; + prevChar = 0; continue; } else if (ch == '&' && (textFlags & (TextFlag.UnderlineHotKeys | TextFlag.HotKeys | TextFlag.UnderlineHotKeysWhenAltPressed))) { pwidths[i] = x; + prevChar = 0; continue; // skip '&' in hot key when measuring } Glyph * glyph = getCharGlyph(pstr[i], true); // TODO: what is better @@ -242,17 +261,23 @@ class Font : RefCountedObject { if (glyph is null) { // if no glyph, use previous width - treat as zero width pwidths[i] = x; + prevChar = 0; continue; } - int w = x + glyph.width; // using advance - int w2 = x + glyph.originX + glyph.correctedBlackBoxX; // using black box - if (w < w2) // choose bigger value - w = w2; + int kerningDelta = useKerning && prevChar ? getKerningOffset(ch, prevChar) : 0; + int width = ((glyph.widthScaled + kerningDelta + 63) >> 6); + if (width < glyph.originX + glyph.correctedBlackBoxX) + width = glyph.originX + glyph.correctedBlackBoxX; + int w = x + width; // using advance + //int w2 = x + glyph.originX + glyph.correctedBlackBoxX; // using black box + //if (w < w2) // choose bigger value + // w = w2; pwidths[i] = w; - x += glyph.width; + x += width; charsMeasured = i + 1; if (x > maxWidth) break; + prevChar = ch; } return charsMeasured; } diff --git a/src/dlangui/graphics/ftfonts.d b/src/dlangui/graphics/ftfonts.d index fd1e085f..00e78ae8 100644 --- a/src/dlangui/graphics/ftfonts.d +++ b/src/dlangui/graphics/ftfonts.d @@ -118,6 +118,8 @@ class FreeTypeFontFile { private int _weight; private bool _italic; + private bool _allowKerning = true; + /// filename @property string filename() { return _filename; } // properties as detected after opening of file @@ -186,7 +188,7 @@ class FreeTypeFontFile { clear(); return false; } - _height = cast(int)(_face.size.metrics.height >> 6); + _height = cast(int)((_face.size.metrics.height + 63) >> 6); _size = size; _baseline = _height + cast(int)(_face.size.metrics.descender >> 6); _weight = _face.style_flags & FT_STYLE_FLAG_BOLD ? FontWeight.Bold : FontWeight.Normal; @@ -218,6 +220,11 @@ class FreeTypeFontFile { return ch_glyph_index; } + /// allow kerning + @property bool allowKerning() { + return FT_HAS_KERNING( _face ); + } + /// retrieve glyph information, filling glyph struct; returns false if glyph not found bool getGlyphInfo(dchar code, ref Glyph glyph, dchar def_char, bool withImage = true) { @@ -240,11 +247,12 @@ class FreeTypeFontFile { if ( error ) return false; glyph.lastUsage = 1; - glyph.blackBoxX = cast(ushort)(_slot.metrics.width >> 6); - glyph.blackBoxY = cast(ubyte)(_slot.metrics.height >> 6); - glyph.originX = cast(byte)(_slot.metrics.horiBearingX >> 6); - glyph.originY = cast(byte)(_slot.metrics.horiBearingY >> 6); - glyph.width = cast(ubyte)(myabs(cast(int)(_slot.metrics.horiAdvance)) >> 6); + glyph.blackBoxX = cast(ushort)((_slot.metrics.width + 32) >> 6); + glyph.blackBoxY = cast(ubyte)((_slot.metrics.height + 32) >> 6); + glyph.originX = cast(byte)((_slot.metrics.horiBearingX + 32) >> 6); + glyph.originY = cast(byte)((_slot.metrics.horiBearingY + 32) >> 6); + glyph.widthScaled = cast(ushort)(myabs(cast(int)(_slot.metrics.horiAdvance))); + glyph.widthPixels = cast(ubyte)(myabs(cast(int)(_slot.metrics.horiAdvance + 32)) >> 6); glyph.subpixelMode = subpixel; //glyph.glyphIndex = cast(ushort)code; if (withImage) { @@ -301,6 +309,19 @@ class FreeTypeFontFile { _face = null; } + int getKerningOffset(FT_UInt prevCharIndex, FT_UInt nextCharIndex) { + const FT_KERNING_DEFAULT = 0; + FT_Vector delta; + int error = FT_Get_Kerning( _face, /* handle to face object */ + prevCharIndex, /* left glyph index */ + nextCharIndex, /* right glyph index */ + FT_KERNING_DEFAULT, /* kerning mode */ + &delta); /* target vector */ + const RSHIFT = 0; + if ( !error ) + return (delta.x) >> RSHIFT; + return 0; + } } /** @@ -318,6 +339,7 @@ class FreeTypeFont : Font { _fontItem = item; _size = size; _height = size; + _allowKerning = true; debug ++_instanceCount; debug(resalloc) Log.d("Created font, count=", _instanceCount); } @@ -357,6 +379,28 @@ class FreeTypeFont : Font { return false; } + /// override to allow kerning + override @property bool allowKerning() { + return _allowKerning; + } + + /// override to implement kerning offset calculation + override int getKerningOffset(dchar prevChar, dchar currentChar) { + if (!_allowKerning || !prevChar || !currentChar) + return 0; + FT_UInt index1; + FreeTypeFontFile file1; + if (!findGlyph(prevChar, 0, index1, file1)) + return 0; + FT_UInt index2; + FreeTypeFontFile file2; + if (!findGlyph(currentChar, 0, index2, file2)) + return 0; + if (file1 !is file2) + return 0; + return file1.getKerningOffset(index1, index2); + } + override Glyph * getCharGlyph(dchar ch, bool withImage = true) { if (ch > 0xFFFF) // do not support unicode chars above 0xFFFF - due to cache limitations return null; @@ -429,7 +473,7 @@ private derelict.util.exception.ShouldThrow missingSymFunc( string symName ) { static import derelict.util.exception; foreach(s; ["FT_New_Face", "FT_Attach_File", "FT_Set_Pixel_Sizes", "FT_Get_Char_Index", "FT_Load_Glyph", "FT_Done_Face", - "FT_Init_FreeType", "FT_Done_FreeType"]) { + "FT_Init_FreeType", "FT_Done_FreeType", "FT_Get_Kerning"]) { if (symName.equal(s)) // Symbol is used return derelict.util.exception.ShouldThrow.Yes; } @@ -532,12 +576,12 @@ class FreeTypeFontManager : FontManager { Log.v("DerelictFT: Loading FreeType library"); if (!DerelictFT) { Log.w("DerelictFT is null. Compiler bug? Applying workaround to fix it."); - version(Android) { - //DerelictFT = new DerelictFTLoader("libft2.so"); - DerelictFT = new DerelictFTLoader; - } else { - DerelictFT = new DerelictFTLoader; - } + version(Android) { + //DerelictFT = new DerelictFTLoader("libft2.so"); + DerelictFT = new DerelictFTLoader; + } else { + DerelictFT = new DerelictFTLoader; + } } DerelictFT.missingSymbolCallback = &missingSymFunc; Log.v("DerelictFT: Missing symbols callback is registered"); diff --git a/src/dlangui/platforms/windows/win32fonts.d b/src/dlangui/platforms/windows/win32fonts.d index b4d545b2..26a7d182 100644 --- a/src/dlangui/platforms/windows/win32fonts.d +++ b/src/dlangui/platforms/windows/win32fonts.d @@ -344,7 +344,8 @@ class Win32Font : Font { g.blackBoxY = cast(ubyte)metrics.gmBlackBoxY; g.originX = cast(byte)((metrics.gmptGlyphOrigin.x + 0) / 3); g.originY = cast(byte)metrics.gmptGlyphOrigin.y; - g.width = cast(ubyte)((metrics.gmCellIncX + 2) / 3); + g.widthPixels = cast(ubyte)((metrics.gmCellIncX + 2) / 3); + g.widthScaled = g.widthPixels << 6; g.subpixelMode = FontManager.subpixelRenderingMode; //Log.d(" *3 : blackBoxX=", metrics.gmBlackBoxX, " \tblackBoxY=", metrics.gmBlackBoxY, " \torigin.x=", metrics.gmptGlyphOrigin.x, " \torigin.y=", metrics.gmptGlyphOrigin.y, " \tgmCellIncX=", metrics.gmCellIncX); //Log.d(" /3 : blackBoxX=", g.blackBoxX, " \tblackBoxY=", g.blackBoxY, " \torigin.x=", g.originX, " \torigin.y=", g.originY, "\tgmCellIncX=", g.width); @@ -353,7 +354,8 @@ class Win32Font : Font { g.blackBoxY = cast(ubyte)metrics.gmBlackBoxY; g.originX = cast(byte)metrics.gmptGlyphOrigin.x; g.originY = cast(byte)metrics.gmptGlyphOrigin.y; - g.width = cast(ubyte)metrics.gmCellIncX; + g.widthPixels = cast(ubyte)metrics.gmCellIncX; + g.widthScaled = g.widthPixels << 6; } if (g.blackBoxX > 0 && g.blackBoxY > 0) {