diff --git a/src/eepp/graphics/linewrap.cpp b/src/eepp/graphics/linewrap.cpp index 3a2b73d00..797659332 100644 --- a/src/eepp/graphics/linewrap.cpp +++ b/src/eepp/graphics/linewrap.cpp @@ -64,14 +64,14 @@ Float LineWrap::computeOffsets( const String::View& string, Font* font, Uint32 c return 0.f; } -template -T LineWrap::computeLineBreaksInternal( const String::View& string, Font* font, Uint32 characterSize, - Float maxWidth, LineWrapMode mode, Uint32 fontStyle, - Float outlineThickness, bool keepIndentation, - Uint32 tabWidth, - Float whiteSpaceWidth /* 0 = should calculate it */, - Uint32 textDrawHints, bool tabStops, Float initialXOffset ) { - T info; +template +LineWrapType +LineWrap::computeLineBreaksInternal( const String::View& string, Font* font, Uint32 characterSize, + Float maxWidth, LineWrapMode mode, Uint32 fontStyle, + Float outlineThickness, bool keepIndentation, Uint32 tabWidth, + Float whiteSpaceWidth /* 0 = should calculate it */, + Uint32 textDrawHints, bool tabStops, Float initialXOffset ) { + LineWrapType info; info.wraps.push_back( 0 ); if ( string.empty() || nullptr == font || mode == LineWrapMode::NoWrap || maxWidth == 0 ) @@ -84,7 +84,7 @@ T LineWrap::computeLineBreaksInternal( const String::View& string, Font* font, U string, font, characterSize, fontStyle, tabWidth, outlineThickness, tabStops ? initialXOffset : std::optional{}, textDrawHints, TextDirection::LeftToRight, mode, maxWidth, keepIndentation, initialXOffset ); - T info; + LineWrapType info; if ( layout->paragraphs.empty() ) return info; @@ -101,7 +101,7 @@ T LineWrap::computeLineBreaksInternal( const String::View& string, Font* font, U } } - if constexpr ( std::is_same_v ) { + if constexpr ( std::is_same_v ) { for ( auto& paragraph : layout->paragraphs ) { for ( const auto& wrapWidth : paragraph.wrapInfo.wrapsWidth ) { info.wrapsWidth.push_back( wrapWidth ); @@ -142,16 +142,21 @@ T LineWrap::computeLineBreaksInternal( const String::View& string, Font* font, U for ( const auto& curChar : string ) { if ( curChar == '\n' ) { - if constexpr ( std::is_same_v ) { - info.wrapsWidth.back() = xoffset; // Finalize width of current line + if constexpr ( std::is_same_v ) { + if ( info.wrapsWidth.empty() ) + info.wrapsWidth.push_back( std::ceil( xoffset ) ); + else + info.wrapsWidth.back() = std::ceil( xoffset ); // Finalize width of current line } xoffset = 0; - if constexpr ( std::is_same_v ) { - info.wrapsWidth.push_back( 0.f ); // Placeholder for new line's width + if constexpr ( std::is_same_v ) { + if ( idx + 1 != string.size() ) + info.wrapsWidth.push_back( 0.f ); // Placeholder for new line's width } lastSpace = idx; info.wraps.push_back( lastSpace ); idx++; + prevChar = 0; continue; } @@ -175,26 +180,35 @@ T LineWrap::computeLineBreaksInternal( const String::View& string, Font* font, U if ( xoffset > maxWidth ) { if ( mode == LineWrapMode::Word && lastSpace ) { - if constexpr ( std::is_same_v ) { - info.wrapsWidth.back() = lastWordWrapWidth; + if constexpr ( std::is_same_v ) { + if ( info.wrapsWidth.empty() ) + info.wrapsWidth.push_back( std::ceil( lastWordWrapWidth ) ); + else + info.wrapsWidth.back() = std::ceil( lastWordWrapWidth ); } info.wraps.push_back( lastSpace + 1 ); - if constexpr ( std::is_same_v ) { - info.wrapsWidth.push_back( 0.f ); + if constexpr ( std::is_same_v ) { + if ( idx + 1 != string.size() ) + info.wrapsWidth.push_back( 0.f ); } xoffset = info.paddingStart + ( xoffset - lastWidth ); } else { - if constexpr ( std::is_same_v ) { - info.wrapsWidth.back() = xoffset - w; // Width up to char *before* current one + if constexpr ( std::is_same_v ) { + // Width up to char *before* current one + if ( info.wrapsWidth.empty() ) + info.wrapsWidth.push_back( std::ceil( xoffset - w ) ); + else + info.wrapsWidth.back() = std::ceil( xoffset - w ); } info.wraps.push_back( idx ); - if constexpr ( std::is_same_v ) { - info.wrapsWidth.push_back( 0.f ); + if constexpr ( std::is_same_v ) { + if ( idx + 1 != string.size() ) + info.wrapsWidth.push_back( 0.f ); } xoffset = info.paddingStart; @@ -210,10 +224,9 @@ T LineWrap::computeLineBreaksInternal( const String::View& string, Font* font, U idx++; } - if constexpr ( std::is_same_v ) { - if ( !info.wrapsWidth.empty() ) { // Ensure there's at least one line - info.wrapsWidth.back() = xoffset; - } + if constexpr ( std::is_same_v ) { + if ( !string.empty() && string[string.size() - 1] != '\n' && !info.wrapsWidth.empty() ) + info.wrapsWidth.back() = std::ceil( xoffset ); } return info; diff --git a/src/eepp/graphics/textlayout.cpp b/src/eepp/graphics/textlayout.cpp index fc16a0717..93903b6fa 100644 --- a/src/eepp/graphics/textlayout.cpp +++ b/src/eepp/graphics/textlayout.cpp @@ -420,7 +420,8 @@ TextLayout::Cache TextLayout::layout( const String::View& string, Font* font, if ( run.runIsNewLine() ) { curParagraph->size.x = std::ceil( pen.x ); - curParagraph->wrapInfo.wrapsWidth.push_back( curParagraph->size.x ); + curParagraph->wrapInfo.wrapsWidth.push_back( + std::ceil( curParagraph->size.x ) ); maxWidth = eemax( maxWidth, curParagraph->size.x ); pen.x = 0; pen.y += vspace; @@ -440,7 +441,7 @@ TextLayout::Cache TextLayout::layout( const String::View& string, Font* font, Uint32 curChar = string[i]; if ( curChar == '\n' ) { curParagraph->size.x = std::ceil( pen.x ); - curParagraph->wrapInfo.wrapsWidth.push_back( curParagraph->size.x ); + curParagraph->wrapInfo.wrapsWidth.push_back( std::ceil( curParagraph->size.x ) ); maxWidth = eemax( maxWidth, pen.x ); pen.x = 0; pen.y += vspace; @@ -495,12 +496,12 @@ TextLayout::Cache TextLayout::layout( const String::View& string, Font* font, } // pen.y doesn't have the last line height counted unless the last run ended with a new line - if ( string[string.size() - 1] != '\n' ) + if ( string[string.size() - 1] != '\n' ) { pen.y += vspace; - + curParagraph->wrapInfo.wrapsWidth.push_back( std::ceil( curParagraph->size.x ) ); + } curParagraph->size.x = std::ceil( pen.x ); curParagraph->size.y = pen.y; - curParagraph->wrapInfo.wrapsWidth.push_back( curParagraph->size.x ); maxWidth = eemax( maxWidth, curParagraph->size.x ); result.size = { maxWidth, std::ceil( pen.y ) }; result.hasMixedDirection = !!gdc.ltr + !!gdc.rtl + !!gdc.ttb + !!gdc.btt + !!gdc.other > 1; @@ -591,7 +592,7 @@ void TextLayout::wrapLayout( const String::View& string, TextLayout& result, ShapedGlyph& breakGlyph = sp.shapedGlyphs[breakIndex]; sp.wrapInfo.wraps.push_back( breakStringIdx ); - sp.wrapInfo.wrapsWidth.push_back( breakGlyph.position.x + breakGlyph.advance.x ); + sp.wrapInfo.wrapsWidth.push_back( std::ceil( breakGlyph.position.x ) ); Vector2f adjustment( -breakGlyph.position.x + sp.wrapInfo.paddingStart, vspace ); for ( std::size_t k = breakIndex; k <= idx; ++k ) @@ -612,8 +613,8 @@ void TextLayout::wrapLayout( const String::View& string, TextLayout& result, // Restore the original wraps which are the paragraph wraps (no wrapping occurred) sp.wrapInfo.wrapsWidth = std::move( wrapsWidth ); } else if ( !sp.shapedGlyphs.empty() ) { - sp.wrapInfo.wrapsWidth.push_back( sp.shapedGlyphs.back().position.x + - sp.shapedGlyphs.back().advance.x ); + sp.wrapInfo.wrapsWidth.push_back( + std::ceil( sp.shapedGlyphs.back().position.x + sp.shapedGlyphs.back().advance.x ) ); } sp.size = maxSize; diff --git a/src/tests/unit_tests/fontrendering.cpp b/src/tests/unit_tests/fontrendering.cpp index e1423505b..75cb68f07 100644 --- a/src/tests/unit_tests/fontrendering.cpp +++ b/src/tests/unit_tests/fontrendering.cpp @@ -969,8 +969,6 @@ UTEST( FontRendering, TextLayoutWrap ) { UTEST( FontRendering, LineWrapInfo ) { FileSystem::changeWorkingDirectory( Sys::getProcessPath() ); - std::string string; - FileSystem::fileGet( "assets/textfiles/test-hard-wrap.uext", string ); UIApplication app( WindowSettings( 1024, 650, "eepp - LineWrapInfo Test", WindowStyle::Default, @@ -980,32 +978,67 @@ UTEST( FontRendering, LineWrapInfo ) { Font* font = app.getUI()->getUIThemeManager()->getDefaultFont(); Float width = app.getWindow()->getSize().getWidth(); + int fontSize = 16; LineWrapMode mode = LineWrapMode::Word; - LineWrapInfo lineWrapShaperDisabled; - LineWrapInfo lineWrapShaperEnabled; - { - UTEST_PRINT_STEP( "Text Shaper disabled" ); - BoolScopedOp op( Text::TextShaperEnabled, false ); - String str( string ); - lineWrapShaperDisabled = LineWrap::computeLineBreaks( string, font, 16, width, mode ); - } + const auto runTest = [&]( const std::string& path ) { + UTEST_PRINT_STEP( String::format( "Test File: %s", path.c_str() ).c_str() ); + UTEST_PRINT_STEP( "Line Breaks" ); - UTEST_PRINT_STEP( "Text Shaper enabled" ); - { - BoolScopedOp op( Text::TextShaperEnabled, true ); - String str( string ); - lineWrapShaperEnabled = LineWrap::computeLineBreaks( string, font, 16, width, mode ); + std::string string; + FileSystem::fileGet( path, string ); + LineWrapInfoEx lineWrapShaperDisabled; + LineWrapInfoEx lineWrapShaperEnabled; + LineWrapInfoEx lineWrapShaperEnabledWOO; { - UTEST_PRINT_STEP( "Text Shaper enabled w/o optimizations" ); - BoolScopedOp op2( Text::TextShaperOptimizations, false ); + UTEST_PRINT_STEP( "Text Shaper disabled" ); + BoolScopedOp op( Text::TextShaperEnabled, false ); String str( string ); - lineWrapShaperEnabled = LineWrap::computeLineBreaks( string, font, 16, width, mode ); + lineWrapShaperDisabled = + LineWrap::computeLineBreaksEx( string, font, fontSize, width, mode ); } - } - EXPECT_VECTOREQ( lineWrapShaperDisabled.wraps, lineWrapShaperEnabled.wraps ); + UTEST_PRINT_STEP( "Text Shaper enabled" ); + { + BoolScopedOp op( Text::TextShaperEnabled, true ); + String str( string ); + lineWrapShaperEnabled = + LineWrap::computeLineBreaksEx( string, font, fontSize, width, mode ); + + EXPECT_VECTOREQ( lineWrapShaperDisabled.wraps, lineWrapShaperEnabled.wraps ); + + { + UTEST_PRINT_STEP( "Text Shaper enabled w/o optimizations" ); + BoolScopedOp op2( Text::TextShaperOptimizations, false ); + String str( string ); + lineWrapShaperEnabledWOO = + LineWrap::computeLineBreaksEx( string, font, fontSize, width, mode ); + EXPECT_VECTOREQ( lineWrapShaperDisabled.wraps, lineWrapShaperEnabledWOO.wraps ); + } + } + + UTEST_PRINT_STEP( "Test Widths" ); + + Text text; + text.setFont( font ); + text.setFontSize( fontSize ); + text.setString( string ); + text.hardWrapText( width ); + + const auto linesWidth = text.getLinesWidth(); + + UTEST_PRINT_STEP( "Text Shaper disabled" ); + EXPECT_VECTOREQ( linesWidth, lineWrapShaperDisabled.wrapsWidth ); + UTEST_PRINT_STEP( "Text Shaper enabled" ); + EXPECT_VECTOREQ( linesWidth, lineWrapShaperEnabled.wrapsWidth ); + UTEST_PRINT_STEP( "Text Shaper enabled w/o optimizations" ); + EXPECT_VECTOREQ( linesWidth, lineWrapShaperEnabledWOO.wrapsWidth ); + }; + + runTest( "assets/textfiles/test-hard-wrap.uext" ); + runTest( "assets/textfiles/lorem-ipsum.uext" ); + // runTest( "assets/textfiles/test-tabs.txt" ); } UTEST( FontRendering, TextHardWrap ) {