From 635d8fc9fdc4405d5fb036d5a006cbbdf2d7bf8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Romero?= Date: Sun, 8 Nov 2026 20:22:27 +0100 Subject: [PATCH] fix: include floats in shrink-to-fit widths --- crates/blitz-dom/src/layout/inline.rs | 6 ++- crates/renderer-probe/tests/blitz_upgrade.rs | 10 +++++ patches/blitz/0004-inline-float-layout.patch | 44 ++++++++++++++------ patches/blitz/README.md | 4 +- 4 files changed, 48 insertions(+), 16 deletions(-) diff --git a/crates/blitz-dom/src/layout/inline.rs b/crates/blitz-dom/src/layout/inline.rs index fd0735e4..49a34940 100644 --- a/crates/blitz-dom/src/layout/inline.rs +++ b/crates/blitz-dom/src/layout/inline.rs @@ -515,7 +515,6 @@ impl LayoutPassState<'_> { #[cfg(feature = "floats")] let float_width = match available_space.width { - AvailableSpace::Definite(_) => 0.0, AvailableSpace::MinContent => { let mut width: f32 = 0.0; for ibox in inline_layout.layout.inline_boxes_mut() { @@ -542,7 +541,10 @@ impl LayoutPassState<'_> { width * scale } - AvailableSpace::MaxContent => { + // A definite available width only occurs here when shrink-to-fit sizing, + // which needs the max-content contribution of the floats (backported from + // DioxusLabs/blitz#980). + AvailableSpace::MaxContent | AvailableSpace::Definite(_) => { // When computing a max-content size the available width is effectively // infinite, so floats never wrap onto a new "band" due to a lack of // horizontal space. They only move below preceding floats when the `clear` diff --git a/crates/renderer-probe/tests/blitz_upgrade.rs b/crates/renderer-probe/tests/blitz_upgrade.rs index 396ef211..215fcdcd 100644 --- a/crates/renderer-probe/tests/blitz_upgrade.rs +++ b/crates/renderer-probe/tests/blitz_upgrade.rs @@ -595,3 +595,13 @@ fn floats_shrink_within_their_margins() { close(float.location.x, 40.0); close(float.size.width, 260.0); } + +#[test] +fn shrink_to_fit_boxes_make_room_for_their_floats() { + // DioxusLabs/blitz#980: an outer float sized to fit its contents must + // include its inner float, not just the short text beside it. + let doc = document( + "
Hi
", + ); + assert!(node(&doc, "#outer").final_layout().size.width >= 150.0); +} diff --git a/patches/blitz/0004-inline-float-layout.patch b/patches/blitz/0004-inline-float-layout.patch index 5fc89ffd..a075ad5d 100644 --- a/patches/blitz/0004-inline-float-layout.patch +++ b/patches/blitz/0004-inline-float-layout.patch @@ -16,7 +16,7 @@ index a1b07ff..777cc97 100644 } else { InlineBoxKind::InFlow diff --git a/crates/blitz-dom/src/layout/inline.rs b/crates/blitz-dom/src/layout/inline.rs -index 1f98570..fd0735e 100644 +index 1f98570..49a3494 100644 --- a/crates/blitz-dom/src/layout/inline.rs +++ b/crates/blitz-dom/src/layout/inline.rs @@ -16,7 +16,7 @@ use taffy::{ @@ -52,7 +52,15 @@ index 1f98570..fd0735e 100644 #[cfg(feature = "floats")] let is_floated = style.float().is_floated(); -@@ -518,9 +524,10 @@ impl LayoutPassState<'_> { +@@ -509,7 +515,6 @@ impl LayoutPassState<'_> { + + #[cfg(feature = "floats")] + let float_width = match available_space.width { +- AvailableSpace::Definite(_) => 0.0, + AvailableSpace::MinContent => { + let mut width: f32 = 0.0; + for ibox in inline_layout.layout.inline_boxes_mut() { +@@ -518,9 +523,10 @@ impl LayoutPassState<'_> { self.child_layout_style(&self.nodes[NodeId::from_u64(ibox.id)]); ( style.float().is_floated(), @@ -66,7 +74,19 @@ index 1f98570..fd0735e 100644 ) }; -@@ -554,9 +561,10 @@ impl LayoutPassState<'_> { +@@ -535,7 +541,10 @@ impl LayoutPassState<'_> { + + width * scale + } +- AvailableSpace::MaxContent => { ++ // A definite available width only occurs here when shrink-to-fit sizing, ++ // which needs the max-content contribution of the floats (backported from ++ // DioxusLabs/blitz#980). ++ AvailableSpace::MaxContent | AvailableSpace::Definite(_) => { + // When computing a max-content size the available width is effectively + // infinite, so floats never wrap onto a new "band" due to a lack of + // horizontal space. They only move below preceding floats when the `clear` +@@ -554,9 +563,10 @@ impl LayoutPassState<'_> { ( style.float(), style.clear(), @@ -80,7 +100,7 @@ index 1f98570..fd0735e 100644 ) }; -@@ -671,6 +679,12 @@ impl LayoutPassState<'_> { +@@ -671,6 +681,12 @@ impl LayoutPassState<'_> { // Out-of-flow candidates bubbled up from this container and its in-flow subtree. // These are laid out by the out-of-flow positioning pass (`compute_oof_layout`). let mut oof_candidates = OofCandidates::new(); @@ -93,7 +113,7 @@ index 1f98570..fd0735e 100644 // Perform inline layout #[cfg(feature = "floats")] -@@ -680,7 +694,10 @@ impl LayoutPassState<'_> { +@@ -680,7 +696,10 @@ impl LayoutPassState<'_> { let mut has_active_floats = initial_slot.segment_id.is_some(); let state = breaker.state_mut(); state.set_layout_max_advance(width); @@ -105,7 +125,7 @@ index 1f98570..fd0735e 100644 state.set_line_x(initial_slot.x * scale); state.set_line_y((initial_slot.y * scale) as f64); -@@ -705,7 +722,9 @@ impl LayoutPassState<'_> { +@@ -705,7 +724,9 @@ impl LayoutPassState<'_> { block_ctx.find_content_slot(min_y as f32, Clear::None, None); has_active_floats = next_slot.segment_id.is_some(); @@ -116,7 +136,7 @@ index 1f98570..fd0735e 100644 state.set_line_x(next_slot.x * scale); state.set_line_y((next_slot.y * scale) as f64); } else { -@@ -715,15 +734,18 @@ impl LayoutPassState<'_> { +@@ -715,15 +736,18 @@ impl LayoutPassState<'_> { continue; } @@ -139,7 +159,7 @@ index 1f98570..fd0735e 100644 let style = self.child_layout_style(&self.nodes[node_id]); // We can assume that the box is a float because we only set `break_on_box: true` for floats let direction = match style.float() { -@@ -736,15 +758,18 @@ impl LayoutPassState<'_> { +@@ -736,15 +760,18 @@ impl LayoutPassState<'_> { style.clear(), style .margin() @@ -160,7 +180,7 @@ index 1f98570..fd0735e 100644 ); let min_y = state.line_y() as f32 / scale; -@@ -762,7 +787,9 @@ impl LayoutPassState<'_> { +@@ -762,7 +789,9 @@ impl LayoutPassState<'_> { block_ctx.find_content_slot(min_y as f32, Clear::None, None); has_active_floats = next_slot.segment_id.is_some(); @@ -171,7 +191,7 @@ index 1f98570..fd0735e 100644 state.set_line_x(next_slot.x * scale); state.set_line_y((next_slot.y * scale) as f64); -@@ -777,9 +804,10 @@ impl LayoutPassState<'_> { +@@ -777,9 +806,10 @@ impl LayoutPassState<'_> { // Translate anchors from item-relative to container-relative // coordinates and collect candidates bubbled from the float's subtree @@ -183,7 +203,7 @@ index 1f98570..fd0735e 100644 } // dbg!(&layout.size); -@@ -902,9 +930,10 @@ impl LayoutPassState<'_> { +@@ -902,9 +932,10 @@ impl LayoutPassState<'_> { let container_direction = self.nodes[node_id].layout_style().direction(); @@ -197,7 +217,7 @@ index 1f98570..fd0735e 100644 let align_content = BlockContainerStyle::align_content(&self.nodes[node_id].layout_style()); let block_offset = if align_content.keyword() == taffy::AlignContentKeyword::Normal { 0.0 -@@ -916,6 +945,21 @@ impl LayoutPassState<'_> { +@@ -916,6 +947,21 @@ impl LayoutPassState<'_> { inline_layout.block_offset = block_offset; let line_box_top = container_pb.top + block_offset; diff --git a/patches/blitz/README.md b/patches/blitz/README.md index 97c01013..f7d71692 100644 --- a/patches/blitz/README.md +++ b/patches/blitz/README.md @@ -27,8 +27,8 @@ desktop/Android/OpenHarmony patch wiring, and all three resolved lockfiles. | 0007 unresolvable resource URLs | Skips images, stylesheets, form actions and `@import` rules whose URL cannot be resolved instead of panicking. The reader has no base URL, so a relative `` failed the whole message. | | 0008 offscreen culling bounds | Builds each element's cull box at its own origin, like its scaled scrollable overflow. Tiles far down a message no longer paint the elements above them. | -0002–0006 are not upstream yet, except 0004's float slot clamp, which backports -upstream #1443, and 0003's table rebuild on border, padding and size changes, +0002–0006 are not upstream yet, except 0004's float slot clamp and shrink-to-fit +float widths, which backport upstream #1443 and #980, and 0003's table rebuild on border, padding and size changes, which backports #1276's `table_needs_rebuild`. Upstream main adds `cellspacing` and `cellpadding` hints (#1366), which overlap 0003. 0007 backports upstream #1374 and the `` part of #1318; its `@import` change is local. 0008 backports