fix rich text propagated float placement loop

- require propagated float placement to make forward progress
  - avoid zero-displacement loops when float edges touch
  - recompute collisions after each vertical adjustment
  - add regression coverage for touching propagated and active floats
This commit is contained in:
Martín Lucas Golini
2026-07-30 20:39:07 -03:00
parent 774e09efc2
commit 58cd2ec327
2 changed files with 27 additions and 8 deletions
+9 -8
View File
@@ -1379,24 +1379,25 @@ class RichTextInlineLayouter {
result.lines.back().y = curY; result.lines.back().y = curY;
} }
Float placedY = curY; Float placedY = curY;
bool moved; while ( true ) {
do { Float nextPlacedY = placedY;
moved = false;
for ( const auto& propagated : *payload.propagatedFloats ) { for ( const auto& propagated : *payload.propagatedFloats ) {
Rectf rect = propagated.rect; Rectf rect = propagated.rect;
rect.move( { curX, placedY } ); rect.move( { curX, placedY } );
auto avoidActiveFloat = [&]( const Rectf& active ) { auto avoidActiveFloat = [&]( const Rectf& active ) {
if ( rect.intersect( active ) && active.Bottom > placedY ) { if ( rect.intersect( active ) )
placedY += active.Bottom - rect.Top; nextPlacedY = std::max(
moved = true; nextPlacedY, placedY + active.Bottom - rect.Top );
}
}; };
for ( const auto& active : leftFloats ) for ( const auto& active : leftFloats )
avoidActiveFloat( active ); avoidActiveFloat( active );
for ( const auto& active : rightFloats ) for ( const auto& active : rightFloats )
avoidActiveFloat( active ); avoidActiveFloat( active );
} }
} while ( moved ); if ( nextPlacedY <= placedY + 0.01f )
break;
placedY = nextPlacedY;
}
if ( placedY > curY ) { if ( placedY > curY ) {
curY = placedY; curY = placedY;
result.lines.back().y = curY; result.lines.back().y = curY;
@@ -61,6 +61,24 @@ UTEST( UIHTMLFloat, structure_FloatAndClearEnums ) {
EXPECT_EQ( (int)CSSClear::None, (int)CSSClearHelper::fromString( "garbage" ) ); EXPECT_EQ( (int)CSSClear::None, (int)CSSClearHelper::fromString( "garbage" ) );
} }
UTEST( UIHTMLFloat, propagatedFloatTouchingActiveFloatMakesForwardProgress ) {
RichText richText;
richText.setMaxWidth( 200.f );
richText.setExternalFloatExclusions(
{ { Rectf( 0.f, 0.f, 100.f, 20.f ), RichText::InlineFloat::Left } } );
auto propagated = std::make_shared<std::vector<RichText::FloatExclusion>>();
propagated->push_back( { Rectf( -100.f, 20.f, 10.f, 30.f ), RichText::InlineFloat::Left } );
richText.addCustomSize( { 20.f, 10.f }, RichText::InlineFloat::None,
RichText::InlineClear::None, -1.f, {}, {}, false, false, propagated );
const Sizef size = richText.getSize();
EXPECT_TRUE( std::isfinite( size.getWidth() ) );
EXPECT_TRUE( std::isfinite( size.getHeight() ) );
EXPECT_GT( size.getWidth(), 0.f );
EXPECT_GT( size.getHeight(), 0.f );
}
UTEST( UIHTMLFloat, property_DefaultsAreNone ) { UTEST( UIHTMLFloat, property_DefaultsAreNone ) {
UIHTMLWidget* w = UIHTMLWidget::New(); UIHTMLWidget* w = UIHTMLWidget::New();
EXPECT_EQ( CSSFloat::None, w->getCSSFloat() ); EXPECT_EQ( CSSFloat::None, w->getCSSFloat() );