diff --git a/include/eepp/graphics/fonttruetype.hpp b/include/eepp/graphics/fonttruetype.hpp index d75dd6d8d..78241e9c0 100644 --- a/include/eepp/graphics/fonttruetype.hpp +++ b/include/eepp/graphics/fonttruetype.hpp @@ -246,6 +246,8 @@ class EE_API FontTrueType : public Font { mutable UnorderedMap mClosestCharacterSize; mutable UnorderedMap mCodePointIndexCache; mutable UnorderedMap> mKeyCache; + mutable UnorderedMap mKerningCache; // For codepoints (getKerning) + mutable UnorderedMap mKerningGlyphCache; // For glyph indices FontHinting mHinting{ FontHinting::Full }; FontAntialiasing mAntialiasing{ FontAntialiasing::Grayscale }; FontTrueType* mFontBold{ nullptr }; diff --git a/src/eepp/graphics/fonttruetype.cpp b/src/eepp/graphics/fonttruetype.cpp index 559f2809d..84a4fa08b 100644 --- a/src/eepp/graphics/fonttruetype.cpp +++ b/src/eepp/graphics/fonttruetype.cpp @@ -235,6 +235,22 @@ static inline Uint64 getCodePointKey( Uint32 codePoint, bool bold, bool italics, ( static_cast( 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( first & 0x1FFFFF ) << 43 ) | + ( static_cast( second & 0x1FFFFF ) << 22 ) | + ( static_cast( characterSize & 0xFFF ) << 10 ) | + ( static_cast( bold ) << 9 ) | ( static_cast( italic ) << 8 ) | + ( static_cast( static_cast( 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( mLibrary ), "ot-svg", "svg-hooks", &svg_hooks ); + FT_Property_Set( static_cast( 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( 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( glyph1.rsbDelta ); - auto secondLsbDelta = static_cast( 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( kerning.x ); + if ( !FT_IS_SCALABLE( face ) ) { + kerningVal = static_cast( kerning.x ); + } else { + auto firstRsbDelta = static_cast( glyph1.rsbDelta ); + auto secondLsbDelta = static_cast( glyph2.lsbDelta ); + kerningVal = std::floor( + ( secondLsbDelta - firstRsbDelta + static_cast( kerning.x ) + 32 ) / + static_cast( 1 << 6 ) ); + } } - - // Return the X advance - return std::floor( - ( secondLsbDelta - firstRsbDelta + static_cast( kerning.x ) + 32 ) / - static_cast( 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( 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( kerning.x ); + kerningVal = static_cast( kerning.x ); + } else { + // Get the X advance + kerningVal = std::floor( + ( secondLsbDelta - firstRsbDelta + static_cast( kerning.x ) + 32 ) / + static_cast( 1 << 6 ) ); } - - // Return the X advance - Float val = - std::floor( ( secondLsbDelta - firstRsbDelta + static_cast( kerning.x ) + 32 ) / - static_cast( 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().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++; }