aboutsummaryrefslogtreecommitdiffstats
path: root/components/layout_2020/flow/float.rs
diff options
context:
space:
mode:
authorPatrick Walton <pcwalton@mimiga.net>2020-07-22 10:52:11 -0700
committerOriol Brufau <obrufau@igalia.com>2023-06-03 06:09:21 +0200
commitcdec48328ed82e9e84d5a9c2ef7d3b6b10a59fe7 (patch)
tree2cd7fbc70593b00a7151455e2dec6774ff3d0a7e /components/layout_2020/flow/float.rs
parent053a0aa4fd7360c8504a65ba177d7047077129b2 (diff)
downloadservo-cdec48328ed82e9e84d5a9c2ef7d3b6b10a59fe7.tar.gz
servo-cdec48328ed82e9e84d5a9c2ef7d3b6b10a59fe7.zip
Place floats in layout 2020, but don't flow text around the floats yet.
This commit puts floats behind the `layout.floats.enabled` pref, because of the following issues and unimplemented features: * Inline formatting contexts don't take floats into account, so text doesn't flow around the floats yet. * Non-floated block formatting contexts don't take floats into account, so BFCs can overlap floats. * Block formatting contexts that contain floats don't expand vertically to contain all the floats. That is, floats can stick out the bottom of BFCs, contra spec.
Diffstat (limited to 'components/layout_2020/flow/float.rs')
-rw-r--r--components/layout_2020/flow/float.rs426
1 files changed, 390 insertions, 36 deletions
diff --git a/components/layout_2020/flow/float.rs b/components/layout_2020/flow/float.rs
index eeae46cba61..d85b364b694 100644
--- a/components/layout_2020/flow/float.rs
+++ b/components/layout_2020/flow/float.rs
@@ -6,16 +6,25 @@
//!
//! See CSS 2.1 § 9.5.1: https://www.w3.org/TR/CSS2/visuren.html#float-position
+use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
use crate::dom::NodeExt;
use crate::dom_traversal::{Contents, NodeAndStyleInfo};
use crate::formatting_contexts::IndependentFormattingContext;
+use crate::fragments::{BoxFragment, CollapsedBlockMargins, CollapsedMargin, FloatFragment};
+use crate::fragments::{Fragment, HoistedFloatFragment};
use crate::geom::flow_relative::{Rect, Vec2};
-use crate::style_ext::DisplayInside;
+use crate::positioned::PositioningContext;
+use crate::style_ext::{ComputedValuesExt, DisplayInside};
+use crate::ContainingBlock;
use euclid::num::Zero;
use servo_arc::Arc;
use std::f32;
+use std::fmt::{Debug, Formatter, Result as FmtResult};
use std::ops::Range;
+use style::computed_values::clear::T as ClearProperty;
+use style::computed_values::float::T as FloatProperty;
+use style::properties::ComputedValues;
use style::values::computed::Length;
use style::values::specified::text::TextDecorationLine;
@@ -40,6 +49,25 @@ pub struct FloatContext {
/// The current (logically) vertical position. No new floats may be placed (logically) above
/// this line.
pub ceiling: Length,
+ /// Distances from the logical left side of the block formatting context to the logical sides
+ /// of the current containing block.
+ pub walls: InlineWalls,
+ /// The (logically) lowest margin edge of the last left float.
+ pub clear_left_position: Length,
+ /// The (logically) lowest margin edge of the last right float.
+ pub clear_right_position: Length,
+}
+
+/// Distances from the logical left side of the block formatting context to the logical sides of
+/// the current containing block.
+#[derive(Clone, Copy, Debug)]
+pub struct InlineWalls {
+ /// The distance from the logical left side of the block formatting context to the logical
+ /// left side of the current containing block.
+ pub left: Length,
+ /// The distance from the logical *left* side of the block formatting context to the logical
+ /// right side of this object's containing block.
+ pub right: Length,
}
impl FloatContext {
@@ -60,6 +88,9 @@ impl FloatContext {
FloatContext {
bands,
ceiling: Length::zero(),
+ walls: InlineWalls::new(),
+ clear_left_position: Length::zero(),
+ clear_right_position: Length::zero(),
}
}
@@ -81,9 +112,19 @@ impl FloatContext {
/// This should be used for placing inline elements and block formatting contexts so that they
/// don't collide with floats.
pub fn place_object(&self, object: &PlacementInfo) -> Vec2<Length> {
+ let ceiling = match object.clear {
+ ClearSide::None => self.ceiling,
+ ClearSide::Left => self.ceiling.max(self.clear_left_position),
+ ClearSide::Right => self.ceiling.max(self.clear_right_position),
+ ClearSide::Both => self
+ .ceiling
+ .max(self.clear_left_position)
+ .max(self.clear_right_position),
+ };
+
// Find the first band this float fits in.
- let mut first_band = self.bands.find(self.ceiling).unwrap();
- while !first_band.object_fits(&object) {
+ let mut first_band = self.bands.find(ceiling).unwrap();
+ while !first_band.object_fits(&object, &self.walls) {
let next_band = self.bands.find_next(first_band.top).unwrap();
if next_band.top.px().is_infinite() {
break;
@@ -95,8 +136,8 @@ impl FloatContext {
match object.side {
FloatSide::Left => {
let left_object_edge = match first_band.left {
- Some(band_left) => band_left.max(object.left_wall),
- None => object.left_wall,
+ Some(band_left) => band_left.max(self.walls.left),
+ None => self.walls.left,
};
Vec2 {
inline: left_object_edge,
@@ -105,8 +146,8 @@ impl FloatContext {
},
FloatSide::Right => {
let right_object_edge = match first_band.right {
- Some(band_right) => band_right.min(object.right_wall),
- None => object.right_wall,
+ Some(band_right) => band_right.min(self.walls.right),
+ None => self.walls.right,
};
Vec2 {
inline: right_object_edge - object.size.inline,
@@ -129,6 +170,20 @@ impl FloatContext {
size: new_float.size.clone(),
};
+ // Update clear.
+ match new_float.side {
+ FloatSide::Left => {
+ self.clear_left_position = self
+ .clear_left_position
+ .max(new_float_rect.max_block_position())
+ },
+ FloatSide::Right => {
+ self.clear_right_position = self
+ .clear_right_position
+ .max(new_float_rect.max_block_position())
+ },
+ }
+
// Split the first band if necessary.
let mut first_band = self.bands.find(new_float_rect.start_corner.block).unwrap();
first_band.top = new_float_rect.start_corner.block;
@@ -155,6 +210,15 @@ impl FloatContext {
}
}
+impl InlineWalls {
+ fn new() -> InlineWalls {
+ InlineWalls {
+ left: Length::zero(),
+ right: Length::new(f32::INFINITY),
+ }
+ }
+}
+
/// Information needed to place an object so that it doesn't collide with existing floats.
#[derive(Clone, Debug)]
pub struct PlacementInfo {
@@ -164,12 +228,6 @@ pub struct PlacementInfo {
pub side: FloatSide,
/// Which side or sides to clear floats on.
pub clear: ClearSide,
- /// The distance from the logical left side of the block formatting context to the logical
- /// left side of this object's containing block.
- pub left_wall: Length,
- /// The distance from the logical *left* side of the block formatting context to the logical
- /// right side of this object's containing block.
- pub right_wall: Length,
}
/// Whether the float is left or right.
@@ -210,39 +268,41 @@ pub struct FloatBand {
pub right: Option<Length>,
}
-impl FloatBand {
- // Returns true if this band is clear of floats on the given side or sides.
- fn is_clear(&self, side: ClearSide) -> bool {
- match (side, self.left, self.right) {
- (ClearSide::Left, Some(_), _) |
- (ClearSide::Right, _, Some(_)) |
- (ClearSide::Both, Some(_), _) |
- (ClearSide::Both, _, Some(_)) => false,
- (ClearSide::None, _, _) |
- (ClearSide::Left, None, _) |
- (ClearSide::Right, _, None) |
- (ClearSide::Both, None, None) => true,
+impl FloatSide {
+ fn from_style(style: &ComputedValues) -> Option<FloatSide> {
+ match style.get_box().float {
+ FloatProperty::None => None,
+ FloatProperty::Left => Some(FloatSide::Left),
+ FloatProperty::Right => Some(FloatSide::Right),
}
}
+}
- // Determines whether an object fits in a band.
- fn object_fits(&self, object: &PlacementInfo) -> bool {
- // If we must be clear on the given side and we aren't, this object doesn't fit.
- if !self.is_clear(object.clear) {
- return false;
+impl ClearSide {
+ pub(crate) fn from_style(style: &ComputedValues) -> ClearSide {
+ match style.get_box().clear {
+ ClearProperty::None => ClearSide::None,
+ ClearProperty::Left => ClearSide::Left,
+ ClearProperty::Right => ClearSide::Right,
+ ClearProperty::Both => ClearSide::Both,
}
+ }
+}
+impl FloatBand {
+ // Determines whether an object fits in a band.
+ fn object_fits(&self, object: &PlacementInfo, walls: &InlineWalls) -> bool {
match object.side {
FloatSide::Left => {
// Compute a candidate left position for the object.
let candidate_left = match self.left {
- None => object.left_wall,
- Some(left) => left.max(object.left_wall),
+ None => walls.left,
+ Some(left) => left.max(walls.left),
};
// If this band has an existing left float in it, then make sure that the object
// doesn't stick out past the right edge (rule 7).
- if self.left.is_some() && candidate_left + object.size.inline > object.right_wall {
+ if self.left.is_some() && candidate_left + object.size.inline > walls.right {
return false;
}
@@ -257,13 +317,13 @@ impl FloatBand {
FloatSide::Right => {
// Compute a candidate right position for the object.
let candidate_right = match self.right {
- None => object.right_wall,
- Some(right) => right.min(object.right_wall),
+ None => walls.right,
+ Some(right) => right.min(walls.right),
};
// If this band has an existing right float in it, then make sure that the new
// object doesn't stick out past the left edge (rule 7).
- if self.right.is_some() && candidate_right - object.size.inline < object.left_wall {
+ if self.right.is_some() && candidate_right - object.size.inline < walls.left {
return false;
}
@@ -540,6 +600,12 @@ impl FloatBandLink {
}
}
+impl Debug for FloatFragment {
+ fn fmt(&self, formatter: &mut Formatter) -> FmtResult {
+ write!(formatter, "FloatFragment")
+ }
+}
+
// Float boxes
impl FloatBox {
@@ -561,4 +627,292 @@ impl FloatBox {
),
}
}
+
+ pub fn layout(
+ &mut self,
+ layout_context: &LayoutContext,
+ positioning_context: &mut PositioningContext,
+ containing_block: &ContainingBlock,
+ mut sequential_layout_state: Option<&mut SequentialLayoutState>,
+ ) {
+ let sequential_layout_state = sequential_layout_state
+ .as_mut()
+ .expect("Tried to lay out a float with no sequential placement state!");
+
+ // Speculate that the float ceiling will be located at the current block position plus the
+ // result of solving any margins we're building up. This is usually right, but it can be
+ // incorrect if there are more in-flow collapsible margins yet to be seen. An example
+ // showing when this can go wrong:
+ //
+ // <div style="margin: 5px"></div>
+ // <div style="float: left"></div>
+ // <div style="margin: 10px"></div>
+ //
+ // Assuming these are all in-flow, the float should be placed 10px down from the start, not
+ // 5px, but we can't know that because we haven't seen the block after this float yet.
+ //
+ // FIXME(pcwalton): Implement the proper behavior when speculation fails. Either detect it
+ // afterward and fix it up, or detect this situation ahead of time via lookahead and make
+ // sure `current_margin` is accurate before calling this method.
+ sequential_layout_state.floats.lower_ceiling(
+ sequential_layout_state.bfc_relative_block_position +
+ sequential_layout_state.current_margin.solve(),
+ );
+
+ let style = match self.contents {
+ IndependentFormattingContext::Replaced(ref replaced) => replaced.style.clone(),
+ IndependentFormattingContext::NonReplaced(ref non_replaced) => {
+ non_replaced.style.clone()
+ },
+ };
+ let float_context = &mut sequential_layout_state.floats;
+ let box_fragment = positioning_context.layout_maybe_position_relative_fragment(
+ layout_context,
+ containing_block,
+ &style,
+ |mut positioning_context| {
+ // Margin is computed this way regardless of whether the element is replaced
+ // or non-replaced.
+ let pbm = style.padding_border_margin(containing_block);
+ let margin = pbm.margin.auto_is(|| Length::zero());
+ let pbm_sums = &(&pbm.padding + &pbm.border) + &margin;
+
+ let (content_size, fragments);
+ match self.contents {
+ IndependentFormattingContext::NonReplaced(ref mut non_replaced) => {
+ // Calculate inline size.
+ // https://drafts.csswg.org/css2/#float-width
+ let box_size = non_replaced.style.content_box_size(&containing_block, &pbm);
+ let max_box_size = non_replaced
+ .style
+ .content_max_box_size(&containing_block, &pbm);
+ let min_box_size = non_replaced
+ .style
+ .content_min_box_size(&containing_block, &pbm)
+ .auto_is(Length::zero);
+
+ let tentative_inline_size = box_size.inline.auto_is(|| {
+ let available_size =
+ containing_block.inline_size - pbm_sums.inline_sum();
+ non_replaced
+ .inline_content_sizes(layout_context)
+ .shrink_to_fit(available_size)
+ });
+ let inline_size = tentative_inline_size
+ .clamp_between_extremums(min_box_size.inline, max_box_size.inline);
+
+ // Calculate block size.
+ // https://drafts.csswg.org/css2/#block-root-margin
+ // FIXME(pcwalton): Is a tree rank of zero correct here?
+ let containing_block_for_children = ContainingBlock {
+ inline_size,
+ block_size: box_size.block,
+ style: &non_replaced.style,
+ };
+ let independent_layout = non_replaced.layout(
+ layout_context,
+ &mut positioning_context,
+ &containing_block_for_children,
+ 0,
+ );
+ content_size = Vec2 {
+ inline: inline_size,
+ block: box_size
+ .block
+ .auto_is(|| independent_layout.content_block_size),
+ };
+ fragments = independent_layout.fragments;
+ },
+ IndependentFormattingContext::Replaced(ref replaced) => {
+ // https://drafts.csswg.org/css2/#float-replaced-width
+ // https://drafts.csswg.org/css2/#inline-replaced-height
+ content_size = replaced.contents.used_size_as_if_inline_element(
+ &containing_block,
+ &replaced.style,
+ None,
+ &pbm,
+ );
+ fragments = replaced
+ .contents
+ .make_fragments(&replaced.style, content_size.clone());
+ },
+ };
+
+ let margin_box_start_corner = float_context.add_float(&PlacementInfo {
+ size: &content_size + &pbm_sums.sum(),
+ side: FloatSide::from_style(&style).expect("Float box wasn't floated!"),
+ clear: ClearSide::from_style(&style),
+ });
+
+ let content_rect = Rect {
+ start_corner: &margin_box_start_corner + &pbm_sums.start_offset(),
+ size: content_size.clone(),
+ };
+
+ // Clearance is handled internally by the float placement logic, so there's no need
+ // to store it explicitly in the fragment.
+ let clearance = Length::zero();
+
+ BoxFragment::new(
+ self.contents.base_fragment_info(),
+ style.clone(),
+ fragments,
+ content_rect,
+ pbm.padding,
+ pbm.border,
+ margin,
+ clearance,
+ CollapsedBlockMargins::zero(),
+ )
+ },
+ );
+ sequential_layout_state.push_float_fragment(ArcRefCell::new(Fragment::Box(box_fragment)));
+ }
+}
+
+// Float fragment storage
+
+// A persistent linked list that stores float fragments that need to be hoisted to their nearest
+// ancestor containing block.
+#[derive(Clone)]
+struct FloatFragmentList {
+ root: FloatFragmentLink,
+}
+
+// A single link in the float fragment list.
+#[derive(Clone)]
+struct FloatFragmentLink(Option<Arc<FloatFragmentNode>>);
+
+// A single node in the float fragment list.
+#[derive(Clone)]
+struct FloatFragmentNode {
+ // The fragment.
+ fragment: ArcRefCell<Fragment>,
+ // The next fragment (previous in document order).
+ next: FloatFragmentLink,
+}
+
+impl FloatFragmentList {
+ fn new() -> FloatFragmentList {
+ FloatFragmentList {
+ root: FloatFragmentLink(None),
+ }
+ }
+}
+
+// Sequential layout state
+
+// Layout state that we maintain when doing sequential traversals of the box tree in document
+// order.
+//
+// This data is only needed for float placement and float interaction, and as such is only present
+// if the current block formatting context contains floats.
+//
+// All coordinates here are relative to the start of the nearest ancestor block formatting context.
+//
+// This structure is expected to be cheap to clone, in order to allow for "snapshots" that enable
+// restarting layout at any point in the tree.
+#[derive(Clone)]
+pub(crate) struct SequentialLayoutState {
+ // Holds all floats in this block formatting context.
+ pub(crate) floats: FloatContext,
+ // A list of all float fragments in this block formatting context. These are gathered up and
+ // hoisted to the top of the BFC.
+ bfc_float_fragments: FloatFragmentList,
+ // The (logically) bottom border edge or top padding edge of the last in-flow block. Floats
+ // cannot be placed above this line.
+ //
+ // This is often, but not always, the same as the float ceiling. The float ceiling can be lower
+ // than this value because this value is calculated based on in-flow boxes only, while
+ // out-of-flow floats can affect the ceiling as well (see CSS 2.1 § 9.5.1 rule 6).
+ bfc_relative_block_position: Length,
+ // Any collapsible margins that we've encountered after `bfc_relative_block_position`.
+ current_margin: CollapsedMargin,
+}
+
+impl SequentialLayoutState {
+ // Creates a new empty `SequentialLayoutState`.
+ pub(crate) fn new() -> SequentialLayoutState {
+ SequentialLayoutState {
+ floats: FloatContext::new(),
+ current_margin: CollapsedMargin::zero(),
+ bfc_relative_block_position: Length::zero(),
+ bfc_float_fragments: FloatFragmentList::new(),
+ }
+ }
+
+ // Moves the current block position (logically) down by `block_distance`.
+ //
+ // Floats may not be placed higher than the current block position.
+ pub(crate) fn advance_block_position(&mut self, block_distance: Length) {
+ self.bfc_relative_block_position += block_distance;
+ self.floats.lower_ceiling(self.bfc_relative_block_position);
+ }
+
+ // Collapses margins, moving the block position down by the collapsed value of `current_margin`
+ // and resetting `current_margin` to zero.
+ //
+ // Call this method before laying out children when it is known that the start margin of the
+ // current fragment can't collapse with the margins of any of its children.
+ pub(crate) fn collapse_margins(&mut self) {
+ self.advance_block_position(self.current_margin.solve());
+ self.current_margin = CollapsedMargin::zero();
+ }
+
+ // Returns the amount of clearance that a block with the given `clear` value at the current
+ // `bfc_relative_block_position` (with top margin included in `current_margin` if applicable)
+ // needs to have.
+ //
+ // https://www.w3.org/TR/2011/REC-CSS2-20110607/visuren.html#flow-control
+ pub(crate) fn calculate_clearance(&self, clear_side: ClearSide) -> Length {
+ if clear_side == ClearSide::None {
+ return Length::zero();
+ }
+
+ let hypothetical_block_position =
+ self.bfc_relative_block_position + self.current_margin.solve();
+ let clear_position = match clear_side {
+ ClearSide::None => unreachable!(),
+ ClearSide::Left => self
+ .floats
+ .clear_left_position
+ .max(hypothetical_block_position),
+ ClearSide::Right => self
+ .floats
+ .clear_right_position
+ .max(hypothetical_block_position),
+ ClearSide::Both => self
+ .floats
+ .clear_left_position
+ .max(self.floats.clear_right_position)
+ .max(hypothetical_block_position),
+ };
+ clear_position - hypothetical_block_position
+ }
+
+ /// Adds a new adjoining margin.
+ pub(crate) fn adjoin_assign(&mut self, margin: &CollapsedMargin) {
+ self.current_margin.adjoin_assign(margin)
+ }
+
+ /// Adds the float fragment to this list.
+ pub(crate) fn push_float_fragment(&mut self, new_float_fragment: ArcRefCell<Fragment>) {
+ self.bfc_float_fragments.root.0 = Some(Arc::new(FloatFragmentNode {
+ fragment: new_float_fragment,
+ next: FloatFragmentLink(self.bfc_float_fragments.root.0.take()),
+ }));
+ }
+
+ /// Adds the float fragments we've been building up to the given vector.
+ pub(crate) fn add_float_fragments_to_list(&self, fragment_list: &mut Vec<Fragment>) {
+ let start_index = fragment_list.len();
+ let mut link = &self.bfc_float_fragments.root;
+ while let Some(ref node) = link.0 {
+ fragment_list.push(Fragment::HoistedFloat(HoistedFloatFragment {
+ fragment: node.fragment.clone(),
+ }));
+ link = &node.next;
+ }
+ fragment_list[start_index..].reverse();
+ }
}