diff --git a/.github/workflows/eepp-linux-build-check.yml b/.github/workflows/eepp-linux-build-check.yml index 74f90b237..6db96090a 100644 --- a/.github/workflows/eepp-linux-build-check.yml +++ b/.github/workflows/eepp-linux-build-check.yml @@ -32,7 +32,7 @@ jobs: tar xvzf premake-5.0.0-beta2-linux.tar.gz - name: Build run: | - ./premake5 gmake2 + ./premake5 --with-text-shaper gmake2 cd make/linux make all -j$(nproc) -e config=release_x86_64 - name: Unit Tests diff --git a/.github/workflows/eepp-macos-build-check.yml b/.github/workflows/eepp-macos-build-check.yml index 448ab471c..613ddd456 100644 --- a/.github/workflows/eepp-macos-build-check.yml +++ b/.github/workflows/eepp-macos-build-check.yml @@ -19,7 +19,7 @@ jobs: brew install wget SDL2 premake - name: Build run: | - premake5 gmake2 + premake5 --with-text-shaper gmake2 make -C make/macosx/ -e config=release_arm64 - name: Unit Tests run: | diff --git a/.github/workflows/eepp-windows-build-check.yml b/.github/workflows/eepp-windows-build-check.yml index ed00e31ec..102e1e0c5 100644 --- a/.github/workflows/eepp-windows-build-check.yml +++ b/.github/workflows/eepp-windows-build-check.yml @@ -28,7 +28,7 @@ jobs: - name: Create project shell: powershell run: | - ./premake5.exe --windows-vc-build vs2022 + ./premake5.exe --windows-vc-build --with-text-shaper vs2022 - name: Build shell: cmd run: | diff --git a/include/eepp/graphics/fonttruetype.hpp b/include/eepp/graphics/fonttruetype.hpp index 1127e51f4..c4e1ce31a 100644 --- a/include/eepp/graphics/fonttruetype.hpp +++ b/include/eepp/graphics/fonttruetype.hpp @@ -174,7 +174,7 @@ class EE_API FontTrueType : public Font { Uint32 getGlyphIndex( const Uint32& codePoint ) const; - Glyph loadGlyph( Uint32 codePoint, unsigned int characterSize, bool bold, bool italic, + Glyph loadGlyphByIndex( Uint32 codePoint, unsigned int characterSize, bool bold, bool italic, Float outlineThickness, Page& page, const Float& maxWidth = 0.f ) const; Rect findGlyphRect( Page& page, unsigned int width, unsigned int height ) const; @@ -214,6 +214,7 @@ class EE_API FontTrueType : public Font { bool mIsItalic{ false }; mutable UnorderedMap mClosestCharacterSize; mutable UnorderedMap mCodePointIndexCache; + mutable UnorderedMap mKerningCache; FontHinting mHinting{ FontHinting::Full }; FontAntialiasing mAntialiasing{ FontAntialiasing::Grayscale }; FontTrueType* mFontBold{ nullptr }; diff --git a/premake4.lua b/premake4.lua index d67b3fe7f..7f5c0402f 100644 --- a/premake4.lua +++ b/premake4.lua @@ -167,6 +167,7 @@ newoption { trigger = "thread-sanitizer", description ="Compile with ThreadSanit newoption { trigger = "address-sanitizer", description = "Compile with AddressSanitizer." } newoption { trigger = "time-trace", description = "Compile with time trace." } newoption { trigger = "disable-static-build", description = "Disables eepp static build project, this is just a helper to avoid rebuilding twice eepp while developing the library." } +newoption { trigger = "with-text-shaper", description = "Enables text-shaping capabilities by relying on harfbuzz." } newoption { trigger = "with-backend", description = "Select the backend to use for window and input handling.\n\t\t\tIf no backend is selected or if the selected is not installed the script will search for a backend present in the system, and will use it.", @@ -741,8 +742,12 @@ function add_static_links() links { "freetype-static", "libpng-static" } end - links { "harfbuzz-static", - "SOIL2-static", + if _OPTIONS["with-text-shaper"] then + links { "harfbuzz-static" } + defines { "EE_TEXT_SHAPER_ENABLED" } + end + + links { "SOIL2-static", "libzip-static", "jpeg-compressor-static", "zlib-static", @@ -1110,14 +1115,16 @@ solution "eepp" includedirs { "src/thirdparty/freetype2/include", "src/thirdparty/libpng" } build_base_configuration( "freetype" ) - project "harfbuzz-static" - kind "StaticLib" - language "C++" - set_targetdir("libs/" .. os.get_real() .. "/thirdparty/") - defines { "HAVE_CONFIG_H" } - files { "src/thirdparty/harfbuzz/**.cc" } - includedirs { "src/thirdparty/freetype2/include", "src/thirdparty/harfbuzz" } - build_base_cpp_configuration( "harfbuzz" ) + if _OPTIONS["with-text-shaper"] then + project "harfbuzz-static" + kind "StaticLib" + language "C++" + set_targetdir("libs/" .. os.get_real() .. "/thirdparty/") + defines { "HAVE_CONFIG_H" } + files { "src/thirdparty/harfbuzz/**.cc" } + includedirs { "src/thirdparty/freetype2/include", "src/thirdparty/harfbuzz" } + build_base_cpp_configuration( "harfbuzz" ) + end project "chipmunk-static" kind "StaticLib" diff --git a/premake5.lua b/premake5.lua index 3d930370a..2607b5142 100644 --- a/premake5.lua +++ b/premake5.lua @@ -15,6 +15,7 @@ newoption { trigger = "thread-sanitizer", description = "Compile with ThreadSani newoption { trigger = "address-sanitizer", description = "Compile with AddressSanitizer." } newoption { trigger = "time-trace", description = "Compile with time tracing." } newoption { trigger = "disable-static-build", description = "Disables eepp static build project, this is just a helper to avoid rebuilding twice eepp while developing the library." } +newoption { trigger = "with-text-shaper", description = "Enables text-shaping capabilities by relying on harfbuzz." } newoption { trigger = "with-backend", description = "Select the backend to use for window and input handling.\n\t\t\tIf no backend is selected or if the selected is not installed the script will search for a backend present in the system, and will use it.", @@ -497,8 +498,12 @@ function add_static_links() links { "freetype-static", "libpng-static" } end - links { "harfbuzz-static", - "SOIL2-static", + if _OPTIONS["with-text-shaper"] then + links { "harfbuzz-static" } + defines { "EE_TEXT_SHAPER_ENABLED" } + end + + links { "SOIL2-static", "chipmunk-static", "libzip-static", "jpeg-compressor-static", diff --git a/projects/linux/ee.config b/projects/linux/ee.config index f43f359f8..71b4fd637 100644 --- a/projects/linux/ee.config +++ b/projects/linux/ee.config @@ -14,3 +14,4 @@ #define DR_FLAC_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION #define ECODE_USE_BACKWARD +#define EE_TEXT_SHAPER_ENABLED diff --git a/projects/linux/ee.creator.user b/projects/linux/ee.creator.user index 5f7665549..da7eef32d 100644 --- a/projects/linux/ee.creator.user +++ b/projects/linux/ee.creator.user @@ -1,6 +1,6 @@ - + EnvironmentId @@ -116,7 +116,7 @@ {388e5431-b31b-42b3-b9ad-9002d279d75d} 10 0 - 19 + 8 ../../make/linux @@ -193,7 +193,7 @@ true - --with-debug-symbols gmake + --with-debug-symbols --with-text-shaper gmake premake4 %{buildDir}../../../ ProjectExplorer.ProcessStep diff --git a/src/eepp/graphics/fonttruetype.cpp b/src/eepp/graphics/fonttruetype.cpp index 201d7b9b8..cdcfa10d3 100644 --- a/src/eepp/graphics/fonttruetype.cpp +++ b/src/eepp/graphics/fonttruetype.cpp @@ -22,8 +22,10 @@ using namespace EE::Window; #include #include +#ifdef EE_TEXT_SHAPER_ENABLED #include #include +#endif namespace { @@ -240,8 +242,9 @@ bool FontTrueType::loadFromPack( Pack* pack, std::string filePackPath ) { bool FontTrueType::setFontFace( void* _face ) { FT_Face face = (FT_Face)_face; mFace = face; +#ifdef EE_TEXT_SHAPER_ENABLED mHBFont = hb_ft_font_create( static_cast( face ), NULL ); - +#endif mIsMonospaceComplete = mIsMonospace = FT_IS_FIXED_WIDTH( face ); mIsColorEmojiFont = checkIsColorEmojiFont( face ); mIsEmojiFont = FT_Get_Char_Index( face, 0x1F600 ) != 0; @@ -334,8 +337,8 @@ const Glyph& FontTrueType::getGlyph( Uint32 codePoint, unsigned int characterSiz static_cast( FontManager::instance()->getColorEmojiFont() ); if ( ( idx = fontEmoji->getGlyphIndex( codePoint ) ) ) { return fontEmoji->getGlyphByIndex( idx, characterSize, bold, italic, - outlineThickness, getPage( characterSize ), - maxWidth ); + 0 /* outline thickness won't work here */, + getPage( characterSize ), maxWidth ); } } else if ( !mIsEmojiFont && FontManager::instance()->getEmojiFont() != nullptr && FontManager::instance()->getEmojiFont()->getType() == FontType::TTF ) { @@ -420,8 +423,8 @@ const Glyph& FontTrueType::getGlyphByIndex( Uint32 index, unsigned int character return it->second; } else { // Not found: we have to load it - Glyph glyph = - loadGlyph( index, characterSize, bold, italic, outlineThickness, page, maxWidth ); + Glyph glyph = loadGlyphByIndex( index, characterSize, bold, italic, outlineThickness, page, + maxWidth ); return glyphs.emplace( key, glyph ).first->second; } @@ -564,12 +567,15 @@ Float FontTrueType::getKerning( Uint32 first, Uint32 second, unsigned int charac // 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 ( glyph1.font == glyph2.font ) { + 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 ); + } // Return the X advance return std::floor( @@ -588,6 +594,11 @@ Float FontTrueType::getKerningFromGlyphIndex( Uint32 index1, Uint32 index2, if ( index1 == 0 || index2 == 0 || isMonospace() ) return 0.f; + auto pair = static_cast( index1 ) << 32 | index2; + auto found = mKerningCache.find( pair ); + if ( found != mKerningCache.end() ) + return found->second; + FT_Face face = static_cast( mFace ); if ( face && setCurrentSize( characterSize ) ) { @@ -604,15 +615,20 @@ Float FontTrueType::getKerningFromGlyphIndex( Uint32 index1, Uint32 index2, FT_Get_Kerning( face, index1, index2, FT_KERNING_UNFITTED, &kerning ); // X advance is already in pixels for bitmap fonts - if ( !FT_IS_SCALABLE( face ) ) + if ( !FT_IS_SCALABLE( face ) ) { + mKerningCache.insert( { pair, static_cast( kerning.x ) } ); return static_cast( kerning.x ); + } // Return the X advance - return std::floor( - ( secondLsbDelta - firstRsbDelta + static_cast( kerning.x ) + 32 ) / - static_cast( 1 << 6 ) ); + Float val = + std::floor( ( secondLsbDelta - firstRsbDelta + static_cast( kerning.x ) + 32 ) / + static_cast( 1 << 6 ) ); + mKerningCache.insert( { pair, val } ); + return val; } else { // Invalid font, or no kerning + mKerningCache.insert( { pair, 0.f } ); return 0.f; } } @@ -728,8 +744,10 @@ void FontTrueType::cleanup() { mCallbacks.clear(); mNumCallBacks = 0; +#ifdef EE_TEXT_SHAPER_ENABLED if ( mHBFont ) hb_font_destroy( (hb_font_t*)mHBFont ); +#endif // Destroy the stroker if ( mStroker ) @@ -797,8 +815,9 @@ fontSetRenderOptions( FT_Library library, FontAntialiasing antialiasing, FontHin return FT_RENDER_MODE_NORMAL; } -Glyph FontTrueType::loadGlyph( Uint32 index, unsigned int characterSize, bool bold, bool /*italic*/, - Float outlineThickness, Page& page, const Float& maxWidth ) const { +Glyph FontTrueType::loadGlyphByIndex( Uint32 index, unsigned int characterSize, bool bold, + bool /*italic*/, Float outlineThickness, Page& page, + const Float& maxWidth ) const { // The glyph to return Glyph glyph; diff --git a/src/eepp/graphics/text.cpp b/src/eepp/graphics/text.cpp index 05451092c..68e6b0706 100644 --- a/src/eepp/graphics/text.cpp +++ b/src/eepp/graphics/text.cpp @@ -12,8 +12,10 @@ #include #include +#ifdef EE_TEXT_SHAPER_ENABLED #include #include +#endif namespace EE { namespace Graphics { @@ -79,6 +81,7 @@ class TextShapeRun { bool mIsNewLine{ false }; FontStyleConfig& mConfig; }; + } // namespace std::string Text::styleFlagToString( const Uint32& flags ) { @@ -218,7 +221,7 @@ static inline void drawGlyph( BatchRenderer* BR, GlyphDrawable* gd, const Vector } } -static inline void idrawUnderline( Font* font, Float fontSize, const Color& fontColor, +static inline void _drawUnderline( Font* font, Float fontSize, const Color& fontColor, const Vector2f& cpos, const Uint32& style, BatchRenderer* BR, Float outlineThickness, const Vector2f& pos, Float width, const Color& shadowColor, const Vector2f& shadowOffset, @@ -253,7 +256,7 @@ void Text::drawUnderline( const Vector2f& pos, Float width, Font* font, Float fo const Color& outlineColor, const Color& shadowColor, const Vector2f& shadowOffset ) { BatchRenderer* BR = GlobalBatchRenderer::instance(); - idrawUnderline( font, fontSize, fontColor, pos, style, BR, outlineThickness, pos, width, + _drawUnderline( font, fontSize, fontColor, pos, style, BR, outlineThickness, pos, width, shadowColor, shadowOffset, outlineColor ); } @@ -329,7 +332,7 @@ Sizef Text::draw( const StringType& string, const Vector2f& pos, Font* font, Flo } case '\n': { if ( style & Text::Underlined ) { - idrawUnderline( font, fontSize, fontColor, cpos, style, BR, outlineThickness, + _drawUnderline( font, fontSize, fontColor, cpos, style, BR, outlineThickness, pos, width, shadowColor, shadowOffset, outlineColor ); } if ( style & Text::StrikeThrough ) { @@ -378,7 +381,7 @@ Sizef Text::draw( const StringType& string, const Vector2f& pos, Font* font, Flo } if ( ( style & Text::Underlined ) && width != 0 ) { - idrawUnderline( font, fontSize, fontColor, cpos, style, BR, outlineThickness, pos, width, + _drawUnderline( font, fontSize, fontColor, cpos, style, BR, outlineThickness, pos, width, shadowColor, shadowOffset, outlineColor ); } @@ -1302,6 +1305,7 @@ void Text::ensureGeometryUpdate() { break; } +#ifdef EE_TEXT_SHAPER_ENABLED if ( mFontStyleConfig.Font->getType() == FontType::TTF ) { FontTrueType* rFont = static_cast( mFontStyleConfig.Font ); hb_buffer_t* hbBuffer = hb_buffer_create(); @@ -1339,25 +1343,54 @@ void Text::ensureGeometryUpdate() { hb_glyph_info_t curGlyph = glyphInfo[i]; hb_glyph_position_t curGlyphPos = glyphPos[i]; + x += rFont->getKerningFromGlyphIndex( + prevGlyphIndex, curGlyph.codepoint, mFontStyleConfig.CharacterSize, bold, + reqItalic, mFontStyleConfig.OutlineThickness ); + + Float currentX = x + ( curGlyphPos.x_offset / 64.f ); + Float currentY = y + ( curGlyphPos.y_offset / 64.f ); + + // Apply the outline + if ( mFontStyleConfig.OutlineThickness != 0 ) { + const Glyph& glyph = font->getGlyphByIndex( + curGlyph.codepoint, mFontStyleConfig.CharacterSize, bold, reqItalic, + mFontStyleConfig.OutlineThickness, + rFont->getPage( mFontStyleConfig.CharacterSize ), 0 ); + + Float left = glyph.bounds.Left; + Float top = glyph.bounds.Top; + Float right = glyph.bounds.Left + glyph.bounds.Right; + Float bottom = glyph.bounds.Top + glyph.bounds.Bottom; + + // Add the outline glyph to the vertices + if ( glyph.bounds.Right > 0 && glyph.bounds.Bottom > 0 ) { + addGlyphQuad( mOutlineVertices, Vector2f( currentX, currentY ), glyph, + italic, mFontStyleConfig.OutlineThickness, centerDiffX ); + } + + // Update the current bounds with the outlined glyph bounds + minX = std::min( minX, x + left - italic * bottom - + mFontStyleConfig.OutlineThickness ); + maxX = std::max( maxX, + x + right - italic * top - mFontStyleConfig.OutlineThickness ); + minY = std::min( minY, y + top - mFontStyleConfig.OutlineThickness ); + maxY = std::max( maxY, y + bottom - mFontStyleConfig.OutlineThickness ); + if ( mCachedWidthNeedUpdate ) { + maxW = std::max( maxW, x + glyph.advance - italic * top - + mFontStyleConfig.OutlineThickness ); + } + } + // Extract the current glyph's description const Glyph& glyph = font->getGlyphByIndex( - curGlyph.codepoint, mFontStyleConfig.CharacterSize, bold, italic, 0, + curGlyph.codepoint, mFontStyleConfig.CharacterSize, bold, reqItalic, 0, rFont->getPage( mFontStyleConfig.CharacterSize ), 0 ); - if ( prevGlyphIndex != 0 ) { - x += rFont->getKerningFromGlyphIndex( prevGlyphIndex, curGlyph.codepoint, - mFontStyleConfig.CharacterSize, bold, - italic, 0 ); - } - Float left = glyph.bounds.Left; Float top = glyph.bounds.Top; Float right = glyph.bounds.Left + glyph.bounds.Right; Float bottom = glyph.bounds.Top + glyph.bounds.Bottom; - Float currentX = x + ( curGlyphPos.x_offset / 64.f ); - Float currentY = y + ( curGlyphPos.y_offset / 64.f ); - // Add a quad for the current character if ( glyph.bounds.Right > 0 && glyph.bounds.Bottom > 0 ) { addGlyphQuad( mVertices, Vector2f( currentX, currentY ), glyph, italic, 0, @@ -1385,7 +1418,7 @@ void Text::ensureGeometryUpdate() { } // If we're using the underlined style, add the last line - if ( underlined ) { + if ( underlined && run.runIsNewLine() ) { addLine( mVertices, x, y, underlineOffset, underlineThickness, 0, centerDiffX ); if ( mFontStyleConfig.OutlineThickness != 0 ) @@ -1394,7 +1427,7 @@ void Text::ensureGeometryUpdate() { } // If we're using the strike through style, add the last line across all characters - if ( strikeThrough ) { + if ( strikeThrough && run.runIsNewLine() ) { addLine( mVertices, x, y, strikeThroughOffset, underlineThickness, 0, centerDiffX ); if ( mFontStyleConfig.OutlineThickness != 0 ) @@ -1428,6 +1461,7 @@ void Text::ensureGeometryUpdate() { return; } +#endif for ( std::size_t i = 0; i < size; ++i ) { Uint32 curChar = mString[i]; @@ -1514,7 +1548,7 @@ void Text::ensureGeometryUpdate() { if ( mFontStyleConfig.OutlineThickness != 0 ) { const Glyph& glyph = mFontStyleConfig.Font->getGlyph( curChar, mFontStyleConfig.CharacterSize, bold, - italic, mFontStyleConfig.OutlineThickness ); + reqItalic, mFontStyleConfig.OutlineThickness ); Float left = glyph.bounds.Left; Float top = glyph.bounds.Top; @@ -1615,9 +1649,10 @@ void Text::ensureColorUpdate() { if ( mContainsColorEmoji ) { auto positions = Font::emojiCodePointsPositions( mString ); - for ( const auto& position : positions ) + for ( const auto& position : positions ) { setFillColor( Color( 255, 255, 255, mFontStyleConfig.FontColor.a ), position, position ); + } } } } @@ -1752,7 +1787,7 @@ void Text::setFillColor( const Color& color, Uint32 from, Uint32 to ) { } } - if ( rto == s ) { + if ( to == s ) { if ( underlined ) { lpos++; Uint32 pos = lpos * GLi->quadVertexs(); @@ -1932,21 +1967,18 @@ void Text::addGlyphQuad( std::vector& vertices, Vector2f position, Uint32 Text::getTotalVertices() { bool underlined = ( mFontStyleConfig.Style & Underlined ) != 0; bool strikeThrough = ( mFontStyleConfig.Style & StrikeThrough ) != 0; - size_t sl = mString.size(); - size_t sv = sl * GLi->quadVertexs(); - - String::StringBaseType* c = &mString[0]; + size_t sv = mString.size() * GLi->quadVertexs(); Uint32 skiped = 0; bool lineHasChars = false; - while ( '\0' != *c ) { + for ( const auto& ch : mString ) { lineHasChars = true; - if ( ' ' == *c || '\n' == *c || '\t' == *c || '\r' == *c ) { + if ( ' ' == ch || '\n' == ch || '\t' == ch || '\r' == ch ) { lineHasChars = false; skiped++; - if ( '\n' == *c ) { + if ( '\n' == ch ) { if ( underlined ) skiped--; @@ -1954,8 +1986,6 @@ Uint32 Text::getTotalVertices() { skiped--; } } - - c++; } if ( lineHasChars ) {