Fixes for line widths calculation.

This commit is contained in:
Martín Lucas Golini
2026-01-27 23:49:02 -03:00
parent b3b5f97ab8
commit 9561334401
3 changed files with 101 additions and 54 deletions

View File

@@ -64,14 +64,14 @@ Float LineWrap::computeOffsets( const String::View& string, Font* font, Uint32 c
return 0.f;
}
template <typename T>
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 <typename LineWrapType>
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<Float>{}, 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<T, LineWrapInfoEx> ) {
if constexpr ( std::is_same_v<LineWrapType, LineWrapInfoEx> ) {
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<T, LineWrapInfoEx> ) {
info.wrapsWidth.back() = xoffset; // Finalize width of current line
if constexpr ( std::is_same_v<LineWrapType, LineWrapInfoEx> ) {
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<T, LineWrapInfoEx> ) {
info.wrapsWidth.push_back( 0.f ); // Placeholder for new line's width
if constexpr ( std::is_same_v<LineWrapType, LineWrapInfoEx> ) {
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<T, LineWrapInfoEx> ) {
info.wrapsWidth.back() = lastWordWrapWidth;
if constexpr ( std::is_same_v<LineWrapType, LineWrapInfoEx> ) {
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<T, LineWrapInfoEx> ) {
info.wrapsWidth.push_back( 0.f );
if constexpr ( std::is_same_v<LineWrapType, LineWrapInfoEx> ) {
if ( idx + 1 != string.size() )
info.wrapsWidth.push_back( 0.f );
}
xoffset = info.paddingStart + ( xoffset - lastWidth );
} else {
if constexpr ( std::is_same_v<T, LineWrapInfoEx> ) {
info.wrapsWidth.back() = xoffset - w; // Width up to char *before* current one
if constexpr ( std::is_same_v<LineWrapType, LineWrapInfoEx> ) {
// 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<T, LineWrapInfoEx> ) {
info.wrapsWidth.push_back( 0.f );
if constexpr ( std::is_same_v<LineWrapType, LineWrapInfoEx> ) {
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<T, LineWrapInfoEx> ) {
if ( !info.wrapsWidth.empty() ) { // Ensure there's at least one line
info.wrapsWidth.back() = xoffset;
}
if constexpr ( std::is_same_v<LineWrapType, LineWrapInfoEx> ) {
if ( !string.empty() && string[string.size() - 1] != '\n' && !info.wrapsWidth.empty() )
info.wrapsWidth.back() = std::ceil( xoffset );
}
return info;

View File

@@ -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;

View File

@@ -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 ) {