use core::ops::Range;
use crate::{debug::debug_log, sys::Vec, AvailableSpace, Clear, Direction, FloatDirection, Point, Size};
#[derive(Debug, Clone, Copy, Default)]
pub struct ContentSlot {
pub segment_id: Option<usize>,
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct BfcSlot {
pub segment_id: Option<usize>,
pub x: f32,
pub y: f32,
pub border_width: f32,
pub stretch_width: f32,
}
#[derive(Debug, Clone, Default)]
pub struct PlacedFloatedBox {
pub width: f32,
pub height: f32,
pub x_inset: f32,
pub y: f32,
}
#[derive(Debug, Clone)]
struct Segment {
y: Range<f32>,
insets: [f32; 2],
has_float: [bool; 2],
}
impl Segment {
fn fits_float_width(
&self,
floated_box: Size<f32>,
direction: FloatDirection,
bfc_width: f32,
cb_insets: [f32; 2],
) -> bool {
float_fits_horizontally(floated_box.width, direction, bfc_width, self.insets, cb_insets)
}
}
fn float_fits_horizontally(
width: f32,
direction: FloatDirection,
bfc_width: f32,
float_insets: [f32; 2],
cb_insets: [f32; 2],
) -> bool {
let lead = direction as usize;
let trail = 1 - lead;
let x_inset = float_insets[lead].max(cb_insets[lead]);
let fits_opposite_floats = float_insets[trail] == 0.0 || x_inset + width <= bfc_width - float_insets[trail];
let fits_containing_block = float_insets[lead] == 0.0 || x_inset + width <= bfc_width - cb_insets[trail];
fits_opposite_floats && fits_containing_block
}
#[derive(Debug, Clone)]
struct FloatFitter {
bfc_width: f32,
slot_height: f64,
float_insets: [f32; 2],
cb_insets: [f32; 2],
}
impl FloatFitter {
fn new(bfc_width: f32, slot_height: f32, cb_insets: [f32; 2]) -> Self {
Self { bfc_width, slot_height: slot_height as f64, float_insets: [0.0, 0.0], cb_insets }
}
fn union_insets(&mut self, insets: [f32; 2]) {
self.float_insets[0] = self.float_insets[0].max(insets[0]);
self.float_insets[1] = self.float_insets[1].max(insets[1]);
}
fn placed_inset(&self, direction: FloatDirection) -> f32 {
let lead = direction as usize;
self.float_insets[lead].max(self.cb_insets[lead])
}
fn fits_horiontally(&self, width: f32, direction: FloatDirection) -> bool {
float_fits_horizontally(width, direction, self.bfc_width, self.float_insets, self.cb_insets)
}
fn add_height(&mut self, height: f32) {
self.slot_height += height as f64;
}
fn fits_vertically(&mut self, height: f32) -> bool {
self.slot_height >= height as f64
}
}
#[derive(Debug, Clone)]
pub struct FloatContext {
available_width: f32,
has_floats: bool,
left_floats: Vec<PlacedFloatedBox>,
right_floats: Vec<PlacedFloatedBox>,
segments: Vec<Segment>,
last_placed_floats: [Range<usize>; 2],
clear_bottoms: [Option<f32>; 2],
float_ceiling: Option<f32>,
}
impl Default for FloatContext {
fn default() -> Self {
Self {
available_width: 0.0,
has_floats: false,
left_floats: Vec::new(),
right_floats: Vec::new(),
segments: Vec::new(),
last_placed_floats: [0..0, 0..0], clear_bottoms: [None, None],
float_ceiling: None,
}
}
}
impl FloatContext {
pub fn new() -> Self {
Default::default()
}
#[inline(always)]
pub fn has_floats(&self) -> bool {
self.has_floats
}
#[inline(always)]
pub fn has_active_floats(&self, min_y: f32) -> bool {
self.has_floats && self.segments.last().map(|seg| seg.y.end).unwrap_or(0.0) > min_y
}
pub fn set_width(&mut self, available_width: f32) {
self.available_width = available_width;
}
pub fn left_floats(&self) -> &[PlacedFloatedBox] {
&self.left_floats
}
pub fn right_floats(&self) -> &[PlacedFloatedBox] {
&self.right_floats
}
fn subdivide_segment(&mut self, idx: usize, divide_at_y: f32) {
let old_segment = &mut self.segments[idx];
let new_segment =
Segment { insets: old_segment.insets, has_float: old_segment.has_float, y: divide_at_y..old_segment.y.end };
if !old_segment.y.contains(÷_at_y) || old_segment.y.start == divide_at_y {
debug_log!("old_segment", dbg:&mut *old_segment);
debug_log!("divide_at_y", dbg:divide_at_y);
assert!(old_segment.y.contains(÷_at_y) && old_segment.y.start != divide_at_y);
}
old_segment.y.end = divide_at_y;
self.segments.splice((idx + 1)..(idx + 1), core::iter::once(new_segment));
}
fn update_last_placed_float(&mut self, direction: FloatDirection, placement: Range<usize>) {
let slot = direction as usize;
self.last_placed_floats[slot].start = self.last_placed_floats[slot].start.max(placement.start);
self.last_placed_floats[slot].end = self.last_placed_floats[slot].end.max(placement.end);
}
pub fn place_floated_box(
&mut self,
floated_box: Size<f32>,
min_y: f32,
containing_block_insets: [f32; 2],
direction: FloatDirection,
clear: Clear,
) -> Point<f32> {
self.has_floats = true;
let placed_floated_box =
self.place_floated_box_inner(floated_box, min_y, containing_block_insets, direction, clear);
let slot = direction as usize;
let bottom = placed_floated_box.y + placed_floated_box.height;
self.clear_bottoms[slot] = Some(self.clear_bottoms[slot].map_or(bottom, |b| b.max(bottom)));
let y = placed_floated_box.y;
self.float_ceiling = Some(self.float_ceiling.map_or(y, |ceiling| ceiling.max(y)));
let x_inset = placed_floated_box.x_inset;
let y = placed_floated_box.y;
match direction {
FloatDirection::Left => {
self.left_floats.push(placed_floated_box);
Point { x: x_inset, y }
}
FloatDirection::Right => {
self.right_floats.push(placed_floated_box);
Point { x: self.available_width - x_inset - floated_box.width, y }
}
}
}
fn place_floated_box_inner(
&mut self,
floated_box: Size<f32>,
min_y: f32,
containing_block_insets: [f32; 2],
direction: FloatDirection,
clear: Clear,
) -> PlacedFloatedBox {
let slot = direction as usize;
let min_y = min_y
.max(self.float_ceiling.unwrap_or(f32::NEG_INFINITY))
.max(self.cleared_threshold(clear).unwrap_or(f32::NEG_INFINITY));
let float_start = self.last_placed_floats[0].start.max(self.last_placed_floats[1].start);
let hwm = match clear {
Clear::Left => {
let left_end = self.last_placed_floats[0].end;
float_start.max(left_end + 1)
}
Clear::Right => {
let right_end = self.last_placed_floats[1].end;
float_start.max(right_end + 1)
}
Clear::Both => {
let left_end = self.last_placed_floats[0].end;
let right_end = self.last_placed_floats[1].end;
left_end.max(right_end) + 1
}
Clear::None => float_start,
};
let start_idx = self
.segments
.get(hwm..)
.and_then(|segments| segments.iter().position(|segment| segment.y.end > min_y).map(|idx| idx + hwm));
let mut start_idx = start_idx.unwrap_or(self.segments.len());
let mut start_y = min_y;
let mut end_idx = start_idx;
let (start, mut end, placed_inset) = 'outer: loop {
let Some(start_segment) = self.segments.get(start_idx) else {
break (None, None, containing_block_insets[slot]);
};
if !start_segment.fits_float_width(floated_box, direction, self.available_width, containing_block_insets) {
start_idx += 1;
end_idx = end_idx.max(start_idx);
continue;
}
start_y = start_y.max(start_segment.y.start);
let available_height = start_segment.y.end - start_y;
let mut fitter = FloatFitter::new(self.available_width, available_height, containing_block_insets);
fitter.union_insets(start_segment.insets);
loop {
let Some(end_segment) = self.segments.get(end_idx) else {
let inset = fitter.placed_inset(direction);
break 'outer (Some(start_idx), None, inset);
};
fitter.union_insets(end_segment.insets);
if !fitter.fits_horiontally(floated_box.width, direction) {
start_idx += 1;
end_idx = end_idx.max(start_idx);
continue 'outer;
}
if end_idx != start_idx {
fitter.add_height(end_segment.y.end - end_segment.y.start);
}
if !fitter.fits_vertically(floated_box.height) {
end_idx += 1;
continue;
}
let inset = fitter.placed_inset(direction);
break 'outer (Some(start_idx), Some(end_idx), inset);
}
};
if floated_box.height == 0.0 {
return PlacedFloatedBox {
width: floated_box.width,
height: floated_box.height,
y: start_y,
x_inset: placed_inset,
};
}
if start.is_none() {
let last_y_end = self.segments.last().map(|seg| seg.y.end).unwrap_or(0.0);
if start_y > last_y_end {
self.segments.push(Segment { y: last_y_end..start_y, insets: [0.0, 0.0], has_float: [false; 2] });
}
let start_y = last_y_end.max(start_y);
let mut insets = containing_block_insets;
insets[slot] += floated_box.width;
let mut has_float = [false; 2];
has_float[slot] = true;
self.segments.push(Segment { y: start_y..(start_y + floated_box.height), insets, has_float });
let start_idx = self.segments.len() - 1;
let end_idx = start_idx + 1;
self.update_last_placed_float(direction, start_idx..end_idx);
return PlacedFloatedBox {
width: floated_box.width,
height: floated_box.height,
y: start_y,
x_inset: containing_block_insets[slot],
};
}
let mut start_idx = start.unwrap();
if start_y != self.segments[start_idx].y.start {
self.subdivide_segment(start_idx, start_y);
start_idx += 1;
if let Some(end_idx) = end.as_mut() {
*end_idx += 1;
}
}
let end_idx = match end {
None => {
let last_y_end = self.segments.last().map(|seg| seg.y.end).unwrap_or(0.0);
if min_y > last_y_end {
self.segments.push(Segment { y: last_y_end..min_y, insets: [0.0, 0.0], has_float: [false; 2] });
}
self.segments.len() - 1
}
Some(mut end_idx) => {
let end_y = start_y + floated_box.height;
while end_idx > start_idx && end_y <= self.segments[end_idx].y.start {
end_idx -= 1;
}
if self.segments[end_idx].y.start < end_y && end_y < self.segments[end_idx].y.end {
self.subdivide_segment(end_idx, end_y);
}
end_idx
}
};
let placed_inset_plus_width = placed_inset + floated_box.width;
for segment in &mut self.segments[start_idx..=end_idx] {
segment.insets[slot] = placed_inset_plus_width;
segment.has_float[slot] = true;
}
self.update_last_placed_float(direction, start_idx..(end_idx + 1));
PlacedFloatedBox { width: floated_box.width, height: floated_box.height, y: start_y, x_inset: placed_inset }
}
fn cleared_segment(&self, clear: Clear) -> Option<usize> {
let left_end = self.last_placed_floats[0].end;
let right_end = self.last_placed_floats[1].end;
match clear {
Clear::Left if left_end > 0 => Some(left_end),
Clear::Right if right_end > 0 => Some(right_end),
Clear::Both if left_end > 0 || right_end > 0 => Some(left_end.max(right_end)),
_ => None,
}
}
pub fn cleared_threshold(&self, clear: Clear) -> Option<f32> {
match clear {
Clear::Left => self.clear_bottoms[0],
Clear::Right => self.clear_bottoms[1],
Clear::Both => match self.clear_bottoms {
[Some(l), Some(r)] => Some(l.max(r)),
[l, r] => l.or(r),
},
Clear::None => None,
}
}
pub fn find_content_slot(
&self,
min_y: f32,
containing_block_insets: [f32; 2],
clear: Clear,
after: Option<usize>,
) -> ContentSlot {
if !self.has_active_floats(min_y) {
return ContentSlot {
segment_id: None,
x: containing_block_insets[0],
y: min_y,
width: self.available_width - containing_block_insets[0] - containing_block_insets[1],
height: f32::INFINITY,
};
}
let min_y = min_y.max(self.cleared_threshold(clear).unwrap_or(f32::NEG_INFINITY));
let at_least = after.map(|idx| idx + 1).unwrap_or(0);
let hwm = at_least.max(self.cleared_segment(clear).map(|idx| idx + 1).unwrap_or(0));
let start_idx = self
.segments
.get(hwm..)
.and_then(|segments| segments.iter().position(|segment| segment.y.end > min_y).map(|idx| idx + hwm));
let start_idx = start_idx.unwrap_or(self.segments.len());
let segment = self.segments.get(start_idx);
match segment {
Some(segment) => {
let inset_left = segment.insets[0].max(containing_block_insets[0]);
let inset_right = segment.insets[1].max(containing_block_insets[1]);
ContentSlot {
segment_id: Some(start_idx),
x: inset_left,
y: segment.y.start.max(min_y),
width: self.available_width - inset_left - inset_right,
height: f32::INFINITY,
}
}
None => ContentSlot {
segment_id: None,
x: containing_block_insets[0],
y: min_y,
width: self.available_width - containing_block_insets[0] - containing_block_insets[1],
height: f32::INFINITY,
},
}
}
pub fn find_bfc_slot(
&self,
min_y: f32,
containing_block_insets: [f32; 2],
margins: [f32; 2],
direction: Direction,
clear: Clear,
after: Option<usize>,
) -> BfcSlot {
let margin_insets = [containing_block_insets[0] + margins[0], containing_block_insets[1] + margins[1]];
let no_float_width = self.available_width - margin_insets[0] - margin_insets[1];
let no_float_slot = BfcSlot {
segment_id: None,
x: margin_insets[0],
y: min_y,
border_width: no_float_width,
stretch_width: no_float_width,
};
if !self.has_active_floats(min_y) {
return no_float_slot;
}
let min_y = min_y.max(self.cleared_threshold(clear).unwrap_or(f32::NEG_INFINITY));
let at_least = after.map(|idx| idx + 1).unwrap_or(0);
let hwm = at_least.max(self.cleared_segment(clear).map(|idx| idx + 1).unwrap_or(0));
let start_idx = self
.segments
.get(hwm..)
.and_then(|segments| segments.iter().position(|segment| segment.y.end > min_y).map(|idx| idx + hwm));
let start_idx = start_idx.unwrap_or(self.segments.len());
match self.segments.get(start_idx) {
Some(segment) => {
let lead = match direction {
Direction::Ltr => 0,
Direction::Rtl => 1,
};
let trail = 1 - lead;
let has_lead_float = segment.has_float[lead];
let has_trail_float = segment.has_float[trail];
let mut fit_insets = [0.0; 2];
let mut stretch_insets = [0.0; 2];
fit_insets[lead] =
if has_lead_float { segment.insets[lead].max(margin_insets[lead]) } else { margin_insets[lead] };
stretch_insets[lead] = fit_insets[lead];
fit_insets[trail] = if has_trail_float {
segment.insets[trail].max(containing_block_insets[trail])
} else {
margin_insets[trail].min(containing_block_insets[trail])
};
stretch_insets[trail] = if has_trail_float {
segment.insets[trail].max(margin_insets[trail])
} else {
margin_insets[trail]
};
BfcSlot {
segment_id: Some(start_idx),
x: fit_insets[0],
y: segment.y.start.max(min_y),
border_width: self.available_width - fit_insets[0] - fit_insets[1],
stretch_width: self.available_width - stretch_insets[0] - stretch_insets[1],
}
}
None => BfcSlot {
y: self.segments.last().map(|segment| segment.y.end).unwrap_or(min_y).max(min_y),
..no_float_slot
},
}
}
}
pub struct FloatIntrinsicWidthCalculator {
available_width: AvailableSpace,
contribution: f32,
widest: f32,
}
impl FloatIntrinsicWidthCalculator {
pub fn new(available_width: AvailableSpace) -> Self {
Self { available_width, contribution: 0.0, widest: 0.0 }
}
pub fn add_float(&mut self, width: f32, _direction: FloatDirection, _clear: Clear) {
match self.available_width {
AvailableSpace::Definite(_) | AvailableSpace::MaxContent => self.contribution += width,
AvailableSpace::MinContent => self.contribution = self.contribution.max(width),
};
self.widest = self.widest.max(width);
}
pub fn result(&self) -> f32 {
match self.available_width {
AvailableSpace::Definite(available_width) => self.contribution.min(available_width).max(self.widest),
_ => self.contribution,
}
}
}