Added kerning cache in FontTrueType.

This commit is contained in:
Martín Lucas Golini
2026-04-25 02:03:21 -03:00
parent c76f9bf3c2
commit bc37296f66
2 changed files with 65 additions and 36 deletions

View File

@@ -246,6 +246,8 @@ class EE_API FontTrueType : public Font {
mutable UnorderedMap<unsigned int, unsigned int> mClosestCharacterSize;
mutable UnorderedMap<Uint32, Uint32> mCodePointIndexCache;
mutable UnorderedMap<Uint32, std::tuple<Uint32, Uint32, bool>> mKeyCache;
mutable UnorderedMap<Uint64, Float> mKerningCache; // For codepoints (getKerning)
mutable UnorderedMap<Uint64, Float> mKerningGlyphCache; // For glyph indices
FontHinting mHinting{ FontHinting::Full };
FontAntialiasing mAntialiasing{ FontAntialiasing::Grayscale };
FontTrueType* mFontBold{ nullptr };

View File

@@ -235,6 +235,22 @@ static inline Uint64 getCodePointKey( Uint32 codePoint, bool bold, bool italics,
( static_cast<EE::Uint64>( italics ) << 32 ) | codePoint;
}
// Combine kerning parameters into a single 64-bit key for O(1) lookups.
// - first/index1 : 21 bits (covers full Unicode range up to 0x10FFFF)
// - second/index2: 21 bits
// - characterSize: 12 bits (max font size 4095)
// - bold : 1 bit
// - italic : 1 bit
// - outline : 8 bits (max outline thickness 2.55 scaled by 100)
static inline Uint64 getKerningKey( Uint32 first, Uint32 second, unsigned int characterSize,
bool bold, bool italic, Float outlineThickness ) {
return ( static_cast<Uint64>( first & 0x1FFFFF ) << 43 ) |
( static_cast<Uint64>( second & 0x1FFFFF ) << 22 ) |
( static_cast<Uint64>( characterSize & 0xFFF ) << 10 ) |
( static_cast<Uint64>( bold ) << 9 ) | ( static_cast<Uint64>( italic ) << 8 ) |
( static_cast<Uint64>( static_cast<Uint32>( outlineThickness * 100.f ) & 0xFF ) );
}
FontTrueType* FontTrueType::New( const std::string& FontName ) {
return eeNew( FontTrueType, ( FontName ) );
}
@@ -455,7 +471,7 @@ bool FontTrueType::setFontFace( void* _face ) {
if ( mHasSvgGlyphs ) {
#ifdef EE_TRUETYPE_SVG_FONT_ENABLED
FT_Property_Set( static_cast<FT_Library>( mLibrary ), "ot-svg", "svg-hooks", &svg_hooks );
FT_Property_Set( static_cast<FT_Library>( mLibrary ), "ot-svg", "svg-hooks", &svg_hooks );
#else
return false;
#endif
@@ -800,44 +816,45 @@ Float FontTrueType::getKerning( Uint32 first, Uint32 second, unsigned int charac
if ( first == 0 || second == 0 || isMonospace() )
return 0.f;
Uint64 key = getKerningKey( first, second, characterSize, bold, italic, outlineThickness );
auto it = mKerningCache.find( key );
if ( it != mKerningCache.end() ) {
return it->second;
}
Float kerningVal = 0.f;
FT_Face face = static_cast<FT_Face>( mFace );
if ( face && setCurrentSize( characterSize ) ) {
auto glyph1 = getGlyph( first, characterSize, bold, italic, outlineThickness );
auto glyph2 = getGlyph( second, characterSize, bold, italic, outlineThickness );
if ( glyph1.font != glyph2.font )
return 0.f;
// Convert the characters to indices
FT_UInt index1 = getGlyphIndex( first );
FT_UInt index2 = getGlyphIndex( second );
// Retrieve position compensation deltas generated by FT_LOAD_FORCE_AUTOHINT flag
auto firstRsbDelta = static_cast<Float>( glyph1.rsbDelta );
auto secondLsbDelta = static_cast<Float>( glyph2.lsbDelta );
// Get the kerning vector
FT_Vector kerning;
kerning.x = kerning.y = 0;
if ( glyph1.font == glyph2.font ) {
// Convert the characters to indices
FT_UInt index1 = getGlyphIndex( first );
FT_UInt index2 = getGlyphIndex( second );
// Get the kerning vector
FT_Vector kerning;
kerning.x = kerning.y = 0;
if ( FT_HAS_KERNING( face ) )
FT_Get_Kerning( face, index1, index2, FT_KERNING_UNFITTED, &kerning );
// X advance is already in pixels for bitmap fonts
if ( !FT_IS_SCALABLE( face ) )
return static_cast<Float>( kerning.x );
if ( !FT_IS_SCALABLE( face ) ) {
kerningVal = static_cast<Float>( kerning.x );
} else {
auto firstRsbDelta = static_cast<Float>( glyph1.rsbDelta );
auto secondLsbDelta = static_cast<Float>( glyph2.lsbDelta );
kerningVal = std::floor(
( secondLsbDelta - firstRsbDelta + static_cast<float>( kerning.x ) + 32 ) /
static_cast<float>( 1 << 6 ) );
}
}
// Return the X advance
return std::floor(
( secondLsbDelta - firstRsbDelta + static_cast<float>( kerning.x ) + 32 ) /
static_cast<float>( 1 << 6 ) );
} else {
// Invalid font, or no kerning
return 0.f;
}
mKerningCache[key] = kerningVal;
return kerningVal;
}
Float FontTrueType::getKerningFromGlyphIndex( Uint32 index1, Uint32 index2,
@@ -847,6 +864,13 @@ Float FontTrueType::getKerningFromGlyphIndex( Uint32 index1, Uint32 index2,
if ( index1 == 0 || index2 == 0 || isMonospace() )
return 0.f;
Uint64 key = getKerningKey( index1, index2, characterSize, bold, italic, outlineThickness );
auto it = mKerningGlyphCache.find( key );
if ( it != mKerningGlyphCache.end() ) {
return it->second;
}
Float kerningVal = 0.f;
FT_Face face = static_cast<FT_Face>( mFace );
if ( face && setCurrentSize( characterSize ) ) {
@@ -864,18 +888,17 @@ Float FontTrueType::getKerningFromGlyphIndex( Uint32 index1, Uint32 index2,
// X advance is already in pixels for bitmap fonts
if ( !FT_IS_SCALABLE( face ) ) {
return static_cast<Float>( kerning.x );
kerningVal = static_cast<Float>( kerning.x );
} else {
// Get the X advance
kerningVal = std::floor(
( secondLsbDelta - firstRsbDelta + static_cast<float>( kerning.x ) + 32 ) /
static_cast<float>( 1 << 6 ) );
}
// Return the X advance
Float val =
std::floor( ( secondLsbDelta - firstRsbDelta + static_cast<float>( kerning.x ) + 32 ) /
static_cast<float>( 1 << 6 ) );
return val;
} else {
// Invalid font, or no kerning
return 0.f;
}
mKerningGlyphCache[key] = kerningVal;
return kerningVal;
}
Float FontTrueType::getLineSpacing( unsigned int characterSize ) const {
@@ -1062,6 +1085,8 @@ void FontTrueType::cleanup() {
mFontBoldItalicCb = 0;
mPages.clear();
std::vector<Uint8>().swap( mPixelBuffer );
mKerningCache.clear();
mKerningGlyphCache.clear();
mCodePointIndexCache.clear();
mKeyCache.clear();
mClosestCharacterSize.clear();
@@ -1768,6 +1793,8 @@ void FontTrueType::clearCache() {
mClosestCharacterSize.clear();
mCodePointIndexCache.clear();
mKeyCache.clear();
mKerningCache.clear();
mKerningGlyphCache.clear();
Text::GlobalInvalidationId++;
}