Place replaced and non-auto inline size independent FCs next to floats (#29977)

* Place replaced and non-auto inline size independent FCs next to floats

The CSS2 specification says that replaced content and independent
formatting contexts should be placed next to floats. This change adds
support for that, but punts on support for independent formatting
contexts that have an auto inline size. With an auto inline size, we
which requires a much more complex layout algorithm.

Co-authored-by: Oriol Brufau <obrufau@igalia.com>

* Fix issue with where last band was taken into account for inline size

* adjustment_from_floats should prevent margin collapse

* Properly handle elements with 0 height

---------

Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Martin Robinson 2023-07-18 20:43:45 +02:00 committed by GitHub
parent 64adc98e64
commit ae3f33b9d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 421 additions and 131 deletions

View file

@ -17,6 +17,7 @@ use crate::style_ext::{ComputedValuesExt, DisplayInside};
use crate::ContainingBlock;
use euclid::num::Zero;
use servo_arc::Arc;
use std::collections::VecDeque;
use std::fmt::{Debug, Formatter, Result as FmtResult};
use std::ops::Range;
use std::{f32, mem};
@ -70,6 +71,113 @@ impl ContainingBlockPositionInfo {
}
}
/// This data strucure is used to try to place non-floating content among float content.
/// This is used primarily to place replaced content and independent formatting contexts
/// next to floats, as the specifcation dictates.
pub(crate) struct PlacementAmongFloats<'a> {
/// The [FloatContext] to use for this placement.
float_context: &'a FloatContext,
/// The current bands we are considering for this placement.
current_bands: VecDeque<FloatBand>,
/// The next band, needed to know the height of the last band in current_bands.
next_band: FloatBand,
/// The size of the object to place.
object_size: Vec2<Length>,
/// The minimum position in the block direction for the placement. Objects should not
/// be placed before this point.
ceiling: Length,
}
impl<'a> PlacementAmongFloats<'a> {
pub(crate) fn new(
float_context: &'a FloatContext,
ceiling: Length,
object_size: Vec2<Length>,
) -> Self {
assert!(!ceiling.px().is_infinite());
let current_band = float_context.bands.find(ceiling).unwrap();
let current_bands = VecDeque::from([current_band]);
let next_band = float_context.bands.find_next(current_band.top).unwrap();
PlacementAmongFloats {
float_context,
current_bands,
next_band,
object_size,
ceiling,
}
}
fn top_of_placement_for_current_bands(&self) -> Length {
self.ceiling.max(self.current_bands.front().unwrap().top)
}
fn current_bands_height(&self) -> Length {
assert!(!self.current_bands.is_empty());
self.next_band.top - self.top_of_placement_for_current_bands()
}
fn accumulate_enough_bands_for_block_size(&mut self) {
while self.current_bands_height() < self.object_size.block {
assert!(!self.next_band.top.px().is_infinite());
self.current_bands.push_back(self.next_band);
self.next_band = self
.float_context
.bands
.find_next(self.next_band.top)
.unwrap();
}
}
fn calculate_viable_inline_space(&self) -> (Length, Length) {
let mut max_inline_start = self.float_context.containing_block_info.inline_start;
let mut min_inline_end = self.float_context.containing_block_info.inline_end;
assert!(!self.current_bands.is_empty());
for band in self.current_bands.iter() {
if let Some(left) = band.left {
max_inline_start = max_inline_start.max(left);
}
if let Some(right) = band.right {
min_inline_end = min_inline_end.min(right);
}
}
return (max_inline_start, min_inline_end);
}
pub(crate) fn try_place_once(&mut self) -> Option<Vec2<Length>> {
self.accumulate_enough_bands_for_block_size();
let (inline_start, inline_end) = self.calculate_viable_inline_space();
if inline_end - inline_start >= self.object_size.inline {
return Some(Vec2 {
inline: inline_start,
block: self.top_of_placement_for_current_bands(),
});
}
self.current_bands.pop_front();
None
}
/// Run the placement algorithm for this [PlacementAmongFloats].
pub(crate) fn place(&mut self) -> Vec2<Length> {
while self.current_bands.len() > 0 {
if let Some(result) = self.try_place_once() {
return result;
}
}
// We could not fit the object in among the floats, so we place it as if it
// cleared all floats.
return Vec2 {
inline: self.float_context.containing_block_info.inline_start,
block: self
.ceiling
.max(self.float_context.clear_left_position)
.max(self.float_context.clear_right_position),
};
}
}
/// 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,
@ -133,13 +241,12 @@ 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> {
pub(crate) fn place_object(&self, object: &PlacementInfo, ceiling: Length) -> 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
ClearSide::None => ceiling,
ClearSide::Left => ceiling.max(self.clear_left_position),
ClearSide::Right => ceiling.max(self.clear_right_position),
ClearSide::Both => ceiling
.max(self.clear_left_position)
.max(self.clear_right_position),
};
@ -163,7 +270,7 @@ impl FloatContext {
};
Vec2 {
inline: left_object_edge,
block: first_band.top.max(self.ceiling),
block: first_band.top.max(ceiling),
}
},
FloatSide::Right => {
@ -173,16 +280,16 @@ impl FloatContext {
};
Vec2 {
inline: right_object_edge - object.size.inline,
block: first_band.top.max(self.ceiling),
block: first_band.top.max(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> {
pub(crate) 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_origin = self.place_object(&new_float, self.ceiling);
let new_float_extent = match new_float.side {
FloatSide::Left => new_float_origin.inline + new_float.size.inline,
FloatSide::Right => new_float_origin.inline,
@ -242,7 +349,7 @@ impl FloatContext {
/// Information needed to place an object so that it doesn't collide with existing floats.
#[derive(Clone, Debug)]
pub struct PlacementInfo {
pub(crate) struct PlacementInfo {
/// The *margin* box size of the object.
pub size: Vec2<Length>,
/// Whether the object is (logically) aligned to the left or right.
@ -909,7 +1016,7 @@ impl SequentialLayoutState {
&mut self,
box_fragment: &mut BoxFragment,
margins_collapsing_with_parent_containing_block: CollapsedMargin,
block_offset_from_containining_block_top: CSSPixelLength,
block_offset_from_containing_block_top: Length,
) {
let block_start_of_containing_block_in_bfc = self.floats.containing_block_info.block_start +
self.floats
@ -919,7 +1026,7 @@ impl SequentialLayoutState {
.solve();
self.floats.lower_ceiling(
block_start_of_containing_block_in_bfc + block_offset_from_containining_block_top,
block_start_of_containing_block_in_bfc + block_offset_from_containing_block_top,
);
let pbm_sums = &(&box_fragment.padding + &box_fragment.border) + &box_fragment.margin;