diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/components/main/layout/block.rs | 40 | ||||
-rw-r--r-- | src/components/main/layout/box.rs | 36 | ||||
-rw-r--r-- | src/components/main/layout/box_builder.rs | 48 | ||||
-rw-r--r-- | src/components/main/layout/float.rs | 241 | ||||
-rw-r--r-- | src/components/main/layout/float_context.rs | 258 | ||||
-rw-r--r-- | src/components/main/layout/flow.rs | 28 | ||||
-rw-r--r-- | src/components/main/layout/inline.rs | 55 | ||||
-rw-r--r-- | src/components/main/layout/layout_task.rs | 6 | ||||
-rw-r--r-- | src/components/main/layout/model.rs | 16 | ||||
-rwxr-xr-x | src/components/main/servo.rc | 2 |
10 files changed, 694 insertions, 36 deletions
diff --git a/src/components/main/layout/block.rs b/src/components/main/layout/block.rs index f7f07b14df2..3fa9906423a 100644 --- a/src/components/main/layout/block.rs +++ b/src/components/main/layout/block.rs @@ -8,9 +8,10 @@ use layout::box::{RenderBox}; use layout::context::LayoutContext; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; use layout::display_list_builder::{FlowDisplayListBuilderMethods}; -use layout::flow::{BlockFlow, FlowContext, FlowData, InlineBlockFlow}; +use layout::flow::{BlockFlow, FlowContext, FlowData, InlineBlockFlow, FloatFlow}; use layout::inline::InlineLayout; use layout::model::{MaybeAuto, Specified, Auto}; +use layout::float_context::FloatContext; use core::cell::Cell; use geom::point::Point2D; @@ -72,7 +73,7 @@ impl BlockLayout for FlowContext { fn starts_block_flow(&self) -> bool { match *self { - BlockFlow(*) | InlineBlockFlow(*) => true, + BlockFlow(*) | InlineBlockFlow(*) | FloatFlow(*) => true, _ => false } } @@ -91,14 +92,17 @@ impl BlockFlowData { pub fn bubble_widths_block(@mut self, ctx: &LayoutContext) { let mut min_width = Au(0); let mut pref_width = Au(0); + let mut num_floats = 0; /* find max width from child block contexts */ for BlockFlow(self).each_child |child_ctx| { assert!(child_ctx.starts_block_flow() || child_ctx.starts_inline_flow()); - do child_ctx.with_base |child_node| { + do child_ctx.with_mut_base |child_node| { min_width = geometry::max(min_width, child_node.min_width); pref_width = geometry::max(pref_width, child_node.pref_width); + + num_floats = num_floats + child_node.num_floats; } } @@ -116,6 +120,7 @@ impl BlockFlowData { self.common.min_width = min_width; self.common.pref_width = pref_width; + self.common.num_floats = num_floats; } /// Computes left and right margins and width based on CSS 2.1 secion 10.3.3. @@ -180,6 +185,7 @@ impl BlockFlowData { debug!("Setting root position"); self.common.position.origin = Au::zero_point(); self.common.position.size.width = ctx.screen_size.size.width; + self.common.floats_in = FloatContext::new(self.common.num_floats); } //position was set to the containing block by the flow's parent @@ -240,17 +246,41 @@ impl BlockFlowData { } } - pub fn assign_height_block(@mut self, ctx: &LayoutContext) { + pub fn assign_height_block(@mut self, ctx: &mut LayoutContext) { let mut cur_y = Au(0); let mut top_offset = Au(0); + let mut left_offset = Au(0); for self.box.each |&box| { do box.with_model |model| { top_offset = model.margin.top + model.border.top + model.padding.top; cur_y += top_offset; + left_offset = model.offset(); } } + // TODO(eatkinson): the translation here is probably + // totally wrong. We need to do it right or pages + // with floats will look very strange. + + // Floats for blocks work like this: + // self.floats_in -> child[0].floats_in + // visit child[0] + // child[i-1].floats_out -> child[i].floats_in + // visit child[i] + // repeat until all children are visited. + // last_child.floats_out -> self.floats_out (done at the end of this method) + let mut float_ctx = self.common.floats_in.translate(Point2D(-left_offset, -top_offset)); + for BlockFlow(self).each_child |kid| { + do kid.with_mut_base |child_node| { + child_node.floats_in = float_ctx.clone(); + } + kid.assign_height(ctx); + do kid.with_mut_base |child_node| { + float_ctx = child_node.floats_out.translate(Point2D(Au(0), -child_node.position.size.height)); + } + + } for BlockFlow(self).each_child |kid| { do kid.with_mut_base |child_node| { child_node.position.origin.y = cur_y; @@ -281,6 +311,8 @@ impl BlockFlowData { //TODO(eatkinson): compute heights using the 'height' property. self.common.position.size.height = height + noncontent_height; + + self.common.floats_out = float_ctx.translate(Point2D(left_offset, self.common.position.size.height)); } pub fn build_display_list_block<E:ExtraDisplayListData>(@mut self, diff --git a/src/components/main/layout/box.rs b/src/components/main/layout/box.rs index 141425c30ed..7d7db5410bc 100644 --- a/src/components/main/layout/box.rs +++ b/src/components/main/layout/box.rs @@ -8,7 +8,7 @@ use css::node_style::StyledNode; use layout::context::LayoutContext; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData, ToGfxColor}; use layout::flow::FlowContext; -use layout::model::BoxModel; +use layout::model::{BoxModel, MaybeAuto}; use layout::text; use core::cell::Cell; @@ -370,11 +370,41 @@ pub impl RenderBox { } } + /// Guess the intrinsic width of this box for + /// computation of min and preferred widths. + // + // TODO(eatkinson): this is unspecified in + // CSS 2.1, but we need to do something reasonable + // here. What this function does currently is + // NOT reasonable. + // + // TODO(eatkinson): integrate with + // get_min_width and get_pref_width? + priv fn guess_width (&self) -> Au { + do self.with_base |base| { + if(!base.node.is_element()) { + Au(0) + } else { + + let w = MaybeAuto::from_width(self.style().width(), Au(0)).spec_or_default(Au(0)); + let ml = MaybeAuto::from_margin(self.style().margin_left(), Au(0)).spec_or_default(Au(0)); + let mr = MaybeAuto::from_margin(self.style().margin_right(), Au(0)).spec_or_default(Au(0)); + let pl = base.model.compute_padding_length(self.style().padding_left(), Au(0)); + let pr = base.model.compute_padding_length(self.style().padding_right(), Au(0)); + let bl = base.model.compute_border_width(self.style().border_left_width()); + let br = base.model.compute_border_width(self.style().border_right_width()); + + w + ml + mr + pl + pr + bl + br + } + } + } + /// Returns the *minimum width* of this render box as defined by the CSS specification. fn get_min_width(&self, _: &LayoutContext) -> Au { // FIXME(pcwalton): I think we only need to calculate this if the damage says that CSS // needs to be restyled. - match *self { + + self.guess_width() + match *self { // TODO: This should account for the minimum width of the box element in isolation. // That includes borders, margins, and padding, but not child widths. The block // `FlowContext` will combine the width of this element and that of its children to @@ -397,7 +427,7 @@ pub impl RenderBox { /// Returns the *preferred width* of this render box as defined by the CSS specification. fn get_pref_width(&self, _: &LayoutContext) -> Au { - match *self { + self.guess_width() + match *self { // TODO: This should account for the preferred width of the box element in isolation. // That includes borders, margins, and padding, but not child widths. The block // `FlowContext` will combine the width of this element and that of its children to diff --git a/src/components/main/layout/box_builder.rs b/src/components/main/layout/box_builder.rs index b446f8a7172..8d5134b7c4e 100644 --- a/src/components/main/layout/box_builder.rs +++ b/src/components/main/layout/box_builder.rs @@ -6,6 +6,7 @@ use layout::aux::LayoutAuxMethods; use layout::block::BlockFlowData; +use layout::float::FloatFlowData; use layout::box::{GenericRenderBoxClass, ImageRenderBox, ImageRenderBoxClass, RenderBox}; use layout::box::{RenderBoxBase, RenderBoxType, RenderBox_Generic, RenderBox_Image}; use layout::box::{RenderBox_Text, UnscannedTextRenderBox, UnscannedTextRenderBoxClass}; @@ -22,6 +23,7 @@ use newcss::values::{CSSDisplayTableRowGroup, CSSDisplayTableHeaderGroup, CSSDis use newcss::values::{CSSDisplayTableRow, CSSDisplayTableColumnGroup, CSSDisplayTableColumn}; use newcss::values::{CSSDisplayTableCell, CSSDisplayTableCaption}; use newcss::values::{CSSDisplayNone}; +use newcss::values::{CSSFloatNone}; use script::dom::element::*; use script::dom::node::{AbstractNode, CommentNodeTypeId, DoctypeNodeTypeId}; use script::dom::node::{ElementNodeTypeId, LayoutView, TextNodeTypeId}; @@ -159,6 +161,18 @@ impl BoxGenerator { assert!(block.box.is_none()); block.box = Some(new_box); }, + FloatFlow(float) => { + 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)", + float.common.id, + new_box.id(), + node.debug_str()); + + assert!(float.box.is_none()); + float.box = Some(new_box); + }, _ => warn!("push_node() not implemented for flow f%d", self.flow.id()), } } @@ -349,8 +363,24 @@ pub impl LayoutTreeBuilder { None => None, Some(gen) => Some(gen.flow) }; + + // TODO(eatkinson): use the value of the float property to + // determine whether to float left or right. + let is_float = if (node.is_element()) { + match node.style().float() { + CSSFloatNone => false, + _ => true + } + } else { + false + }; + let new_generator = match (display, parent_generator.flow, sibling_flow) { + (CSSDisplayBlock, BlockFlow(_), _) if is_float => { + self.create_child_generator(node, parent_generator, Flow_Float) + } + (CSSDisplayBlock, BlockFlow(info), _) => match (info.is_root, node.parent_node()) { // If this is the root node, then use the root flow's // context. Otherwise, make a child block context. @@ -360,6 +390,11 @@ pub impl LayoutTreeBuilder { self.create_child_generator(node, parent_generator, Flow_Block) } }, + + (CSSDisplayBlock, FloatFlow(*), _) => { + self.create_child_generator(node, parent_generator, Flow_Block) + } + // Inlines that are children of inlines are part of the same flow (CSSDisplayInline, InlineFlow(*), _) => parent_generator, (CSSDisplayInlineBlock, InlineFlow(*), _) => parent_generator, @@ -371,6 +406,15 @@ pub impl LayoutTreeBuilder { self.create_child_generator(node, parent_generator, Flow_Inline) } + // FIXME(eatkinson): this is bogus. Floats should not be able to split + // inlines. They should be appended as children of the inline flow. + (CSSDisplayInline, _, Some(FloatFlow(*))) | + (CSSDisplayInlineBlock, _, Some(FloatFlow(*))) | + (CSSDisplayInline, FloatFlow(*), _) | + (CSSDisplayInlineBlock, FloatFlow(*), _) => { + self.create_child_generator(node, parent_generator, Flow_Inline) + } + // Inlines whose previous sibling was not a block try to use their // sibling's flow context. (CSSDisplayInline, BlockFlow(*), _) | @@ -383,8 +427,6 @@ pub impl LayoutTreeBuilder { // TODO(eatkinson): blocks that are children of inlines need // to split their parent flows. - // - // TODO(eatkinson): floats and positioned elements. _ => parent_generator }; @@ -520,7 +562,7 @@ pub impl LayoutTreeBuilder { let result = match ty { Flow_Absolute => AbsoluteFlow(@mut info), Flow_Block => BlockFlow(@mut BlockFlowData::new(info)), - Flow_Float => FloatFlow(@mut info), + Flow_Float => FloatFlow(@mut FloatFlowData::new(info)), Flow_InlineBlock => InlineBlockFlow(@mut info), Flow_Inline => InlineFlow(@mut InlineFlowData::new(info)), Flow_Root => BlockFlow(@mut BlockFlowData::new_root(info)), diff --git a/src/components/main/layout/float.rs b/src/components/main/layout/float.rs new file mode 100644 index 00000000000..d1ea3c3fdc8 --- /dev/null +++ b/src/components/main/layout/float.rs @@ -0,0 +1,241 @@ +/* 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 layout::box::{RenderBox}; +use layout::context::LayoutContext; +use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; +use layout::display_list_builder::{FlowDisplayListBuilderMethods}; +use layout::flow::{FloatFlow, FlowData}; +use layout::model::{MaybeAuto}; +use layout::float_context::{FloatContext, PlacementInfo, FloatLeft}; + +use core::cell::Cell; +use geom::point::Point2D; +use geom::rect::Rect; +use gfx::display_list::DisplayList; +use gfx::geometry::Au; +use gfx::geometry; +use servo_util::tree::{TreeNodeRef, TreeUtils}; + +pub struct FloatFlowData { + /// Data common to all flows. + common: FlowData, + + /// The associated render box. + box: Option<RenderBox>, + + containing_width: Au, + + /// Parent clobbers our position, so store it separately + rel_pos: Point2D<Au>, + + /// Index into the box list for inline floats + index: Option<uint>, + +} + +impl FloatFlowData { + pub fn new(common: FlowData) -> FloatFlowData { + FloatFlowData { + common: common, + containing_width: Au(0), + box: None, + index: None, + rel_pos: Point2D(Au(0), Au(0)), + } + } + + pub fn teardown(&mut self) { + self.common.teardown(); + for self.box.each |box| { + box.teardown(); + } + self.box = None; + self.index = None; + } +} + +impl FloatFlowData { + pub fn bubble_widths_float(@mut self, ctx: &LayoutContext) { + let mut min_width = Au(0); + let mut pref_width = Au(0); + + self.common.num_floats = 1; + + for FloatFlow(self).each_child |child_ctx| { + //assert!(child_ctx.starts_block_flow() || child_ctx.starts_inline_flow()); + + do child_ctx.with_mut_base |child_node| { + min_width = geometry::max(min_width, child_node.min_width); + pref_width = geometry::max(pref_width, child_node.pref_width); + child_node.floats_in = FloatContext::new(child_node.num_floats); + } + } + + self.box.map(|&box| { + let style = box.style(); + do box.with_model |model| { + model.compute_borders(style) + } + + min_width = min_width.add(&box.get_min_width(ctx)); + pref_width = pref_width.add(&box.get_pref_width(ctx)); + }); + + self.common.min_width = min_width; + self.common.pref_width = pref_width; + } + + pub fn assign_widths_float(@mut self, _: &LayoutContext) { + debug!("assign_widths_float: assigning width for flow %?", self.common.id); + // position.size.width is set by parent even though we don't know + // position.origin yet. + let mut remaining_width = self.common.position.size.width; + self.containing_width = remaining_width; + let mut x_offset = Au(0); + + for self.box.each |&box| { + let style = box.style(); + do box.with_model |model| { + // Can compute padding here since we know containing block width. + model.compute_padding(style, remaining_width); + + // Margins for floats are 0 if auto. + let margin_top = MaybeAuto::from_margin(style.margin_top(), + remaining_width).spec_or_default(Au(0)); + let margin_bottom = MaybeAuto::from_margin(style.margin_bottom(), + remaining_width).spec_or_default(Au(0)); + let margin_left = MaybeAuto::from_margin(style.margin_left(), + remaining_width).spec_or_default(Au(0)); + let margin_right = MaybeAuto::from_margin(style.margin_right(), + remaining_width).spec_or_default(Au(0)); + + + + let shrink_to_fit = geometry::min(self.common.pref_width, + geometry::max(self.common.min_width, + remaining_width)); + + + let width = MaybeAuto::from_width(style.width(), + remaining_width).spec_or_default(shrink_to_fit); + debug!("assign_widths_float -- width: %?", width); + + model.margin.top = margin_top; + model.margin.right = margin_right; + model.margin.bottom = margin_bottom; + model.margin.left = margin_left; + + x_offset = model.offset(); + remaining_width = width; + } + + do box.with_mut_base |base| { + //The associated box is the border box of this flow + base.position.origin.x = base.model.margin.left; + + let pb = base.model.padding.left + base.model.padding.right + + base.model.border.left + base.model.border.right; + base.position.size.width = remaining_width + pb; + } + } + + self.common.position.size.width = remaining_width; + + for FloatFlow(self).each_child |kid| { + //assert!(kid.starts_block_flow() || kid.starts_inline_flow()); + + do kid.with_mut_base |child_node| { + child_node.position.origin.x = x_offset; + child_node.position.size.width = remaining_width; + } + } + } + + pub fn assign_height_float(@mut self, ctx: &mut LayoutContext) { + for FloatFlow(self).each_child |kid| { + kid.assign_height(ctx); + } + + let mut cur_y = Au(0); + let mut top_offset = Au(0); + + for self.box.each |&box| { + do box.with_model |model| { + top_offset = model.margin.top + model.border.top + model.padding.top; + cur_y += top_offset; + } + } + + for FloatFlow(self).each_child |kid| { + do kid.with_mut_base |child_node| { + child_node.position.origin.y = cur_y; + cur_y += child_node.position.size.height; + } + } + + let mut height = cur_y - top_offset; + + let mut noncontent_height = Au(0); + self.box.map(|&box| { + do box.with_mut_base |base| { + //The associated box is the border box of this flow + base.position.origin.y = base.model.margin.top; + + noncontent_height = base.model.padding.top + base.model.padding.bottom + + base.model.border.top + base.model.border.bottom; + base.position.size.height = height + noncontent_height; + + noncontent_height += base.model.margin.top + base.model.margin.bottom; + } + }); + + + //TODO(eatkinson): compute heights properly using the 'height' property. + for self.box.each |&box| { + + let height_prop = + MaybeAuto::from_height(box.style().height(), Au(0)).spec_or_default(Au(0)); + + height = geometry::max(height, height_prop) + noncontent_height; + debug!("assign_height_float -- height: %?", height); + do box.with_mut_base |base| { + base.position.size.height = height; + } + } + + let info = PlacementInfo { + width: self.common.position.size.width, + height: height, + ceiling: Au(0), + max_width: self.containing_width, + f_type: FloatLeft, + }; + + // Place the float and return the FloatContext back to the parent flow. + // After, grab the position and use that to set our position. + self.common.floats_out = self.common.floats_in.add_float(&info); + self.rel_pos = self.common.floats_out.last_float_pos(); + } + + pub fn build_display_list_float<E:ExtraDisplayListData>(@mut self, + builder: &DisplayListBuilder, + dirty: &Rect<Au>, + offset: &Point2D<Au>, + list: &Cell<DisplayList<E>>) { + + let offset = *offset + self.rel_pos; + self.box.map(|&box| { + box.build_display_list(builder, dirty, &offset, list) + }); + + + // go deeper into the flow tree + let flow = FloatFlow(self); + for flow.each_child |child| { + flow.build_display_list_for_child(builder, child, dirty, &offset, list) + } + } +} + diff --git a/src/components/main/layout/float_context.rs b/src/components/main/layout/float_context.rs new file mode 100644 index 00000000000..50fbb5a452b --- /dev/null +++ b/src/components/main/layout/float_context.rs @@ -0,0 +1,258 @@ +/* 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::size::Size2D; +use geom::rect::Rect; +use gfx::geometry::{Au, max, min}; +use core::util::replace; + +pub enum FloatType{ + FloatLeft, + FloatRight +} + +struct FloatContextBase{ + float_data: ~[Option<FloatData>], + floats_used: uint, + max_y : Au, + offset: Point2D<Au> +} + +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: &fn(&mut FloatContextBase) -> R) -> R { + match *self { + Invalid => fail!("Float context no longer available"), + Valid(ref mut base) => callback(base) + } + } + + #[inline(always)] + pub fn with_base<R>(&self, callback: &fn(&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 { + do 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>> { + do self.with_base |base| { + base.available_rect(top, height, max_x) + } + } + + #[inline(always)] + pub fn add_float(&mut self, info: &PlacementInfo) -> FloatContext{ + do self.with_mut_base |base| { + base.add_float(info); + } + replace(self, Invalid) + } + + #[inline(always)] + pub fn last_float_pos(&mut self) -> Point2D<Au> { + do self.with_base |base| { + base.last_float_pos() + } + } +} + +impl FloatContextBase{ + fn new(num_floats: uint) -> FloatContextBase { + debug!("Creating float context of size %?", num_floats); + let new_data = vec::from_elem(num_floats, None); + FloatContextBase { + float_data: new_data, + floats_used: 0, + max_y: Au(0), + offset: Point2D(Au(0), Au(0)) + } + } + + fn translate(&mut self, trans: Point2D<Au>) { + 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[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)) + } + + debug!("available_rect: trying to find space at %?", top); + + let top = top - self.offset.y; + + // Relevant dimensions for the right-most left float + let mut (max_left, l_top, l_bottom) = (Au(0) - self.offset.x, None, None); + // Relevant dimensions for the left-most right float + let mut (min_right, r_top, r_bottom) = (max_x - self.offset.x, None, None); + + // Find the float collisions for the given vertical range. + for self.float_data.each |float| { + match *float{ + None => (), + Some(data) => { + let float_pos = data.bounds.origin; + let float_size = data.bounds.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); + } + } + 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); + } + } + } + } + }; + } + + // 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. + 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(r_top, r_bottom, l_top, l_bottom), + + (None, None, Some(l_top), Some(l_bottom)) => (l_top, l_bottom), + (Some(r_top), Some(r_bottom), None, None) => (r_top, r_bottom), + (None, None, None, None) => return None, + _ => fail!("Reached unreachable state when computing float area") + }; + + // When the window is smaller than the float, we will return a rect + // with negative width. + assert!(max_left < min_right + || max_left > max_x - self.offset.x + || min_right < Au(0) - self.offset.x + ,"Float position error"); + + //TODO(eatkinson): do we need to do something similar for heights? + 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) { + debug!("Floats_used: %?, Floats available: %?", self.floats_used, self.float_data.len()); + assert!(self.floats_used < self.float_data.len() && + self.float_data[self.floats_used].is_none()); + + let new_float = FloatData { + bounds: Rect { + origin: self.place_float(info) - self.offset, + size: Size2D(info.width, info.height) + }, + f_type: info.f_type + }; + self.float_data[self.floats_used] = Some(new_float); + self.floats_used += 1; + } + + /// Given necessary info, finds the position of the float in + /// LOCAL COORDINATES. i.e. must be translated before placed + /// in the float list + fn place_float(&self, info: &PlacementInfo) -> Point2D<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 = max(info.ceiling, self.max_y + self.offset.y); + 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(eatknson): integrate with overflow + None => return Point2D(Au(0), float_y), + 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) { + return Point2D(rect.origin.x, float_y); + } + + // Try to place at the next-lowest location. + // Need to be careful of fencepost errors. + float_y = rect.origin.y + rect.size.height; + } + } + } + } +} + diff --git a/src/components/main/layout/flow.rs b/src/components/main/layout/flow.rs index 900a7b0a130..e2c14ce22e9 100644 --- a/src/components/main/layout/flow.rs +++ b/src/components/main/layout/flow.rs @@ -26,10 +26,12 @@ /// similar methods. use layout::block::BlockFlowData; +use layout::float::FloatFlowData; use layout::box::RenderBox; use layout::context::LayoutContext; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; use layout::inline::{InlineFlowData}; +use layout::float_context::{FloatContext, Invalid}; use core::cell::Cell; use geom::point::Point2D; @@ -44,7 +46,7 @@ use servo_util::tree::{TreeNode, TreeNodeRef, TreeUtils}; pub enum FlowContext { AbsoluteFlow(@mut FlowData), BlockFlow(@mut BlockFlowData), - FloatFlow(@mut FlowData), + FloatFlow(@mut FloatFlowData), InlineBlockFlow(@mut FlowData), InlineFlow(@mut InlineFlowData), TableFlow(@mut FlowData), @@ -70,10 +72,10 @@ impl FlowContext { pub fn teardown(&self) { match *self { AbsoluteFlow(data) | - FloatFlow(data) | InlineBlockFlow(data) | TableFlow(data) => data.teardown(), BlockFlow(data) => data.teardown(), + FloatFlow(data) => data.teardown(), InlineFlow(data) => data.teardown() } } @@ -110,7 +112,7 @@ impl TreeNodeRef<FlowData> for FlowContext { BlockFlow(info) => { callback(&info.common) } - FloatFlow(info) => callback(info), + FloatFlow(info) => callback(&info.common), InlineBlockFlow(info) => callback(info), InlineFlow(info) => { callback(&info.common) @@ -124,7 +126,7 @@ impl TreeNodeRef<FlowData> for FlowContext { BlockFlow(info) => { callback(&mut info.common) } - FloatFlow(info) => callback(info), + FloatFlow(info) => callback(&mut info.common), InlineBlockFlow(info) => callback(info), InlineFlow(info) => { callback(&mut info.common) @@ -156,6 +158,9 @@ pub struct FlowData { min_width: Au, pref_width: Au, position: Rect<Au>, + floats_in: FloatContext, + floats_out: FloatContext, + num_floats: uint, } impl TreeNode<FlowContext> for FlowData { @@ -216,6 +221,9 @@ impl FlowData { min_width: Au(0), pref_width: Au(0), position: Au::zero_rect(), + floats_in: Invalid, + floats_out: Invalid, + num_floats: 0, } } } @@ -264,6 +272,7 @@ impl<'self> FlowContext { match *self { BlockFlow(info) => info.bubble_widths_block(ctx), InlineFlow(info) => info.bubble_widths_inline(ctx), + FloatFlow(info) => info.bubble_widths_float(ctx), _ => fail!(fmt!("Tried to bubble_widths of flow: f%d", self.id())) } } @@ -272,6 +281,7 @@ impl<'self> FlowContext { match *self { BlockFlow(info) => info.assign_widths_block(ctx), InlineFlow(info) => info.assign_widths_inline(ctx), + FloatFlow(info) => info.assign_widths_float(ctx), _ => fail!(fmt!("Tried to assign_widths of flow: f%d", self.id())) } } @@ -280,6 +290,7 @@ impl<'self> FlowContext { match *self { BlockFlow(info) => info.assign_height_block(ctx), InlineFlow(info) => info.assign_height_inline(ctx), + FloatFlow(info) => info.assign_height_float(ctx), _ => fail!(fmt!("Tried to assign_height of flow: f%d", self.id())) } } @@ -296,6 +307,7 @@ impl<'self> FlowContext { match *self { BlockFlow(info) => info.build_display_list_block(builder, dirty, offset, list), InlineFlow(info) => info.build_display_list_inline(builder, dirty, offset, list), + FloatFlow(info) => info.build_display_list_float(builder, dirty, offset, list), _ => fail!(fmt!("Tried to build_display_list_recurse of flow: %?", self)) } } @@ -408,11 +420,17 @@ impl<'self> FlowContext { None => ~"BlockFlow", } }, + FloatFlow(float) => { + match float.box { + Some(box) => fmt!("FloatFlow(box=b%d)", box.id()), + None => ~"FloatFlow", + } + }, _ => ~"(Unknown flow)" }; do self.with_base |base| { - fmt!("f%? %? size %?", base.id, repr, base.position) + fmt!("f%? %? floats %? size %?", base.id, repr, base.num_floats, base.position) } } } diff --git a/src/components/main/layout/inline.rs b/src/components/main/layout/inline.rs index 1e14c36dec9..4894da2fb1c 100644 --- a/src/components/main/layout/inline.rs +++ b/src/components/main/layout/inline.rs @@ -10,6 +10,7 @@ use layout::context::LayoutContext; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; use layout::flow::{FlowContext, FlowData, InlineFlow}; use layout::text::{UnscannedMethods, adapt_textbox_with_range}; +use layout::float_context::FloatContext; use core::util; use geom::{Point2D, Rect, Size2D}; @@ -25,6 +26,7 @@ use newcss::values::{CSSLineHeightNormal, CSSLineHeightNumber, CSSLineHeightLeng use servo_util::range::Range; use std::deque::Deque; +use servo_util::tree::{TreeNodeRef, TreeUtils}; /* Lineboxes are represented as offsets into the child list, rather than @@ -394,7 +396,7 @@ impl TextRunScanner { struct PendingLine { range: Range, - width: Au + bounds: Rect<Au> } struct LineboxScanner { @@ -413,8 +415,8 @@ impl LineboxScanner { flow: inline, new_boxes: ~[], work_list: @mut Deque::new(), - pending_line: PendingLine {mut range: Range::empty(), mut width: Au(0)}, - line_spans: ~[] + pending_line: PendingLine {mut range: Range::empty(), mut bounds: Rect(Point2D(Au(0), Au(0)), Size2D(Au(0), Au(0)))}, + line_spans: ~[], } } @@ -427,7 +429,7 @@ impl LineboxScanner { fn reset_linebox(&mut self) { self.pending_line.range.reset(0,0); - self.pending_line.width = Au(0); + self.pending_line.bounds = Rect(Point2D(Au(0), Au(0)), Size2D(Au(0), Au(0))); } pub fn scan_for_lines(&mut self, ctx: &LayoutContext) { @@ -508,7 +510,7 @@ impl LineboxScanner { linebox_align = CSSTextAlignLeft; } - let slack_width = self.flow.position().size.width - self.pending_line.width; + 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` @@ -548,7 +550,7 @@ impl LineboxScanner { // return value: whether any box was appended. fn try_append_to_line(&mut self, ctx: &LayoutContext, in_box: RenderBox) -> bool { - let remaining_width = self.flow.position().size.width - self.pending_line.width; + let remaining_width = self.flow.position().size.width - self.pending_line.bounds.size.width; let in_box_width = in_box.position().size.width; let line_is_empty: bool = self.pending_line.range.length() == 0; @@ -639,7 +641,7 @@ impl LineboxScanner { self.pending_line.range.reset(self.new_boxes.len(), 0); } self.pending_line.range.extend_by(1); - self.pending_line.width += box.position().size.width; + self.pending_line.bounds.size.width += box.position().size.width; self.new_boxes.push(box); } } @@ -696,6 +698,15 @@ impl InlineFlowData { pub fn bubble_widths_inline(@mut self, ctx: &mut LayoutContext) { let mut scanner = TextRunScanner::new(); scanner.scan_for_runs(ctx, InlineFlow(self)); + let mut num_floats = 0; + + for InlineFlow(self).each_child |kid| { + do kid.with_mut_base |base| { + num_floats += base.num_floats; + base.floats_in = FloatContext::new(base.num_floats); + } + } + { let this = &mut *self; @@ -711,12 +722,13 @@ impl InlineFlowData { this.common.min_width = min_width; this.common.pref_width = pref_width; + this.common.num_floats = num_floats; } } /// Recursively (top-down) determines the actual width of child contexts and boxes. When called /// on this context, the context has had its width set by the parent context. - pub fn assign_widths_inline(@mut self, ctx: &mut LayoutContext) { + pub fn assign_widths_inline(@mut self, _: &mut LayoutContext) { // Initialize content box widths if they haven't been initialized already. // // TODO: Combine this with `LineboxScanner`'s walk in the box list, or put this into @@ -745,9 +757,11 @@ impl InlineFlowData { } // End of for loop. } - let mut scanner = LineboxScanner::new(InlineFlow(self)); - scanner.scan_for_lines(ctx); - + for InlineFlow(self).each_child |kid| { + do kid.with_mut_base |base| { + base.position.size.width = self.common.position.size.width; + } + } // There are no child contexts, so stop here. // TODO(Issue #225): once there are 'inline-block' elements, this won't be @@ -757,7 +771,18 @@ impl InlineFlowData { // 'inline-block' box that created this flow before recursing. } - pub fn assign_height_inline(&mut self, _: &mut LayoutContext) { + pub fn assign_height_inline(@mut self, ctx: &mut LayoutContext) { + + for InlineFlow(self).each_child |kid| { + kid.assign_height(ctx); + } + + + // 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 // determine minimum linebox height. // @@ -774,10 +799,8 @@ impl InlineFlowData { let mut linebox_height = Au(0); let mut baseline_offset = Au(0); - let boxes = &mut self.boxes; - for line_span.eachi |box_i| { - let cur_box = boxes[box_i]; // FIXME: borrow checker workaround + let cur_box = self.boxes[box_i]; // Compute the height and bounding box of each box. let bounding_box = match cur_box { @@ -849,7 +872,7 @@ impl InlineFlowData { // Now go back and adjust the Y coordinates to match the baseline we determined. for line_span.eachi |box_i| { - let cur_box = 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` // when calculating line box height. Then we should go back over and set Y offsets diff --git a/src/components/main/layout/layout_task.rs b/src/components/main/layout/layout_task.rs index 0389f7135ce..0d683ec25f4 100644 --- a/src/components/main/layout/layout_task.rs +++ b/src/components/main/layout/layout_task.rs @@ -225,9 +225,9 @@ impl Layout { for layout_root.traverse_preorder |flow| { flow.assign_widths(&mut layout_ctx); }; - for layout_root.traverse_postorder |flow| { - flow.assign_height(&mut layout_ctx); - }; + + // For now, this is an inorder traversal + layout_root.assign_height(&mut layout_ctx); } // Build the display list if necessary, and send it to the renderer. diff --git a/src/components/main/layout/model.rs b/src/components/main/layout/model.rs index 37195aa757e..bd465db83b9 100644 --- a/src/components/main/layout/model.rs +++ b/src/components/main/layout/model.rs @@ -20,6 +20,7 @@ use newcss::units::{Em, Pt, Px}; use newcss::values::{CSSBorderWidth, CSSBorderWidthLength, CSSBorderWidthMedium}; use newcss::values::{CSSBorderWidthThick, CSSBorderWidthThin}; use newcss::values::{CSSWidth, CSSWidthLength, CSSWidthPercentage, CSSWidthAuto}; +use newcss::values::{CSSHeight, CSSHeightLength, CSSHeightPercentage, CSSHeightAuto}; use newcss::values::{CSSMargin, CSSMarginLength, CSSMarginPercentage, CSSMarginAuto}; use newcss::values::{CSSPadding, CSSPaddingLength, CSSPaddingPercentage}; /// Encapsulates the borders, padding, and margins, which we collectively call the "box model". @@ -61,6 +62,17 @@ impl MaybeAuto{ } } + pub fn from_height(height: CSSHeight, cb_height: Au) -> MaybeAuto{ + match height { + CSSHeightAuto => Auto, + CSSHeightPercentage(percent) => Specified(cb_height.scale_by(percent/100.0)), + //FIXME(eatkinson): Compute pt and em values properly + CSSHeightLength(Px(v)) | + CSSHeightLength(Pt(v)) | + CSSHeightLength(Em(v)) => Specified(Au::from_frac_px(v)), + } + } + pub fn spec_or_default(&self, default: Au) -> Au{ match *self{ Auto => default, @@ -112,7 +124,7 @@ impl BoxModel { } /// Helper function to compute the border width in app units from the CSS border width. - priv fn compute_border_width(&self, width: CSSBorderWidth) -> Au { + pub fn compute_border_width(&self, width: CSSBorderWidth) -> Au { match width { CSSBorderWidthLength(Px(v)) | CSSBorderWidthLength(Em(v)) | @@ -126,7 +138,7 @@ impl BoxModel { } } - fn compute_padding_length(&self, padding: CSSPadding, content_box_width: Au) -> Au { + pub fn compute_padding_length(&self, padding: CSSPadding, content_box_width: Au) -> Au { match padding { CSSPaddingLength(Px(v)) | CSSPaddingLength(Pt(v)) | diff --git a/src/components/main/servo.rc b/src/components/main/servo.rc index 99073d701a6..4cdfb710ee5 100755 --- a/src/components/main/servo.rc +++ b/src/components/main/servo.rc @@ -73,6 +73,8 @@ pub mod layout { pub mod inline; pub mod model; pub mod text; + pub mod float_context; + pub mod float; mod aux; } |