diff --git a/bin/unit_tests/assets/fontrendering/eepp-textedit-bengali.webp b/bin/unit_tests/assets/fontrendering/eepp-textedit-bengali.webp index 63af90304..591dbacc5 100644 Binary files a/bin/unit_tests/assets/fontrendering/eepp-textedit-bengali.webp and b/bin/unit_tests/assets/fontrendering/eepp-textedit-bengali.webp differ diff --git a/bin/unit_tests/assets/textfiles/test-bengali.uext b/bin/unit_tests/assets/textfiles/test-bengali.uext index aa6f39ae3..91fb4f784 100644 --- a/bin/unit_tests/assets/textfiles/test-bengali.uext +++ b/bin/unit_tests/assets/textfiles/test-bengali.uext @@ -21,4 +21,4 @@ How much is this?: এটার দাম কত? Where is the bathroom?: বাথরুম কোথায়? Help!: বাঁচাও! Stop!: থামুন! -Call the police!: পুলিশ ডাকুন! +Call the police!: পুলিশ ডাকুন! \ No newline at end of file diff --git a/include/eepp/graphics/shapedglyph.hpp b/include/eepp/graphics/shapedglyph.hpp index 73609dd30..4caeae62a 100644 --- a/include/eepp/graphics/shapedglyph.hpp +++ b/include/eepp/graphics/shapedglyph.hpp @@ -14,6 +14,7 @@ struct ShapedGlyph { Uint32 glyphIndex{ 0 }; Uint32 stringIndex{ 0 }; Vector2f position; + Vector2f advance; }; } // namespace EE::Graphics diff --git a/include/eepp/graphics/text.hpp b/include/eepp/graphics/text.hpp index 18f2cafcb..6d367ea1a 100644 --- a/include/eepp/graphics/text.hpp +++ b/include/eepp/graphics/text.hpp @@ -304,6 +304,8 @@ class EE_API Text { const FontStyleConfig& getFontStyleConfig() const { return mFontStyleConfig; } + Uint32 getTextHints() const { return mTextHints; } + protected: struct VertexCoords { Vector2f texCoords; diff --git a/include/eepp/graphics/textshaperun.hpp b/include/eepp/graphics/textshaperun.hpp index c80b92dd3..43564c839 100644 --- a/include/eepp/graphics/textshaperun.hpp +++ b/include/eepp/graphics/textshaperun.hpp @@ -26,8 +26,6 @@ class EE_API TextShapeRun { FontTrueType* font(); - bool isRTL() const; - protected: void findNextEnd(); @@ -41,7 +39,6 @@ class EE_API TextShapeRun { Font* mCurFont{ nullptr }; Font* mStartFont{ nullptr }; bool mIsNewLine{ false }; - bool mIsRTL{ false }; }; } // namespace EE::Graphics diff --git a/projects/linux/ee.creator.user b/projects/linux/ee.creator.user index 245217c8b..013b04a3e 100644 --- a/projects/linux/ee.creator.user +++ b/projects/linux/ee.creator.user @@ -1,6 +1,6 @@ - + EnvironmentId @@ -126,7 +126,7 @@ {388e5431-b31b-42b3-b9ad-9002d279d75d} 10 0 - 19 + 28 ../../make/linux @@ -1600,7 +1600,7 @@ debug-all GenericProjectManager.GenericBuildConfiguration 0 - 19 + 28 0 @@ -1877,6 +1877,7 @@ ProjectExplorer.CustomExecutableRunConfiguration true + --text-shaper 0 false 1 @@ -16160,6 +16161,7 @@ ProjectExplorer.CustomExecutableRunConfiguration true + --text-shaper 0 false 1 diff --git a/projects/linux/ee.includes b/projects/linux/ee.includes index 1e9d82768..65df34d5b 100644 --- a/projects/linux/ee.includes +++ b/projects/linux/ee.includes @@ -15,5 +15,7 @@ ../../src/tools/ecode ../../src/thirdparty/mojoAL/ ../../src/thirdparty/pcre2/src +../../src/thirdparty/SheenBidi/Headers +../../src/thirdparty/SheenBidi/Headers/SheenBidi ../../src/modules/languages-syntax-highlighting/src /usr/include/freetype2/ diff --git a/src/eepp/graphics/textlayouter.cpp b/src/eepp/graphics/textlayouter.cpp index d4a52c462..4c47821fc 100644 --- a/src/eepp/graphics/textlayouter.cpp +++ b/src/eepp/graphics/textlayouter.cpp @@ -5,87 +5,162 @@ #include #ifdef EE_TEXT_SHAPER_ENABLED +#include #include #include #endif namespace EE::Graphics { -using LRULayoutCache = LRUCache<1024, Uint64, TextLayout>; +using LRULayoutCache = LRUCache<2048, Uint64, TextLayout>; #ifdef EE_TEXT_SHAPER_ENABLED -static bool -shapeAndRun( const String& string, FontTrueType* font, Uint32 characterSize, Uint32 style, - Float outlineThickness, - const std::function& cb ) { - TextShapeRun run( string.view(), font, characterSize, style, outlineThickness ); + +struct TextSegment { + std::size_t offset{}; + std::size_t length{}; + hb_script_t script{}; + hb_direction_t direction{}; +}; + +// Split string into segments with uniform text properties +static void segmentString( String::View input, + std::function cb ) { + const SBCodepointSequence codepointSequence{ + SBStringEncodingUTF32, static_cast( input.data() ), input.size() }; + auto* const scriptLocator = SBScriptLocatorCreate(); + auto* const algorithm = SBAlgorithmCreate( &codepointSequence ); + SBUInteger paragraphOffset = 0; + + while ( paragraphOffset < input.size() ) { + SBUInteger paragraphLength{}; + SBUInteger separatorLength{}; + SBAlgorithmGetParagraphBoundary( algorithm, paragraphOffset, + std::numeric_limits::max(), ¶graphLength, + &separatorLength ); + + auto* const paragraph = SBAlgorithmCreateParagraph( algorithm, paragraphOffset, + paragraphLength, SBLevelDefaultLTR ); + auto* const line = SBParagraphCreateLine( paragraph, paragraphOffset, paragraphLength ); + const auto runCount = SBLineGetRunCount( line ); + const auto* runArray = SBLineGetRunsPtr( line ); + + for ( SBUInteger i = 0; i < runCount; i++ ) { + // Odd levels are RTL, even levels are LTR + const auto direction = ( runArray[i].level % 2 ) ? HB_DIRECTION_RTL : HB_DIRECTION_LTR; + + const SBCodepointSequence codepointSubsequence{ + SBStringEncodingUTF32, + static_cast( input.data() + runArray[i].offset ), runArray[i].length }; + + SBScriptLocatorLoadCodepoints( scriptLocator, &codepointSubsequence ); + + while ( SBScriptLocatorMoveNext( scriptLocator ) ) { + const auto* agent = SBScriptLocatorGetAgent( scriptLocator ); + const auto script = + hb_script_from_iso15924_tag( SBScriptGetUnicodeTag( agent->script ) ); + + if ( !cb( TextSegment{ runArray[i].offset + agent->offset, agent->length, script, + direction } ) ) + break; + } + + SBScriptLocatorReset( scriptLocator ); + } + + SBLineRelease( line ); + SBParagraphRelease( paragraph ); + + paragraphOffset += paragraphLength; + } + + SBAlgorithmRelease( algorithm ); + SBScriptLocatorRelease( scriptLocator ); +} + +static inline bool isSimpleScript( hb_script_t script ) { + return script == HB_SCRIPT_LATIN || script == HB_SCRIPT_GREEK || script == HB_SCRIPT_CYRILLIC || + script == HB_SCRIPT_INVALID || script == HB_SCRIPT_COMMON; +} + +static bool shapeAndRun( const String& string, FontTrueType* font, Uint32 characterSize, + Uint32 style, Float outlineThickness, + const std::function& cb ) { + String::View input = string.view(); hb_buffer_t* hbBuffer = hb_buffer_create(); bool completeRun = true; - 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_guess_segment_properties( hbBuffer ); - hb_segment_properties_t props; - hb_buffer_get_segment_properties( hbBuffer, &props ); + segmentString( input, [&]( const TextSegment& segment ) { + TextShapeRun run( input.substr( segment.offset, segment.length ), font, characterSize, + style, outlineThickness ); - // We use our own kerning algo - static const hb_feature_t features[] = { - hb_feature_t{ HB_TAG( 'k', 'e', 'r', 'n' ), 0, HB_FEATURE_GLOBAL_START, - HB_FEATURE_GLOBAL_END }, - hb_feature_t{ HB_TAG( 'l', 'i', 'g', 'a' ), 0, HB_FEATURE_GLOBAL_START, - HB_FEATURE_GLOBAL_END }, - hb_feature_t{ HB_TAG( 'c', 'l', 'i', 'g' ), 0, HB_FEATURE_GLOBAL_START, - HB_FEATURE_GLOBAL_END }, - hb_feature_t{ HB_TAG( 'd', 'l', 'i', 'g' ), 0, HB_FEATURE_GLOBAL_START, - HB_FEATURE_GLOBAL_END }, - }; + 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() ); - // whitelist cross-platforms shapers only - static const char* shaper_list[] = { "ot", "graphite2", "fallback", nullptr }; + 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; - if ( !font || !font->hb() ) { - eeASSERT( font && font->hb() ); - completeRun = false; - break; + // 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 }; + + if ( !font || !font->hb() ) { + eeASSERT( font && font->hb() ); + completeRun = false; + 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 { + completeRun = false; + return false; + 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, run ) ) - run.next(); - else { - completeRun = false; - break; - } - } + return true; + } ); hb_buffer_destroy( hbBuffer ); return completeRun; } -// New helper function to identify scripts where our custom kerning is safe to apply. -static inline bool isSimpleScript( hb_script_t script ) { - // This list can be expanded, but covers the most common simple LTR scripts. - return script == HB_SCRIPT_LATIN || script == HB_SCRIPT_GREEK || script == HB_SCRIPT_CYRILLIC || - script == HB_SCRIPT_INVALID; -} - #endif template @@ -136,7 +211,8 @@ TextLayout TextLayouter::layout( const StringType& string, Font* font, const Uin shapeAndRun( string, rFont, characterSize, style, outlineThickness, [&]( hb_glyph_info_t* glyphInfo, hb_glyph_position_t* glyphPos, Uint32 glyphCount, - const hb_segment_properties_t& props, TextShapeRun& run ) { + const hb_segment_properties_t& props, const TextSegment& segment, + TextShapeRun& run ) { FontTrueType* currentRunFont = run.font(); if ( !currentRunFont ) return true; @@ -146,7 +222,7 @@ TextLayout TextLayouter::layout( const StringType& string, Font* font, const Uin if ( isSimpleScript( props.script ) ) { for ( size_t i = 0; i < glyphCount; ++i ) { Uint32 cluster = glyphInfo[i].cluster; - String::StringBaseType ch = string[run.pos() + cluster]; + String::StringBaseType ch = string[segment.offset + run.pos() + cluster]; if ( ch == '\t' ) { Float advance = Text::tabAdvance( hspace, tabWidth, @@ -155,8 +231,9 @@ TextLayout TextLayouter::layout( const StringType& string, Font* font, const Uin ShapedGlyph sg; sg.font = currentRunFont; sg.glyphIndex = glyphInfo[i].codepoint; - sg.stringIndex = run.pos() + cluster; + sg.stringIndex = segment.offset + run.pos() + cluster; sg.position = pen; + sg.advance = { advance, 0 }; result.shapedGlyphs.emplace_back( std::move( sg ) ); pen.x += advance; @@ -176,10 +253,11 @@ TextLayout TextLayouter::layout( const StringType& string, Font* font, const Uin ShapedGlyph sg; sg.font = currentRunFont; sg.glyphIndex = glyphInfo[i].codepoint; - sg.stringIndex = run.pos() + glyphInfo[i].cluster; + sg.stringIndex = segment.offset + run.pos() + glyphInfo[i].cluster; float offsetX = glyphPos[i].x_offset / 64.f; float offsetY = glyphPos[i].y_offset / 64.f; + sg.advance = { offsetX, offsetY }; sg.position.x = pen.x + offsetX; sg.position.y = pen.y - offsetY; result.shapedGlyphs.emplace_back( std::move( sg ) ); @@ -190,7 +268,7 @@ TextLayout TextLayouter::layout( const StringType& string, Font* font, const Uin } else { for ( size_t i = 0; i < glyphCount; ++i ) { Uint32 cluster = glyphInfo[i].cluster; - String::StringBaseType ch = string[run.pos() + cluster]; + String::StringBaseType ch = string[segment.offset + run.pos() + cluster]; if ( ch == '\t' ) { Float advance = Text::tabAdvance( hspace, tabWidth, @@ -199,7 +277,8 @@ TextLayout TextLayouter::layout( const StringType& string, Font* font, const Uin ShapedGlyph sg; sg.font = currentRunFont; sg.glyphIndex = glyphInfo[i].codepoint; - sg.stringIndex = run.pos() + cluster; + sg.stringIndex = segment.offset + run.pos() + cluster; + sg.advance = { advance, 0 }; sg.position = pen; result.shapedGlyphs.emplace_back( std::move( sg ) ); @@ -211,20 +290,26 @@ TextLayout TextLayouter::layout( const StringType& string, Font* font, const Uin ShapedGlyph sg; sg.font = currentRunFont; sg.glyphIndex = glyphInfo[i].codepoint; - sg.stringIndex = run.pos() + glyphInfo[i].cluster; + sg.stringIndex = segment.offset + run.pos() + glyphInfo[i].cluster; float offsetX = glyphPos[i].x_offset / 64.f; float offsetY = glyphPos[i].y_offset / 64.f; - sg.position.x = pen.x + offsetX; - sg.position.y = pen.y - offsetY; + sg.advance = { offsetX, offsetY }; + sg.position.x = std::round( pen.x + offsetX ); + sg.position.y = std::round( pen.y - offsetY ); result.shapedGlyphs.emplace_back( std::move( sg ) ); - pen.x += glyphPos[i].x_advance / 64.f; + pen.x += Font::isEmojiCodePoint( ch ) + ? currentRunFont + ->getGlyphByIndex( glyphInfo[i].codepoint, characterSize, + bold, italic, outlineThickness ) + .advance + : glyphPos[i].x_advance / 64.f; pen.y += glyphPos[i].y_advance / 64.f; } } if ( run.runIsNewLine() ) { - result.linesWidth.push_back( pen.x ); - maxWidth = eemax( maxWidth, pen.x ); + result.linesWidth.push_back( std::ceil( pen.x ) ); + maxWidth = eemax( maxWidth, result.linesWidth[result.linesWidth.size() - 1] ); pen.x = 0; pen.y += vspace; } @@ -255,13 +340,16 @@ TextLayout TextLayouter::layout( const StringType& string, Font* font, const Uin prevChar = curChar; if ( curChar == '\t' ) { - pen.x += Text::tabAdvance( - hspace, tabWidth, - tabOffset ? ( tabOffset ? *tabOffset + pen.x : std::optional{} ) - : std::optional{} ); + ShapedGlyph sg; sg.stringIndex = i; + sg.advance = { Text::tabAdvance( hspace, tabWidth, + tabOffset ? ( tabOffset ? *tabOffset + pen.x + : std::optional{} ) + : std::optional{} ), + 0 }; sg.position = pen; + pen.x += sg.advance.x; result.shapedGlyphs.emplace_back( std::move( sg ) ); continue; } @@ -270,9 +358,11 @@ TextLayout TextLayouter::layout( const StringType& string, Font* font, const Uin sg.font = static_cast( font ); sg.glyphIndex = sg.font->getGlyphIndex( curChar ); sg.stringIndex = i; + sg.advance = { + font->getGlyph( curChar, characterSize, bold, italic, outlineThickness ).advance, + 0 }; sg.position = pen; - pen.x += - font->getGlyph( curChar, characterSize, bold, italic, outlineThickness ).advance; + pen.x += sg.advance.x; result.shapedGlyphs.emplace_back( std::move( sg ) ); } } @@ -281,9 +371,9 @@ TextLayout TextLayouter::layout( const StringType& string, Font* font, const Uin if ( string[string.size() - 1] != '\n' ) pen.y += vspace; - result.linesWidth.push_back( pen.x ); - maxWidth = eemax( maxWidth, pen.x ); - result.size = { maxWidth, pen.y }; + result.linesWidth.push_back( std::ceil( pen.x ) ); + maxWidth = eemax( maxWidth, result.linesWidth[result.linesWidth.size() - 1] ); + result.size = { maxWidth, std::ceil( pen.y ) }; sLayoutCache.put( hash, result ); return result; diff --git a/src/eepp/graphics/textshaperun.cpp b/src/eepp/graphics/textshaperun.cpp index bec303ea6..228556b97 100644 --- a/src/eepp/graphics/textshaperun.cpp +++ b/src/eepp/graphics/textshaperun.cpp @@ -50,11 +50,9 @@ void TextShapeRun::findNextEnd() { Font* lFont = mStartFont ? mStartFont : mFont; std::size_t len = mString.size(); std::size_t pos = 0; - hb_script_t curScript = HB_SCRIPT_UNKNOWN; for ( std::size_t idx = mIndex; idx < len; ++idx, ++pos ) { auto ch = mString[idx]; - hb_script_t script = hb_unicode_script( hb_unicode_funcs_get_default(), ch ); auto font = mFont ->getGlyph( ch, mCharacterSize, mStyle & Text::Bold, mStyle & Text::Italic, mOutlineThickness ) @@ -62,33 +60,22 @@ void TextShapeRun::findNextEnd() { mIsNewLine = ( ch == '\n' ); if ( idx == mIndex ) { - curScript = script; mStartFont = font; lFont = font; mCurFont = font; - if ( curScript == HB_SCRIPT_COMMON || curScript == HB_SCRIPT_INHERITED ) - curScript = HB_SCRIPT_LATIN; - mIsRTL = hb_script_get_horizontal_direction( curScript ) == HB_DIRECTION_RTL; } // Break run if: // - Newline // - Font changed - // - Script changed - hb_script_t effectiveScript = - ( script == HB_SCRIPT_COMMON || script == HB_SCRIPT_INHERITED ) ? (hb_script_t)curScript - : script; - - if ( mIsNewLine || ( lFont != nullptr && font != lFont ) || effectiveScript != curScript ) { + if ( mIsNewLine || ( lFont != nullptr && font != lFont ) ) { mLen = mIsNewLine ? pos + 1 : pos; mCurFont = lFont; - mIsRTL = hb_script_get_horizontal_direction( effectiveScript ) == HB_DIRECTION_RTL; return; } lFont = font; mCurFont = font; - curScript = effectiveScript; } mLen = len - mIndex; @@ -116,8 +103,4 @@ void TextShapeRun::findNextEnd() { #endif } -bool TextShapeRun::isRTL() const { - return mIsRTL; -} - } // namespace EE::Graphics diff --git a/src/tests/ui_perf_test/ui_perf_test.cpp b/src/tests/ui_perf_test/ui_perf_test.cpp index 2ec944681..36a53f3d8 100644 --- a/src/tests/ui_perf_test/ui_perf_test.cpp +++ b/src/tests/ui_perf_test/ui_perf_test.cpp @@ -153,34 +153,37 @@ void mainLoop() { EE_MAIN_FUNC int main( int, char*[] ) { { - Text::TextShaperEnabled = false; + Text::TextShaperEnabled = true; + Text::TextShaperOptimizations = false; UIApplication app( WindowSettings( 1024, 650, "eepp - TextEdit", WindowStyle::Default, WindowBackend::Default, 32, {}, 1, false, true ), UIApplication::Settings( {}, 1.5f ) ); FileSystem::changeWorkingDirectory( Sys::getProcessPath() ); auto ll = UILinearLayout::NewVertical(); ll->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent ); - auto editor = UICodeEditor::New(); + auto editor = UITextEdit::New(); editor->setShowLineNumber( false ); editor->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent ); editor->setParent( ll ); - // editor->setFontSize( PixelDensity::dpToPx( 32 ) ); - /* FontManager::instance()->addFallbackFont( - FontTrueType::New( "arabic", "unit_tests/assets/fonts/NotoNaskhArabic-Regular.ttf" ) ); */ + editor->setFontSize( PixelDensity::dpToPx( 12 ) ); + // FontManager::instance()->addFallbackFont( + // FontTrueType::New( "arabic", "unit_tests/assets/fonts/NotoNaskhArabic-Regular.ttf" ) ); FontManager::instance()->addFallbackFont( FontTrueType::New( "NotoSerifBengali-Regular", "unit_tests/assets/fonts/NotoSansBengali-Regular.ttf" ) ); // editor->setLineWrapMode( LineWrapMode::Word ); // editor->setFont( FontManager::instance()->getByName( "monospace" ) ); // editor->loadFromFile( "unit_tests/assets/textfiles/test-arabic-simple.uext" ); - editor->loadFromFile( "unit_tests/assets/textfiles/test-arabic.uext" ); - // editor->loadFromFile( "unit_tests/assets/textfiles/test-bengali.uext" ); + // editor->loadFromFile( "unit_tests/assets/textfiles/test-arabic.uext" ); + editor->loadFromFile( "unit_tests/assets/textfiles/test-bengali.uext" ); // editor->loadFromFile( "unit_tests/assets/textfiles/test-flags.uext" ); // editor->loadFromFile( "unit_tests/assets/textformat/english.utf8.lf.nobom.txt" ); // editor->getDocument().textInput( "اسمي..." ); // editor->getDocument().textInput( " হ্যাঁ " ); + // editor->getDocument().textInput( "I'm from...: আমি ... থেকে এসেছি।" ); + editor->setFont( app.getUI()->getUIThemeManager()->getDefaultFont() ); editor->on( Event::KeyUp, [&]( const Event* event ) { - if ( event->asKeyEvent()->getKeyCode() == KEY_F1 ){ + if ( event->asKeyEvent()->getKeyCode() == KEY_F1 ) { Text::TextShaperEnabled = !Text::TextShaperEnabled; app.getUI()->getRoot()->invalidateDraw(); } diff --git a/src/tests/unit_tests/fontrendering.cpp b/src/tests/unit_tests/fontrendering.cpp index eef6ca5c1..a0d406735 100644 --- a/src/tests/unit_tests/fontrendering.cpp +++ b/src/tests/unit_tests/fontrendering.cpp @@ -418,6 +418,7 @@ UTEST( FontRendering, textEditBengaliTest ) { FontManager::instance()->addFallbackFont( bengaliFont ); UTEST_PRINT_STEP( "Text Shaper enabled" ); auto* editor = UITextEdit::New(); + // editor->setFontSize( PixelDensity::dpToPx( 12 ) ); editor->setPixelsSize( app.getUI()->getPixelsSize() ); editor->loadFromFile( "assets/textfiles/test-bengali.uext" ); SceneManager::instance()->update(); diff --git a/src/tools/ecode/appconfig.cpp b/src/tools/ecode/appconfig.cpp index 37e6f8cfd..39f0b65fc 100644 --- a/src/tools/ecode/appconfig.cpp +++ b/src/tools/ecode/appconfig.cpp @@ -143,6 +143,8 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath, FontTrueType::fontHintingFromString( ini.getValue( "ui", "font_hinting", "full" ) ); ui.fontAntialiasing = FontTrueType::fontAntialiasingFromString( ini.getValue( "ui", "font_antialiasing", "grayscale" ) ); + Text::TextShaperEnabled |= ini.getValueB( "ui", "text_shaper", false ); + Text::TextShaperOptimizations |= ini.getValueB( "ui", "text_shaper_optimizations", true ); doc.trimTrailingWhitespaces = ini.getValueB( "document", "trim_trailing_whitespaces", false ); doc.forceNewLineAtEndOfFile = ini.getValueB( "document", "force_new_line_at_end_of_file", false );