layout: Rewrite the float context.

This rewrites the float context to avoid dynamic failures resulting from
`.clone()` misuse. It also renames the float context to the simpler
`Floats`. The new version is modeled on WebKit's `FloatingObjects`.
This commit is contained in:
Patrick Walton 2014-02-26 14:15:55 -08:00
parent 7ff35c0abe
commit 014cf702e4
8 changed files with 448 additions and 451 deletions

View file

@ -8,7 +8,7 @@ use layout::box_::Box;
use layout::construct::FlowConstructor; use layout::construct::FlowConstructor;
use layout::context::LayoutContext; use layout::context::LayoutContext;
use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData};
use layout::float_context::{FloatContext, PlacementInfo, Invalid, FloatType}; use layout::floats::{FloatKind, Floats, PlacementInfo};
use layout::flow::{BaseFlow, BlockFlowClass, FlowClass, Flow, ImmutableFlowUtils}; use layout::flow::{BaseFlow, BlockFlowClass, FlowClass, Flow, ImmutableFlowUtils};
use layout::flow; use layout::flow;
use layout::model::{MaybeAuto, Specified, Auto, specified_or_none, specified}; use layout::model::{MaybeAuto, Specified, Auto, specified_or_none, specified};
@ -34,17 +34,17 @@ pub struct FloatedBlockInfo {
floated_children: uint, floated_children: uint,
/// Left or right? /// Left or right?
float_type: FloatType float_kind: FloatKind,
} }
impl FloatedBlockInfo { impl FloatedBlockInfo {
pub fn new(float_type: FloatType) -> FloatedBlockInfo { pub fn new(float_kind: FloatKind) -> FloatedBlockInfo {
FloatedBlockInfo { FloatedBlockInfo {
containing_width: Au(0), containing_width: Au(0),
rel_pos: Point2D(Au(0), Au(0)), rel_pos: Point2D(Au(0), Au(0)),
index: None, index: None,
floated_children: 0, floated_children: 0,
float_type: float_type float_kind: float_kind,
} }
} }
} }
@ -68,7 +68,9 @@ pub struct BlockFlow {
} }
impl BlockFlow { impl BlockFlow {
pub fn from_node(constructor: &mut FlowConstructor, node: &ThreadSafeLayoutNode, is_fixed: bool) pub fn from_node(constructor: &mut FlowConstructor,
node: &ThreadSafeLayoutNode,
is_fixed: bool)
-> BlockFlow { -> BlockFlow {
BlockFlow { BlockFlow {
base: BaseFlow::new((*node).clone()), base: BaseFlow::new((*node).clone()),
@ -81,14 +83,14 @@ impl BlockFlow {
pub fn float_from_node(constructor: &mut FlowConstructor, pub fn float_from_node(constructor: &mut FlowConstructor,
node: &ThreadSafeLayoutNode, node: &ThreadSafeLayoutNode,
float_type: FloatType) float_kind: FloatKind)
-> BlockFlow { -> BlockFlow {
BlockFlow { BlockFlow {
base: BaseFlow::new((*node).clone()), base: BaseFlow::new((*node).clone()),
box_: Some(Box::new(constructor, node)), box_: Some(Box::new(constructor, node)),
is_root: false, is_root: false,
is_fixed: false, is_fixed: false,
float: Some(~FloatedBlockInfo::new(float_type)) float: Some(~FloatedBlockInfo::new(float_kind))
} }
} }
@ -102,13 +104,13 @@ impl BlockFlow {
} }
} }
pub fn new_float(base: BaseFlow, float_type: FloatType) -> BlockFlow { pub fn new_float(base: BaseFlow, float_kind: FloatKind) -> BlockFlow {
BlockFlow { BlockFlow {
base: base, base: base,
box_: None, box_: None,
is_root: false, is_root: false,
is_fixed: false, is_fixed: false,
float: Some(~FloatedBlockInfo::new(float_type)) float: Some(~FloatedBlockInfo::new(float_kind))
} }
} }
@ -257,13 +259,12 @@ impl BlockFlow {
let mut top_offset = Au::new(0); let mut top_offset = Au::new(0);
let mut bottom_offset = Au::new(0); let mut bottom_offset = Au::new(0);
let mut left_offset = Au::new(0); let mut left_offset = Au::new(0);
let mut float_ctx = Invalid;
for box_ in self.box_.iter() { for box_ in self.box_.iter() {
clearance = match box_.clear() { clearance = match box_.clear() {
None => Au::new(0), None => Au::new(0),
Some(clear) => { Some(clear) => {
self.base.floats_in.clearance(clear) self.base.floats.clearance(clear)
} }
}; };
@ -277,18 +278,20 @@ impl BlockFlow {
if inorder { if inorder {
// Floats for blocks work like this: // Floats for blocks work like this:
// self.floats_in -> child[0].floats_in // self.floats -> child[0].floats
// visit child[0] // visit child[0]
// child[i-1].floats_out -> child[i].floats_in // child[i-1].floats -> child[i].floats
// visit child[i] // visit child[i]
// repeat until all children are visited. // repeat until all children are visited.
// last_child.floats_out -> self.floats_out (done at the end of this method) // last_child.floats -> self.floats (done at the end of this method)
float_ctx = self.base.floats_in.translate(Point2D(-left_offset, -top_offset)); self.base.floats.translate(Point2D(-left_offset, -top_offset));
let mut floats = self.base.floats.clone();
for kid in self.base.child_iter() { for kid in self.base.child_iter() {
flow::mut_base(kid).floats_in = float_ctx.clone(); flow::mut_base(kid).floats = floats;
kid.assign_height_inorder(ctx); kid.assign_height_inorder(ctx);
float_ctx = flow::mut_base(kid).floats_out.clone(); floats = flow::mut_base(kid).floats.clone();
} }
self.base.floats = floats;
} }
// The amount of margin that we can potentially collapse with // The amount of margin that we can potentially collapse with
@ -434,9 +437,7 @@ impl BlockFlow {
if inorder { if inorder {
let extra_height = height - (cur_y - top_offset) + bottom_offset; let extra_height = height - (cur_y - top_offset) + bottom_offset;
self.base.floats_out = float_ctx.translate(Point2D(left_offset, -extra_height)); self.base.floats.translate(Point2D(left_offset, -extra_height));
} else {
self.base.floats_out = self.base.floats_in.clone();
} }
} }
@ -453,7 +454,7 @@ impl BlockFlow {
height = box_.border_box.get().size.height; height = box_.border_box.get().size.height;
clearance = match box_.clear() { clearance = match box_.clear() {
None => Au(0), None => Au(0),
Some(clear) => self.base.floats_in.clearance(clear), Some(clear) => self.base.floats.clearance(clear),
}; };
let noncontent_width = box_.padding.get().left + box_.padding.get().right + let noncontent_width = box_.padding.get().left + box_.padding.get().right +
@ -465,29 +466,33 @@ impl BlockFlow {
} }
let info = PlacementInfo { let info = PlacementInfo {
width: self.base.position.size.width + full_noncontent_width, size: Size2D(self.base.position.size.width + full_noncontent_width,
height: height + margin_height, height + margin_height),
ceiling: clearance, ceiling: clearance,
max_width: self.float.get_ref().containing_width, max_width: self.float.get_ref().containing_width,
f_type: self.float.get_ref().float_type, kind: self.float.get_ref().float_kind,
}; };
// Place the float and return the FloatContext back to the parent flow. // Place the float and return the `Floats` back to the parent flow.
// After, grab the position and use that to set our position. // After, grab the position and use that to set our position.
self.base.floats_out = self.base.floats_in.add_float(&info); self.base.floats.add_float(&info);
self.float.get_mut_ref().rel_pos = self.base.floats_out.last_float_pos();
self.float.get_mut_ref().rel_pos = self.base.floats.last_float_pos().unwrap();
} }
fn assign_height_float(&mut self, ctx: &mut LayoutContext) { fn assign_height_float(&mut self, ctx: &mut LayoutContext) {
// Now that we've determined our height, propagate that out. // Now that we've determined our height, propagate that out.
let has_inorder_children = self.base.num_floats > 0; let has_inorder_children = self.base.num_floats > 0;
if has_inorder_children { if has_inorder_children {
let mut float_ctx = FloatContext::new(self.float.get_ref().floated_children); let mut floats = Floats::new();
for kid in self.base.child_iter() { for kid in self.base.child_iter() {
flow::mut_base(kid).floats_in = float_ctx.clone(); flow::mut_base(kid).floats = floats;
kid.assign_height_inorder(ctx); kid.assign_height_inorder(ctx);
float_ctx = flow::mut_base(kid).floats_out.clone(); floats = flow::mut_base(kid).floats.clone();
} }
// Floats establish a block formatting context, so we discard the output floats here.
drop(floats);
} }
let mut cur_y = Au(0); let mut cur_y = Au(0);
let mut top_offset = Au(0); let mut top_offset = Au(0);
@ -699,7 +704,7 @@ impl Flow for BlockFlow {
debug!("Setting root position"); debug!("Setting root position");
self.base.position.origin = Au::zero_point(); self.base.position.origin = Au::zero_point();
self.base.position.size.width = ctx.screen_size.width; self.base.position.size.width = ctx.screen_size.width;
self.base.floats_in = FloatContext::new(self.base.num_floats); self.base.floats = Floats::new();
self.base.flags_info.flags.set_inorder(false); self.base.flags_info.flags.set_inorder(false);
} }
@ -788,7 +793,7 @@ impl Flow for BlockFlow {
child_base.flags_info.flags.set_inorder(has_inorder_children); child_base.flags_info.flags.set_inorder(has_inorder_children);
if !child_base.flags_info.flags.inorder() { if !child_base.flags_info.flags.inorder() {
child_base.floats_in = FloatContext::new(0); child_base.floats = Floats::new();
} }
// Per CSS 2.1 § 16.3.1, text decoration propagates to all children in flow. // Per CSS 2.1 § 16.3.1, text decoration propagates to all children in flow.

View file

@ -38,7 +38,7 @@ use css::node_style::StyledNode;
use layout::construct::FlowConstructor; use layout::construct::FlowConstructor;
use layout::context::LayoutContext; use layout::context::LayoutContext;
use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData, ToGfxColor}; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData, ToGfxColor};
use layout::float_context::{ClearType, ClearLeft, ClearRight, ClearBoth}; use layout::floats::{ClearBoth, ClearLeft, ClearRight, ClearType};
use layout::flow::{Flow, FlowFlagsInfo}; use layout::flow::{Flow, FlowFlagsInfo};
use layout::flow; use layout::flow;
use layout::model::{MaybeAuto, specified, Auto, Specified}; use layout::model::{MaybeAuto, specified, Auto, Specified};

View file

@ -26,7 +26,7 @@ use layout::box_::{Box, GenericBox, IframeBox, IframeBoxInfo, ImageBox, ImageBox
use layout::box_::{InlineInfo, InlineParentInfo, SpecificBoxInfo, UnscannedTextBox}; use layout::box_::{InlineInfo, InlineParentInfo, SpecificBoxInfo, UnscannedTextBox};
use layout::box_::{UnscannedTextBoxInfo}; use layout::box_::{UnscannedTextBoxInfo};
use layout::context::LayoutContext; use layout::context::LayoutContext;
use layout::float_context::FloatType; use layout::floats::FloatKind;
use layout::flow::{Flow, MutableOwnedFlowUtils}; use layout::flow::{Flow, MutableOwnedFlowUtils};
use layout::inline::InlineFlow; use layout::inline::InlineFlow;
use layout::text::TextRunScanner; use layout::text::TextRunScanner;
@ -418,9 +418,9 @@ impl<'a> FlowConstructor<'a> {
/// Builds the flow for a node with `float: {left|right}`. This yields a float `BlockFlow` with /// Builds the flow for a node with `float: {left|right}`. This yields a float `BlockFlow` with
/// a `BlockFlow` underneath it. /// a `BlockFlow` underneath it.
fn build_flow_for_floated_block(&mut self, node: &ThreadSafeLayoutNode, float_type: FloatType) fn build_flow_for_floated_block(&mut self, node: &ThreadSafeLayoutNode, float_kind: FloatKind)
-> ~Flow { -> ~Flow {
let mut flow = ~BlockFlow::float_from_node(self, node, float_type) as ~Flow; let mut flow = ~BlockFlow::float_from_node(self, node, float_kind) as ~Flow;
self.build_children_of_block_flow(&mut flow, node); self.build_children_of_block_flow(&mut flow, node);
flow flow
} }
@ -684,8 +684,8 @@ impl<'a> PostorderNodeMutTraversal for FlowConstructor<'a> {
// Floated flows contribute float flow construction results. // Floated flows contribute float flow construction results.
(_, float_value, _) => { (_, float_value, _) => {
let float_type = FloatType::from_property(float_value); let float_kind = FloatKind::from_property(float_value);
let flow = self.build_flow_for_floated_block(node, float_type); let flow = self.build_flow_for_floated_block(node, float_kind);
node.set_flow_construction_result(FlowConstructionResult(flow)) node.set_flow_construction_result(FlowConstructionResult(flow))
} }
} }

View file

@ -1,392 +0,0 @@
/* 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 geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
use servo_util::geometry::{Au, max, min};
use std::i32::max_value;
use std::util::replace;
use std::vec;
use style::computed_values::float;
#[deriving(Clone)]
pub enum FloatType {
FloatLeft,
FloatRight
}
impl FloatType {
pub fn from_property(property: float::T) -> FloatType {
match property {
float::none => fail!("can't create a float type from an unfloated property"),
float::left => FloatLeft,
float::right => FloatRight,
}
}
}
pub enum ClearType {
ClearLeft,
ClearRight,
ClearBoth
}
struct FloatContextBase {
/// This is an option of a vector to avoid allocation in the fast path (no floats).
float_data: Option<~[Option<FloatData>]>,
floats_used: uint,
max_y: Au,
offset: Point2D<Au>,
}
#[deriving(Clone)]
struct FloatData {
bounds: Rect<Au>,
f_type: FloatType
}
/// All information necessary to place a float
pub struct PlacementInfo {
width: Au, // The dimensions of the float
height: Au,
ceiling: Au, // The minimum top of the float, as determined by earlier elements
max_width: Au, // The maximum right of the float, generally determined by the contining block
f_type: FloatType // left or right
}
/// Wrappers around float methods. To avoid allocating data we'll never use,
/// destroy the context on modification.
pub enum FloatContext {
Invalid,
Valid(FloatContextBase)
}
impl FloatContext {
pub fn new(num_floats: uint) -> FloatContext {
Valid(FloatContextBase::new(num_floats))
}
#[inline(always)]
pub fn clone(&mut self) -> FloatContext {
match *self {
Invalid => fail!("Can't clone an invalid float context"),
Valid(_) => replace(self, Invalid)
}
}
#[inline(always)]
fn with_mut_base<R>(&mut self, callback: |&mut FloatContextBase| -> R) -> R {
match *self {
Invalid => fail!("Float context no longer available"),
Valid(ref mut base) => callback(&mut *base)
}
}
#[inline(always)]
pub fn with_base<R>(&self, callback: |&FloatContextBase| -> R) -> R {
match *self {
Invalid => fail!("Float context no longer available"),
Valid(ref base) => callback(&*base)
}
}
#[inline(always)]
pub fn translate(&mut self, trans: Point2D<Au>) -> FloatContext {
self.with_mut_base(|base| {
base.translate(trans);
});
replace(self, Invalid)
}
#[inline(always)]
pub fn available_rect(&mut self, top: Au, height: Au, max_x: Au) -> Option<Rect<Au>> {
self.with_base(|base| {
base.available_rect(top, height, max_x)
})
}
#[inline(always)]
pub fn add_float(&mut self, info: &PlacementInfo) -> FloatContext{
self.with_mut_base(|base| {
base.add_float(info);
});
replace(self, Invalid)
}
#[inline(always)]
pub fn place_between_floats(&self, info: &PlacementInfo) -> Rect<Au> {
self.with_base(|base| {
base.place_between_floats(info)
})
}
#[inline(always)]
pub fn last_float_pos(&mut self) -> Point2D<Au> {
self.with_base(|base| {
base.last_float_pos()
})
}
#[inline(always)]
pub fn clearance(&self, clear: ClearType) -> Au {
self.with_base(|base| {
base.clearance(clear)
})
}
}
impl FloatContextBase {
fn new(num_floats: uint) -> FloatContextBase {
debug!("Creating float context of size {}", num_floats);
FloatContextBase {
float_data: if num_floats == 0 {
None
} else {
Some(vec::from_elem(num_floats, None))
},
floats_used: 0,
max_y: Au(0),
offset: Point2D(Au(0), Au(0))
}
}
fn translate(&mut self, trans: Point2D<Au>) {
self.offset = self.offset + trans;
}
fn last_float_pos(&self) -> Point2D<Au> {
assert!(self.floats_used > 0, "Error: tried to access FloatContext with no floats in it");
match self.float_data.get_ref()[self.floats_used - 1] {
None => fail!("FloatContext error: floats should never be None here"),
Some(float) => {
debug!("Returning float position: {}", float.bounds.origin + self.offset);
float.bounds.origin + self.offset
}
}
}
/// Returns a rectangle that encloses the region from top to top + height,
/// with width 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 width).
fn available_rect(&self, top: Au, height: Au, max_x: Au) -> Option<Rect<Au>> {
fn range_intersect(top_1: Au, bottom_1: Au, top_2: Au, bottom_2: Au) -> (Au, Au) {
(max(top_1, top_2), min(bottom_1, bottom_2))
}
let top = top - self.offset.y;
debug!("available_rect: trying to find space at {}", top);
// Relevant dimensions for the right-most left float
let mut max_left = Au(0) - self.offset.x;
let mut l_top = None;
let mut l_bottom = None;
// Relevant dimensions for the left-most right float
let mut min_right = max_x - self.offset.x;
let mut r_top = None;
let mut r_bottom = None;
// Find the float collisions for the given vertical range.
for floats in self.float_data.iter() {
for float in floats.iter() {
debug!("available_rect: Checking for collision against float");
match *float {
None => (),
Some(data) => {
let float_pos = data.bounds.origin;
let float_size = data.bounds.size;
debug!("float_pos: {}, float_size: {}", float_pos, float_size);
match data.f_type {
FloatLeft => {
if(float_pos.x + float_size.width > max_left &&
float_pos.y + float_size.height > top && float_pos.y < top + height) {
max_left = float_pos.x + float_size.width;
l_top = Some(float_pos.y);
l_bottom = Some(float_pos.y + float_size.height);
debug!("available_rect: collision with left float: new max_left is {}",
max_left);
}
}
FloatRight => {
if(float_pos.x < min_right &&
float_pos.y + float_size.height > top && float_pos.y < top + height) {
min_right = float_pos.x;
r_top = Some(float_pos.y);
r_bottom = Some(float_pos.y + float_size.height);
debug!("available_rect: collision with right float: new min_right is {}",
min_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 top smaller than the
// given upper bound.
let (top, bottom) = match (r_top, r_bottom, l_top, l_bottom) {
(Some(r_top), Some(r_bottom), Some(l_top), Some(l_bottom)) =>
range_intersect(max(top, r_top), r_bottom, max(top, l_top), l_bottom),
(None, None, Some(l_top), Some(l_bottom)) => (max(top, l_top), l_bottom),
(Some(r_top), Some(r_bottom), None, None) => (max(top, r_top), r_bottom),
(None, None, None, None) => return None,
_ => fail!("Reached unreachable state when computing float area")
};
// This assertion is too strong and fails in some cases. It is OK to
// return negative widths since we check against that right away, but
// we should still undersrtand why they occur and add a stronger
// assertion here.
//assert!(max_left < min_right);
assert!(top <= bottom, "Float position error");
Some(Rect{
origin: Point2D(max_left, top) + self.offset,
size: Size2D(min_right - max_left, bottom - top)
})
}
fn add_float(&mut self, info: &PlacementInfo) {
assert!(self.float_data.is_some());
debug!("Floats_used: {}, Floats available: {}",
self.floats_used,
self.float_data.get_ref().len());
assert!(self.floats_used < self.float_data.get_ref().len() &&
self.float_data.get_ref()[self.floats_used].is_none());
let new_info = PlacementInfo {
width: info.width,
height: info.height,
ceiling: max(info.ceiling, self.max_y + self.offset.y),
max_width: info.max_width,
f_type: info.f_type
};
debug!("add_float: added float with info {:?}", new_info);
let new_float = FloatData {
bounds: Rect {
origin: self.place_between_floats(&new_info).origin - self.offset,
size: Size2D(info.width, info.height)
},
f_type: info.f_type
};
self.float_data.get_mut_ref()[self.floats_used] = Some(new_float);
self.max_y = max(self.max_y, new_float.bounds.origin.y);
self.floats_used += 1;
}
/// Given the top 3 sides of the rectange, finds the largest height that
/// will result in the rectange not colliding with any floats. Returns
/// None if that height is infinite.
fn max_height_for_bounds(&self, left: Au, top: Au, width: Au) -> Option<Au> {
let top = top - self.offset.y;
let left = left - self.offset.x;
let mut max_height = None;
for floats in self.float_data.iter() {
for float in floats.iter() {
match *float {
None => (),
Some(f_data) => {
if f_data.bounds.origin.y + f_data.bounds.size.height > top &&
f_data.bounds.origin.x + f_data.bounds.size.width > left &&
f_data.bounds.origin.x < left + width {
let new_y = f_data.bounds.origin.y;
max_height = Some(min(max_height.unwrap_or(new_y), new_y));
}
}
}
}
}
max_height.map(|h| h + self.offset.y)
}
/// Given necessary info, finds the closest place a box can be positioned
/// without colliding with any floats.
fn place_between_floats(&self, info: &PlacementInfo) -> Rect<Au>{
debug!("place_float: Placing float with width {} and height {}", info.width, info.height);
// Can't go any higher than previous floats or
// previous elements in the document.
let mut float_y = info.ceiling;
loop {
let maybe_location = self.available_rect(float_y, info.height, info.max_width);
debug!("place_float: Got available rect: {:?} for y-pos: {}", maybe_location, float_y);
match maybe_location {
// If there are no floats blocking us, return the current location
// TODO(eatkinson): integrate with overflow
None => return match info.f_type {
FloatLeft => Rect(Point2D(Au(0), float_y),
Size2D(info.max_width, Au(max_value))),
FloatRight => Rect(Point2D(info.max_width - info.width, float_y),
Size2D(info.max_width, Au(max_value)))
},
Some(rect) => {
assert!(rect.origin.y + rect.size.height != float_y,
"Non-terminating float placement");
// Place here if there is enough room
if (rect.size.width >= info.width) {
let height = self.max_height_for_bounds(rect.origin.x,
rect.origin.y,
rect.size.width);
let height = height.unwrap_or(Au(max_value));
return match info.f_type {
FloatLeft => Rect(Point2D(rect.origin.x, float_y),
Size2D(rect.size.width, height)),
FloatRight => {
Rect(Point2D(rect.origin.x + rect.size.width - info.width, float_y),
Size2D(rect.size.width, height))
}
};
}
// Try to place at the next-lowest location.
// Need to be careful of fencepost errors.
float_y = rect.origin.y + rect.size.height;
}
}
}
}
fn clearance(&self, clear: ClearType) -> Au {
let mut clearance = Au(0);
for floats in self.float_data.iter() {
for float in floats.iter() {
match *float {
None => (),
Some(f_data) => {
match (clear, f_data.f_type) {
(ClearLeft, FloatLeft) |
(ClearRight, FloatRight) |
(ClearBoth, _) => {
clearance = max(
clearance,
self.offset.y + f_data.bounds.origin.y + f_data.bounds.size.height);
}
_ => ()
}
}
}
}
}
clearance
}
}

View file

@ -0,0 +1,383 @@
/* 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 geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
use servo_util::cowarc::CowArc;
use servo_util::geometry::{Au, max, min};
use std::i32;
use style::computed_values::float;
/// The kind of float: left or right.
#[deriving(Clone)]
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: Rect<Au>,
/// The kind of float: left or right.
kind: FloatKind,
}
/// 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: ~[Float],
/// Cached copy of the maximum top offset of the float.
max_top: Au,
}
impl FloatList {
fn new() -> FloatList {
FloatList {
floats: ~[],
max_top: Au(0),
}
}
}
/// 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<CowArc<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.get()),
}
}
#[inline]
fn get_mut<'a>(&'a mut self) -> &'a mut FloatList {
if self.list.is_none() {
self.list = Some(CowArc::new(FloatList::new()))
}
self.list.as_mut().unwrap().get_mut()
}
}
/// All the information necessary to place a float.
pub struct PlacementInfo {
/// The dimensions of the float.
size: Size2D<Au>,
/// The minimum top of the float, as determined by earlier elements.
ceiling: Au,
/// The maximum right position of the float, generally determined by the containing block.
max_width: Au,
/// The kind of float.
kind: FloatKind
}
fn range_intersect(top_1: Au, bottom_1: Au, top_2: Au, bottom_2: Au) -> (Au, Au) {
(max(top_1, top_2), min(bottom_1, bottom_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.
priv list: FloatListRef,
/// The offset of the flow relative to the first float.
priv offset: Point2D<Au>,
}
impl Floats {
/// Creates a new `Floats` object.
pub fn new() -> Floats {
Floats {
list: FloatListRef::new(),
offset: Point2D(Au(0), Au(0)),
}
}
/// Adjusts the recorded offset of the flow relative to the first float.
pub fn translate(&mut self, delta: Point2D<Au>) {
self.offset = self.offset + delta
}
/// Returns the position of the last float in flow coordinates.
pub fn last_float_pos(&self) -> Option<Point2D<Au>> {
match self.list.get() {
None => None,
Some(list) => {
match list.floats.last_opt() {
None => None,
Some(float) => Some(float.bounds.origin + self.offset),
}
}
}
}
/// Returns a rectangle that encloses the region from top to top + height, with width 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 width.)
pub fn available_rect(&self, top: Au, height: Au, max_x: Au) -> Option<Rect<Au>> {
let list = match self.list.get() {
None => return None,
Some(list) => list,
};
let top = top - self.offset.y;
debug!("available_rect: trying to find space at {}", top);
// Relevant dimensions for the right-most left float
let mut max_left = Au(0) - self.offset.x;
let mut l_top = None;
let mut l_bottom = None;
// Relevant dimensions for the left-most right float
let mut min_right = max_x - self.offset.x;
let mut r_top = None;
let mut r_bottom = 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.origin;
let float_size = float.bounds.size;
debug!("float_pos: {}, float_size: {}", float_pos, float_size);
match float.kind {
FloatLeft if float_pos.x + float_size.width > max_left &&
float_pos.y + float_size.height > top && float_pos.y < top + height => {
max_left = float_pos.x + float_size.width;
l_top = Some(float_pos.y);
l_bottom = Some(float_pos.y + float_size.height);
debug!("available_rect: collision with left float: new max_left is {}",
max_left);
}
FloatRight if float_pos.x < min_right &&
float_pos.y + float_size.height > top && float_pos.y < top + height => {
min_right = float_pos.x;
r_top = Some(float_pos.y);
r_bottom = Some(float_pos.y + float_size.height);
debug!("available_rect: collision with right float: new min_right is {}",
min_right);
}
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 top smaller than the
// given upper bound.
let (top, bottom) = match (r_top, r_bottom, l_top, l_bottom) {
(Some(r_top), Some(r_bottom), Some(l_top), Some(l_bottom)) =>
range_intersect(max(top, r_top), r_bottom, max(top, l_top), l_bottom),
(None, None, Some(l_top), Some(l_bottom)) => (max(top, l_top), l_bottom),
(Some(r_top), Some(r_bottom), None, None) => (max(top, r_top), r_bottom),
(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 widths since we check against that right away, but we should still
// undersrtand why they occur and add a stronger assertion here.
// assert!(max_left < min_right);
assert!(top <= bottom, "Float position error");
Some(Rect {
origin: Point2D(max_left, top) + self.offset,
size: Size2D(min_right - max_left, bottom - top)
})
}
/// 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_top + self.offset.y),
max_width: info.max_width,
kind: info.kind
}
}
debug!("add_float: added float with info {:?}", new_info);
let new_float = Float {
bounds: Rect {
origin: self.place_between_floats(&new_info).origin - self.offset,
size: info.size,
},
kind: info.kind
};
let list = self.list.get_mut();
list.floats.push(new_float);
list.max_top = max(list.max_top, new_float.bounds.origin.y);
}
/// Given the top 3 sides of the rectangle, finds the largest height that will result in the
/// rectangle not colliding with any floats. Returns None if that height is infinite.
fn max_height_for_bounds(&self, left: Au, top: Au, width: Au) -> Option<Au> {
let list = match self.list.get() {
None => return None,
Some(list) => list,
};
let top = top - self.offset.y;
let left = left - self.offset.x;
let mut max_height = None;
for float in list.floats.iter() {
if float.bounds.origin.y + float.bounds.size.height > top &&
float.bounds.origin.x + float.bounds.size.width > left &&
float.bounds.origin.x < left + width {
let new_y = float.bounds.origin.y;
max_height = Some(min(max_height.unwrap_or(new_y), new_y));
}
}
max_height.map(|h| h + self.offset.y)
}
/// Given placement information, finds the closest place a box can be positioned without
/// colliding with any floats.
pub fn place_between_floats(&self, info: &PlacementInfo) -> Rect<Au> {
debug!("place_between_floats: Placing object with width {} and height {}",
info.size.width,
info.size.height);
// If no floats, use this fast path.
if !self.list.is_present() {
match info.kind {
FloatLeft => {
return Rect(Point2D(Au(0), info.ceiling),
Size2D(info.max_width, Au(i32::max_value)))
}
FloatRight => {
return Rect(Point2D(info.max_width - info.size.width, info.ceiling),
Size2D(info.max_width, Au(i32::max_value)))
}
}
}
// Can't go any higher than previous floats or previous elements in the document.
let mut float_y = info.ceiling;
loop {
let maybe_location = self.available_rect(float_y, info.size.height, info.max_width);
debug!("place_float: Got available rect: {:?} for y-pos: {}", maybe_location, float_y);
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 => {
Rect(Point2D(Au(0), float_y),
Size2D(info.max_width, Au(i32::max_value)))
}
FloatRight => {
Rect(Point2D(info.max_width - info.size.width, float_y),
Size2D(info.max_width, Au(i32::max_value)))
}
}
}
Some(rect) => {
assert!(rect.origin.y + rect.size.height != float_y,
"Non-terminating float placement");
// Place here if there is enough room
if rect.size.width >= info.size.width {
let height = self.max_height_for_bounds(rect.origin.x,
rect.origin.y,
rect.size.width);
let height = height.unwrap_or(Au(i32::max_value));
return match info.kind {
FloatLeft => {
Rect(Point2D(rect.origin.x, float_y),
Size2D(rect.size.width, height))
}
FloatRight => {
Rect(Point2D(rect.origin.x + rect.size.width - info.size.width,
float_y),
Size2D(rect.size.width, height))
}
}
}
// Try to place at the next-lowest location.
// Need to be careful of fencepost errors.
float_y = rect.origin.y + rect.size.height;
}
}
}
}
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 y = self.offset.y + float.bounds.origin.y + float.bounds.size.height;
clearance = max(clearance, y);
}
_ => {}
}
}
clearance
}
}

View file

@ -30,7 +30,7 @@ use layout::block::BlockFlow;
use layout::box_::Box; use layout::box_::Box;
use layout::context::LayoutContext; use layout::context::LayoutContext;
use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData};
use layout::float_context::{FloatContext, Invalid}; use layout::floats::Floats;
use layout::incremental::RestyleDamage; use layout::incremental::RestyleDamage;
use layout::inline::InlineFlow; use layout::inline::InlineFlow;
use layout::parallel::FlowParallelInfo; use layout::parallel::FlowParallelInfo;
@ -511,9 +511,13 @@ pub struct BaseFlow {
/// TODO(pcwalton): Group with other transient data to save space. /// TODO(pcwalton): Group with other transient data to save space.
parallel: FlowParallelInfo, parallel: FlowParallelInfo,
floats_in: FloatContext, /// The floats next to this flow.
floats_out: FloatContext, floats: Floats,
/// The number of floated descendants of this flow (including this flow, if it's floated).
num_floats: uint, num_floats: uint,
/// The position of this flow in page coordinates, computed during display list construction.
abs_position: Point2D<Au>, abs_position: Point2D<Au>,
/// Whether this flow has been destroyed. /// Whether this flow has been destroyed.
@ -569,8 +573,7 @@ impl BaseFlow {
parallel: FlowParallelInfo::new(), parallel: FlowParallelInfo::new(),
floats_in: Invalid, floats: Floats::new(),
floats_out: Invalid,
num_floats: 0, num_floats: 0,
abs_position: Point2D(Au::new(0), Au::new(0)), abs_position: Point2D(Au::new(0), Au::new(0)),

View file

@ -7,9 +7,9 @@ use layout::box_::{Box, CannotSplit, GenericBox, IframeBox, ImageBox, ScannedTex
use layout::box_::{SplitDidNotFit, UnscannedTextBox, InlineInfo}; use layout::box_::{SplitDidNotFit, UnscannedTextBox, InlineInfo};
use layout::context::LayoutContext; use layout::context::LayoutContext;
use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData};
use layout::floats::{FloatLeft, Floats, PlacementInfo};
use layout::flow::{BaseFlow, FlowClass, Flow, InlineFlowClass}; use layout::flow::{BaseFlow, FlowClass, Flow, InlineFlowClass};
use layout::flow; use layout::flow;
use layout::float_context::{FloatContext, FloatLeft, PlacementInfo};
use layout::util::ElementMapping; use layout::util::ElementMapping;
use layout::wrapper::ThreadSafeLayoutNode; use layout::wrapper::ThreadSafeLayoutNode;
@ -56,7 +56,7 @@ struct LineBox {
} }
struct LineboxScanner { struct LineboxScanner {
floats: FloatContext, floats: Floats,
new_boxes: ~[Box], new_boxes: ~[Box],
work_list: RingBuf<Box>, work_list: RingBuf<Box>,
pending_line: LineBox, pending_line: LineBox,
@ -65,7 +65,7 @@ struct LineboxScanner {
} }
impl LineboxScanner { impl LineboxScanner {
pub fn new(float_ctx: FloatContext) -> LineboxScanner { pub fn new(float_ctx: Floats) -> LineboxScanner {
LineboxScanner { LineboxScanner {
floats: float_ctx, floats: float_ctx,
new_boxes: ~[], new_boxes: ~[],
@ -80,7 +80,7 @@ impl LineboxScanner {
} }
} }
pub fn floats_out(&mut self) -> FloatContext { pub fn floats(&mut self) -> Floats {
self.floats.clone() self.floats.clone()
} }
@ -193,11 +193,10 @@ impl LineboxScanner {
}; };
let mut info = PlacementInfo { let mut info = PlacementInfo {
width: placement_width, size: Size2D(placement_width, first_box_size.height),
height: first_box_size.height,
ceiling: ceiling, ceiling: ceiling,
max_width: flow.base.position.size.width, max_width: flow.base.position.size.width,
f_type: FloatLeft kind: FloatLeft,
}; };
let line_bounds = self.floats.place_between_floats(&info); let line_bounds = self.floats.place_between_floats(&info);
@ -253,7 +252,7 @@ impl LineboxScanner {
(None, None) => fail!("This case makes no sense.") (None, None) => fail!("This case makes no sense.")
}; };
info.width = actual_box_width; info.size.width = actual_box_width;
let new_bounds = self.floats.place_between_floats(&info); let new_bounds = self.floats.place_between_floats(&info);
debug!("LineboxScanner: case=new line position: {}", new_bounds); debug!("LineboxScanner: case=new line position: {}", new_bounds);
@ -626,7 +625,7 @@ impl Flow for InlineFlow {
for kid in self.base.child_iter() { for kid in self.base.child_iter() {
let child_base = flow::mut_base(kid); let child_base = flow::mut_base(kid);
num_floats += child_base.num_floats; num_floats += child_base.num_floats;
child_base.floats_in = FloatContext::new(child_base.num_floats); child_base.floats = Floats::new();
} }
let mut min_width = Au::new(0); let mut min_width = Au::new(0);
@ -653,7 +652,7 @@ impl Flow for InlineFlow {
// //
// TODO: Combine this with `LineboxScanner`'s walk in the box list, or put this into `Box`. // TODO: Combine this with `LineboxScanner`'s walk in the box list, or put this into `Box`.
debug!("InlineFlow::assign_widths: floats_in: {:?}", self.base.floats_in); debug!("InlineFlow::assign_widths: floats in: {:?}", self.base.floats);
{ {
let this = &mut *self; let this = &mut *self;
@ -698,12 +697,12 @@ impl Flow for InlineFlow {
// determine its height for computing linebox height. // determine its height for computing linebox height.
// //
// TODO(pcwalton): Cache the linebox scanner? // TODO(pcwalton): Cache the linebox scanner?
debug!("assign_height_inline: floats_in: {:?}", self.base.floats_in); debug!("assign_height_inline: floats in: {:?}", self.base.floats);
// assign height for inline boxes // assign height for inline boxes
for box_ in self.boxes.iter() { for box_ in self.boxes.iter() {
box_.assign_height(); box_.assign_height();
} }
let scanner_floats = self.base.floats_in.clone(); let scanner_floats = self.base.floats.clone();
let mut scanner = LineboxScanner::new(scanner_floats); let mut scanner = LineboxScanner::new(scanner_floats);
// Access the linebox scanner. // Access the linebox scanner.
@ -880,9 +879,8 @@ impl Flow for InlineFlow {
Au::new(0) Au::new(0)
}; };
self.base.floats_out = scanner.floats_out() self.base.floats = scanner.floats();
.translate(Point2D(Au::new(0), self.base.floats.translate(Point2D(Au::new(0), -self.base.position.size.height));
-self.base.position.size.height));
} }
fn collapse_margins(&mut self, fn collapse_margins(&mut self,

View file

@ -90,7 +90,7 @@ pub mod layout {
pub mod construct; pub mod construct;
pub mod context; pub mod context;
pub mod display_list_builder; pub mod display_list_builder;
pub mod float_context; pub mod floats;
pub mod flow; pub mod flow;
pub mod flow_list; pub mod flow_list;
pub mod layout_task; pub mod layout_task;