servo/components/layout_2020/flow/float.rs
Patrick Walton cdec48328e 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.
2023-06-03 06:09:21 +02:00

918 lines
35 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Float layout.
//!
//! 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::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;
/// A floating box.
#[derive(Debug, Serialize)]
pub(crate) struct FloatBox {
/// The formatting context that makes up the content of this box.
pub contents: IndependentFormattingContext,
}
/// Data kept during layout about the floats in a given block formatting context.
///
/// This is a persistent data structure. Each float has its own private copy of the float context,
/// although such copies may share portions of the `bands` tree.
#[derive(Clone, Debug)]
pub struct FloatContext {
/// A persistent AA tree of float bands.
///
/// This tree is immutable; modification operations return the new tree, which may share nodes
/// with previous versions of the tree.
pub bands: FloatBandTree,
/// 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 {
/// Returns a new float context representing a containing block with the given content
/// inline-size.
pub fn new() -> Self {
let mut bands = FloatBandTree::new();
bands = bands.insert(FloatBand {
top: Length::zero(),
left: None,
right: None,
});
bands = bands.insert(FloatBand {
top: Length::new(f32::INFINITY),
left: None,
right: None,
});
FloatContext {
bands,
ceiling: Length::zero(),
walls: InlineWalls::new(),
clear_left_position: Length::zero(),
clear_right_position: Length::zero(),
}
}
/// Returns the current ceiling value. No new floats may be placed (logically) above this line.
pub fn ceiling(&self) -> Length {
self.ceiling
}
/// (Logically) lowers the ceiling to at least `new_ceiling` units.
///
/// If the ceiling is already logically lower (i.e. larger) than this, does nothing.
pub fn lower_ceiling(&mut self, new_ceiling: Length) {
self.ceiling = self.ceiling.max(new_ceiling);
}
/// Determines where a float with the given placement would go, but leaves the float context
/// unmodified. Returns the start corner of its margin box.
///
/// 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(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;
}
first_band = next_band;
}
// The object fits perfectly here. Place it.
match object.side {
FloatSide::Left => {
let left_object_edge = match first_band.left {
Some(band_left) => band_left.max(self.walls.left),
None => self.walls.left,
};
Vec2 {
inline: left_object_edge,
block: first_band.top.max(self.ceiling),
}
},
FloatSide::Right => {
let right_object_edge = match first_band.right {
Some(band_right) => band_right.min(self.walls.right),
None => self.walls.right,
};
Vec2 {
inline: right_object_edge - object.size.inline,
block: first_band.top.max(self.ceiling),
}
},
}
}
/// Places a new float and adds it to the list. Returns the start corner of its margin box.
pub fn add_float(&mut self, new_float: &PlacementInfo) -> Vec2<Length> {
// Place the float.
let new_float_origin = self.place_object(new_float);
let new_float_extent = match new_float.side {
FloatSide::Left => new_float_origin.inline + new_float.size.inline,
FloatSide::Right => new_float_origin.inline,
};
let new_float_rect = Rect {
start_corner: new_float_origin,
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;
self.bands = self.bands.insert(first_band);
// Split the last band if necessary.
let mut last_band = self
.bands
.find(new_float_rect.max_block_position())
.unwrap();
last_band.top = new_float_rect.max_block_position();
self.bands = self.bands.insert(last_band);
// Update all bands that contain this float to reflect the new available size.
let block_range = new_float_rect.start_corner.block..new_float_rect.max_block_position();
self.bands = self
.bands
.set_range(&block_range, new_float.side, new_float_extent);
// CSS 2.1 § 9.5.1 rule 6: The outer top of a floating box may not be higher than the outer
// top of any block or floated box generated by an element earlier in the source document.
self.ceiling = self.ceiling.max(new_float_rect.start_corner.block);
new_float_rect.start_corner
}
}
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 {
/// The *margin* box size of the object.
pub size: Vec2<Length>,
/// Whether the object is (logically) aligned to the left or right.
pub side: FloatSide,
/// Which side or sides to clear floats on.
pub clear: ClearSide,
}
/// Whether the float is left or right.
///
/// See CSS 2.1 § 9.5.1: https://www.w3.org/TR/CSS2/visuren.html#float-position
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum FloatSide {
Left,
Right,
}
/// Which side or sides to clear floats on.
///
/// See CSS 2.1 § 9.5.2: https://www.w3.org/TR/CSS2/visuren.html#flow-control
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ClearSide {
None = 0,
Left = 1,
Right = 2,
Both = 3,
}
/// Internal data structure that describes a nonoverlapping vertical region in which floats may be
/// placed. Floats must go between "left edge + `left`" and "right edge - `right`".
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FloatBand {
/// The logical vertical position of the top of this band.
pub top: Length,
/// The distance from the left edge of the block formatting context to the first legal
/// (logically) horizontal position where floats may be placed. If `None`, there are no floats
/// to the left; distinguishing between the cases of "a zero-width float is present" and "no
/// floats at all are present" is necessary to, for example, clear past zero-width floats.
pub left: Option<Length>,
/// The distance from the *left* edge of the block formatting context to the first legal
/// (logically) horizontal position where floats may be placed. If `None`, there are no floats
/// to the right; distinguishing between the cases of "a zero-width float is present" and "no
/// floats at all are present" is necessary to, for example, clear past zero-width floats.
pub right: Option<Length>,
}
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),
}
}
}
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 => 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 > walls.right {
return false;
}
// If this band has an existing right float in it, make sure we don't collide with
// it (rule 3).
match self.right {
None => true,
Some(right) => object.size.inline <= right - candidate_left,
}
},
FloatSide::Right => {
// Compute a candidate right position for the object.
let candidate_right = match self.right {
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 < walls.left {
return false;
}
// If this band has an existing left float in it, make sure we don't collide with
// it (rule 3).
match self.left {
None => true,
Some(left) => object.size.inline <= candidate_right - left,
}
},
}
}
}
// Float band storage
/// A persistent AA tree for float band storage.
///
/// Bands here are nonoverlapping, and there is guaranteed to be a band at block-position 0 and
/// another band at block-position infinity.
///
/// AA trees were chosen for simplicity.
///
/// See: https://en.wikipedia.org/wiki/AA_tree
/// https://arxiv.org/pdf/1412.4882.pdf
#[derive(Clone, Debug)]
pub struct FloatBandTree {
pub root: FloatBandLink,
}
/// A single edge (or lack thereof) in the float band tree.
#[derive(Clone, Debug)]
pub struct FloatBandLink(pub Option<Arc<FloatBandNode>>);
/// A single node in the float band tree.
#[derive(Clone, Debug)]
pub struct FloatBandNode {
/// The actual band.
pub band: FloatBand,
/// The left child.
pub left: FloatBandLink,
/// The right child.
pub right: FloatBandLink,
/// The level, which increases as you go up the tree.
///
/// This value is needed for tree balancing.
pub level: i32,
}
impl FloatBandTree {
/// Creates a new float band tree.
pub fn new() -> FloatBandTree {
FloatBandTree {
root: FloatBandLink(None),
}
}
/// Returns the first band whose top is less than or equal to the given `block_position`.
pub fn find(&self, block_position: Length) -> Option<FloatBand> {
self.root.find(block_position)
}
/// Returns the first band whose top is strictly greater than to the given `block_position`.
pub fn find_next(&self, block_position: Length) -> Option<FloatBand> {
self.root.find_next(block_position)
}
/// Sets the side values of all bands within the given half-open range to be at least
/// `new_value`.
#[must_use]
pub fn set_range(
&self,
range: &Range<Length>,
side: FloatSide,
new_value: Length,
) -> FloatBandTree {
FloatBandTree {
root: FloatBandLink(
self.root
.0
.as_ref()
.map(|root| root.set_range(range, side, new_value)),
),
}
}
/// Inserts a new band into the tree. If the band has the same level as a pre-existing one,
/// replaces the existing band with the new one.
#[must_use]
pub fn insert(&self, band: FloatBand) -> FloatBandTree {
FloatBandTree {
root: self.root.insert(band),
}
}
}
impl FloatBandNode {
fn new(band: FloatBand) -> FloatBandNode {
FloatBandNode {
band,
left: FloatBandLink(None),
right: FloatBandLink(None),
level: 1,
}
}
/// Sets the side values of all bands within the given half-open range to be at least
/// `new_value`.
fn set_range(
&self,
range: &Range<Length>,
side: FloatSide,
new_value: Length,
) -> Arc<FloatBandNode> {
let mut new_band = self.band.clone();
if self.band.top >= range.start && self.band.top < range.end {
match side {
FloatSide::Left => match new_band.left {
None => new_band.left = Some(new_value),
Some(ref mut old_value) => *old_value = old_value.max(new_value),
},
FloatSide::Right => match new_band.right {
None => new_band.right = Some(new_value),
Some(ref mut old_value) => *old_value = old_value.min(new_value),
},
}
}
let new_left = match self.left.0 {
None => FloatBandLink(None),
Some(ref old_left) if range.start < new_band.top => {
FloatBandLink(Some(old_left.set_range(range, side, new_value)))
},
Some(ref old_left) => FloatBandLink(Some((*old_left).clone())),
};
let new_right = match self.right.0 {
None => FloatBandLink(None),
Some(ref old_right) if range.end > new_band.top => {
FloatBandLink(Some(old_right.set_range(range, side, new_value)))
},
Some(ref old_right) => FloatBandLink(Some((*old_right).clone())),
};
Arc::new(FloatBandNode {
band: new_band,
left: new_left,
right: new_right,
level: self.level,
})
}
}
impl FloatBandLink {
/// Returns the first band whose top is less than or equal to the given `block_position`.
fn find(&self, block_position: Length) -> Option<FloatBand> {
let this = match self.0 {
None => return None,
Some(ref node) => node,
};
if block_position < this.band.top {
return this.left.find(block_position);
}
// It's somewhere in this subtree, but we aren't sure whether it's here or in the right
// subtree.
if let Some(band) = this.right.find(block_position) {
return Some(band);
}
Some(this.band.clone())
}
/// Returns the first band whose top is strictly greater than the given `block_position`.
fn find_next(&self, block_position: Length) -> Option<FloatBand> {
let this = match self.0 {
None => return None,
Some(ref node) => node,
};
if block_position >= this.band.top {
return this.right.find_next(block_position);
}
// It's somewhere in this subtree, but we aren't sure whether it's here or in the left
// subtree.
if let Some(band) = this.left.find_next(block_position) {
return Some(band);
}
Some(this.band.clone())
}
// Inserts a new band into the tree. If the band has the same level as a pre-existing one,
// replaces the existing band with the new one.
fn insert(&self, band: FloatBand) -> FloatBandLink {
let mut this = match self.0 {
None => return FloatBandLink(Some(Arc::new(FloatBandNode::new(band)))),
Some(ref this) => (**this).clone(),
};
if band.top < this.band.top {
this.left = this.left.insert(band);
return FloatBandLink(Some(Arc::new(this))).skew().split();
}
if band.top > this.band.top {
this.right = this.right.insert(band);
return FloatBandLink(Some(Arc::new(this))).skew().split();
}
this.band = band;
FloatBandLink(Some(Arc::new(this)))
}
// Corrects tree balance:
//
// T L
// / \ / \
// L R → A T if level(T) = level(L)
// / \ / \
// A B B R
fn skew(&self) -> FloatBandLink {
if let Some(ref this) = self.0 {
if let Some(ref left) = this.left.0 {
if this.level == left.level {
return FloatBandLink(Some(Arc::new(FloatBandNode {
level: this.level,
left: left.left.clone(),
band: left.band.clone(),
right: FloatBandLink(Some(Arc::new(FloatBandNode {
level: this.level,
left: left.right.clone(),
band: this.band.clone(),
right: this.right.clone(),
}))),
})));
}
}
}
(*self).clone()
}
// Corrects tree balance:
//
// T R
// / \ / \
// A R → T X if level(T) = level(X)
// / \ / \
// B X A B
fn split(&self) -> FloatBandLink {
if let Some(ref this) = self.0 {
if let Some(ref right) = this.right.0 {
if let Some(ref right_right) = right.right.0 {
if this.level == right_right.level {
return FloatBandLink(Some(Arc::new(FloatBandNode {
level: this.level + 1,
left: FloatBandLink(Some(Arc::new(FloatBandNode {
level: this.level,
left: this.left.clone(),
band: this.band.clone(),
right: right.left.clone(),
}))),
band: right.band.clone(),
right: right.right.clone(),
})));
}
}
}
}
(*self).clone()
}
}
impl Debug for FloatFragment {
fn fmt(&self, formatter: &mut Formatter) -> FmtResult {
write!(formatter, "FloatFragment")
}
}
// Float boxes
impl FloatBox {
/// Creates a new float box.
pub fn construct<'dom>(
context: &LayoutContext,
info: &NodeAndStyleInfo<impl NodeExt<'dom>>,
display_inside: DisplayInside,
contents: Contents,
) -> Self {
Self {
contents: IndependentFormattingContext::construct(
context,
info,
display_inside,
contents,
// Text decorations are not propagated to any out-of-flow descendants
TextDecorationLine::NONE,
),
}
}
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();
}
}