/* 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 http://mozilla.org/MPL/2.0/. */ use app_units::{Au, MAX_AU}; use block::FormattingContextType; use flow::{self, CLEARS_LEFT, CLEARS_RIGHT, Flow, ImmutableFlowUtils}; use persistent_list::PersistentList; use std::cmp::{max, min}; use std::fmt; use style::computed_values::float; use style::logical_geometry::{LogicalRect, LogicalSize, WritingMode}; use style::values::computed::LengthOrPercentageOrAuto; /// The kind of float: left or right. #[derive(Clone, Copy, Debug, Serialize)] pub enum FloatKind { Left, Right } impl FloatKind { pub fn from_property(property: float::T) -> Option { match property { float::T::none => None, float::T::left => Some(FloatKind::Left), float::T::right => Some(FloatKind::Right), } } } /// The kind of clearance: left, right, or both. #[derive(Clone, Copy)] pub enum ClearType { Left, Right, Both, } /// Information about a single float. #[derive(Clone, Copy)] struct Float { /// The boundaries of this float. bounds: LogicalRect, /// The kind of float: left or right. kind: FloatKind, } impl fmt::Debug for Float { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "bounds={:?} kind={:?}", self.bounds, self.kind) } } /// Information about the floats next to a flow. #[derive(Clone)] struct FloatList { /// Information about each of the floats here. floats: PersistentList, /// Cached copy of the maximum block-start offset of the float. max_block_start: Option, } impl FloatList { fn new() -> FloatList { FloatList { floats: PersistentList::new(), max_block_start: None, } } /// Returns true if the list is allocated and false otherwise. If false, there are guaranteed /// not to be any floats. fn is_present(&self) -> bool { self.floats.len() > 0 } } impl fmt::Debug for FloatList { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "max_block_start={:?} floats={}", self.max_block_start, self.floats.len())?; for float in self.floats.iter() { write!(f, " {:?}", float)?; } Ok(()) } } /// All the information necessary to place a float. pub struct PlacementInfo { /// The dimensions of the float. pub size: LogicalSize, /// The minimum block-start of the float, as determined by earlier elements. pub ceiling: Au, /// The maximum inline-end position of the float, generally determined by the containing block. pub max_inline_size: Au, /// The kind of float. pub kind: FloatKind } impl fmt::Debug for PlacementInfo { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "size={:?} ceiling={:?} max_inline_size={:?} kind={:?}", self.size, self.ceiling, self.max_inline_size, self.kind) } } fn range_intersect(block_start_1: Au, block_end_1: Au, block_start_2: Au, block_end_2: Au) -> (Au, Au) { (max(block_start_1, block_start_2), min(block_end_1, block_end_2)) } /// Encapsulates information about floats. This is optimized to avoid allocation if there are /// no floats, and to avoid copying when translating the list of floats downward. #[derive(Clone)] pub struct Floats { /// The list of floats. list: FloatList, /// The offset of the flow relative to the first float. offset: LogicalSize, /// The writing mode of these floats. pub writing_mode: WritingMode, } impl fmt::Debug for Floats { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if !self.list.is_present() { write!(f, "[empty]") } else { write!(f, "offset={:?} floats={:?}", self.offset, self.list) } } } impl Floats { /// Creates a new `Floats` object. pub fn new(writing_mode: WritingMode) -> Floats { Floats { list: FloatList::new(), offset: LogicalSize::zero(writing_mode), writing_mode: writing_mode, } } /// Adjusts the recorded offset of the flow relative to the first float. pub fn translate(&mut self, delta: LogicalSize) { self.offset = self.offset + delta } /// Returns the position of the last float in flow coordinates. pub fn last_float_pos(&self) -> Option> { match self.list.floats.front() { None => None, Some(float) => Some(float.bounds.translate_by_size(self.offset)), } } /// Returns a rectangle that encloses the region from block-start to block-start + block-size, /// with inline-size small enough that it doesn't collide with any floats. max_x is the /// inline-size beyond which floats have no effect. (Generally this is the containing block /// inline-size.) pub fn available_rect(&self, block_start: Au, block_size: Au, max_x: Au) -> Option> { let list = &self.list; let block_start = block_start - self.offset.block; debug!("available_rect: trying to find space at {:?}", block_start); // Relevant dimensions for the inline-end-most inline-start float let mut max_inline_start = Au(0) - self.offset.inline; let mut l_block_start = None; let mut l_block_end = None; // Relevant dimensions for the inline-start-most inline-end float let mut min_inline_end = max_x - self.offset.inline; let mut r_block_start = None; let mut r_block_end = None; // Find the float collisions for the given range in the block direction. for float in list.floats.iter() { debug!("available_rect: Checking for collision against float"); let float_pos = float.bounds.start; let float_size = float.bounds.size; debug!("float_pos: {:?}, float_size: {:?}", float_pos, float_size); match float.kind { FloatKind::Left if float_pos.i + float_size.inline > max_inline_start && float_pos.b + float_size.block > block_start && float_pos.b < block_start + block_size => { max_inline_start = float_pos.i + float_size.inline; l_block_start = Some(float_pos.b); l_block_end = Some(float_pos.b + float_size.block); debug!("available_rect: collision with inline_start float: new \ max_inline_start is {:?}", max_inline_start); } FloatKind::Right if float_pos.i < min_inline_end && float_pos.b + float_size.block > block_start && float_pos.b < block_start + block_size => { min_inline_end = float_pos.i; r_block_start = Some(float_pos.b); r_block_end = Some(float_pos.b + float_size.block); debug!("available_rect: collision with inline_end float: new min_inline_end \ is {:?}", min_inline_end); } FloatKind::Left | FloatKind::Right => {} } } // Extend the vertical range of the rectangle to the closest floats. // If there are floats on both sides, take the intersection of the // two areas. Also make sure we never return a block-start smaller than the // given upper bound. let (block_start, block_end) = match (r_block_start, r_block_end, l_block_start, l_block_end) { (Some(r_block_start), Some(r_block_end), Some(l_block_start), Some(l_block_end)) => { range_intersect(max(block_start, r_block_start), r_block_end, max(block_start, l_block_start), l_block_end) } (None, None, Some(l_block_start), Some(l_block_end)) => { (max(block_start, l_block_start), l_block_end) } (Some(r_block_start), Some(r_block_end), None, None) => { (max(block_start, r_block_start), r_block_end) } (None, None, None, None) => return None, _ => panic!("Reached unreachable state when computing float area") }; // FIXME(eatkinson): This assertion is too strong and fails in some cases. It is OK to // return negative inline-sizes since we check against that inline-end away, but we should // still understand why they occur and add a stronger assertion here. // assert!(max_inline-start < min_inline-end); assert!(block_start <= block_end, "Float position error"); Some(LogicalRect::new(self.writing_mode, max_inline_start + self.offset.inline, block_start + self.offset.block, min_inline_end - max_inline_start, block_end - block_start)) } /// Adds a new float to the list. pub fn add_float(&mut self, info: &PlacementInfo) { let new_info = PlacementInfo { size: info.size, ceiling: match self.list.max_block_start { None => info.ceiling, Some(max_block_start) => max(info.ceiling, max_block_start + self.offset.block), }, max_inline_size: info.max_inline_size, kind: info.kind }; debug!("add_float: added float with info {:?}", new_info); let new_float = Float { bounds: LogicalRect::from_point_size( self.writing_mode, self.place_between_floats(&new_info).start - self.offset, info.size, ), kind: info.kind }; self.list.floats = self.list.floats.prepend_elem(new_float); self.list.max_block_start = match self.list.max_block_start { None => Some(new_float.bounds.start.b), Some(max_block_start) => Some(max(max_block_start, new_float.bounds.start.b)), } } /// Given the three sides of the bounding rectangle in the block-start direction, finds the /// largest block-size that will result in the rectangle not colliding with any floats. Returns /// `None` if that block-size is infinite. fn max_block_size_for_bounds(&self, inline_start: Au, block_start: Au, inline_size: Au) -> Option { let list = &self.list; let block_start = block_start - self.offset.block; let inline_start = inline_start - self.offset.inline; let mut max_block_size = None; for float in list.floats.iter() { if float.bounds.start.b + float.bounds.size.block > block_start && float.bounds.start.i + float.bounds.size.inline > inline_start && float.bounds.start.i < inline_start + inline_size { let new_y = float.bounds.start.b; max_block_size = Some(min(max_block_size.unwrap_or(new_y), new_y)); } } max_block_size.map(|h| h + self.offset.block) } /// Given placement information, finds the closest place a fragment can be positioned without /// colliding with any floats. pub fn place_between_floats(&self, info: &PlacementInfo) -> LogicalRect { debug!("place_between_floats: Placing object with {:?}", info.size); // If no floats, use this fast path. if !self.list.is_present() { match info.kind { FloatKind::Left => { return LogicalRect::new( self.writing_mode, Au(0), info.ceiling, info.max_inline_size, MAX_AU) } FloatKind::Right => { return LogicalRect::new( self.writing_mode, info.max_inline_size - info.size.inline, info.ceiling, info.max_inline_size, MAX_AU) } } } // Can't go any higher than previous floats or previous elements in the document. let mut float_b = info.ceiling; loop { let maybe_location = self.available_rect(float_b, info.size.block, info.max_inline_size); debug!("place_float: got available rect: {:?} for block-pos: {:?}", maybe_location, float_b); match maybe_location { // If there are no floats blocking us, return the current location // TODO(eatkinson): integrate with overflow None => { return match info.kind { FloatKind::Left => { LogicalRect::new( self.writing_mode, Au(0), float_b, info.max_inline_size, MAX_AU) } FloatKind::Right => { LogicalRect::new( self.writing_mode, info.max_inline_size - info.size.inline, float_b, info.max_inline_size, MAX_AU) } } } Some(rect) => { assert!(rect.start.b + rect.size.block != float_b, "Non-terminating float placement"); // Place here if there is enough room if rect.size.inline >= info.size.inline { let block_size = self.max_block_size_for_bounds(rect.start.i, rect.start.b, rect.size.inline); let block_size = block_size.unwrap_or(MAX_AU); return match info.kind { FloatKind::Left => { LogicalRect::new( self.writing_mode, rect.start.i, float_b, rect.size.inline, block_size) } FloatKind::Right => { LogicalRect::new( self.writing_mode, rect.start.i + rect.size.inline - info.size.inline, float_b, rect.size.inline, block_size) } } } // Try to place at the next-lowest location. // Need to be careful of fencepost errors. float_b = rect.start.b + rect.size.block; } } } } pub fn clearance(&self, clear: ClearType) -> Au { let list = &self.list; let mut clearance = Au(0); for float in list.floats.iter() { match (clear, float.kind) { (ClearType::Left, FloatKind::Left) | (ClearType::Right, FloatKind::Right) | (ClearType::Both, _) => { let b = self.offset.block + float.bounds.start.b + float.bounds.size.block; clearance = max(clearance, b); } _ => {} } } clearance } pub fn is_present(&self) -> bool { self.list.is_present() } } /// The speculated inline sizes of floats flowing through or around a flow (depending on whether /// the flow is a block formatting context). These speculations are always *upper bounds*; the /// actual inline sizes might be less. Note that this implies that a speculated value of zero is a /// guarantee that there will be no floats on that side. /// /// This is used for two purposes: (a) determining whether we can lay out blocks in parallel; (b) /// guessing the inline-sizes of block formatting contexts in an effort to lay them out in /// parallel. #[derive(Clone, Copy)] pub struct SpeculatedFloatPlacement { /// The estimated inline size (an upper bound) of the left floats flowing through this flow. pub left: Au, /// The estimated inline size (an upper bound) of the right floats flowing through this flow. pub right: Au, } impl fmt::Debug for SpeculatedFloatPlacement { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "L {:?} R {:?}", self.left, self.right) } } impl SpeculatedFloatPlacement { /// Returns a `SpeculatedFloatPlacement` objects with both left and right speculated inline /// sizes initialized to zero. pub fn zero() -> SpeculatedFloatPlacement { SpeculatedFloatPlacement { left: Au(0), right: Au(0), } } /// Given the speculated inline size of the floats out for the inorder predecessor of this /// flow, computes the speculated inline size of the floats flowing in. pub fn compute_floats_in(&mut self, flow: &mut Flow) { let base_flow = flow::base(flow); if base_flow.flags.contains(CLEARS_LEFT) { self.left = Au(0) } if base_flow.flags.contains(CLEARS_RIGHT) { self.right = Au(0) } } /// Given the speculated inline size of the floats out for this flow's last child, computes the /// speculated inline size of the floats out for this flow. pub fn compute_floats_out(&mut self, flow: &mut Flow) { if flow.is_block_like() { let block_flow = flow.as_block(); if block_flow.formatting_context_type() != FormattingContextType::None { *self = block_flow.base.speculated_float_placement_in; } else { if self.left > Au(0) || self.right > Au(0) { let speculated_inline_content_edge_offsets = block_flow.fragment.guess_inline_content_edge_offsets(); if self.left > Au(0) && speculated_inline_content_edge_offsets.start > Au(0) { self.left = self.left + speculated_inline_content_edge_offsets.start } if self.right > Au(0) && speculated_inline_content_edge_offsets.end > Au(0) { self.right = self.right + speculated_inline_content_edge_offsets.end } } self.left = max(self.left, block_flow.base.speculated_float_placement_in.left); self.right = max(self.right, block_flow.base.speculated_float_placement_in.right); } } let base_flow = flow::base(flow); if !base_flow.flags.is_float() { return } let mut float_inline_size = base_flow.intrinsic_inline_sizes.preferred_inline_size; if float_inline_size == Au(0) { if flow.is_block_like() { // Hack: If the size of the float is a percentage, then there's no way we can guess // at its size now. So just pick an arbitrary nonzero value (in this case, 1px) so // that the layout traversal logic will know that objects later in the document // might flow around this float. if let LengthOrPercentageOrAuto::Percentage(percentage) = flow.as_block().fragment.style.content_inline_size() { if percentage.0 > 0.0 { float_inline_size = Au::from_px(1) } } } } match base_flow.flags.float_kind() { float::T::none => {} float::T::left => self.left = self.left + float_inline_size, float::T::right => self.right = self.right + float_inline_size, } } /// Given a flow, computes the speculated inline size of the floats in of its first child. pub fn compute_floats_in_for_first_child(parent_flow: &mut Flow) -> SpeculatedFloatPlacement { if !parent_flow.is_block_like() { return flow::base(parent_flow).speculated_float_placement_in } let parent_block_flow = parent_flow.as_block(); if parent_block_flow.formatting_context_type() != FormattingContextType::None { return SpeculatedFloatPlacement::zero() } let mut placement = parent_block_flow.base.speculated_float_placement_in; let speculated_inline_content_edge_offsets = parent_block_flow.fragment.guess_inline_content_edge_offsets(); if speculated_inline_content_edge_offsets.start > Au(0) { placement.left = if placement.left > speculated_inline_content_edge_offsets.start { placement.left - speculated_inline_content_edge_offsets.start } else { Au(0) } } if speculated_inline_content_edge_offsets.end > Au(0) { placement.right = if placement.right > speculated_inline_content_edge_offsets.end { placement.right - speculated_inline_content_edge_offsets.end } else { Au(0) } } placement } }