From 7bc446e9f1ff93418a04f800b9858652ef8bd0f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Wed, 7 Jan 2026 00:42:14 -0300 Subject: [PATCH] Allow to set TextDirection as text segmentation hint. Update efsw. --- include/eepp/graphics/text.hpp | 71 +++++++----- include/eepp/graphics/textlayout.hpp | 9 +- include/eepp/ui/uicodeeditor.hpp | 5 + src/eepp/graphics/text.cpp | 117 +++++++++++-------- src/eepp/graphics/textlayout.cpp | 162 ++++++++++++++++----------- src/eepp/ui/doc/documentview.cpp | 3 +- src/eepp/ui/uicodeeditor.cpp | 37 ++++-- src/thirdparty/efsw | 2 +- 8 files changed, 249 insertions(+), 157 deletions(-) diff --git a/include/eepp/graphics/text.hpp b/include/eepp/graphics/text.hpp index c2b343019..90fc185a0 100644 --- a/include/eepp/graphics/text.hpp +++ b/include/eepp/graphics/text.hpp @@ -53,19 +53,23 @@ class EE_API Text { static Float getTextWidth( Font* font, const Uint32& fontSize, const String& string, const Uint32& style, const Uint32& tabWidth = 4, const Float& outlineThickness = 0.f, Uint32 textDrawHints = 0, + TextDirection direction = TextDirection::Unspecified, std::optional tabOffset = {} ); static Float getTextWidth( Font* font, const Uint32& fontSize, const String::View& string, const Uint32& style, const Uint32& tabWidth = 4, const Float& outlineThickness = 0.f, Uint32 textDrawHints = 0, + TextDirection direction = TextDirection::Unspecified, std::optional tabOffset = {} ); static Float getTextWidth( const String& string, const FontStyleConfig& config, const Uint32& tabWidth = 4, Uint32 textDrawHints = 0, + TextDirection direction = TextDirection::Unspecified, std::optional tabOffset = {} ); static Float getTextWidth( const String::View& string, const FontStyleConfig& config, const Uint32& tabWidth = 4, Uint32 textDrawHints = 0, + TextDirection direction = TextDirection::Unspecified, std::optional tabOffset = {} ); static Sizef draw( const String& string, const Vector2f& pos, Font* font, Float fontSize, @@ -74,10 +78,12 @@ class EE_API Text { const Color& shadowColor = Color::Black, const Vector2f& shadowOffset = { 1, 1 }, const Uint32& tabWidth = 4, Uint32 textDrawHints = 0, + TextDirection direction = TextDirection::Unspecified, const WhitespaceDisplayConfig& whitespaceDisplayConfig = {} ); static Sizef draw( const String& string, const Vector2f& pos, const FontStyleConfig& config, const Uint32& tabWidth = 4, Uint32 textDrawHints = 0, + TextDirection direction = TextDirection::Unspecified, const WhitespaceDisplayConfig& whitespaceDisplayConfig = {} ); static Sizef draw( const String::View& string, const Vector2f& pos, Font* font, Float fontSize, @@ -86,11 +92,13 @@ class EE_API Text { const Color& shadowColor = Color::Black, const Vector2f& shadowOffset = { 1, 1 }, const Uint32& tabWidth = 4, Uint32 textDrawHints = 0, + TextDirection direction = TextDirection::Unspecified, const WhitespaceDisplayConfig& whitespaceDisplayConfig = {} ); static Sizef draw( const String::View& string, const Vector2f& pos, const FontStyleConfig& config, const Uint32& tabWidth = 4, Uint32 textDrawHints = 0, + TextDirection direction = TextDirection::Unspecified, const WhitespaceDisplayConfig& whitespaceDisplayConfig = {} ); static void drawUnderline( const Vector2f& pos, Float width, Font* font, Float fontSize, @@ -107,40 +115,42 @@ class EE_API Text { const Uint32& fontSize, const String& string, const Uint32& style, const Uint32& tabWidth = 4, const Float& outlineThickness = 0.f, - std::optional tabOffset = {}, Uint32 textHints = 0 ); + std::optional tabOffset = {}, Uint32 textHints = 0, + TextDirection direction = TextDirection::Unspecified ); static Vector2f findCharacterPos( std::size_t index, Font* font, const Uint32& fontSize, const String& string, const Uint32& style, const Uint32& tabWidth = 4, const Float& outlineThickness = 0.f, std::optional tabOffset = {}, bool allowNewLine = true, - Uint32 textHints = 0 ); + Uint32 textHints = 0, + TextDirection direction = TextDirection::Unspecified ); - static std::size_t findLastCharPosWithinLength( Font* font, const Uint32& fontSize, - const String& string, Float maxWidth, - const Uint32& style, const Uint32& tabWidth = 4, - const Float& outlineThickness = 0.f, - std::optional tabOffset = {}, - Uint32 textHints = 0 ); + static std::size_t + findLastCharPosWithinLength( Font* font, const Uint32& fontSize, const String& string, + Float maxWidth, const Uint32& style, const Uint32& tabWidth = 4, + const Float& outlineThickness = 0.f, + std::optional tabOffset = {}, Uint32 textHints = 0, + TextDirection direction = TextDirection::Unspecified ); - static std::size_t findLastCharPosWithinLength( Font* font, const Uint32& fontSize, - const String::View& string, Float maxWidth, - const Uint32& style, const Uint32& tabWidth = 4, - const Float& outlineThickness = 0.f, - std::optional tabOffset = {}, - Uint32 textHints = 0 ); + static std::size_t + findLastCharPosWithinLength( Font* font, const Uint32& fontSize, const String::View& string, + Float maxWidth, const Uint32& style, const Uint32& tabWidth = 4, + const Float& outlineThickness = 0.f, + std::optional tabOffset = {}, Uint32 textHints = 0, + TextDirection direction = TextDirection::Unspecified ); - static std::size_t findLastCharPosWithinLength( const String& string, Float maxWidth, - const FontStyleConfig& config, - const Uint32& tabWidth = 4, - std::optional tabOffset = {}, - Uint32 textHints = 0 ); + static std::size_t + findLastCharPosWithinLength( const String& string, Float maxWidth, + const FontStyleConfig& config, const Uint32& tabWidth = 4, + std::optional tabOffset = {}, Uint32 textHints = 0, + TextDirection direction = TextDirection::Unspecified ); - static std::size_t findLastCharPosWithinLength( const String::View& string, Float maxWidth, - const FontStyleConfig& config, - const Uint32& tabWidth = 4, - std::optional tabOffset = {}, - Uint32 textHints = 0 ); + static std::size_t + findLastCharPosWithinLength( const String::View& string, Float maxWidth, + const FontStyleConfig& config, const Uint32& tabWidth = 4, + std::optional tabOffset = {}, Uint32 textHints = 0, + TextDirection direction = TextDirection::Unspecified ); static bool wrapText( Font* font, const Uint32& fontSize, String& string, const Float& maxWidth, const Uint32& style, const Uint32& tabWidth = 4, @@ -311,6 +321,10 @@ class EE_API Text { void setTextHints( Uint32 textHints ); + void setDirection( TextDirection direction ); + + TextDirection getDirection() const; + protected: struct VertexCoords { Vector2f texCoords; @@ -339,6 +353,7 @@ class EE_API Text { std::vector mOutlineVertices; std::vector mOutlineColors; std::vector mLinesWidth; + TextDirection mDirection{ TextDirection::Unspecified }; void ensureGeometryUpdate(); @@ -370,6 +385,7 @@ class EE_API Text { static Float getTextWidth( Font* font, const Uint32& fontSize, const StringType& string, const Uint32& style, const Uint32& tabWidth = 4, const Float& outlineThickness = 0.f, Uint32 textDrawHints = 0, + TextDirection direction = TextDirection::Unspecified, std::optional tabOffset = {} ); template @@ -378,11 +394,13 @@ class EE_API Text { const Color& fontColor, Uint32 style = 0, Float outlineThickness = 0.f, const Color& outlineColor = Color::Black, const Color& shadowColor = Color::Black, const Vector2f& shadowOffset = { 1, 1 }, const Uint32& tabWidth = 4, - Uint32 textDrawHints = 0, const WhitespaceDisplayConfig& whitespaceDisplayConfig = {} ); + Uint32 textDrawHints = 0, TextDirection direction = TextDirection::Unspecified, + const WhitespaceDisplayConfig& whitespaceDisplayConfig = {} ); template static Sizef draw( const StringType& string, const Vector2f& pos, const FontStyleConfig& config, const Uint32& tabWidth = 4, Uint32 textDrawHints = 0, + TextDirection direction = TextDirection::Unspecified, const WhitespaceDisplayConfig& whitespaceDisplayConfig = {} ); template @@ -390,7 +408,8 @@ class EE_API Text { findLastCharPosWithinLength( Font* font, const Uint32& fontSize, const StringType& string, Float width, const Uint32& style, const Uint32& tabWidth = 4, const Float& outlineThickness = 0.f, - std::optional tabOffset = {}, Uint32 textHints = 0 ); + std::optional tabOffset = {}, Uint32 textHints = 0, + TextDirection direction = TextDirection::Unspecified ); template static bool wrapText( Font* font, const Uint32& fontSize, StringType& string, diff --git a/include/eepp/graphics/textlayout.hpp b/include/eepp/graphics/textlayout.hpp index d7043a8bc..f25e03a33 100644 --- a/include/eepp/graphics/textlayout.hpp +++ b/include/eepp/graphics/textlayout.hpp @@ -26,19 +26,22 @@ class EE_API TextLayout { static Cache layout( const String& string, Font* font, const Uint32& fontSize, const Uint32& style, const Uint32& tabWidth = 4, const Float& outlineThickness = 0.f, std::optional tabOffset = {}, - Uint32 textDrawHints = 0 ); + Uint32 textDrawHints = 0, + TextDirection baseDirection = TextDirection::LeftToRight ); static Cache layout( const String::View& string, Font* font, const Uint32& fontSize, const Uint32& style, const Uint32& tabWidth = 4, const Float& outlineThickness = 0.f, std::optional tabOffset = {}, - Uint32 textDrawHints = 0 ); + Uint32 textDrawHints = 0, + TextDirection baseDirection = TextDirection::LeftToRight ); protected: template static Cache layout( const StringType& string, Font* font, const Uint32& fontSize, const Uint32& style, const Uint32& tabWidth = 4, const Float& outlineThickness = 0.f, std::optional tabOffset = {}, - Uint32 textDrawHints = 0 ); + Uint32 textDrawHints = 0, + TextDirection baseDirection = TextDirection::LeftToRight ); }; } // namespace EE::Graphics diff --git a/include/eepp/ui/uicodeeditor.hpp b/include/eepp/ui/uicodeeditor.hpp index fa51df908..3701a0f91 100644 --- a/include/eepp/ui/uicodeeditor.hpp +++ b/include/eepp/ui/uicodeeditor.hpp @@ -827,6 +827,10 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { bool isKerningEnabled() const; + void setTextDirection( TextDirection direction ); + + TextDirection getTextDirection() const; + protected: struct LastXOffset { TextPosition position{ 0, 0 }; @@ -958,6 +962,7 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { Uint32 mTabIndentCharacter{ 187 /*'ยป'*/ }; CharacterAlignment mTabIndentAlignment{ CharacterAlignment::Center }; std::vector mTokens; + TextDirection mTextDirection{ TextDirection::LeftToRight }; UICodeEditor( const std::string& elementTag, const bool& autoRegisterBaseCommands = true, const bool& autoRegisterBaseKeybindings = true ); diff --git a/src/eepp/graphics/text.cpp b/src/eepp/graphics/text.cpp index e6c9b2da3..233a94766 100644 --- a/src/eepp/graphics/text.cpp +++ b/src/eepp/graphics/text.cpp @@ -93,64 +93,65 @@ Uint32 Text::stringToStyleFlag( const std::string& str ) { Float Text::getTextWidth( Font* font, const Uint32& fontSize, const String& string, const Uint32& style, const Uint32& tabWidth, const Float& outlineThickness, Uint32 textDrawHints, - std::optional tabOffset ) { + TextDirection direction, std::optional tabOffset ) { return getTextWidth( font, fontSize, string, style, tabWidth, outlineThickness, - textDrawHints, tabOffset ); + textDrawHints, direction, tabOffset ); } Float Text::getTextWidth( Font* font, const Uint32& fontSize, const String::View& string, const Uint32& style, const Uint32& tabWidth, const Float& outlineThickness, Uint32 textDrawHints, - std::optional tabOffset ) { + TextDirection direction, std::optional tabOffset ) { return getTextWidth( font, fontSize, string, style, tabWidth, outlineThickness, - textDrawHints, tabOffset ); + textDrawHints, direction, tabOffset ); } Float Text::getTextWidth( const String& string, const FontStyleConfig& config, - const Uint32& tabWidth, Uint32 textDrawHints, + const Uint32& tabWidth, Uint32 textDrawHints, TextDirection direction, std::optional tabOffset ) { return getTextWidth( config.Font, config.CharacterSize, string, config.Style, tabWidth, - config.OutlineThickness, textDrawHints, tabOffset ); + config.OutlineThickness, textDrawHints, direction, tabOffset ); } Float Text::getTextWidth( const String::View& string, const FontStyleConfig& config, - const Uint32& tabWidth, Uint32 textDrawHints, + const Uint32& tabWidth, Uint32 textDrawHints, TextDirection direction, std::optional tabOffset ) { return getTextWidth( config.Font, config.CharacterSize, string, config.Style, - tabWidth, config.OutlineThickness, textDrawHints, + tabWidth, config.OutlineThickness, textDrawHints, direction, tabOffset ); } Sizef Text::draw( const String& string, const Vector2f& pos, Font* font, Float fontSize, const Color& fontColor, Uint32 style, Float outlineThickness, const Color& outlineColor, const Color& shadowColor, const Vector2f& shadowOffset, - const Uint32& tabWidth, Uint32 textDrawHints, + const Uint32& tabWidth, Uint32 textDrawHints, TextDirection direction, const WhitespaceDisplayConfig& whitespaceDisplayConfig ) { return draw( string, pos, font, fontSize, fontColor, style, outlineThickness, outlineColor, shadowColor, shadowOffset, tabWidth, textDrawHints, - whitespaceDisplayConfig ); + direction, whitespaceDisplayConfig ); } Sizef Text::draw( const String& string, const Vector2f& pos, const FontStyleConfig& config, - const Uint32& tabWidth, Uint32 textDrawHints, + const Uint32& tabWidth, Uint32 textDrawHints, TextDirection direction, const WhitespaceDisplayConfig& whitespaceDisplayConfig ) { - return draw( string, pos, config, tabWidth, textDrawHints, whitespaceDisplayConfig ); + return draw( string, pos, config, tabWidth, textDrawHints, direction, + whitespaceDisplayConfig ); } Sizef Text::draw( const String::View& string, const Vector2f& pos, Font* font, Float fontSize, const Color& fontColor, Uint32 style, Float outlineThickness, const Color& outlineColor, const Color& shadowColor, const Vector2f& shadowOffset, - const Uint32& tabWidth, Uint32 textDrawHints, + const Uint32& tabWidth, Uint32 textDrawHints, TextDirection direction, const WhitespaceDisplayConfig& whitespaceDisplayConfig ) { return draw( string, pos, font, fontSize, fontColor, style, outlineThickness, outlineColor, shadowColor, shadowOffset, tabWidth, textDrawHints, - whitespaceDisplayConfig ); + direction, whitespaceDisplayConfig ); } Sizef Text::draw( const String::View& string, const Vector2f& pos, const FontStyleConfig& config, - const Uint32& tabWidth, Uint32 textDrawHints, + const Uint32& tabWidth, Uint32 textDrawHints, TextDirection direction, const WhitespaceDisplayConfig& whitespaceDisplayConfig ) { - return draw( string, pos, config, tabWidth, textDrawHints, + return draw( string, pos, config, tabWidth, textDrawHints, direction, whitespaceDisplayConfig ); } @@ -271,7 +272,7 @@ template Sizef Text::draw( const StringType& string, const Vector2f& pos, Font* font, Float fontSize, const Color& fontColor, Uint32 style, Float outlineThickness, const Color& outlineColor, const Color& shadowColor, const Vector2f& shadowOffset, - const Uint32& tabWidth, Uint32 textDrawHints, + const Uint32& tabWidth, Uint32 textDrawHints, TextDirection direction, const WhitespaceDisplayConfig& whitespaceDisplayConfig ) { Vector2f cpos{ pos }; String::StringBaseType ch; @@ -312,7 +313,7 @@ Sizef Text::draw( const StringType& string, const Vector2f& pos, Font* font, Flo FontTrueType* rFont = static_cast( font ); auto layout = TextLayout::layout( string, rFont, fontSize, style, tabWidth, - outlineThickness, tabOffset, textDrawHints ); + outlineThickness, tabOffset, textDrawHints, direction ); for ( const ShapedGlyph& sg : layout->shapedGlyphs ) { auto ch = string[sg.stringIndex]; @@ -504,12 +505,12 @@ Sizef Text::draw( const StringType& string, const Vector2f& pos, Font* font, Flo template Sizef Text::draw( const StringType& string, const Vector2f& pos, const FontStyleConfig& config, - const Uint32& tabWidth, Uint32 textDrawHints, + const Uint32& tabWidth, Uint32 textDrawHints, TextDirection direction, const WhitespaceDisplayConfig& whitespaceDisplayConfig ) { return draw( string, pos, config.Font, config.CharacterSize, config.FontColor, config.Style, config.OutlineThickness, config.OutlineColor, config.ShadowColor, config.ShadowOffset, tabWidth, textDrawHints, - whitespaceDisplayConfig ); + direction, whitespaceDisplayConfig ); } template @@ -883,7 +884,7 @@ template Float Text::getTextWidth( Font* font, const Uint32& fontSize, const StringType& string, const Uint32& style, const Uint32& tabWidth, const Float& outlineThickness, Uint32 textDrawHints, - std::optional tabOffset ) { + TextDirection direction, std::optional tabOffset ) { if ( NULL == font || string.empty() ) return 0; Float width = 0; @@ -953,7 +954,7 @@ std::size_t Text::findLastCharPosWithinLength( Font* font, const Uint32& fontSize, const StringType& string, Float maxWidth, const Uint32& style, const Uint32& tabWidth, const Float& outlineThickness, std::optional tabOffset, - Uint32 textDrawHints ) { + Uint32 textDrawHints, TextDirection direction ) { if ( NULL == font || string.empty() ) return 0; String::StringBaseType codepoint; @@ -969,7 +970,7 @@ Text::findLastCharPosWithinLength( Font* font, const Uint32& fontSize, const Str !canSkipShaping( textDrawHints ) ) { auto layout = TextLayout::layout( string, static_cast( font ), fontSize, style, - tabWidth, outlineThickness, tabOffset /* , textDrawHints */ ); + tabWidth, outlineThickness, tabOffset, 0, direction ); size_t lastStringIndex = 0; for ( const ShapedGlyph& sg : layout->shapedGlyphs ) { Glyph metrics = @@ -1006,7 +1007,8 @@ Text::findLastCharPosWithinLength( Font* font, const Uint32& fontSize, const Str Vector2f Text::findCharacterPos( std::size_t index, Font* font, const Uint32& fontSize, const String& string, const Uint32& style, const Uint32& tabWidth, const Float& outlineThickness, std::optional tabOffset, - bool allowNewLine, Uint32 textDrawHints ) { + bool allowNewLine, Uint32 textDrawHints, + TextDirection direction ) { // Make sure that we have a valid font if ( !font ) return Vector2f(); @@ -1029,7 +1031,7 @@ Vector2f Text::findCharacterPos( std::size_t index, Font* font, const Uint32& fo if ( TextShaperEnabled && font->getType() == FontType::TTF && !canSkipShaping( textDrawHints ) ) { auto layout = TextLayout::layout( string, font, fontSize, style, tabWidth, outlineThickness, - tabOffset ); + tabOffset, 0, direction ); Uint32 maxStringIndex = 0; Uint32 closestDist = std::numeric_limits::max(); @@ -1121,7 +1123,8 @@ Vector2f Text::findCharacterPos( std::size_t index, Font* font, const Uint32& fo Int32 Text::findCharacterFromPos( const Vector2i& pos, bool returnNearest, Font* font, const Uint32& fontSize, const String& string, const Uint32& style, const Uint32& tabWidth, const Float& outlineThickness, - std::optional tabOffset, Uint32 textDrawHints ) { + std::optional tabOffset, Uint32 textDrawHints, + TextDirection direction ) { if ( NULL == font ) return 0; @@ -1144,7 +1147,7 @@ Int32 Text::findCharacterFromPos( const Vector2i& pos, bool returnNearest, Font* if ( TextShaperEnabled && font->getType() == FontType::TTF && !canSkipShaping( textDrawHints ) ) { auto layout = TextLayout::layout( string, font, fontSize, style, tabWidth, outlineThickness, - tabOffset ); + tabOffset, 0, direction ); auto sgs = layout->shapedGlyphs.size(); if ( sgs == 0 ) @@ -1277,37 +1280,41 @@ std::size_t Text::findLastCharPosWithinLength( Font* font, const Uint32& fontSiz const String& string, Float maxWidth, const Uint32& style, const Uint32& tabWidth, const Float& outlineThickness, - std::optional tabOffset, Uint32 textHints ) { + std::optional tabOffset, Uint32 textHints, + TextDirection direction ) { return findLastCharPosWithinLength( font, fontSize, string, maxWidth, style, tabWidth, - outlineThickness, tabOffset, textHints ); + outlineThickness, tabOffset, textHints, direction ); } std::size_t Text::findLastCharPosWithinLength( Font* font, const Uint32& fontSize, const String::View& string, Float maxWidth, const Uint32& style, const Uint32& tabWidth, const Float& outlineThickness, - std::optional tabOffset, Uint32 textHints ) { - return findLastCharPosWithinLength( - font, fontSize, string, maxWidth, style, tabWidth, outlineThickness, tabOffset, textHints ); + std::optional tabOffset, Uint32 textHints, + TextDirection direction ) { + return findLastCharPosWithinLength( font, fontSize, string, maxWidth, style, + tabWidth, outlineThickness, tabOffset, + textHints, direction ); } std::size_t Text::findLastCharPosWithinLength( const String& string, Float maxWidth, const FontStyleConfig& config, const Uint32& tabWidth, - std::optional tabOffset, Uint32 textHints ) { + std::optional tabOffset, Uint32 textHints, + TextDirection direction ) { return findLastCharPosWithinLength( config.Font, config.CharacterSize, string, maxWidth, config.Style, tabWidth, config.OutlineThickness, - tabOffset, textHints ); + tabOffset, textHints, direction ); } std::size_t Text::findLastCharPosWithinLength( const String::View& string, Float maxWidth, const FontStyleConfig& config, const Uint32& tabWidth, - std::optional tabOffset, - Uint32 textDrawHints ) { + std::optional tabOffset, Uint32 textDrawHints, + TextDirection direction ) { return findLastCharPosWithinLength( config.Font, config.CharacterSize, string, maxWidth, config.Style, tabWidth, - config.OutlineThickness, tabOffset, textDrawHints ); + config.OutlineThickness, tabOffset, textDrawHints, direction ); } void Text::updateWidthCache() { @@ -1328,9 +1335,9 @@ void Text::updateWidthCache() { #ifdef EE_TEXT_SHAPER_ENABLED if ( TextShaperEnabled && mFontStyleConfig.Font->getType() == FontType::TTF && !canSkipShaping( mTextHints ) ) { - auto layout = TextLayout::layout( mString, mFontStyleConfig.Font, - mFontStyleConfig.CharacterSize, mFontStyleConfig.Style, - mTabWidth, mFontStyleConfig.OutlineThickness ); + auto layout = TextLayout::layout( + mString, mFontStyleConfig.Font, mFontStyleConfig.CharacterSize, mFontStyleConfig.Style, + mTabWidth, mFontStyleConfig.OutlineThickness, {}, mTextHints, mDirection ); mLinesWidth = layout->linesWidth; mCachedWidth = layout->size.getWidth(); return; @@ -1655,9 +1662,9 @@ void Text::ensureGeometryUpdate() { if ( TextShaperEnabled && mFontStyleConfig.Font->getType() == FontType::TTF && !canSkipShaping( mTextHints ) ) { FontTrueType* rFont = static_cast( mFontStyleConfig.Font ); - auto layout = TextLayout::layout( mString, rFont, mFontStyleConfig.CharacterSize, - mFontStyleConfig.Style, mTabWidth, - mFontStyleConfig.OutlineThickness ); + auto layout = TextLayout::layout( + mString, rFont, mFontStyleConfig.CharacterSize, mFontStyleConfig.Style, mTabWidth, + mFontStyleConfig.OutlineThickness, {}, mTextHints, mDirection ); mLinesWidth = layout->linesWidth; mCachedWidth = layout->size.getWidth(); @@ -2109,9 +2116,9 @@ void Text::setFillColor( const Color& color, Uint32 from, Uint32 to ) { if ( TextShaperEnabled && mFontStyleConfig.Font->getType() == FontType::TTF && !canSkipShaping( mTextHints ) ) { FontTrueType* rFont = static_cast( mFontStyleConfig.Font ); - auto layout = TextLayout::layout( mString, rFont, mFontStyleConfig.CharacterSize, - mFontStyleConfig.Style, mTabWidth, - mFontStyleConfig.OutlineThickness ); + auto layout = TextLayout::layout( + mString, rFont, mFontStyleConfig.CharacterSize, mFontStyleConfig.Style, mTabWidth, + mFontStyleConfig.OutlineThickness, {}, mTextHints, mDirection ); size_t vIdx = 0; bool bold = ( mFontStyleConfig.Style & Bold ) != 0; bool italic = ( mFontStyleConfig.Style & Italic ) != 0; @@ -2237,9 +2244,9 @@ void Text::setFillColor( const std::vector& colors ) { if ( TextShaperEnabled && mFontStyleConfig.Font->getType() == FontType::TTF && !canSkipShaping( mTextHints ) ) { FontTrueType* rFont = static_cast( mFontStyleConfig.Font ); - auto layout = TextLayout::layout( mString, rFont, mFontStyleConfig.CharacterSize, - mFontStyleConfig.Style, mTabWidth, - mFontStyleConfig.OutlineThickness ); + auto layout = TextLayout::layout( + mString, rFont, mFontStyleConfig.CharacterSize, mFontStyleConfig.Style, mTabWidth, + mFontStyleConfig.OutlineThickness, {}, mTextHints, mDirection ); size_t vIdx = 0; bool bold = ( mFontStyleConfig.Style & Bold ) != 0; bool italic = ( mFontStyleConfig.Style & Italic ) != 0; @@ -2482,4 +2489,16 @@ Uint32 Text::getTotalVertices() { return sv; } +void Text::setDirection( TextDirection direction ) { + if ( direction == mDirection ) + return; + + mDirection = direction; + invalidate(); +} + +TextDirection Text::getDirection() const { + return mDirection; +} + }} // namespace EE::Graphics diff --git a/src/eepp/graphics/textlayout.cpp b/src/eepp/graphics/textlayout.cpp index 9ed4e8e04..0b3ae5a4c 100644 --- a/src/eepp/graphics/textlayout.cpp +++ b/src/eepp/graphics/textlayout.cpp @@ -59,7 +59,8 @@ static SBScriptLocator* getThreadLocalSbScriptLocator() { // Split string into segments with uniform text properties template -static void segmentString( TextLayout& result, String::View input, Callable cb ) { +static void segmentString( TextLayout& result, String::View input, Callable cb, + TextDirection direction ) { const SBCodepointSequence codepointSequence{ SBStringEncodingUTF32, static_cast( input.data() ), input.size() }; auto* const scriptLocator = getThreadLocalSbScriptLocator(); @@ -81,8 +82,24 @@ static void segmentString( TextLayout& result, String::View input, Callable cb ) if ( separatorLength < paragraphLength ) paragraphLength -= separatorLength; - auto* const paragraph = SBAlgorithmCreateParagraph( algorithm, paragraphOffset, - paragraphLength, SBLevelDefaultLTR ); + SBLevel level = 0; + + switch ( direction ) { + case TextDirection::LeftToRight: + level = 0; + break; + case TextDirection::RightToLeft: + level = 1; + break; + case TextDirection::TopToBottom: + case TextDirection::BottomToTop: + case TextDirection::Unspecified: + level = SBLevelDefaultLTR; + break; + } + + auto* const paragraph = + SBAlgorithmCreateParagraph( algorithm, paragraphOffset, paragraphLength, level ); auto* const line = SBParagraphCreateLine( paragraph, paragraphOffset, paragraphLength ); const auto runCount = SBLineGetRunCount( line ); const auto* runArray = SBLineGetRunsPtr( line ); @@ -126,71 +143,77 @@ static void segmentString( TextLayout& result, String::View input, Callable cb ) template static void shapeAndRun( TextLayout& result, const String& string, FontTrueType* font, - Uint32 characterSize, Uint32 style, Float outlineThickness, Callable cb ) { + Uint32 characterSize, Uint32 style, Float outlineThickness, + TextDirection baseDirection, Callable cb ) { String::View input = string.view(); hb_buffer_t* hbBuffer = getThreadLocalHbBuffer(); - segmentString( result, input, [&]( const TextSegment& segment ) { - TextShapeRun run( input.substr( segment.offset, segment.length ), font, characterSize, - style, outlineThickness, segment.direction == HB_DIRECTION_RTL ); + segmentString( + result, input, + [&]( const TextSegment& segment ) { + TextShapeRun run( input.substr( segment.offset, segment.length ), font, characterSize, + style, outlineThickness, segment.direction == HB_DIRECTION_RTL ); - while ( run.hasNext() ) { - FontTrueType* font = run.font(); - if ( font == nullptr ) { // empty line - run.next(); - continue; - } - String::View curRun( run.curRun() ); - font->setCurrentSize( characterSize ); - hb_buffer_reset( hbBuffer ); - hb_buffer_set_cluster_level( hbBuffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS ); - hb_buffer_add_utf32( hbBuffer, (Uint32*)curRun.data(), curRun.size(), 0, - curRun.size() ); + while ( run.hasNext() ) { + FontTrueType* font = run.font(); + if ( font == nullptr ) { // empty line + run.next(); + continue; + } + String::View curRun( run.curRun() ); + font->setCurrentSize( characterSize ); + hb_buffer_reset( hbBuffer ); + hb_buffer_set_cluster_level( hbBuffer, + HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS ); + hb_buffer_add_utf32( hbBuffer, (Uint32*)curRun.data(), curRun.size(), 0, + curRun.size() ); - hb_buffer_set_direction( hbBuffer, segment.direction ); - hb_buffer_set_script( hbBuffer, segment.script ); - hb_buffer_guess_segment_properties( hbBuffer ); - hb_segment_properties_t props; - hb_buffer_get_segment_properties( hbBuffer, &props ); - std::uint32_t featuresEnabled = !isSimpleScript( segment.script ) ? 1 : 0; + hb_buffer_set_direction( hbBuffer, segment.direction ); + hb_buffer_set_script( hbBuffer, segment.script ); + hb_buffer_guess_segment_properties( hbBuffer ); + hb_segment_properties_t props; + hb_buffer_get_segment_properties( hbBuffer, &props ); + std::uint32_t featuresEnabled = !isSimpleScript( segment.script ) ? 1 : 0; - // We use our own kerning algo - const hb_feature_t features[] = { - hb_feature_t{ HB_TAG( 'k', 'e', 'r', 'n' ), featuresEnabled, - HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END }, - hb_feature_t{ HB_TAG( 'l', 'i', 'g', 'a' ), featuresEnabled, - HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END }, - hb_feature_t{ HB_TAG( 'c', 'l', 'i', 'g' ), featuresEnabled, - HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END }, - hb_feature_t{ HB_TAG( 'd', 'l', 'i', 'g' ), featuresEnabled, - HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END }, - }; + // We use our own kerning algo + const hb_feature_t features[] = { + hb_feature_t{ HB_TAG( 'k', 'e', 'r', 'n' ), featuresEnabled, + HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END }, + hb_feature_t{ HB_TAG( 'l', 'i', 'g', 'a' ), featuresEnabled, + HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END }, + hb_feature_t{ HB_TAG( 'c', 'l', 'i', 'g' ), featuresEnabled, + HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END }, + hb_feature_t{ HB_TAG( 'd', 'l', 'i', 'g' ), featuresEnabled, + HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END }, + }; - // whitelist cross-platforms shapers only - static const char* shaper_list[] = { "ot", "graphite2", "fallback", nullptr }; + // whitelist cross-platforms shapers only + static const char* shaper_list[] = { "ot", "graphite2", "fallback", nullptr }; - if ( !font || !font->hb() ) { - eeASSERT( font && font->hb() ); - break; + if ( !font || !font->hb() ) { + eeASSERT( font && font->hb() ); + break; + } + + hb_shape_full( static_cast( font->hb() ), hbBuffer, features, + eeARRAY_SIZE( features ), shaper_list ); + + // from the shaped text we get the glyphs and positions + unsigned int glyphCount; + hb_glyph_info_t* glyphInfo = hb_buffer_get_glyph_infos( hbBuffer, &glyphCount ); + hb_glyph_position_t* glyphPos = + hb_buffer_get_glyph_positions( hbBuffer, &glyphCount ); + + if ( cb( glyphInfo, glyphPos, glyphCount, props, segment, run ) ) + run.next(); + else { + return false; + } } - hb_shape_full( static_cast( font->hb() ), hbBuffer, features, - eeARRAY_SIZE( features ), shaper_list ); - - // from the shaped text we get the glyphs and positions - unsigned int glyphCount; - hb_glyph_info_t* glyphInfo = hb_buffer_get_glyph_infos( hbBuffer, &glyphCount ); - hb_glyph_position_t* glyphPos = hb_buffer_get_glyph_positions( hbBuffer, &glyphCount ); - - if ( cb( glyphInfo, glyphPos, glyphCount, props, segment, run ) ) - run.next(); - else { - return false; - } - } - - return true; - } ); + return true; + }, + baseDirection ); } #endif @@ -199,18 +222,21 @@ template static inline Uint64 textLayoutHash( const StringType& string, Font* font, const Uint32& characterSize, const Uint32& style, const Uint32& tabWidth, const Float& outlineThickness, - std::optional tabOffset ) { + std::optional tabOffset, TextDirection direction ) { return hashCombine( std::hash()( string ), std::hash()( font ), std::hash()( characterSize ), std::hash()( style ), std::hash()( tabWidth ), std::hash()( outlineThickness ), - std::hash>()( tabOffset ) ); + std::hash>()( tabOffset ), + std::hash>()( + static_cast>( direction ) ) ); } template TextLayout::Cache TextLayout::layout( const StringType& string, Font* font, const Uint32& characterSize, const Uint32& style, const Uint32& tabWidth, const Float& outlineThickness, - std::optional tabOffset, Uint32 textDrawHints ) { + std::optional tabOffset, Uint32 textDrawHints, + TextDirection baseDirection ) { static LRULayoutCache sLayoutCache; if ( !font || string.empty() ) { @@ -222,7 +248,7 @@ TextLayout::Cache TextLayout::layout( const StringType& string, Font* font, Uint64 hash = 0; if ( !Text::canSkipShaping( textDrawHints ) ) { hash = textLayoutHash( string, font, characterSize, style, tabWidth, outlineThickness, - tabOffset ); + tabOffset, baseDirection ); auto cacheHit = sLayoutCache.get( hash ); if ( cacheHit.has_value() ) @@ -245,7 +271,7 @@ TextLayout::Cache TextLayout::layout( const StringType& string, Font* font, !Text::canSkipShaping( textDrawHints ) ) { FontTrueType* rFont = static_cast( font ); shapeAndRun( - result, string, rFont, characterSize, style, outlineThickness, + result, string, rFont, characterSize, style, outlineThickness, baseDirection, [&]( hb_glyph_info_t* glyphInfo, hb_glyph_position_t* glyphPos, Uint32 glyphCount, const hb_segment_properties_t& props, const TextSegment& segment, TextShapeRun& run ) { @@ -428,17 +454,19 @@ TextLayout::Cache TextLayout::layout( const StringType& string, Font* font, TextLayout::Cache TextLayout::layout( const String& string, Font* font, const Uint32& fontSize, const Uint32& style, const Uint32& tabWidth, const Float& outlineThickness, std::optional tabOffset, - Uint32 textDrawHints ) { + Uint32 textDrawHints, TextDirection baseDirection ) { return TextLayout::layout( string, font, fontSize, style, tabWidth, outlineThickness, - tabOffset, textDrawHints ); + tabOffset, textDrawHints, baseDirection ); } TextLayout::Cache TextLayout::layout( const String::View& string, Font* font, const Uint32& fontSize, const Uint32& style, const Uint32& tabWidth, const Float& outlineThickness, - std::optional tabOffset, Uint32 textDrawHints ) { + std::optional tabOffset, Uint32 textDrawHints, + TextDirection baseDirection ) { return TextLayout::layout( string, font, fontSize, style, tabWidth, - outlineThickness, tabOffset, textDrawHints ); + outlineThickness, tabOffset, textDrawHints, + baseDirection ); } } // namespace EE::Graphics diff --git a/src/eepp/ui/doc/documentview.cpp b/src/eepp/ui/doc/documentview.cpp index 08bedd369..880473342 100644 --- a/src/eepp/ui/doc/documentview.cpp +++ b/src/eepp/ui/doc/documentview.cpp @@ -55,7 +55,8 @@ Float DocumentView::computeOffsets( const String::View& string, const FontStyleC auto nonIndentPos = string.find_first_not_of( sepSpaces.data() ); if ( nonIndentPos != String::View::npos ) { Float w = Text::getTextWidth( string.substr( 0, nonIndentPos ), fontStyle, tabWidth, - TextHints::AllAscii, tabStops ? 0 : std::optional{} ); + TextHints::AllAscii, TextDirection::LeftToRight, + tabStops ? 0 : std::optional{} ); return maxWidth != 0.f ? ( w > maxWidth ? 0.f : w ) : w; } return 0.f; diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index 38978ac12..0dc912cef 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -2554,7 +2554,8 @@ Vector2d UICodeEditor::getTextPositionOffset( const TextPosition& position, position.column() - info.range.start().column(), mFont, getCharacterSize(), partialLine, mFontStyleConfig.Style, mTabWidth, mFontStyleConfig.OutlineThickness, mTabStops ? 0 : std::optional(), - false, mDoc->line( position.line() ).getTextHints() | getWidgetTextDrawHints() ) + false, mDoc->line( position.line() ).getTextHints() | getWidgetTextDrawHints(), + mTextDirection ) .x; if ( visualizeNewLine && allowVisualLineEnd && position.column() == (Int64)mDoc->line( position.line() ).getText().size() - 1 ) @@ -2588,7 +2589,8 @@ Vector2d UICodeEditor::getTextPositionOffset( const TextPosition& position, isLastChar ? position.column() - 1 : position.column(), mFont, getCharacterSize(), mDoc->line( position.line() ).getText(), mFontStyleConfig.Style, mTabWidth, mFontStyleConfig.OutlineThickness, mTabStops ? 0 : std::optional(), false, - mDoc->line( position.line() ).getTextHints() | getWidgetTextDrawHints() ) + mDoc->line( position.line() ).getTextHints() | getWidgetTextDrawHints(), + mTextDirection ) .x; if ( visualizeNewLine && isLastChar ) x += getGlyphWidth(); @@ -2650,7 +2652,7 @@ Float UICodeEditor::getTextWidth( const StringType& line, bool fromMonospaceLine std::optional tabOffset, Uint32 textHints ) const { if ( !fromMonospaceLine && isNotMonospace() ) { return Text::getTextWidth( mFont, getCharacterSize(), line, mFontStyleConfig.Style, - mTabWidth, 0.f, textHints, tabOffset ); + mTabWidth, 0.f, textHints, mTextDirection, tabOffset ); } Float glyphWidth = getGlyphWidth(); @@ -3228,7 +3230,8 @@ Int64 UICodeEditor::getColFromXOffset( VisibleIndex visibleIndex, const Float& x Vector2i( eemax( -xOffset + x, 0.f ), 0 ), true, mFont, getCharacterSize(), line, mFontStyleConfig.Style, mTabWidth, 0.f, mTabStops ? 0 : std::optional(), - mDoc->line( pos.line() ).getTextHints() | getWidgetTextDrawHints() ); + mDoc->line( pos.line() ).getTextHints() | getWidgetTextDrawHints(), + mTextDirection ); } Int64 len = line.length(); @@ -3255,7 +3258,7 @@ Int64 UICodeEditor::getColFromXOffset( VisibleIndex visibleIndex, const Float& x return Text::findCharacterFromPos( Vector2i( x, 0 ), true, mFont, getCharacterSize(), mDoc->line( pos.line() ).getText(), mFontStyleConfig.Style, mTabWidth, 0.f, mTabStops ? 0 : std::optional(), - mDoc->line( pos.line() ).getTextHints() | getWidgetTextDrawHints() ); + mDoc->line( pos.line() ).getTextHints() | getWidgetTextDrawHints(), mTextDirection ); } const String& line = mDoc->line( pos.line() ).getText(); @@ -4025,7 +4028,7 @@ void UICodeEditor::drawLineText( const Int64& line, Vector2f position, const Flo position.x += Text::draw( text, { position.x, position.y + lineOffset }, fontStyle, - mTabWidth, drawHints, whitespaceDisplayConfig ) + mTabWidth, drawHints, mTextDirection, whitespaceDisplayConfig ) .getWidth(); } @@ -4100,18 +4103,21 @@ void UICodeEditor::drawLineText( const Int64& line, Vector2f position, const Flo size = Text::draw( text.substr( start, end ), { position.x + start * getGlyphWidth(), position.y + lineOffset }, - fontStyle, mTabWidth, drawHints, whitespaceDisplayConfig ); + fontStyle, mTabWidth, drawHints, mTextDirection, + whitespaceDisplayConfig ); if ( minimumCharsToCoverScreen == end ) break; } } else { size = Text::draw( text.substr( 0, eemin( curCharsWidth, maxWidth ) ), { position.x, position.y + lineOffset }, fontStyle, - mTabWidth, drawHints, whitespaceDisplayConfig ); + mTabWidth, drawHints, mTextDirection, + whitespaceDisplayConfig ); } } else { - size = Text::draw( text, { position.x, position.y + lineOffset }, fontStyle, - mTabWidth, drawHints, whitespaceDisplayConfig ); + size = + Text::draw( text, { position.x, position.y + lineOffset }, fontStyle, + mTabWidth, drawHints, mTextDirection, whitespaceDisplayConfig ); } if ( !isMonospace ) @@ -5493,4 +5499,15 @@ bool UICodeEditor::isKerningEnabled() const { return mKerningEnabled; } +void UICodeEditor::setTextDirection( TextDirection direction ) { + if ( direction == mTextDirection ) + return; + mTextDirection = direction; + invalidateDraw(); +} + +TextDirection UICodeEditor::getTextDirection() const { + return mTextDirection; +} + }} // namespace EE::UI diff --git a/src/thirdparty/efsw b/src/thirdparty/efsw index 0f5ff6def..04d17474f 160000 --- a/src/thirdparty/efsw +++ b/src/thirdparty/efsw @@ -1 +1 @@ -Subproject commit 0f5ff6def7fbffa9bbf728ec00e7833cc92733b3 +Subproject commit 04d17474f132910430320a885c3bb8c151d17672