auto merge of #585 : eric93/servo/floats-2, r=metajack

Everything except for floats in an inline formatting context and clear.
This commit is contained in:
bors-servo 2013-07-12 10:03:29 -07:00
commit 9099892d76
7 changed files with 637 additions and 228 deletions

View file

@ -249,12 +249,14 @@ impl BlockFlowData {
pub fn assign_height_block(@mut self, ctx: &mut LayoutContext) { pub fn assign_height_block(@mut self, ctx: &mut LayoutContext) {
let mut cur_y = Au(0); let mut cur_y = Au(0);
let mut top_offset = Au(0); let mut top_offset = Au(0);
let mut bottom_offset = Au(0);
let mut left_offset = Au(0); let mut left_offset = Au(0);
for self.box.iter().advance |&box| { for self.box.iter().advance |&box| {
do box.with_model |model| { do box.with_model |model| {
top_offset = model.margin.top + model.border.top + model.padding.top; top_offset = model.margin.top + model.border.top + model.padding.top;
cur_y = cur_y + top_offset; cur_y = cur_y + top_offset;
bottom_offset = model.margin.bottom + model.border.bottom + model.padding.bottom;
left_offset = model.offset(); left_offset = model.offset();
}; };
} }
@ -277,7 +279,7 @@ impl BlockFlowData {
} }
kid.assign_height(ctx); kid.assign_height(ctx);
do kid.with_mut_base |child_node| { do kid.with_mut_base |child_node| {
float_ctx = child_node.floats_out.translate(Point2D(Au(0), -child_node.position.size.height)); float_ctx = child_node.floats_out.clone();
} }
} }
@ -288,12 +290,19 @@ impl BlockFlowData {
}; };
} }
let height = if self.is_root { let mut height = if self.is_root {
Au::max(ctx.screen_size.size.height, cur_y) Au::max(ctx.screen_size.size.height, cur_y)
} else { } else {
cur_y - top_offset cur_y - top_offset
}; };
for self.box.iter().advance |&box| {
let style = box.style();
let maybe_height = MaybeAuto::from_height(style.height(), Au(0));
let maybe_height = maybe_height.spec_or_default(Au(0));
height = geometry::max(height, maybe_height);
}
let mut noncontent_height = Au(0); let mut noncontent_height = Au(0);
self.box.map(|&box| { self.box.map(|&box| {
do box.with_mut_base |base| { do box.with_mut_base |base| {
@ -311,8 +320,8 @@ impl BlockFlowData {
//TODO(eatkinson): compute heights using the 'height' property. //TODO(eatkinson): compute heights using the 'height' property.
self.common.position.size.height = height + noncontent_height; self.common.position.size.height = height + noncontent_height;
let extra_height = height - (cur_y - top_offset) + bottom_offset;
self.common.floats_out = float_ctx.translate(Point2D(left_offset, self.common.position.size.height)); self.common.floats_out = float_ctx.translate(Point2D(left_offset, -extra_height));
} }
pub fn build_display_list_block<E:ExtraDisplayListData>(@mut self, pub fn build_display_list_block<E:ExtraDisplayListData>(@mut self,

View file

@ -24,12 +24,13 @@ use newcss::values::{CSSDisplayTableRowGroup, CSSDisplayTableHeaderGroup, CSSDis
use newcss::values::{CSSDisplayTableRow, CSSDisplayTableColumnGroup, CSSDisplayTableColumn}; use newcss::values::{CSSDisplayTableRow, CSSDisplayTableColumnGroup, CSSDisplayTableColumn};
use newcss::values::{CSSDisplayTableCell, CSSDisplayTableCaption}; use newcss::values::{CSSDisplayTableCell, CSSDisplayTableCaption};
use newcss::values::{CSSDisplayNone}; use newcss::values::{CSSDisplayNone};
use newcss::values::{CSSFloatNone}; use newcss::values::{CSSFloatNone, CSSFloatLeft, CSSFloatRight};
use layout::float_context::{FloatLeft, FloatRight};
use script::dom::element::*; use script::dom::element::*;
use script::dom::node::{AbstractNode, CommentNodeTypeId, DoctypeNodeTypeId}; use script::dom::node::{AbstractNode, CommentNodeTypeId, DoctypeNodeTypeId};
use script::dom::node::{ElementNodeTypeId, LayoutView, TextNodeTypeId}; use script::dom::node::{ElementNodeTypeId, LayoutView, TextNodeTypeId};
use servo_util::range::Range; use servo_util::range::Range;
use servo_util::tree::{TreeNodeRef, TreeUtils}; use servo_util::tree::{TreeNodeRef, TreeNode, TreeUtils};
pub struct LayoutTreeBuilder { pub struct LayoutTreeBuilder {
root_flow: Option<FlowContext>, root_flow: Option<FlowContext>,
@ -165,15 +166,42 @@ impl BoxGenerator {
}, },
FloatFlow(float) => { FloatFlow(float) => {
debug!("BoxGenerator[f%d]: point b", float.common.id); debug!("BoxGenerator[f%d]: point b", float.common.id);
let new_box = self.make_box(ctx, box_type, node, self.flow, builder);
debug!("BoxGenerator[f%d]: attaching box[b%d] to float flow (node: %s)", let mut parent_flow = None;
float.common.id,
new_box.id(),
node.debug_str());
assert!(float.box.is_none()); do self.flow.with_base |base| {
float.box = Some(new_box); parent_flow = base.parent_node();
}
match parent_flow {
None => fail!("Float flow as root node"),
Some(BlockFlow(*)) |
Some(FloatFlow(*)) => {
let new_box = self.make_box(ctx, box_type, node, self.flow, builder);
debug!("BoxGenerator[f%d]: attaching box[b%d] to float flow (node: %s)",
float.common.id,
new_box.id(),
node.debug_str());
assert!(float.box.is_none() && float.index.is_none());
float.box = Some(new_box);
}
Some(InlineFlow(inline)) => {
let new_box = self.make_box(ctx, box_type, node, self.flow, builder);
debug!("BoxGenerator[f%d]: attaching box[b%d] to float flow (node: %s)",
float.common.id,
new_box.id(),
node.debug_str());
assert!(float.box.is_none() && float.index.is_none());
inline.boxes.push(new_box);
float.index = Some(inline.boxes.len() - 1);
}
_ => warn!("push_node() not implemented for flow f%d", self.flow.id())
}
}, },
_ => warn!("push_node() not implemented for flow f%d", self.flow.id()), _ => warn!("push_node() not implemented for flow f%d", self.flow.id()),
} }
@ -214,7 +242,7 @@ impl BoxGenerator {
/// Disambiguate between different methods here instead of inlining, since each case has very /// Disambiguate between different methods here instead of inlining, since each case has very
/// different complexity. /// different complexity.
fn make_box(&mut self, fn make_box(&self,
layout_ctx: &LayoutContext, layout_ctx: &LayoutContext,
ty: RenderBoxType, ty: RenderBoxType,
node: AbstractNode<LayoutView>, node: AbstractNode<LayoutView>,
@ -231,7 +259,7 @@ impl BoxGenerator {
result result
} }
fn make_image_box(&mut self, fn make_image_box(&self,
layout_ctx: &LayoutContext, layout_ctx: &LayoutContext,
node: AbstractNode<LayoutView>, node: AbstractNode<LayoutView>,
base: RenderBoxBase) base: RenderBoxBase)
@ -286,10 +314,11 @@ impl LayoutTreeBuilder {
-> Option<@mut BoxGenerator> { -> Option<@mut BoxGenerator> {
debug!("Considering node: %s", cur_node.debug_str()); debug!("Considering node: %s", cur_node.debug_str());
let this_generator = match self.box_generator_for_node(cur_node, // Skip over nodes that don't belong in the flow tree
parent_generator, let (this_generator, next_generator) =
prev_sibling_generator) { match self.box_generator_for_node(cur_node, parent_generator, prev_sibling_generator) {
Some(gen) => gen,
Some((gen, n_gen)) => (gen, n_gen),
None => { return prev_sibling_generator; } None => { return prev_sibling_generator; }
}; };
@ -318,14 +347,14 @@ impl LayoutTreeBuilder {
dom_node.layout_data().flow = Some(child_flow); dom_node.layout_data().flow = Some(child_flow);
} }
} }
Some(this_generator) Some(next_generator)
} }
pub fn box_generator_for_node(&mut self, pub fn box_generator_for_node(&mut self,
node: AbstractNode<LayoutView>, node: AbstractNode<LayoutView>,
parent_generator: @mut BoxGenerator, parent_generator: @mut BoxGenerator,
sibling_generator: Option<@mut BoxGenerator>) sibling_generator: Option<@mut BoxGenerator>)
-> Option<@mut BoxGenerator> { -> Option<(@mut BoxGenerator, @mut BoxGenerator)> {
fn is_root(node: AbstractNode<LayoutView>) -> bool { fn is_root(node: AbstractNode<LayoutView>) -> bool {
match node.parent_node() { match node.parent_node() {
@ -333,7 +362,7 @@ impl LayoutTreeBuilder {
Some(_) => false Some(_) => false
} }
} }
let display = if (node.is_element()) { let display = if node.is_element() {
match node.style().display(is_root(node)) { match node.style().display(is_root(node)) {
CSSDisplayNone => return None, // tree ends here if 'display: none' CSSDisplayNone => return None, // tree ends here if 'display: none'
// TODO(eatkinson) these are hacks so that the code doesn't crash // TODO(eatkinson) these are hacks so that the code doesn't crash
@ -370,17 +399,34 @@ impl LayoutTreeBuilder {
// determine whether to float left or right. // determine whether to float left or right.
let is_float = if (node.is_element()) { let is_float = if (node.is_element()) {
match node.style().float() { match node.style().float() {
CSSFloatNone => false, CSSFloatNone => None,
_ => true CSSFloatLeft => Some(FloatLeft),
CSSFloatRight => Some(FloatRight)
} }
} else { } else {
false None
}; };
let new_generator = match (display, parent_generator.flow, sibling_flow) { let new_generator = match (display, parent_generator.flow, sibling_flow) {
(CSSDisplayBlock, BlockFlow(_), _) if is_float => { // Floats
self.create_child_generator(node, parent_generator, Flow_Float) (CSSDisplayBlock, BlockFlow(_), _) if !is_float.is_none() => {
self.create_child_generator(node, parent_generator, Flow_Float(is_float.get()))
}
// If we're placing a float after an inline, append the float to the inline flow,
// then continue building from the inline flow in case there are more inlines
// afterward.
(CSSDisplayBlock, _, Some(InlineFlow(_))) if !is_float.is_none() => {
let float_generator = self.create_child_generator(node,
sibling_generator.get(),
Flow_Float(is_float.get()));
return Some((float_generator, sibling_generator.get()));
}
// This is a catch-all case for when:
// a) sibling_flow is None
// b) sibling_flow is a BlockFlow
(CSSDisplayBlock, InlineFlow(_), _) if !is_float.is_none() => {
self.create_child_generator(node, parent_generator, Flow_Float(is_float.get()))
} }
(CSSDisplayBlock, BlockFlow(info), _) => match (info.is_root, node.parent_node()) { (CSSDisplayBlock, BlockFlow(info), _) => match (info.is_root, node.parent_node()) {
@ -408,8 +454,8 @@ impl LayoutTreeBuilder {
self.create_child_generator(node, parent_generator, Flow_Inline) self.create_child_generator(node, parent_generator, Flow_Inline)
} }
// FIXME(eatkinson): this is bogus. Floats should not be able to split // The first two cases should only be hit when a FloatFlow
// inlines. They should be appended as children of the inline flow. // is the first child of a BlockFlow. Other times, we will
(CSSDisplayInline, _, Some(FloatFlow(*))) | (CSSDisplayInline, _, Some(FloatFlow(*))) |
(CSSDisplayInlineBlock, _, Some(FloatFlow(*))) | (CSSDisplayInlineBlock, _, Some(FloatFlow(*))) |
(CSSDisplayInline, FloatFlow(*), _) | (CSSDisplayInline, FloatFlow(*), _) |
@ -432,7 +478,9 @@ impl LayoutTreeBuilder {
_ => parent_generator _ => parent_generator
}; };
Some(new_generator) // Usually, the node we add boxes to will be prev_sibling on the
// next call to this function.
Some((new_generator, new_generator))
} }
pub fn create_child_generator(&mut self, pub fn create_child_generator(&mut self,
@ -574,13 +622,13 @@ impl LayoutTreeBuilder {
pub fn make_flow(&mut self, ty: FlowContextType, node: AbstractNode<LayoutView>) -> FlowContext { pub fn make_flow(&mut self, ty: FlowContextType, node: AbstractNode<LayoutView>) -> FlowContext {
let info = FlowData::new(self.next_flow_id(), node); let info = FlowData::new(self.next_flow_id(), node);
let result = match ty { let result = match ty {
Flow_Absolute => AbsoluteFlow(@mut info), Flow_Absolute => AbsoluteFlow(@mut info),
Flow_Block => BlockFlow(@mut BlockFlowData::new(info)), Flow_Block => BlockFlow(@mut BlockFlowData::new(info)),
Flow_Float => FloatFlow(@mut FloatFlowData::new(info)), Flow_Float(f_type) => FloatFlow(@mut FloatFlowData::new(info, f_type)),
Flow_InlineBlock => InlineBlockFlow(@mut info), Flow_InlineBlock => InlineBlockFlow(@mut info),
Flow_Inline => InlineFlow(@mut InlineFlowData::new(info)), Flow_Inline => InlineFlow(@mut InlineFlowData::new(info)),
Flow_Root => BlockFlow(@mut BlockFlowData::new_root(info)), Flow_Root => BlockFlow(@mut BlockFlowData::new_root(info)),
Flow_Table => TableFlow(@mut info), Flow_Table => TableFlow(@mut info),
}; };
debug!("LayoutTreeBuilder: created flow: %s", result.debug_str()); debug!("LayoutTreeBuilder: created flow: %s", result.debug_str());
result result

View file

@ -8,7 +8,7 @@ use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData};
use layout::display_list_builder::{FlowDisplayListBuilderMethods}; use layout::display_list_builder::{FlowDisplayListBuilderMethods};
use layout::flow::{FloatFlow, FlowData}; use layout::flow::{FloatFlow, FlowData};
use layout::model::{MaybeAuto}; use layout::model::{MaybeAuto};
use layout::float_context::{FloatContext, PlacementInfo, FloatLeft}; use layout::float_context::{FloatContext, PlacementInfo, FloatType};
use std::cell::Cell; use std::cell::Cell;
use geom::point::Point2D; use geom::point::Point2D;
@ -27,21 +27,25 @@ pub struct FloatFlowData {
containing_width: Au, containing_width: Au,
/// Parent clobbers our position, so store it separately /// Offset relative to where the parent tried to position this flow
rel_pos: Point2D<Au>, rel_pos: Point2D<Au>,
/// Left or right?
float_type: FloatType,
/// Index into the box list for inline floats /// Index into the box list for inline floats
index: Option<uint>, index: Option<uint>,
} }
impl FloatFlowData { impl FloatFlowData {
pub fn new(common: FlowData) -> FloatFlowData { pub fn new(common: FlowData, float_type: FloatType) -> FloatFlowData {
FloatFlowData { FloatFlowData {
common: common, common: common,
containing_width: Au(0), containing_width: Au(0),
box: None, box: None,
index: None, index: None,
float_type: float_type,
rel_pos: Point2D(Au(0), Au(0)), rel_pos: Point2D(Au(0), Au(0)),
} }
} }
@ -210,7 +214,7 @@ impl FloatFlowData {
height: height, height: height,
ceiling: Au(0), ceiling: Au(0),
max_width: self.containing_width, max_width: self.containing_width,
f_type: FloatLeft, f_type: self.float_type,
}; };
// Place the float and return the FloatContext back to the parent flow. // Place the float and return the FloatContext back to the parent flow.

View file

@ -8,6 +8,7 @@ use geom::rect::Rect;
use gfx::geometry::{Au, max, min}; use gfx::geometry::{Au, max, min};
use std::util::replace; use std::util::replace;
use std::vec; use std::vec;
use std::i32::max_value;
pub enum FloatType{ pub enum FloatType{
FloatLeft, FloatLeft,
@ -94,6 +95,13 @@ impl FloatContext {
replace(self, Invalid) replace(self, Invalid)
} }
#[inline(always)]
pub fn place_between_floats(&self, info: &PlacementInfo) -> Rect<Au> {
do self.with_base |base| {
base.place_between_floats(info)
}
}
#[inline(always)] #[inline(always)]
pub fn last_float_pos(&mut self) -> Point2D<Au> { pub fn last_float_pos(&mut self) -> Point2D<Au> {
do self.with_base |base| { do self.with_base |base| {
@ -139,10 +147,10 @@ impl FloatContextBase{
(max(top_1, top_2), min(bottom_1, bottom_2)) (max(top_1, top_2), min(bottom_1, bottom_2))
} }
debug!("available_rect: trying to find space at %?", top);
let top = top - self.offset.y; let top = top - self.offset.y;
debug!("available_rect: trying to find space at %?", top);
// Relevant dimensions for the right-most left float // Relevant dimensions for the right-most left float
let mut max_left = Au(0) - self.offset.x; let mut max_left = Au(0) - self.offset.x;
let mut l_top = None; let mut l_top = None;
@ -154,11 +162,13 @@ impl FloatContextBase{
// Find the float collisions for the given vertical range. // Find the float collisions for the given vertical range.
for self.float_data.iter().advance |float| { for self.float_data.iter().advance |float| {
debug!("available_rect: Checking for collision against float");
match *float{ match *float{
None => (), None => (),
Some(data) => { Some(data) => {
let float_pos = data.bounds.origin; let float_pos = data.bounds.origin;
let float_size = data.bounds.size; let float_size = data.bounds.size;
debug!("float_pos: %?, float_size: %?", float_pos, float_size);
match data.f_type { match data.f_type {
FloatLeft => { FloatLeft => {
if(float_pos.x + float_size.width > max_left && if(float_pos.x + float_size.width > max_left &&
@ -167,6 +177,9 @@ impl FloatContextBase{
l_top = Some(float_pos.y); l_top = Some(float_pos.y);
l_bottom = Some(float_pos.y + float_size.height); l_bottom = Some(float_pos.y + float_size.height);
debug!("available_rect: collision with left float: new max_left is %?",
max_left);
} }
} }
FloatRight => { FloatRight => {
@ -176,6 +189,8 @@ impl FloatContextBase{
r_top = Some(float_pos.y); r_top = Some(float_pos.y);
r_bottom = Some(float_pos.y + float_size.height); r_bottom = Some(float_pos.y + float_size.height);
debug!("available_rect: collision with right float: new min_right is %?",
min_right);
} }
} }
} }
@ -185,26 +200,25 @@ impl FloatContextBase{
// Extend the vertical range of the rectangle to the closest floats. // Extend the vertical range of the rectangle to the closest floats.
// If there are floats on both sides, take the intersection of the // If there are floats on both sides, take the intersection of the
// two areas. // 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) { let (top, bottom) = match (r_top, r_bottom, l_top, l_bottom) {
(Some(r_top), Some(r_bottom), Some(l_top), Some(l_bottom)) => (Some(r_top), Some(r_bottom), Some(l_top), Some(l_bottom)) =>
range_intersect(r_top, r_bottom, l_top, l_bottom), range_intersect(max(top, r_top), r_bottom, max(top, l_top), l_bottom),
(None, None, Some(l_top), Some(l_bottom)) => (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) => (r_top, r_bottom), (Some(r_top), Some(r_bottom), None, None) => (max(top, r_top), r_bottom),
(None, None, None, None) => return None, (None, None, None, None) => return None,
_ => fail!("Reached unreachable state when computing float area") _ => fail!("Reached unreachable state when computing float area")
}; };
// When the window is smaller than the float, we will return a rect // This assertion is too strong and fails in some cases. It is OK to
// with negative width. // return negative widths since we check against that right away, but
assert!(max_left < min_right // we should still undersrtand why they occur and add a stronger
|| max_left > max_x - self.offset.x // assertion here.
|| min_right < Au(0) - self.offset.x //assert!(max_left < min_right);
,"Float position error");
assert!(top <= bottom, "Float position error");
//TODO(eatkinson): do we need to do something similar for heights?
assert!(top < bottom, "Float position error");
Some(Rect{ Some(Rect{
origin: Point2D(max_left, top) + self.offset, origin: Point2D(max_left, top) + self.offset,
@ -217,39 +231,112 @@ impl FloatContextBase{
assert!(self.floats_used < self.float_data.len() && assert!(self.floats_used < self.float_data.len() &&
self.float_data[self.floats_used].is_none()); self.float_data[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 { let new_float = FloatData {
bounds: Rect { bounds: Rect {
origin: self.place_float(info) - self.offset, origin: self.place_between_floats(&new_info).origin - self.offset,
size: Size2D(info.width, info.height) size: Size2D(info.width, info.height)
}, },
f_type: info.f_type f_type: info.f_type
}; };
self.float_data[self.floats_used] = Some(new_float); self.float_data[self.floats_used] = Some(new_float);
self.max_y = max(self.max_y, new_float.bounds.origin.y);
self.floats_used += 1; self.floats_used += 1;
} }
/// Given necessary info, finds the position of the float in /// Returns true if the given rect overlaps with any floats.
/// LOCAL COORDINATES. i.e. must be translated before placed fn collides_with_float(&self, bounds: &Rect<Au>) -> bool {
/// in the float list for self.float_data.iter().advance |float| {
fn place_float(&self, info: &PlacementInfo) -> Point2D<Au>{ match *float{
None => (),
Some(data) => {
if data.bounds.translate(&self.offset).intersects(bounds) {
return true;
}
}
};
}
return false;
}
/// 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 self.float_data.iter().advance |float| {
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.get_or_default(new_y), new_y));
}
}
}
}
match max_height {
None => None,
Some(h) => Some(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); debug!("place_float: Placing float with width %? and height %?", info.width, info.height);
// Can't go any higher than previous floats or // Can't go any higher than previous floats or
// previous elements in the document. // previous elements in the document.
let mut float_y = max(info.ceiling, self.max_y + self.offset.y); let mut float_y = info.ceiling;
loop { loop {
let maybe_location = self.available_rect(float_y, info.height, info.max_width); 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); debug!("place_float: Got available rect: %? for y-pos: %?", maybe_location, float_y);
match maybe_location { match maybe_location {
// If there are no floats blocking us, return the current location // If there are no floats blocking us, return the current location
// TODO(eatknson): integrate with overflow // TODO(eatknson): integrate with overflow
None => return Point2D(Au(0), float_y), 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) => { Some(rect) => {
assert!(rect.origin.y + rect.size.height != float_y, assert!(rect.origin.y + rect.size.height != float_y,
"Non-terminating float placement"); "Non-terminating float placement");
// Place here if there is enough room // Place here if there is enough room
if (rect.size.width >= info.width) { if (rect.size.width >= info.width) {
return Point2D(rect.origin.x, float_y); let height = self.max_height_for_bounds(rect.origin.x,
rect.origin.y,
rect.size.width);
let height = height.get_or_default(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. // Try to place at the next-lowest location.

View file

@ -31,7 +31,7 @@ use layout::box::RenderBox;
use layout::context::LayoutContext; use layout::context::LayoutContext;
use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData};
use layout::inline::{InlineFlowData}; use layout::inline::{InlineFlowData};
use layout::float_context::{FloatContext, Invalid}; use layout::float_context::{FloatContext, Invalid, FloatType};
use std::cell::Cell; use std::cell::Cell;
use std::uint; use std::uint;
@ -56,7 +56,7 @@ pub enum FlowContext {
pub enum FlowContextType { pub enum FlowContextType {
Flow_Absolute, Flow_Absolute,
Flow_Block, Flow_Block,
Flow_Float, Flow_Float(FloatType),
Flow_InlineBlock, Flow_InlineBlock,
Flow_Inline, Flow_Inline,
Flow_Root, Flow_Root,

View file

@ -10,6 +10,7 @@ use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData};
use layout::flow::{FlowContext, FlowData, InlineFlow}; use layout::flow::{FlowContext, FlowData, InlineFlow};
use layout::float_context::FloatContext; use layout::float_context::FloatContext;
use layout::util::{ElementMapping}; use layout::util::{ElementMapping};
use layout::float_context::{PlacementInfo, FloatLeft};
use std::u16; use std::u16;
use std::util; use std::util;
@ -43,44 +44,65 @@ things like "start outer box, text, start inner box, text, end inner
box, text, end outer box, text". This seems a little complicated to box, text, end outer box, text". This seems a little complicated to
serve as the starting point, but the current design doesn't make it serve as the starting point, but the current design doesn't make it
hard to try out that alternative. hard to try out that alternative.
Line boxes also contain some metadata used during line breaking. The
green zone is the area that the line can expand to before it collides
with a float or a horizontal wall of the containing block. The top
left corner of the green zone is the same as that of the line, but
the green zone can be taller and wider than the line itself.
*/ */
struct PendingLine { struct LineBox {
range: Range, range: Range,
bounds: Rect<Au> bounds: Rect<Au>,
green_zone: Size2D<Au>
} }
struct LineboxScanner { struct LineboxScanner {
flow: FlowContext, flow: FlowContext,
floats: FloatContext,
new_boxes: ~[RenderBox], new_boxes: ~[RenderBox],
work_list: @mut Deque<RenderBox>, work_list: @mut Deque<RenderBox>,
pending_line: PendingLine, pending_line: LineBox,
line_spans: ~[Range], lines: ~[LineBox],
cur_y: Au,
} }
impl LineboxScanner { impl LineboxScanner {
pub fn new(inline: FlowContext) -> LineboxScanner { pub fn new(inline: FlowContext, float_ctx: FloatContext) -> LineboxScanner {
assert!(inline.starts_inline_flow()); assert!(inline.starts_inline_flow());
LineboxScanner { LineboxScanner {
flow: inline, flow: inline,
floats: float_ctx,
new_boxes: ~[], new_boxes: ~[],
work_list: @mut Deque::new(), work_list: @mut Deque::new(),
pending_line: PendingLine {range: Range::empty(), bounds: Rect(Point2D(Au(0), Au(0)), Size2D(Au(0), Au(0)))}, pending_line: LineBox {
line_spans: ~[], range: Range::empty(),
bounds: Rect(Point2D(Au(0), Au(0)), Size2D(Au(0), Au(0))),
green_zone: Size2D(Au(0), Au(0))
},
lines: ~[],
cur_y: Au(0)
} }
} }
pub fn floats_out(&mut self) -> FloatContext {
self.floats.clone()
}
fn reset_scanner(&mut self) { fn reset_scanner(&mut self) {
debug!("Resetting line box scanner's state for flow f%d.", self.flow.id()); debug!("Resetting line box scanner's state for flow f%d.", self.flow.id());
self.line_spans = ~[]; self.lines = ~[];
self.new_boxes = ~[]; self.new_boxes = ~[];
self.cur_y = Au(0);
self.reset_linebox(); self.reset_linebox();
} }
fn reset_linebox(&mut self) { fn reset_linebox(&mut self) {
self.pending_line.range.reset(0,0); self.pending_line.range.reset(0,0);
self.pending_line.bounds = Rect(Point2D(Au(0), Au(0)), Size2D(Au(0), Au(0))); self.pending_line.bounds = Rect(Point2D(Au(0), self.cur_y), Size2D(Au(0), Au(0)));
self.pending_line.green_zone = Size2D(Au(0), Au(0))
} }
pub fn scan_for_lines(&mut self, ctx: &LayoutContext) { pub fn scan_for_lines(&mut self, ctx: &LayoutContext) {
@ -108,16 +130,16 @@ impl LineboxScanner {
let box_was_appended = self.try_append_to_line(ctx, cur_box); let box_was_appended = self.try_append_to_line(ctx, cur_box);
if !box_was_appended { if !box_was_appended {
debug!("LineboxScanner: Box wasn't appended, because line %u was full.", debug!("LineboxScanner: Box wasn't appended, because line %u was full.",
self.line_spans.len()); self.lines.len());
self.flush_current_line(); self.flush_current_line();
} else { } else {
debug!("LineboxScanner: appended a box to line %u", self.line_spans.len()); debug!("LineboxScanner: appended a box to line %u", self.lines.len());
} }
} }
if self.pending_line.range.length() > 0 { if self.pending_line.range.length() > 0 {
debug!("LineboxScanner: Partially full linebox %u left at end of scanning.", debug!("LineboxScanner: Partially full linebox %u left at end of scanning.",
self.line_spans.len()); self.lines.len());
self.flush_current_line(); self.flush_current_line();
} }
} }
@ -131,153 +153,294 @@ impl LineboxScanner {
fn swap_out_results(&mut self) { fn swap_out_results(&mut self) {
debug!("LineboxScanner: Propagating scanned lines[n=%u] to inline flow f%d", debug!("LineboxScanner: Propagating scanned lines[n=%u] to inline flow f%d",
self.line_spans.len(), self.lines.len(),
self.flow.id()); self.flow.id());
let inline: &mut InlineFlowData = self.flow.inline(); let inline: &mut InlineFlowData = self.flow.inline();
util::swap(&mut inline.boxes, &mut self.new_boxes); util::swap(&mut inline.boxes, &mut self.new_boxes);
util::swap(&mut inline.lines, &mut self.line_spans); util::swap(&mut inline.lines, &mut self.lines);
} }
fn flush_current_line(&mut self) { fn flush_current_line(&mut self) {
debug!("LineboxScanner: Flushing line %u: %?", debug!("LineboxScanner: Flushing line %u: %?",
self.line_spans.len(), self.pending_line); self.lines.len(), self.pending_line);
// set box horizontal offsets
let line_range = self.pending_line.range;
let mut offset_x = Au(0);
// TODO(Issue #199): interpretation of CSS 'direction' will change how boxes are positioned.
debug!("LineboxScanner: Setting horizontal offsets for boxes in line %u range: %?",
self.line_spans.len(), line_range);
// Get the text alignment.
// TODO(Issue #222): use 'text-align' property from InlineFlow's
// block container, not from the style of the first box child.
let linebox_align;
if self.pending_line.range.begin() < self.new_boxes.len() {
let first_box = self.new_boxes[self.pending_line.range.begin()];
linebox_align = first_box.text_align();
} else {
// Nothing to lay out, so assume left alignment.
linebox_align = CSSTextAlignLeft;
}
let slack_width = self.flow.position().size.width - self.pending_line.bounds.size.width;
match linebox_align {
// So sorry, but justified text is more complicated than shuffling linebox coordinates.
// TODO(Issue #213): implement `text-align: justify`
CSSTextAlignLeft | CSSTextAlignJustify => {
for line_range.eachi |i| {
do self.new_boxes[i].with_mut_base |base| {
base.position.origin.x = offset_x;
offset_x = offset_x + base.position.size.width;
};
}
},
CSSTextAlignCenter => {
offset_x = slack_width.scale_by(0.5f);
for line_range.eachi |i| {
do self.new_boxes[i].with_mut_base |base| {
base.position.origin.x = offset_x;
offset_x = offset_x + base.position.size.width;
};
}
},
CSSTextAlignRight => {
offset_x = slack_width;
for line_range.eachi |i| {
do self.new_boxes[i].with_mut_base |base| {
base.position.origin.x = offset_x;
offset_x = offset_x + base.position.size.width;
};
}
},
}
// clear line and add line mapping // clear line and add line mapping
debug!("LineboxScanner: Saving information for flushed line %u.", self.line_spans.len()); debug!("LineboxScanner: Saving information for flushed line %u.", self.lines.len());
self.line_spans.push(line_range); self.lines.push(self.pending_line);
self.cur_y = self.pending_line.bounds.origin.y + self.pending_line.bounds.size.height;
self.reset_linebox(); self.reset_linebox();
} }
// return value: whether any box was appended. fn box_height(&self, box: RenderBox) -> Au {
fn try_append_to_line(&mut self, ctx: &LayoutContext, in_box: RenderBox) -> bool { match box {
let remaining_width = self.flow.position().size.width - self.pending_line.bounds.size.width; ImageRenderBoxClass(image_box) => {
let in_box_width = in_box.position().size.width; let size = image_box.image.get_size();
let height = Au::from_px(size.get_or_default(Size2D(0, 0)).height);
image_box.base.position.size.height = height;
debug!("box_height: found image height: %?", height);
height
}
TextRenderBoxClass(text_box) => {
let range = &text_box.range;
let run = &text_box.run;
// Compute the height based on the line-height and font size
let text_bounds = run.metrics_for_range(range).bounding_box;
let em_size = text_bounds.size.height;
let line_height = match box.line_height() {
CSSLineHeightNormal => em_size.scale_by(1.14f),
CSSLineHeightNumber(l) => em_size.scale_by(l),
CSSLineHeightLength(Em(l)) => em_size.scale_by(l),
CSSLineHeightLength(Px(l)) => Au::from_frac_px(l),
CSSLineHeightLength(Pt(l)) => Au::from_pt(l),
CSSLineHeightPercentage(p) => em_size.scale_by(p / 100.0f)
};
line_height
}
GenericRenderBoxClass(_) => {
Au(0)
}
_ => {
fail!(fmt!("Tried to get height of unknown Box variant: %s", box.debug_str()))
}
}
}
// FIXME(eatkinson): this assumes that the tallest box in the line determines the line height
// This might not be the case with some weird text fonts.
fn new_height_for_line(&self, new_box: RenderBox) -> Au {
let box_height = self.box_height(new_box);
if box_height > self.pending_line.bounds.size.height {
box_height
} else {
self.pending_line.bounds.size.height
}
}
/// Computes the position of a line that has only the provided RenderBox.
/// Returns: the bounding rect of the line's green zone (whose origin coincides
/// with the line's origin) and the actual width of the first box after splitting.
fn initial_line_placement (&self, ctx: &LayoutContext, first_box: RenderBox, ceiling: Au) -> (Rect<Au>, Au) {
debug!("LineboxScanner: Trying to place first box of line %?", self.lines.len());
debug!("LineboxScanner: box size: %?", first_box.position().size);
let splitable = first_box.can_split();
let line_is_empty: bool = self.pending_line.range.length() == 0; let line_is_empty: bool = self.pending_line.range.length() == 0;
debug!("LineboxScanner: Trying to append box to line %u (box width: %?, remaining width: \ // Initally, pretend a splitable box has 0 width.
// We will move it later if it has nonzero width
// and that causes problems.
let placement_width = if splitable {
Au(0)
} else {
first_box.position().size.width
};
let mut info = PlacementInfo {
width: placement_width,
height: first_box.position().size.height,
ceiling: ceiling,
max_width: self.flow.position().size.width,
f_type: FloatLeft
};
let line_bounds = self.floats.place_between_floats(&info);
debug!("LineboxScanner: found position for line: %? using placement_info: %?", line_bounds, info);
// Simple case: if the box fits, then we can stop here
if line_bounds.size.width > first_box.position().size.width {
debug!("LineboxScanner: case=box fits");
return (line_bounds, first_box.position().size.width);
}
// If not, but we can't split the box, then we'll place
// the line here and it will overflow.
if !splitable {
debug!("LineboxScanner: case=line doesn't fit, but is unsplittable");
return (line_bounds, first_box.position().size.width);
}
// Otherwise, try and split the box
// FIXME(eatkinson): calling split_to_width here seems excessive and expensive.
// We should find a better abstraction or merge it with the call in
// try_append_to_line.
match first_box.split_to_width(ctx, line_bounds.size.width, line_is_empty) {
CannotSplit(_) => {
error!("LineboxScanner: Tried to split unsplittable render box! %s",
first_box.debug_str());
return (line_bounds, first_box.position().size.width);
}
SplitDidFit(left, right) => {
debug!("LineboxScanner: case=box split and fit");
let actual_box_width = match (left, right) {
(Some(l_box), Some(_)) => l_box.position().size.width,
(Some(l_box), None) => l_box.position().size.width,
(None, Some(r_box)) => r_box.position().size.width,
(None, None) => fail!("This cas makes no sense.")
};
return (line_bounds, actual_box_width);
}
SplitDidNotFit(left, right) => {
// The split didn't fit, but we might be able to
// push it down past floats.
debug!("LineboxScanner: case=box split and fit didn't fit; trying to push it down");
let actual_box_width = match (left, right) {
(Some(l_box), Some(_)) => l_box.position().size.width,
(Some(l_box), None) => l_box.position().size.width,
(None, Some(r_box)) => r_box.position().size.width,
(None, None) => fail!("This cas makes no sense.")
};
info.width = actual_box_width;
let new_bounds = self.floats.place_between_floats(&info);
debug!("LineboxScanner: case=new line position: %?", new_bounds);
return (new_bounds, actual_box_width);
}
}
}
/// Returns false only if we should break the line.
fn try_append_to_line(&mut self, ctx: &LayoutContext, in_box: RenderBox) -> bool {
let line_is_empty: bool = self.pending_line.range.length() == 0;
if line_is_empty {
let (line_bounds, _) = self.initial_line_placement(ctx, in_box, self.cur_y);
self.pending_line.bounds.origin = line_bounds.origin;
self.pending_line.green_zone = line_bounds.size;
}
debug!("LineboxScanner: Trying to append box to line %u (box size: %?, green zone: \
%?): %s", %?): %s",
self.line_spans.len(), self.lines.len(),
in_box_width, in_box.position().size,
remaining_width, self.pending_line.green_zone,
in_box.debug_str()); in_box.debug_str());
if in_box_width <= remaining_width {
let green_zone = self.pending_line.green_zone;
//assert!(green_zone.width >= self.pending_line.bounds.size.width &&
// green_zone.height >= self.pending_line.bounds.size.height,
// "Committed a line that overlaps with floats");
let new_height = self.new_height_for_line(in_box);
if new_height > green_zone.height {
// Uh-oh. Adding this box is going to increase the height,
// and because of that we will collide with some floats.
// We have two options here:
// 1) Move the entire line so that it doesn't collide any more.
// 2) Break the line and put the new box on the next line.
// The problem with option 1 is that we might move the line
// and then wind up breaking anyway, which violates the standard.
// But option 2 is going to look weird sometimes.
// So we'll try to move the line whenever we can, but break
// if we have to.
// First predict where the next line is going to be
let this_line_y = self.pending_line.bounds.origin.y;
let (next_line, first_box_width) = self.initial_line_placement(ctx, in_box, this_line_y);
let next_green_zone = next_line.size;
let new_width = self.pending_line.bounds.size.width + first_box_width;
// Now, see if everything can fit at the new location.
if next_green_zone.width >= new_width && next_green_zone.height >= new_height{
debug!("LineboxScanner: case=adding box collides vertically with floats: moving line");
self.pending_line.bounds.origin = next_line.origin;
self.pending_line.green_zone = next_green_zone;
assert!(!line_is_empty, "Non-terminating line breaking");
self.work_list.add_front(in_box);
return true;
} else {
debug!("LineboxScanner: case=adding box collides vertically with floats: breaking line");
self.work_list.add_front(in_box);
return false;
}
}
// If we're not going to overflow the green zone vertically, we might still do so
// horizontally. We'll try to place the whole box on this line and break somewhere
// if it doesn't fit.
let new_width = self.pending_line.bounds.size.width + in_box.position().size.width;
if(new_width <= green_zone.width){
debug!("LineboxScanner: case=box fits without splitting"); debug!("LineboxScanner: case=box fits without splitting");
self.push_box_to_line(in_box); self.push_box_to_line(in_box);
return true; return true;
} }
if !in_box.can_split() { if !in_box.can_split() {
// force it onto the line anyway, if its otherwise empty
// TODO(Issue #224): signal that horizontal overflow happened? // TODO(Issue #224): signal that horizontal overflow happened?
if line_is_empty { if line_is_empty {
debug!("LineboxScanner: case=box can't split and line %u is empty, so \ debug!("LineboxScanner: case=box can't split and line %u is empty, so \
overflowing.", overflowing.",
self.line_spans.len()); self.lines.len());
self.push_box_to_line(in_box); self.push_box_to_line(in_box);
return true; return true;
} else { } else {
debug!("LineboxScanner: Case=box can't split, not appending."); debug!("LineboxScanner: Case=box can't split, not appending.");
return false; return false;
} }
} } else {
let available_width = green_zone.width - self.pending_line.bounds.size.width;
// not enough width; try splitting? match in_box.split_to_width(ctx, available_width, line_is_empty) {
match in_box.split_to_width(ctx, remaining_width, line_is_empty) { CannotSplit(_) => {
CannotSplit(_) => { error!("LineboxScanner: Tried to split unsplittable render box! %s",
error!("LineboxScanner: Tried to split unsplittable render box! %s", in_box.debug_str());
in_box.debug_str()); return false;
return false;
},
SplitDidFit(left, right) => {
debug!("LineboxScanner: case=split box did fit; deferring remainder box.");
match (left, right) {
(Some(left_box), Some(right_box)) => {
self.push_box_to_line(left_box);
self.work_list.add_front(right_box);
},
(Some(left_box), None) => self.push_box_to_line(left_box),
(None, Some(right_box)) => self.push_box_to_line(right_box),
(None, None) => error!("LineboxScanner: This split case makes no sense!"),
} }
return true; SplitDidFit(left, right) => {
}, debug!("LineboxScanner: case=split box did fit; deferring remainder box.");
SplitDidNotFit(left, right) => {
if line_is_empty {
debug!("LineboxScanner: case=split box didn't fit and line %u is empty, so overflowing and deferring remainder box.",
self.line_spans.len());
// TODO(Issue #224): signal that horizontal overflow happened?
match (left, right) { match (left, right) {
(Some(left_box), Some(right_box)) => { (Some(left_box), Some(right_box)) => {
self.push_box_to_line(left_box); self.push_box_to_line(left_box);
self.work_list.add_front(right_box); self.work_list.add_front(right_box);
},
(Some(left_box), None) => {
self.push_box_to_line(left_box);
}
(None, Some(right_box)) => {
self.push_box_to_line(right_box);
},
(None, None) => {
error!("LineboxScanner: This split case makes no sense!");
} }
(Some(left_box), None) => self.push_box_to_line(left_box),
(None, Some(right_box)) => self.push_box_to_line(right_box),
(None, None) => error!("LineboxScanner: This split case makes no sense!"),
} }
return true; return true;
} else { }
debug!("LineboxScanner: case=split box didn't fit, not appending and deferring original box."); SplitDidNotFit(left, right) => {
self.work_list.add_front(in_box); if line_is_empty {
return false; debug!("LineboxScanner: case=split box didn't fit and line %u is empty, so overflowing and deferring remainder box.",
self.lines.len());
// TODO(Issue #224): signal that horizontal overflow happened?
match (left, right) {
(Some(left_box), Some(right_box)) => {
self.push_box_to_line(left_box);
self.work_list.add_front(right_box);
}
(Some(left_box), None) => {
self.push_box_to_line(left_box);
}
(None, Some(right_box)) => {
self.push_box_to_line(right_box);
}
(None, None) => {
error!("LineboxScanner: This split case makes no sense!");
}
}
return true;
} else {
debug!("LineboxScanner: case=split box didn't fit, not appending and deferring original box.");
self.work_list.add_front(in_box);
return false;
}
} }
} }
} }
@ -285,7 +448,7 @@ impl LineboxScanner {
// unconditional push // unconditional push
fn push_box_to_line(&mut self, box: RenderBox) { fn push_box_to_line(&mut self, box: RenderBox) {
debug!("LineboxScanner: Pushing box b%d to line %u", box.id(), self.line_spans.len()); debug!("LineboxScanner: Pushing box b%d to line %u", box.id(), self.lines.len());
if self.pending_line.range.length() == 0 { if self.pending_line.range.length() == 0 {
assert!(self.new_boxes.len() <= (u16::max_value as uint)); assert!(self.new_boxes.len() <= (u16::max_value as uint));
@ -293,6 +456,8 @@ impl LineboxScanner {
} }
self.pending_line.range.extend_by(1); self.pending_line.range.extend_by(1);
self.pending_line.bounds.size.width = self.pending_line.bounds.size.width + box.position().size.width; self.pending_line.bounds.size.width = self.pending_line.bounds.size.width + box.position().size.width;
self.pending_line.bounds.size.height = Au::max(self.pending_line.bounds.size.height,
box.position().size.height);
self.new_boxes.push(box); self.new_boxes.push(box);
} }
} }
@ -306,7 +471,8 @@ pub struct InlineFlowData {
boxes: ~[RenderBox], boxes: ~[RenderBox],
// vec of ranges into boxes that represents line positions. // vec of ranges into boxes that represents line positions.
// these ranges are disjoint, and are the result of inline layout. // these ranges are disjoint, and are the result of inline layout.
lines: ~[Range], // also some metadata used for positioning lines
lines: ~[LineBox],
// vec of ranges into boxes that represent elements. These ranges // vec of ranges into boxes that represent elements. These ranges
// must be well-nested, and are only related to the content of // must be well-nested, and are only related to the content of
// boxes (not lines). Ranges are only kept for non-leaf elements. // boxes (not lines). Ranges are only kept for non-leaf elements.
@ -425,42 +591,83 @@ impl InlineFlowData {
kid.assign_height(ctx); kid.assign_height(ctx);
} }
// Divide the boxes into lines
// TODO(eatkinson): line boxes need to shrink if there are floats
let mut scanner = LineboxScanner::new(InlineFlow(self));
scanner.scan_for_lines(ctx);
self.common.floats_out = self.common.floats_in.clone();
// TODO(#226): Get the CSS `line-height` property from the containing block's style to // TODO(#226): Get the CSS `line-height` property from the containing block's style to
// determine minimum linebox height. // determine minimum linebox height.
// //
// TODO(#226): Get the CSS `line-height` property from each non-replaced inline element to // TODO(#226): Get the CSS `line-height` property from each non-replaced inline element to
// determine its height for computing linebox height. // determine its height for computing linebox height.
let mut scanner = LineboxScanner::new(InlineFlow(self), self.common.floats_in.clone());
scanner.scan_for_lines(ctx);
let mut cur_y = Au(0); // Now, go through each line and lay out the boxes inside
for self.lines.iter().advance |line| {
// We need to distribute extra width based on text-align.
let mut slack_width = line.green_zone.width - line.bounds.size.width;
if slack_width < Au(0) {
slack_width = Au(0);
}
//assert!(slack_width >= Au(0), "Too many boxes on line");
for self.lines.iter().enumerate().advance |(i, line_span)| { // Get the text alignment.
debug!("assign_height_inline: processing line %u with box span: %?", i, line_span); // TODO(Issue #222): use 'text-align' property from InlineFlow's
// block container, not from the style of the first box child.
let linebox_align;
if line.range.begin() < self.boxes.len() {
let first_box = self.boxes[line.range.begin()];
linebox_align = first_box.text_align();
} else {
// Nothing to lay out, so assume left alignment.
linebox_align = CSSTextAlignLeft;
}
// These coordinates are relative to the left baseline. // Set the box x positions
let mut linebox_bounding_box = Au::zero_rect(); let mut offset_x = line.bounds.origin.x;
let mut linebox_height = Au(0); match linebox_align {
// So sorry, but justified text is more complicated than shuffling linebox coordinates.
// TODO(Issue #213): implement `text-align: justify`
CSSTextAlignLeft | CSSTextAlignJustify => {
for line.range.eachi |i| {
do self.boxes[i].with_mut_base |base| {
base.position.origin.x = offset_x;
offset_x = offset_x + base.position.size.width;
}
}
}
CSSTextAlignCenter => {
offset_x = offset_x + slack_width.scale_by(0.5f);
for line.range.eachi |i| {
do self.boxes[i].with_mut_base |base| {
base.position.origin.x = offset_x;
offset_x = offset_x + base.position.size.width;
}
}
}
CSSTextAlignRight => {
offset_x = offset_x + slack_width;
for line.range.eachi |i| {
do self.boxes[i].with_mut_base |base| {
base.position.origin.x = offset_x;
offset_x = offset_x + base.position.size.width;
}
}
}
};
// Get the baseline offset, assuming that the tallest text box will determine
// the baseline.
let mut baseline_offset = Au(0); let mut baseline_offset = Au(0);
let mut max_height = Au(0);
for line_span.eachi |box_i| { for line.range.eachi |box_i| {
let cur_box = self.boxes[box_i]; let cur_box = self.boxes[box_i];
// Compute the height and bounding box of each box. match cur_box {
let bounding_box = match cur_box {
ImageRenderBoxClass(image_box) => { ImageRenderBoxClass(image_box) => {
let size = image_box.image.get_size(); let size = image_box.image.get_size();
let height = Au::from_px(size.get_or_default(Size2D(0, 0)).height); let height = Au::from_px(size.get_or_default(Size2D(0, 0)).height);
image_box.base.position.size.height = height; image_box.base.position.size.height = height;
if height > linebox_height {
linebox_height = height;
}
image_box.base.position.translate(&Point2D(Au(0), -height)) image_box.base.position.translate(&Point2D(Au(0), -height))
} }
TextRenderBoxClass(text_box) => { TextRenderBoxClass(text_box) => {
@ -484,8 +691,9 @@ impl InlineFlowData {
// calculations. // calculations.
// TODO: this will need to take into account type of line-height // TODO: this will need to take into account type of line-height
// and the vertical-align value. // and the vertical-align value.
if line_height > linebox_height { if line_height > max_height {
linebox_height = line_height; max_height = line_height;
let linebox_height = line.bounds.size.height;
// Offset from the top of the linebox is 1/2 of the leading + ascent // Offset from the top of the linebox is 1/2 of the leading + ascent
baseline_offset = text_box.run.font.metrics.ascent + baseline_offset = text_box.run.font.metrics.ascent +
(linebox_height - em_size).scale_by(0.5f); (linebox_height - em_size).scale_by(0.5f);
@ -493,13 +701,6 @@ impl InlineFlowData {
text_bounds.translate(&Point2D(text_box.base.position.origin.x, Au(0))) text_bounds.translate(&Point2D(text_box.base.position.origin.x, Au(0)))
} }
GenericRenderBoxClass(generic_box) => { GenericRenderBoxClass(generic_box) => {
// TODO(Issue #225): There will be different cases here for `inline-block`
// and other replaced content.
// FIXME(pcwalton): This seems clownshoes; can we remove?
generic_box.position.size.height = Au::from_px(30);
if generic_box.position.size.height > linebox_height {
linebox_height = generic_box.position.size.height;
}
generic_box.position generic_box.position
} }
// FIXME(pcwalton): This isn't very type safe! // FIXME(pcwalton): This isn't very type safe!
@ -508,18 +709,10 @@ impl InlineFlowData {
cur_box.debug_str())) cur_box.debug_str()))
} }
}; };
debug!("assign_height_inline: bounding box for box b%d = %?",
cur_box.id(),
bounding_box);
linebox_bounding_box = linebox_bounding_box.union(&bounding_box);
debug!("assign_height_inline: linebox bounding box = %?", linebox_bounding_box);
} }
// Now go back and adjust the Y coordinates to match the baseline we determined. // Now go back and adjust the Y coordinates to match the baseline we determined.
for line_span.eachi |box_i| { for line.range.eachi |box_i| {
let cur_box = self.boxes[box_i]; let cur_box = self.boxes[box_i];
// TODO(#226): This is completely wrong. We need to use the element's `line-height` // TODO(#226): This is completely wrong. We need to use the element's `line-height`
@ -533,14 +726,20 @@ impl InlineFlowData {
}; };
do cur_box.with_mut_base |base| { do cur_box.with_mut_base |base| {
base.position.origin.y = offset + cur_y; base.position.origin.y = offset + line.bounds.origin.y;
} }
} }
cur_y = cur_y + linebox_height;
} // End of `lines.each` loop. } // End of `lines.each` loop.
self.common.position.size.height = cur_y; self.common.position.size.height =
if self.lines.len() > 0 {
self.lines.last().bounds.origin.y + self.lines.last().bounds.size.height
} else {
Au(0)
};
self.common.floats_out = scanner.floats_out().translate(Point2D(Au(0),
-self.common.position.size.height));
} }
pub fn build_display_list_inline<E:ExtraDisplayListData>(&self, pub fn build_display_list_inline<E:ExtraDisplayListData>(&self,

View file

@ -0,0 +1,62 @@
<html>
<head>
<style type = "text/css">
#float {
float: left;
width: 300px;
height: 500px;
background: blue;
}
#float1 {
float: left;
width: 500px;
height: 75px;
background: red;
}
#float3 {
float: left;
width: 200px;
height: 100px;
background: red;
}
#float2 {
float: right;
width: 100px;
height: 50px;
background: green;
}
#float4 {
float: right;
width: 100px;
height: 250px;
background: green;
}
#float5 {
float: left;
width: 300px;
height: 100px;
background: yellow;
}
#outer {
padding: 100px;
height: 250px;
background: gray;
}
#firstouter {
height: 100px;
}
</style>
</head>
<body>
<div id = "firstouter">
<div id = "float"></div>
</div>
<div id = "outer">
<div id = "float1"></div>
<div id = "float2"></div>
<div id = "float3"></div>
<div id = "float4"></div>
</div>
<div id = "float5"></div>
</body>
</html>