diff options
Diffstat (limited to 'components/layout/floats.rs')
-rw-r--r-- | components/layout/floats.rs | 439 |
1 files changed, 439 insertions, 0 deletions
diff --git a/components/layout/floats.rs b/components/layout/floats.rs new file mode 100644 index 00000000000..94017e6b3f7 --- /dev/null +++ b/components/layout/floats.rs @@ -0,0 +1,439 @@ +/* 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 servo_util::geometry::{Au, max, min}; +use servo_util::logical_geometry::WritingMode; +use servo_util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize}; +use std::i32; +use std::fmt; +use style::computed_values::float; +use sync::Arc; + +/// The kind of float: left or right. +#[deriving(Clone, Encodable)] +pub enum FloatKind { + FloatLeft, + FloatRight +} + +impl FloatKind { + pub fn from_property(property: float::T) -> FloatKind { + match property { + float::none => fail!("can't create a float type from an unfloated property"), + float::left => FloatLeft, + float::right => FloatRight, + } + } +} + +/// The kind of clearance: left, right, or both. +pub enum ClearType { + ClearLeft, + ClearRight, + ClearBoth, +} + +/// Information about a single float. +#[deriving(Clone)] +struct Float { + /// The boundaries of this float. + bounds: LogicalRect<Au>, + /// The kind of float: left or right. + kind: FloatKind, +} + +impl fmt::Show 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. +/// +/// FIXME(pcwalton): When we have fast `MutexArc`s, try removing `#[deriving(Clone)]` and wrap in a +/// mutex. +#[deriving(Clone)] +struct FloatList { + /// Information about each of the floats here. + floats: Vec<Float>, + /// Cached copy of the maximum block-start offset of the float. + max_block_start: Au, +} + +impl FloatList { + fn new() -> FloatList { + FloatList { + floats: vec!(), + max_block_start: Au(0), + } + } +} + +impl fmt::Show for FloatList { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "max_block_start={} floats={:?}", self.max_block_start, self.floats) + } +} + +/// Wraps a `FloatList` to avoid allocation in the common case of no floats. +/// +/// FIXME(pcwalton): When we have fast `MutexArc`s, try removing `CowArc` and use a mutex instead. +#[deriving(Clone)] +struct FloatListRef { + list: Option<Arc<FloatList>>, +} + +impl FloatListRef { + fn new() -> FloatListRef { + FloatListRef { + list: 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.list.is_some() + } + + #[inline] + fn get<'a>(&'a self) -> Option<&'a FloatList> { + match self.list { + None => None, + Some(ref list) => Some(&**list), + } + } + + #[allow(experimental)] + #[inline] + fn get_mut<'a>(&'a mut self) -> &'a mut FloatList { + if self.list.is_none() { + self.list = Some(Arc::new(FloatList::new())) + } + self.list.as_mut().unwrap().make_unique() + } +} + +/// All the information necessary to place a float. +pub struct PlacementInfo { + /// The dimensions of the float. + pub size: LogicalSize<Au>, + /// 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::Show 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. +#[deriving(Clone)] +pub struct Floats { + /// The list of floats. + list: FloatListRef, + /// The offset of the flow relative to the first float. + offset: LogicalSize<Au>, + pub writing_mode: WritingMode, +} + +impl fmt::Show for Floats { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.list.get() { + None => { + write!(f, "[empty]") + } + Some(list) => { + write!(f, "offset={} floats={}", self.offset, list) + } + } + } +} + +impl Floats { + /// Creates a new `Floats` object. + pub fn new(writing_mode: WritingMode) -> Floats { + Floats { + list: FloatListRef::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<Au>) { + self.offset = self.offset + delta + } + + /// Returns the position of the last float in flow coordinates. + pub fn last_float_pos(&self) -> Option<LogicalPoint<Au>> { + match self.list.get() { + None => None, + Some(list) => { + match list.floats.last() { + None => None, + Some(float) => Some(float.bounds.start + 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 x-coordinate 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<LogicalRect<Au>> { + let list = match self.list.get() { + None => return None, + Some(list) => 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 vertical range. + 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 { + FloatLeft 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); + } + FloatRight 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); + } + FloatLeft | FloatRight => {} + } + } + + // 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, + _ => fail!("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 + // undersrtand 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; + { + let list = self.list.get_mut(); + new_info = PlacementInfo { + size: info.size, + ceiling: max(info.ceiling, list.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 + }; + + let list = self.list.get_mut(); + list.floats.push(new_float); + list.max_block_start = max(list.max_block_start, new_float.bounds.start.b); + } + + /// Given the block-start 3 sides of the rectangle, 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<Au> { + let list = match self.list.get() { + None => return None, + Some(list) => 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<Au> { + debug!("place_between_floats: Placing object with {}", info.size); + + // If no floats, use this fast path. + if !self.list.is_present() { + match info.kind { + FloatLeft => { + return LogicalRect::new( + self.writing_mode, + Au(0), + info.ceiling, + info.max_inline_size, + Au(i32::MAX)) + } + FloatRight => { + return LogicalRect::new( + self.writing_mode, + info.max_inline_size - info.size.inline, + info.ceiling, + info.max_inline_size, + Au(i32::MAX)) + } + } + } + + // 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 y-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 { + FloatLeft => { + LogicalRect::new( + self.writing_mode, + Au(0), + float_b, + info.max_inline_size, + Au(i32::MAX)) + } + FloatRight => { + LogicalRect::new( + self.writing_mode, + info.max_inline_size - info.size.inline, + float_b, + info.max_inline_size, + Au(i32::MAX)) + } + } + } + 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(Au(i32::MAX)); + return match info.kind { + FloatLeft => { + LogicalRect::new( + self.writing_mode, + rect.start.i, + float_b, + rect.size.inline, + block_size) + } + FloatRight => { + 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 = match self.list.get() { + None => return Au(0), + Some(list) => list, + }; + + let mut clearance = Au(0); + for float in list.floats.iter() { + match (clear, float.kind) { + (ClearLeft, FloatLeft) | + (ClearRight, FloatRight) | + (ClearBoth, _) => { + let b = self.offset.block + float.bounds.start.b + float.bounds.size.block; + clearance = max(clearance, b); + } + _ => {} + } + } + clearance + } +} + |