diff options
21 files changed, 2852 insertions, 307 deletions
diff --git a/src/components/main/layout/block.rs b/src/components/main/layout/block.rs index a69ec3d42c3..53585c47df7 100644 --- a/src/components/main/layout/block.rs +++ b/src/components/main/layout/block.rs @@ -362,6 +362,18 @@ impl BlockFlow { } } + pub fn from_node_and_box(node: &ThreadSafeLayoutNode, + box_: Box) + -> BlockFlow { + BlockFlow { + base: BaseFlow::new((*node).clone()), + box_: Some(box_), + is_root: false, + static_y_offset: Au::new(0), + float: None + } + } + pub fn float_from_node(constructor: &mut FlowConstructor, node: &ThreadSafeLayoutNode, float_kind: FloatKind) @@ -433,7 +445,7 @@ impl BlockFlow { } /// Return this flow's box. - fn box_<'a>(&'a mut self) -> &'a mut Box { + pub fn box_<'a>(&'a mut self) -> &'a mut Box { match self.box_ { Some(ref mut box_) => box_, None => fail!("BlockFlow: no principal box found") @@ -625,47 +637,36 @@ impl BlockFlow { self.base.fixed_descendants.static_y_offsets = fixed_descendant_y_offsets; } - /// Assign height for current flow. - /// - /// + Collapse margins for flow's children and set in-flow child flows' - /// y-coordinates now that we know their heights. - /// + Calculate and set the height of the current flow. - /// + Calculate height, vertical margins, and y-coordinate for the flow's - /// box. Ideally, this should be calculated using CSS Section 10.6.7 - /// - /// For absolute flows, store the calculated content height for the flow. - /// Defer the calculation of the other values till a later traversal. - /// - /// inline(always) because this is only ever called by in-order or non-in-order top-level - /// methods - #[inline(always)] - fn assign_height_block_base(&mut self, ctx: &mut LayoutContext, inorder: bool) { - let mut cur_y = Au::new(0); - let mut clearance = Au::new(0); - // Offset to content edge of box_ - let mut top_offset = Au::new(0); - let mut bottom_offset = Au::new(0); - let mut left_offset = Au::new(0); - - for box_ in self.box_.iter() { - // Note: Ignoring clearance for absolute flows as of now. - if !self.is_absolutely_positioned() { - clearance = match box_.clear() { - None => Au::new(0), - Some(clear) => { - self.base.floats.clearance(clear) - } + /// Calculate clearance, top_offset, bottom_offset, and left_offset for the box. + /// If `ignore_clear` is true, clearance does not need to be calculated. + pub fn initialize_offsets(&mut self, ignore_clear: bool) -> (Au, Au, Au, Au) { + match self.box_ { + None => (Au(0), Au(0), Au(0), Au(0)), + Some(ref box_) => { + let clearance = match box_.clear() { + Some(clear) if !ignore_clear => self.base.floats.clearance(clear), + _ => Au::new(0) }; - } - top_offset = clearance + box_.margin.get().top + box_.border.get().top + - box_.padding.get().top; - cur_y = cur_y + top_offset; - bottom_offset = box_.margin.get().bottom + box_.border.get().bottom + - box_.padding.get().bottom; - left_offset = box_.offset(); + // Offsets to content edge of box_ + let top_offset = clearance + box_.top_offset(); + let bottom_offset = box_.bottom_offset(); + let left_offset = box_.left_offset(); + + (clearance, top_offset, bottom_offset, left_offset) + } } + } + /// In case of inorder assign_height traversal and not absolute flow, + /// 'assign_height's of all children are visited + /// and Float info is shared between adjacent children. + /// Float info of the last child is saved in parent flow. + pub fn handle_children_floats_if_necessary(&mut self, + ctx: &mut LayoutContext, + inorder: bool, + left_offset: Au, + top_offset: Au) { // Note: Ignoring floats for absolute flow as of now. if inorder && !self.is_absolutely_positioned() { // Floats for blocks work like this: @@ -684,9 +685,41 @@ impl BlockFlow { } self.base.floats = floats; } + } + + /// Compute margin_top and margin_bottom. Also, it is decided whether top margin and + /// bottom margin are collapsible according to CSS 2.1 § 8.3.1. + pub fn precompute_margin(&mut self) -> (Au, Au, bool, bool) { + match self.box_ { + // Margins for an absolutely positioned element do not collapse with + // its children. + Some(ref box_) if !self.is_absolutely_positioned() => { + let top_margin_collapsible = !self.is_root && + box_.border.get().top == Au(0) && + box_.padding.get().top == Au(0); + + let bottom_margin_collapsible = !self.is_root && + box_.border.get().bottom == Au(0) && + box_.padding.get().bottom == Au(0); + + let margin_top = box_.margin.get().top; + let margin_bottom = box_.margin.get().bottom; + + (margin_top, margin_bottom, top_margin_collapsible, bottom_margin_collapsible) + }, + _ => (Au(0), Au(0), false, false) + } + } - // The amount of margin that we can potentially collapse with - let mut collapsible = Au::new(0); + /// Compute collapsed margins between adjacent children or between the first/last child and parent + /// according to CSS 2.1 § 8.3.1. Current y position(cur_y) is continually updated for collapsing result. + pub fn compute_margin_collapse(&mut self, + cur_y: &mut Au, + top_offset: &mut Au, + margin_top: &mut Au, + margin_bottom: &mut Au, + top_margin_collapsible: bool, + bottom_margin_collapsible: bool) -> Au { // How much to move up by to get to the beginning of // current kid flow. // Example: if previous sibling's margin-bottom is 20px and your @@ -694,55 +727,38 @@ impl BlockFlow { // will be at the bottom margin edge of the previous sibling, we have // to move up by 12px to get to our top margin edge. So, `collapsing` // will be set to 12px - let mut collapsing = Au::new(0); - let mut margin_top = Au::new(0); - let mut margin_bottom = Au::new(0); - let mut top_margin_collapsible = false; - let mut bottom_margin_collapsible = false; let mut first_in_flow = true; - // Margins for an absolutely positioned element do not collapse with - // its children. - if !self.is_absolutely_positioned() { - for box_ in self.box_.iter() { - if !self.is_root() && box_.border.get().top == Au(0) - && box_.padding.get().top == Au(0) { - - collapsible = box_.margin.get().top; - top_margin_collapsible = true; - } - if !self.is_root() && box_.border.get().bottom == Au(0) && - box_.padding.get().bottom == Au(0) { - bottom_margin_collapsible = true; - } - margin_top = box_.margin.get().top; - margin_bottom = box_.margin.get().bottom; - } - } + let mut collapsing = Au::new(0); + // The amount of margin that we can potentially collapse with + let mut collapsible = if top_margin_collapsible { + *margin_top + } else { + Au(0) + }; // At this point, cur_y is at the content edge of the flow's box_ for kid in self.base.child_iter() { // At this point, cur_y is at bottom margin edge of previous kid - if kid.is_absolutely_positioned() { // Assume that the `hypothetical box` for an absolute flow // starts immediately after the bottom margin edge of the // previous flow. - kid.as_block().base.position.origin.y = cur_y; + kid.as_block().base.position.origin.y = *cur_y; // Skip the collapsing for absolute flow kids and continue // with the next flow. } else { kid.collapse_margins(top_margin_collapsible, &mut first_in_flow, - &mut margin_top, - &mut top_offset, + margin_top, + top_offset, &mut collapsing, &mut collapsible); let child_node = flow::mut_base(kid); - cur_y = cur_y - collapsing; + *cur_y = *cur_y - collapsing; // At this point, after moving up by `collapsing`, cur_y is at the // top margin edge of kid - child_node.position.origin.y = cur_y; - cur_y = cur_y + child_node.position.size.height; + child_node.position.origin.y = *cur_y; + *cur_y = *cur_y + child_node.position.size.height; // At this point, cur_y is at the bottom margin edge of kid } } @@ -754,60 +770,41 @@ impl BlockFlow { // The bottom margin for an absolutely positioned element does not // collapse even with its children. collapsing = if bottom_margin_collapsible && !self.is_absolutely_positioned() { - if margin_bottom < collapsible { - margin_bottom = collapsible; + if *margin_bottom < collapsible { + *margin_bottom = collapsible; } collapsible } else { Au::new(0) }; - // TODO: A box's own margins collapse if the 'min-height' property is zero, and it has neither - // top or bottom borders nor top or bottom padding, and it has a 'height' of either 0 or 'auto', - // and it does not contain a line box, and all of its in-flow children's margins (if any) collapse. - - let screen_height = ctx.screen_size.height; - - let mut height = if self.is_root() { - // FIXME(pcwalton): The max is taken here so that you can scroll the page, but this is - // not correct behavior according to CSS 2.1 § 10.5. Instead I think we should treat - // the root element as having `overflow: scroll` and use the layers-based scrolling - // infrastructure to make it scrollable. - Au::max(screen_height, cur_y) - } else { - // (cur_y - collapsing) will get you the the bottom margin-edge of - // the bottom-most child. - // top_offset: top margin-edge of the topmost child. - // hence, height = content height - cur_y - top_offset - collapsing - }; + collapsing + } + /// For an absolutely positioned element, store the content height for use in calculating + /// the absolute flow's dimensions later. + pub fn store_content_height_if_absolutely_positioned(&mut self, + height: Au) -> bool { if self.is_absolutely_positioned() { - // Store the content height for use in calculating the absolute - // flow's dimensions later. for box_ in self.box_.iter() { let mut temp_position = box_.border_box.get(); temp_position.size.height = height; box_.border_box.set(temp_position); } - return; - } - - for box_ in self.box_.iter() { - let style = box_.style(); - - // At this point, `height` is the height of the containing block, so passing `height` - // as the second argument here effectively makes percentages relative to the containing - // block per CSS 2.1 § 10.5. - height = match MaybeAuto::from_style(style.Box.get().height, height) { - Auto => height, - Specified(value) => value - }; + return true; } + false + } + /// Compute the box height and set border_box and margin of the box. + pub fn compute_height_position(&mut self, + height: &mut Au, + border_and_padding: Au, + margin_top: Au, + margin_bottom: Au, + clearance: Au) { // Here, height is content height of box_ - - let mut noncontent_height = Au::new(0); + let mut noncontent_height = border_and_padding; for box_ in self.box_.iter() { let mut position = box_.border_box.get(); let mut margin = box_.margin.get(); @@ -817,12 +814,9 @@ impl BlockFlow { margin.top = margin_top; margin.bottom = margin_bottom; - noncontent_height = box_.padding.get().top + box_.padding.get().bottom + - box_.border.get().top + box_.border.get().bottom; - position.origin.y = clearance + margin.top; // Border box height - position.size.height = height + noncontent_height; + position.size.height = *height + noncontent_height; noncontent_height = noncontent_height + clearance + margin.top + margin.bottom; @@ -831,13 +825,26 @@ impl BlockFlow { } // Height of margin box + clearance - self.base.position.size.height = height + noncontent_height; + self.base.position.size.height = *height + noncontent_height; + } + /// Set floats_out at the last step of the assign height calculation. + pub fn set_floats_out_if_inorder(&mut self, + inorder: bool, + height: Au, + cur_y: Au, + top_offset: Au, + bottom_offset: Au, + left_offset: Au) { if inorder { let extra_height = height - (cur_y - top_offset) + bottom_offset; self.base.floats.translate(Point2D(left_offset, -extra_height)); } + } + /// Assign heights for all flows in absolute flow tree and store overflow for all + /// absolute descendants. + pub fn assign_height_absolute_flows(&mut self, ctx: &mut LayoutContext) { if self.is_root_of_absolute_flow_tree() { // Assign heights for all flows in this Absolute flow tree. // This is preorder because the height of an absolute flow may depend on @@ -848,6 +855,92 @@ impl BlockFlow { layout_context: ctx, }); } + } + + /// Assign height for current flow. + /// + /// + Collapse margins for flow's children and set in-flow child flows' + /// y-coordinates now that we know their heights. + /// + Calculate and set the height of the current flow. + /// + Calculate height, vertical margins, and y-coordinate for the flow's + /// box. Ideally, this should be calculated using CSS Section 10.6.7 + /// + /// For absolute flows, store the calculated content height for the flow. + /// Defer the calculation of the other values till a later traversal. + /// + /// inline(always) because this is only ever called by in-order or non-in-order top-level + /// methods + #[inline(always)] + fn assign_height_block_base(&mut self, ctx: &mut LayoutContext, inorder: bool) { + + // Note: Ignoring clearance for absolute flows as of now. + let ignore_clear = self.is_absolutely_positioned(); + let (clearance, mut top_offset, bottom_offset, left_offset) = + self.initialize_offsets(ignore_clear); + + self.handle_children_floats_if_necessary(ctx, inorder, + left_offset, top_offset); + + let (mut margin_top, mut margin_bottom, + top_margin_collapsible, bottom_margin_collapsible) = self.precompute_margin(); + + let mut cur_y = top_offset; + let collapsing = self.compute_margin_collapse(&mut cur_y, + &mut top_offset, + &mut margin_top, + &mut margin_bottom, + top_margin_collapsible, + bottom_margin_collapsible); + + // TODO: A box's own margins collapse if the 'min-height' property is zero, and it has neither + // top or bottom borders nor top or bottom padding, and it has a 'height' of either 0 or 'auto', + // and it does not contain a line box, and all of its in-flow children's margins (if any) collapse. + + let screen_height = ctx.screen_size.height; + + let mut height = if self.is_root() { + // FIXME(pcwalton): The max is taken here so that you can scroll the page, but this is + // not correct behavior according to CSS 2.1 § 10.5. Instead I think we should treat + // the root element as having `overflow: scroll` and use the layers-based scrolling + // infrastructure to make it scrollable. + Au::max(screen_height, cur_y) + } else { + // (cur_y - collapsing) will get you the the bottom margin-edge of + // the bottom-most child. + // top_offset: top margin-edge of the topmost child. + // hence, height = content height + cur_y - top_offset - collapsing + }; + + // For an absolutely positioned element, store the content height and stop the function. + if self.store_content_height_if_absolutely_positioned(height) { + return; + } + + let mut border_and_padding = Au::new(0); + for box_ in self.box_.iter() { + let style = box_.style(); + + // At this point, `height` is the height of the containing block, so passing `height` + // as the second argument here effectively makes percentages relative to the containing + // block per CSS 2.1 § 10.5. + height = match MaybeAuto::from_style(style.Box.get().height, height) { + Auto => height, + Specified(value) => value + }; + + border_and_padding = box_.padding.get().top + box_.padding.get().bottom + + box_.border.get().top + box_.border.get().bottom; + } + + self.compute_height_position(&mut height, + border_and_padding, + margin_top, + margin_bottom, + clearance); + + self.set_floats_out_if_inorder(inorder, height, cur_y, top_offset, bottom_offset, left_offset); + self.assign_height_absolute_flows(ctx); if self.is_root() { self.assign_height_store_overflow_fixed_flows(ctx); } @@ -892,7 +985,7 @@ impl BlockFlow { /// This function is called on a kid flow by a parent. /// Therefore, assign_height_float was already called on this kid flow by /// the traversal function. So, the values used are well-defined. - fn assign_height_float_inorder(&mut self) { + pub fn assign_height_float_inorder(&mut self) { let mut height = Au(0); let mut clearance = Au(0); let mut full_noncontent_width = Au(0); @@ -936,7 +1029,7 @@ impl BlockFlow { /// should be calculated using CSS Section 10.6.7 /// /// It does not calculate the height of the flow itself. - fn assign_height_float(&mut self, ctx: &mut LayoutContext) { + pub fn assign_height_float(&mut self, ctx: &mut LayoutContext) { // Now that we've determined our height, propagate that out. let has_inorder_children = self.base.num_floats > 0; if has_inorder_children { @@ -990,6 +1083,99 @@ impl BlockFlow { box_.border_box.set(position); } + /// In case of float, initialize containing_width at the beginning step of assign_width. + pub fn set_containing_width_if_float(&mut self, containing_block_width: Au) { + if self.is_float() { + self.float.get_mut_ref().containing_width = containing_block_width; + + // Parent usually sets this, but floats are never inorder + self.base.flags_info.flags.set_inorder(false); + } + } + + /// Assign the computed left_content_edge and content_width to children. + pub fn propagate_assigned_width_to_children(&mut self, left_content_edge: Au, + content_width: Au, + opt_col_widths: Option<~[Au]>) { + let has_inorder_children = if self.is_float() { + self.base.num_floats > 0 + } else { + self.base.flags_info.flags.inorder() || self.base.num_floats > 0 + }; + + let kid_abs_cb_x_offset; + if self.is_positioned() { + match self.box_ { + Some(ref box_) => { + // Pass yourself as a new Containing Block + // The static x offset for any immediate kid flows will be the + // left padding + kid_abs_cb_x_offset = box_.padding.get().left; + } + None => fail!("BlockFlow: no principal box found"), + } + } else { + // For kids, the left margin edge will be at our left content edge. + // The current static offset is at our left margin + // edge. So move in to the left content edge. + kid_abs_cb_x_offset = self.base.absolute_static_x_offset + left_content_edge; + } + let kid_fixed_cb_x_offset = self.base.fixed_static_x_offset + left_content_edge; + + // FIXME(ksh8281): avoid copy + let flags_info = self.base.flags_info.clone(); + + // Left margin edge of kid flow is at our left content edge + let mut kid_left_margin_edge = left_content_edge; + // Width of kid flow is our content width + let mut kid_width = content_width; + for (i, kid) in self.base.child_iter().enumerate() { + assert!(kid.is_block_flow() || kid.is_inline_flow() || kid.is_table_kind()); + match opt_col_widths { + Some(ref col_widths) => { + // If kid is table_rowgroup or table_row, the column widths info should be + // copied from its parent. + if kid.is_table_rowgroup() { + kid.as_table_rowgroup().col_widths = col_widths.clone() + } else if kid.is_table_row() { + kid.as_table_row().col_widths = col_widths.clone() + } else if kid.is_table_cell() { + // If kid is table_cell, the x offset and width for each cell should be + // calculated from parent's column widths info. + kid_left_margin_edge = if i == 0 { + Au(0) + } else { + kid_left_margin_edge + col_widths[i-1] + }; + kid_width = col_widths[i] + } + } + None => {} + } + + if kid.is_block_flow() { + let kid_block = kid.as_block(); + kid_block.base.absolute_static_x_offset = kid_abs_cb_x_offset; + kid_block.base.fixed_static_x_offset = kid_fixed_cb_x_offset; + } + let child_base = flow::mut_base(kid); + child_base.position.origin.x = kid_left_margin_edge; + child_base.position.size.width = kid_width; + child_base.flags_info.flags.set_inorder(has_inorder_children); + + if !child_base.flags_info.flags.inorder() { + child_base.floats = Floats::new(); + } + + // Per CSS 2.1 § 16.3.1, text decoration propagates to all children in flow. + // + // TODO(pcwalton): When we have out-of-flow children, don't unconditionally propagate. + + child_base.flags_info.propagate_text_decoration_from_parent(&flags_info); + child_base.flags_info.propagate_text_alignment_from_parent(&flags_info) + } + } + /// Add display items for current block. /// /// Set the absolute position for children after doing any offsetting for @@ -1264,7 +1450,7 @@ impl Flow for BlockFlow { /* find max width from child block contexts */ for child_ctx in self.base.child_iter() { - assert!(child_ctx.is_block_flow() || child_ctx.is_inline_flow()); + assert!(child_ctx.is_block_flow() || child_ctx.is_inline_flow() || child_ctx.is_table_kind()); let child_base = flow::mut_base(child_ctx); min_width = geometry::max(min_width, child_base.min_width); @@ -1323,12 +1509,7 @@ impl Flow for BlockFlow { let mut left_content_edge = Au::new(0); let mut content_width = containing_block_width; - if self.is_float() { - self.float.get_mut_ref().containing_width = containing_block_width; - - // Parent usually sets this, but floats are never inorder - self.base.flags_info.flags.set_inorder(false); - } + self.set_containing_width_if_float(containing_block_width); self.compute_used_width(ctx, containing_block_width); @@ -1345,59 +1526,7 @@ impl Flow for BlockFlow { self.base.position.size.width = content_width; } - let has_inorder_children = if self.is_float() { - self.base.num_floats > 0 - } else { - self.base.flags_info.flags.inorder() || self.base.num_floats > 0 - }; - - let kid_abs_cb_x_offset; - if self.is_positioned() { - match self.box_ { - Some(ref box_) => { - // Pass yourself as a new Containing Block - // The static x offset for any immediate kid flows will be the - // left padding - kid_abs_cb_x_offset = box_.padding.get().left; - } - None => fail!("BlockFlow: no principal box found"), - } - } else { - // For kids, the left margin edge will be at our left content edge. - // The current static offset is at our left margin - // edge. So move in to the left content edge. - kid_abs_cb_x_offset = self.base.absolute_static_x_offset + left_content_edge; - } - let kid_fixed_cb_x_offset = self.base.fixed_static_x_offset + left_content_edge; - - // FIXME(ksh8281): avoid copy - let flags_info = self.base.flags_info.clone(); - for kid in self.base.child_iter() { - assert!(kid.is_block_flow() || kid.is_inline_flow()); - - if kid.is_block_flow() { - let kid_block = kid.as_block(); - kid_block.base.absolute_static_x_offset = kid_abs_cb_x_offset; - kid_block.base.fixed_static_x_offset = kid_fixed_cb_x_offset; - } - let child_base = flow::mut_base(kid); - // Left margin edge of kid flow is at our left content edge - child_base.position.origin.x = left_content_edge; - // Width of kid flow is our content width - child_base.position.size.width = content_width; - child_base.flags_info.flags.set_inorder(has_inorder_children); - - if !child_base.flags_info.flags.inorder() { - child_base.floats = Floats::new(); - } - - // Per CSS 2.1 § 16.3.1, text decoration propagates to all children in flow. - // - // TODO(pcwalton): When we have out-of-flow children, don't unconditionally propagate. - - child_base.flags_info.propagate_text_decoration_from_parent(&flags_info); - child_base.flags_info.propagate_text_alignment_from_parent(&flags_info) - } + self.propagate_assigned_width_to_children(left_content_edge, content_width, None); } /// This is called on kid flows by a parent. @@ -1550,7 +1679,7 @@ impl Flow for BlockFlow { } /// The inputs for the widths-and-margins constraint equation. -struct WidthConstraintInput { +pub struct WidthConstraintInput { computed_width: MaybeAuto, left_margin: MaybeAuto, right_margin: MaybeAuto, @@ -1561,13 +1690,13 @@ struct WidthConstraintInput { } impl WidthConstraintInput { - fn new(computed_width: MaybeAuto, - left_margin: MaybeAuto, - right_margin: MaybeAuto, - left: MaybeAuto, - right: MaybeAuto, - available_width: Au, - static_x_offset: Au) + pub fn new(computed_width: MaybeAuto, + left_margin: MaybeAuto, + right_margin: MaybeAuto, + left: MaybeAuto, + right: MaybeAuto, + available_width: Au, + static_x_offset: Au) -> WidthConstraintInput { WidthConstraintInput { computed_width: computed_width, @@ -1582,7 +1711,7 @@ impl WidthConstraintInput { } /// The solutions for the widths-and-margins constraint equation. -struct WidthConstraintSolution { +pub struct WidthConstraintSolution { left: Au, right: Au, width: Au, @@ -1591,7 +1720,7 @@ struct WidthConstraintSolution { } impl WidthConstraintSolution { - fn new(width: Au, margin_left: Au, margin_right: Au) -> WidthConstraintSolution { + pub fn new(width: Au, margin_left: Au, margin_right: Au) -> WidthConstraintSolution { WidthConstraintSolution { left: Au(0), right: Au(0), @@ -1620,7 +1749,7 @@ impl WidthConstraintSolution { // Trait to encapsulate the Width and Margin calculation. // // CSS Section 10.3 -trait WidthAndMarginsComputer { +pub trait WidthAndMarginsComputer { /// Compute the inputs for the Width constraint equation. /// /// This is called only once to compute the initial inputs. For diff --git a/src/components/main/layout/box_.rs b/src/components/main/layout/box_.rs index f6540c3f1b8..972ac79da9a 100644 --- a/src/components/main/layout/box_.rs +++ b/src/components/main/layout/box_.rs @@ -29,7 +29,7 @@ use servo_util::str::is_whitespace; use std::cast; use std::cell::RefCell; use std::num::Zero; -use style::{ComputedValues, TElement, TNode}; +use style::{ComputedValues, TElement, TNode, cascade, initial_values}; use style::computed_values::{LengthOrPercentage, LengthOrPercentageOrAuto, overflow, LPA_Auto}; use style::computed_values::{border_style, clear, font_family, line_height, position}; use style::computed_values::{text_align, text_decoration, vertical_align, visibility, white_space}; @@ -108,6 +108,11 @@ pub enum SpecificBoxInfo { ImageBox(ImageBoxInfo), IframeBox(IframeBoxInfo), ScannedTextBox(ScannedTextBoxInfo), + TableBox, + TableCellBox, + TableColumnBox(TableColumnBoxInfo), + TableRowBox, + TableWrapperBox, UnscannedTextBox(UnscannedTextBoxInfo), } @@ -310,6 +315,29 @@ pub struct InlineParentInfo { node: OpaqueNode, } +/// A box that represents a table column. +#[deriving(Clone)] +pub struct TableColumnBoxInfo { + /// the number of columns a <col> element should span + span: Option<int>, +} + +impl TableColumnBoxInfo { + /// Create the information specific to an table column box. + pub fn new(node: &ThreadSafeLayoutNode) -> TableColumnBoxInfo { + let span = { + let element = node.as_element(); + element.get_attr(&namespace::Null, "span").and_then(|string| { + let n: Option<int> = FromStr::from_str(string); + n + }) + }; + TableColumnBoxInfo { + span: span, + } + } +} + // FIXME: Take just one parameter and use concat_ident! (mozilla/rust#12249) macro_rules! def_noncontent( ($side:ident, $get:ident, $inline_get:ident) => ( impl Box { @@ -402,6 +430,49 @@ impl Box { } } + /// Constructs a new `Box` instance from a specific info. + pub fn new_from_specific_info(node: &ThreadSafeLayoutNode, specific: SpecificBoxInfo) -> Box { + Box { + node: OpaqueNode::from_thread_safe_layout_node(node), + style: node.style().clone(), + border_box: RefCell::new(Au::zero_rect()), + border: RefCell::new(Zero::zero()), + padding: RefCell::new(Zero::zero()), + margin: RefCell::new(Zero::zero()), + specific: specific, + position_offsets: RefCell::new(Zero::zero()), + inline_info: RefCell::new(None), + new_line_pos: ~[], + } + } + + /// Constructs a new `Box` instance for an anonymous table object. + pub fn new_anonymous_table_box(node: &ThreadSafeLayoutNode, specific: SpecificBoxInfo) -> Box { + // CSS 2.1 § 17.2.1 This is for non-inherited properties on anonymous table boxes + // example: + // + // <div style="display: table"> + // Foo + // </div> + // + // Anonymous table boxes, TableRowBox and TableCellBox, are generated around `Foo`, but it shouldn't inherit the border. + + let (node_style, _) = cascade(&[], false, Some(node.style().get()), + &initial_values(), None); + Box { + node: OpaqueNode::from_thread_safe_layout_node(node), + style: Arc::new(node_style), + border_box: RefCell::new(Au::zero_rect()), + border: RefCell::new(Zero::zero()), + padding: RefCell::new(Zero::zero()), + margin: RefCell::new(Zero::zero()), + specific: specific, + position_offsets: RefCell::new(Zero::zero()), + inline_info: RefCell::new(None), + new_line_pos: ~[], + } + } + /// Constructs a new `Box` instance from an opaque node. pub fn from_opaque_node_and_style(node: OpaqueNode, style: Arc<ComputedValues>, @@ -521,20 +592,40 @@ impl Box { /// Returns the shared part of the width for computation of minimum and preferred width per /// CSS 2.1. fn guess_width(&self) -> Au { + let style = self.style(); + let mut margin_left = Au::new(0); + let mut margin_right = Au::new(0); + let mut padding_left = Au::new(0); + let mut padding_right = Au::new(0); + match self.specific { - GenericBox | IframeBox(_) | ImageBox(_) => {} - ScannedTextBox(_) | UnscannedTextBox(_) => return Au(0), + GenericBox | IframeBox(_) | ImageBox(_) => { + margin_left = MaybeAuto::from_style(style.Margin.get().margin_left, + Au::new(0)).specified_or_zero(); + margin_right = MaybeAuto::from_style(style.Margin.get().margin_right, + Au::new(0)).specified_or_zero(); + padding_left = self.compute_padding_length(style.Padding.get().padding_left, + Au::new(0)); + padding_right = self.compute_padding_length(style.Padding.get().padding_right, + Au::new(0)); + } + TableBox | TableCellBox => { + padding_left = self.compute_padding_length(style.Padding.get().padding_left, + Au::new(0)); + padding_right = self.compute_padding_length(style.Padding.get().padding_right, + Au::new(0)); + } + TableWrapperBox => { + margin_left = MaybeAuto::from_style(style.Margin.get().margin_left, + Au::new(0)).specified_or_zero(); + margin_right = MaybeAuto::from_style(style.Margin.get().margin_right, + Au::new(0)).specified_or_zero(); + } + TableRowBox => {} + ScannedTextBox(_) | TableColumnBox(_) | UnscannedTextBox(_) => return Au(0), } - let style = self.style(); let width = MaybeAuto::from_style(style.Box.get().width, Au::new(0)).specified_or_zero(); - let margin_left = MaybeAuto::from_style(style.Margin.get().margin_left, - Au::new(0)).specified_or_zero(); - let margin_right = MaybeAuto::from_style(style.Margin.get().margin_right, - Au::new(0)).specified_or_zero(); - - let padding_left = self.compute_padding_length(style.Padding.get().padding_left, Au(0)); - let padding_right = self.compute_padding_length(style.Padding.get().padding_right, Au(0)); width + margin_left + margin_right + padding_left + padding_right + self.border.get().left + self.border.get().right @@ -552,23 +643,31 @@ impl Box { /// /// FIXME(pcwalton): This should not be necessary. Just go to the style. pub fn compute_borders(&self, style: &ComputedValues) { - #[inline] - fn width(width: Au, style: border_style::T) -> Au { - if style == border_style::none { - Au(0) - } else { - width - } - } + let border = match self.specific { + TableWrapperBox => { + SideOffsets2D::new(Au(0), Au(0), Au(0), Au(0)) + }, + _ => { + #[inline] + fn width(width: Au, style: border_style::T) -> Au { + if style == border_style::none { + Au(0) + } else { + width + } + } - self.border.set(SideOffsets2D::new(width(style.Border.get().border_top_width, - style.Border.get().border_top_style), - width(style.Border.get().border_right_width, - style.Border.get().border_right_style), - width(style.Border.get().border_bottom_width, - style.Border.get().border_bottom_style), - width(style.Border.get().border_left_width, - style.Border.get().border_left_style))) + SideOffsets2D::new(width(style.Border.get().border_top_width, + style.Border.get().border_top_style), + width(style.Border.get().border_right_width, + style.Border.get().border_right_style), + width(style.Border.get().border_bottom_width, + style.Border.get().border_bottom_style), + width(style.Border.get().border_left_width, + style.Border.get().border_left_style)) + } + }; + self.border.set(border) } pub fn compute_positioned_offsets(&self, style: &ComputedValues, containing_width: Au, containing_height: Au) { @@ -589,36 +688,51 @@ impl Box { /// If it is auto, it is up to assign-height to ignore this value and /// calculate the correct margin values. pub fn compute_margin_top_bottom(&self, containing_block_width: Au) { - let style = self.style(); - // Note: CSS 2.1 defines margin % values wrt CB *width* (not height). - let margin_top = MaybeAuto::from_style(style.Margin.get().margin_top, - containing_block_width).specified_or_zero(); - let margin_bottom = MaybeAuto::from_style(style.Margin.get().margin_bottom, - containing_block_width).specified_or_zero(); - let mut margin = self.margin.get(); - margin.top = margin_top; - margin.bottom = margin_bottom; - self.margin.set(margin); + match self.specific { + TableBox | TableCellBox | TableRowBox | TableColumnBox(_) => { + self.margin.set(SideOffsets2D::new(Au(0), Au(0), Au(0), Au(0))) + }, + _ => { + let style = self.style(); + // Note: CSS 2.1 defines margin % values wrt CB *width* (not height). + let margin_top = MaybeAuto::from_style(style.Margin.get().margin_top, + containing_block_width).specified_or_zero(); + let margin_bottom = MaybeAuto::from_style(style.Margin.get().margin_bottom, + containing_block_width).specified_or_zero(); + let mut margin = self.margin.get(); + margin.top = margin_top; + margin.bottom = margin_bottom; + self.margin.set(margin); + } + } } /// Populates the box model padding parameters from the given computed style. pub fn compute_padding(&self, style: &ComputedValues, containing_block_width: Au) { - let padding = SideOffsets2D::new(self.compute_padding_length(style.Padding - .get() - .padding_top, - containing_block_width), - self.compute_padding_length(style.Padding - .get() - .padding_right, - containing_block_width), - self.compute_padding_length(style.Padding - .get() - .padding_bottom, - containing_block_width), - self.compute_padding_length(style.Padding - .get() - .padding_left, - containing_block_width)); + let padding = match self.specific { + TableColumnBox(_) | TableRowBox | TableWrapperBox => { + SideOffsets2D::new(Au(0), Au(0), Au(0), Au(0)) + }, + GenericBox | IframeBox(_) | ImageBox(_) | TableBox | TableCellBox | + ScannedTextBox(_) | UnscannedTextBox(_) => { + SideOffsets2D::new(self.compute_padding_length(style.Padding + .get() + .padding_top, + containing_block_width), + self.compute_padding_length(style.Padding + .get() + .padding_right, + containing_block_width), + self.compute_padding_length(style.Padding + .get() + .padding_bottom, + containing_block_width), + self.compute_padding_length(style.Padding + .get() + .padding_left, + containing_block_width)) + } + }; self.padding.set(padding) } @@ -773,9 +887,37 @@ impl Box { self.style().Text.get().text_decoration } - /// Returns the sum of margin, border, and padding on the left. - pub fn offset(&self) -> Au { - self.margin.get().left + self.border.get().left + self.padding.get().left + /// Returns the left offset from margin edge to content edge. + pub fn left_offset(&self) -> Au { + match self.specific { + TableWrapperBox => self.margin.get().left, + TableBox | TableCellBox => self.border.get().left + self.padding.get().left, + TableRowBox => self.border.get().left, + TableColumnBox(_) => Au(0), + _ => self.margin.get().left + self.border.get().left + self.padding.get().left + } + } + + /// Returns the top offset from margin edge to content edge. + pub fn top_offset(&self) -> Au { + match self.specific { + TableWrapperBox => self.margin.get().top, + TableBox | TableCellBox => self.border.get().top + self.padding.get().top, + TableRowBox => self.border.get().top, + TableColumnBox(_) => Au(0), + _ => self.margin.get().top + self.border.get().top + self.padding.get().top + } + } + + /// Returns the bottom offset from margin edge to content edge. + pub fn bottom_offset(&self) -> Au { + match self.specific { + TableWrapperBox => self.margin.get().bottom, + TableBox | TableCellBox => self.border.get().bottom + self.padding.get().bottom, + TableRowBox => self.border.get().bottom, + TableColumnBox(_) => Au(0), + _ => self.margin.get().bottom + self.border.get().bottom + self.padding.get().bottom + } } /// Returns true if this element is replaced content. This is true for images, form elements, @@ -1052,6 +1194,7 @@ impl Box { match self.specific { UnscannedTextBox(_) => fail!("Shouldn't see unscanned boxes here."), + TableColumnBox(_) => fail!("Shouldn't see table column boxes here."), ScannedTextBox(ref text_box) => { let text_color = self.style().Color.get().color.to_gfx_color(); @@ -1141,7 +1284,8 @@ impl Box { }); }); }, - GenericBox | IframeBox(..) => { + GenericBox | IframeBox(..) | TableBox | TableCellBox | TableRowBox | + TableWrapperBox => { lists.with_mut(|lists| { let item = ~ClipDisplayItem { base: BaseDisplayItem { @@ -1246,7 +1390,7 @@ impl Box { IframeBox(ref iframe_box) => { self.finalize_position_and_size_of_iframe(iframe_box, flow_origin, builder.ctx) } - GenericBox | ImageBox(_) | ScannedTextBox(_) | UnscannedTextBox(_) => {} + _ => {} } } @@ -1255,7 +1399,8 @@ impl Box { pub fn minimum_and_preferred_widths(&self) -> (Au, Au) { let guessed_width = self.guess_width(); let (additional_minimum, additional_preferred) = match self.specific { - GenericBox | IframeBox(_) => (Au(0), Au(0)), + GenericBox | IframeBox(_) | TableBox | TableCellBox | TableColumnBox(_) | + TableRowBox | TableWrapperBox => (Au(0), Au(0)), ImageBox(ref image_box_info) => { let image_width = image_box_info.image_width(); (image_width, image_width) @@ -1281,7 +1426,8 @@ impl Box { /// TODO: What exactly does this function return? Why is it Au(0) for GenericBox? pub fn content_width(&self) -> Au { match self.specific { - GenericBox | IframeBox(_) => Au(0), + GenericBox | IframeBox(_) | TableBox | TableCellBox | TableRowBox | + TableWrapperBox => Au(0), ImageBox(ref image_box_info) => { image_box_info.computed_width() } @@ -1290,6 +1436,7 @@ impl Box { let text_bounds = run.get().metrics_for_range(range).bounding_box; text_bounds.size.width } + TableColumnBox(_) => fail!("Table column boxes do not have width"), UnscannedTextBox(_) => fail!("Unscanned text boxes should have been scanned by now!"), } } @@ -1297,7 +1444,8 @@ impl Box { /// pub fn content_height(&self) -> Au { match self.specific { - GenericBox | IframeBox(_) => Au(0), + GenericBox | IframeBox(_) | TableBox | TableCellBox | TableRowBox | + TableWrapperBox => Au(0), ImageBox(ref image_box_info) => { image_box_info.computed_height() } @@ -1308,6 +1456,7 @@ impl Box { let em_size = text_bounds.size.height; self.calculate_line_height(em_size) } + TableColumnBox(_) => fail!("Table column boxes do not have height"), UnscannedTextBox(_) => fail!("Unscanned text boxes should have been scanned by now!"), } } @@ -1322,7 +1471,9 @@ impl Box { /// Split box which includes new-line character pub fn split_by_new_line(&self) -> SplitBoxResult { match self.specific { - GenericBox | IframeBox(_) | ImageBox(_) => CannotSplit, + GenericBox | IframeBox(_) | ImageBox(_) | TableBox | TableCellBox | + TableRowBox | TableWrapperBox => CannotSplit, + TableColumnBox(_) => fail!("Table column boxes do not need to split"), UnscannedTextBox(_) => fail!("Unscanned text boxes should have been scanned by now!"), ScannedTextBox(ref text_box_info) => { let mut new_line_pos = self.new_line_pos.clone(); @@ -1359,7 +1510,9 @@ impl Box { /// Attempts to split this box so that its width is no more than `max_width`. pub fn split_to_width(&self, max_width: Au, starts_line: bool) -> SplitBoxResult { match self.specific { - GenericBox | IframeBox(_) | ImageBox(_) => CannotSplit, + GenericBox | IframeBox(_) | ImageBox(_) | TableBox | TableCellBox | + TableRowBox | TableWrapperBox => CannotSplit, + TableColumnBox(_) => fail!("Table column boxes do not have width"), UnscannedTextBox(_) => fail!("Unscanned text boxes should have been scanned by now!"), ScannedTextBox(ref text_box_info) => { let mut pieces_processed_count: uint = 0; @@ -1478,8 +1631,8 @@ impl Box { /// CSS 2.1 § 10.3.2. pub fn assign_replaced_width_if_necessary(&self,container_width: Au) { match self.specific { - GenericBox | IframeBox(_) => { - } + GenericBox | IframeBox(_) | TableBox | TableCellBox | TableRowBox | + TableWrapperBox => {} ImageBox(ref image_box_info) => { // TODO(ksh8281): compute border,margin,padding let width = ImageBoxInfo::style_length(self.style().Box.get().width, @@ -1518,6 +1671,7 @@ impl Box { position.get().size.width = position.get().size.width + self.noncontent_width() + self.noncontent_inline_left() + self.noncontent_inline_right(); } + TableColumnBox(_) => fail!("Table column boxes do not have width"), UnscannedTextBox(_) => fail!("Unscanned text boxes should have been scanned by now!"), } } @@ -1527,8 +1681,8 @@ impl Box { /// Ideally, this should follow CSS 2.1 § 10.6.2 pub fn assign_replaced_height_if_necessary(&self) { match self.specific { - GenericBox | IframeBox(_) => { - } + GenericBox | IframeBox(_) | TableBox | TableCellBox | TableRowBox | + TableWrapperBox => {} ImageBox(ref image_box_info) => { // TODO(ksh8281): compute border,margin,padding let width = image_box_info.computed_width(); @@ -1566,6 +1720,7 @@ impl Box { position.get().size.height = position.get().size.height + self.noncontent_height() } + TableColumnBox(_) => fail!("Table column boxes do not have height"), UnscannedTextBox(_) => fail!("Unscanned text boxes should have been scanned by now!"), } } @@ -1601,6 +1756,11 @@ impl Box { IframeBox(_) => "IframeBox", ImageBox(_) => "ImageBox", ScannedTextBox(_) => "ScannedTextBox", + TableBox => "TableBox", + TableCellBox => "TableCellBox", + TableColumnBox(_) => "TableColumnBox", + TableRowBox => "TableRowBox", + TableWrapperBox => "TableWrapperBox", UnscannedTextBox(_) => "UnscannedTextBox", }; diff --git a/src/components/main/layout/construct.rs b/src/components/main/layout/construct.rs index 8ae7c06a2d1..8b25937c624 100644 --- a/src/components/main/layout/construct.rs +++ b/src/components/main/layout/construct.rs @@ -22,15 +22,23 @@ use css::node_style::StyledNode; use layout::block::BlockFlow; -use layout::box_::{Box, GenericBox, IframeBox, IframeBoxInfo, ImageBox, ImageBoxInfo}; +use layout::box_::{Box, GenericBox, IframeBox, IframeBoxInfo, ImageBox, ImageBoxInfo, TableBox}; +use layout::box_::{TableCellBox, TableColumnBox, TableColumnBoxInfo, TableRowBox, TableWrapperBox}; use layout::box_::{InlineInfo, InlineParentInfo, SpecificBoxInfo, UnscannedTextBox}; use layout::box_::{UnscannedTextBoxInfo}; use layout::context::LayoutContext; use layout::floats::FloatKind; -use layout::flow::{Flow, MutableOwnedFlowUtils}; +use layout::flow::{Flow, ImmutableFlowUtils, MutableOwnedFlowUtils}; use layout::flow::{Descendants, AbsDescendants, FixedDescendants}; use layout::flow_list::{Rawlink}; use layout::inline::InlineFlow; +use layout::table_wrapper::TableWrapperFlow; +use layout::table::TableFlow; +use layout::table_caption::TableCaptionFlow; +use layout::table_colgroup::TableColGroupFlow; +use layout::table_rowgroup::TableRowGroupFlow; +use layout::table_row::TableRowFlow; +use layout::table_cell::TableCellFlow; use layout::text::TextRunScanner; use layout::util::{LayoutDataAccess, OpaqueNode}; use layout::wrapper::{PostorderNodeMutTraversal, TLayoutNode, ThreadSafeLayoutNode}; @@ -39,6 +47,9 @@ use gfx::font_context::FontContext; use script::dom::bindings::codegen::InheritTypes::TextCast; use script::dom::bindings::js::JS; use script::dom::element::{HTMLIFrameElementTypeId, HTMLImageElementTypeId, HTMLObjectElementTypeId}; +use script::dom::element::{HTMLTableElementTypeId, HTMLTableSectionElementTypeId}; +use script::dom::element::{HTMLTableDataCellElementTypeId, HTMLTableHeaderCellElementTypeId}; +use script::dom::element::{HTMLTableColElementTypeId, HTMLTableRowElementTypeId}; use script::dom::node::{CommentNodeTypeId, DoctypeNodeTypeId, DocumentFragmentNodeTypeId}; use script::dom::node::{DocumentNodeTypeId, ElementNodeTypeId, ProcessingInstructionNodeTypeId}; use script::dom::node::{TextNodeTypeId}; @@ -89,6 +100,8 @@ enum ConstructionItem { InlineBoxesConstructionItem(InlineBoxesConstructionResult), /// Potentially ignorable whitespace. WhitespaceConstructionItem(OpaqueNode, Arc<ComputedValues>), + /// TableColumn Box + TableColumnBoxConstructionItem(Box), } impl ConstructionItem { @@ -102,6 +115,7 @@ impl ConstructionItem { } } WhitespaceConstructionItem(..) => {} + TableColumnBoxConstructionItem(_) => {} } } } @@ -288,20 +302,28 @@ impl<'a> FlowConstructor<'a> { let data = node.get_object_data(&self.layout_context.url); self.build_box_info_for_image(node, data) } + ElementNodeTypeId(HTMLTableElementTypeId) => TableWrapperBox, + ElementNodeTypeId(HTMLTableColElementTypeId) => TableColumnBox(TableColumnBoxInfo::new(node)), + ElementNodeTypeId(HTMLTableDataCellElementTypeId) | + ElementNodeTypeId(HTMLTableHeaderCellElementTypeId) => TableCellBox, + ElementNodeTypeId(HTMLTableRowElementTypeId) | + ElementNodeTypeId(HTMLTableSectionElementTypeId) => TableRowBox, TextNodeTypeId => UnscannedTextBox(UnscannedTextBoxInfo::new(node)), _ => GenericBox, } } - /// Creates an inline flow from a set of inline boxes and adds it as a child of the given flow. + /// Creates an inline flow from a set of inline boxes, then adds it as a child of the given flow + /// or pushes it onto the given flow list. /// /// `#[inline(always)]` because this is performance critical and LLVM will not inline it /// otherwise. #[inline(always)] - fn flush_inline_boxes_to_flow(&mut self, - boxes: ~[Box], - flow: &mut ~Flow, - node: &ThreadSafeLayoutNode) { + fn flush_inline_boxes_to_flow_or_list(&mut self, + boxes: ~[Box], + flow: &mut ~Flow, + flow_list: &mut ~[~Flow], + node: &ThreadSafeLayoutNode) { if boxes.len() == 0 { return } @@ -310,18 +332,23 @@ impl<'a> FlowConstructor<'a> { TextRunScanner::new().scan_for_runs(self.font_context(), inline_flow); inline_flow.finish(self.layout_context); - flow.add_new_child(inline_flow) + if flow.need_anonymous_flow(inline_flow) { + flow_list.push(inline_flow) + } else { + flow.add_new_child(inline_flow) + } } /// Creates an inline flow from a set of inline boxes, if present, and adds it as a child of - /// the given flow. - fn flush_inline_boxes_to_flow_if_necessary(&mut self, - opt_boxes: &mut Option<~[Box]>, - flow: &mut ~Flow, - node: &ThreadSafeLayoutNode) { + /// the given flow or pushes it onto the given flow list. + fn flush_inline_boxes_to_flow_or_list_if_necessary(&mut self, + opt_boxes: &mut Option<~[Box]>, + flow: &mut ~Flow, + flow_list: &mut ~[~Flow], + node: &ThreadSafeLayoutNode) { let opt_boxes = mem::replace(opt_boxes, None); if opt_boxes.len() > 0 { - self.flush_inline_boxes_to_flow(opt_boxes.to_vec(), flow, node) + self.flush_inline_boxes_to_flow_or_list(opt_boxes.to_vec(), flow, flow_list, node) } } @@ -332,12 +359,13 @@ impl<'a> FlowConstructor<'a> { /// this block flow. /// Also, deal with the absolute and fixed descendants bubbled up by /// children nodes. - fn build_block_flow_using_children(&mut self, - mut flow: ~Flow, - node: &ThreadSafeLayoutNode) - -> ConstructionResult { + fn build_flow_using_children(&mut self, + mut flow: ~Flow, + node: &ThreadSafeLayoutNode) + -> ConstructionResult { // Gather up boxes for the inline flows we might need to create. let mut opt_boxes_for_inline_flow = None; + let mut consecutive_siblings = ~[]; let mut first_box = true; // List of absolute descendants, in tree order. let mut abs_descendants = Descendants::new(); @@ -346,25 +374,38 @@ impl<'a> FlowConstructor<'a> { match kid.swap_out_construction_result() { NoConstructionResult => {} FlowConstructionResult(kid_flow, kid_abs_descendants, kid_fixed_descendants) => { - // Strip ignorable whitespace from the start of this flow per CSS 2.1 § - // 9.2.1.1. - if first_box { - strip_ignorable_whitespace_from_start(&mut opt_boxes_for_inline_flow); - first_box = false - } + // If kid_flow is TableCaptionFlow, kid_flow should be added under TableWrapperFlow. + if flow.is_table() && kid_flow.is_table_caption() { + kid.set_flow_construction_result(FlowConstructionResult(kid_flow, + Descendants::new(), + Descendants::new())) + } else if flow.need_anonymous_flow(kid_flow) { + consecutive_siblings.push(kid_flow) + } else { + // Strip ignorable whitespace from the start of this flow per CSS 2.1 § + // 9.2.1.1. + if flow.is_table_kind() || first_box { + strip_ignorable_whitespace_from_start(&mut opt_boxes_for_inline_flow); + first_box = false + } - // Flush any inline boxes that we were gathering up. This allows us to handle - // {ib} splits. - debug!("flushing {} inline box(es) to flow A", - opt_boxes_for_inline_flow.as_ref() - .map_or(0, |boxes| boxes.len())); - self.flush_inline_boxes_to_flow_if_necessary(&mut opt_boxes_for_inline_flow, - &mut flow, - node); - flow.add_new_child(kid_flow); + // Flush any inline boxes that we were gathering up. This allows us to handle + // {ib} splits. + debug!("flushing {} inline box(es) to flow A", + opt_boxes_for_inline_flow.as_ref() + .map_or(0, |boxes| boxes.len())); + self.flush_inline_boxes_to_flow_or_list_if_necessary(&mut opt_boxes_for_inline_flow, + &mut flow, + &mut consecutive_siblings, + node); + if !consecutive_siblings.is_empty() { + self.generate_anonymous_missing_child(consecutive_siblings, &mut flow, node); + consecutive_siblings = ~[]; + } + flow.add_new_child(kid_flow); + } abs_descendants.push_descendants(kid_abs_descendants); fixed_descendants.push_descendants(kid_fixed_descendants); - } ConstructionItemConstructionResult(InlineBoxesConstructionItem( InlineBoxesConstructionResult { @@ -399,14 +440,19 @@ impl<'a> FlowConstructor<'a> { opt_boxes_for_inline_flow.as_ref() .map_or(0, |boxes| boxes.len())); - self.flush_inline_boxes_to_flow_if_necessary( + self.flush_inline_boxes_to_flow_or_list_if_necessary( &mut opt_boxes_for_inline_flow, &mut flow, + &mut consecutive_siblings, node); // Push the flow generated by the {ib} split onto our list of // flows. - flow.add_new_child(kid_flow) + if flow.need_anonymous_flow(kid_flow) { + consecutive_siblings.push(kid_flow) + } else { + flow.add_new_child(kid_flow) + } } } } @@ -419,15 +465,23 @@ impl<'a> FlowConstructor<'a> { ConstructionItemConstructionResult(WhitespaceConstructionItem(..)) => { // Nothing to do here. } + ConstructionItemConstructionResult(TableColumnBoxConstructionItem(_)) => { + // TODO: Implement anonymous table objects for missing parents + // CSS 2.1 § 17.2.1, step 3-2 + } } } // Perform a final flush of any inline boxes that we were gathering up to handle {ib} // splits, after stripping ignorable whitespace. strip_ignorable_whitespace_from_end(&mut opt_boxes_for_inline_flow); - self.flush_inline_boxes_to_flow_if_necessary(&mut opt_boxes_for_inline_flow, - &mut flow, - node); + self.flush_inline_boxes_to_flow_or_list_if_necessary(&mut opt_boxes_for_inline_flow, + &mut flow, + &mut consecutive_siblings, + node); + if !consecutive_siblings.is_empty() { + self.generate_anonymous_missing_child(consecutive_siblings, &mut flow, node); + } // The flow is done. flow.finish(self.layout_context); @@ -456,7 +510,7 @@ impl<'a> FlowConstructor<'a> { /// to happen. fn build_flow_for_block(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult { let flow = ~BlockFlow::from_node(self, node) as ~Flow; - self.build_block_flow_using_children(flow, node) + self.build_flow_using_children(flow, node) } /// Builds the flow for a node with `float: {left|right}`. This yields a float `BlockFlow` with @@ -464,7 +518,7 @@ impl<'a> FlowConstructor<'a> { fn build_flow_for_floated_block(&mut self, node: &ThreadSafeLayoutNode, float_kind: FloatKind) -> ConstructionResult { let flow = ~BlockFlow::float_from_node(self, node, float_kind) as ~Flow; - self.build_block_flow_using_children(flow, node) + self.build_flow_using_children(flow, node) } @@ -536,6 +590,10 @@ impl<'a> FlowConstructor<'a> { whitespace_style, UnscannedTextBox(UnscannedTextBoxInfo::from_text(~" ")))) } + ConstructionItemConstructionResult(TableColumnBoxConstructionItem(_)) => { + // TODO: Implement anonymous table objects for missing parents + // CSS 2.1 § 17.2.1, step 3-2 + } } } @@ -613,7 +671,7 @@ impl<'a> FlowConstructor<'a> { let boxes_len = boxes.len(); parent_box.compute_borders(parent_box.style()); - for (i,box_) in boxes.iter().enumerate() { + for (i, box_) in boxes.iter().enumerate() { if box_.inline_info.with( |data| data.is_none() ) { box_.inline_info.set(Some(InlineInfo::new())); } @@ -684,6 +742,170 @@ impl<'a> FlowConstructor<'a> { self.build_boxes_for_replaced_inline_content(node) } } + + /// TableCaptionFlow is populated underneath TableWrapperFlow + fn place_table_caption_under_table_wrapper(&mut self, + table_wrapper_flow: &mut ~Flow, + node: &ThreadSafeLayoutNode) { + for kid in node.children() { + match kid.swap_out_construction_result() { + NoConstructionResult | ConstructionItemConstructionResult(_) => {} + FlowConstructionResult(kid_flow, _, _) => { + // Only kid flows with table-caption are matched here. + assert!(kid_flow.is_table_caption()); + table_wrapper_flow.add_new_child(kid_flow); + } + } + } + } + + /// Generates an anonymous table flow according to CSS 2.1 § 17.2.1, step 2. + /// If necessary, generate recursively another anonymous table flow. + fn generate_anonymous_missing_child(&mut self, child_flows: ~[~Flow], + flow: &mut ~Flow, node: &ThreadSafeLayoutNode) { + let mut anonymous_flow = flow.generate_missing_child_flow(node); + let mut consecutive_siblings = ~[]; + for kid_flow in child_flows.move_iter() { + if anonymous_flow.need_anonymous_flow(kid_flow) { + consecutive_siblings.push(kid_flow); + continue; + } + if !consecutive_siblings.is_empty() { + self.generate_anonymous_missing_child(consecutive_siblings, &mut anonymous_flow, node); + consecutive_siblings = ~[]; + } + anonymous_flow.add_new_child(kid_flow); + } + if !consecutive_siblings.is_empty() { + self.generate_anonymous_missing_child(consecutive_siblings, &mut anonymous_flow, node); + } + // The flow is done. + anonymous_flow.finish(self.layout_context); + flow.add_new_child(anonymous_flow); + } + + /// Builds a flow for a node with `display: table`. This yields a `TableWrapperFlow` with possibly + /// other `TableCaptionFlow`s or `TableFlow`s underneath it. + fn build_flow_for_table_wrapper(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult { + let box_ = Box::new_from_specific_info(node, TableWrapperBox); + let mut wrapper_flow = ~TableWrapperFlow::from_node_and_box(node, box_) as ~Flow; + + let table_box_ = Box::new_from_specific_info(node, TableBox); + let table_flow = ~TableFlow::from_node_and_box(node, table_box_) as ~Flow; + + // We first populate the TableFlow with other flows than TableCaptionFlow. + // We then populate the TableWrapperFlow with TableCaptionFlow, and attach + // the TableFlow to the TableWrapperFlow + let construction_result = self.build_flow_using_children(table_flow, node); + self.place_table_caption_under_table_wrapper(&mut wrapper_flow, node); + + let mut abs_descendants = Descendants::new(); + let mut fixed_descendants = Descendants::new(); + + // NOTE: The order of captions and table are not the same order as in the DOM tree. + // All caption blocks are placed before the table flow + match construction_result { + FlowConstructionResult(table_flow, table_abs_descendants, table_fixed_descendants) => { + wrapper_flow.add_new_child(table_flow); + abs_descendants.push_descendants(table_abs_descendants); + fixed_descendants.push_descendants(table_fixed_descendants); + } + _ => {} + } + + // The flow is done. + wrapper_flow.finish(self.layout_context); + let is_positioned = wrapper_flow.as_block().is_positioned(); + let is_fixed_positioned = wrapper_flow.as_block().is_fixed(); + let is_absolutely_positioned = wrapper_flow.as_block().is_absolutely_positioned(); + if is_positioned { + // This is the CB for all the absolute descendants. + wrapper_flow.set_abs_descendants(abs_descendants); + abs_descendants = Descendants::new(); + + if is_fixed_positioned { + // Send itself along with the other fixed descendants. + fixed_descendants.push(Rawlink::some(wrapper_flow)); + } else if is_absolutely_positioned { + // This is now the only absolute flow in the subtree which hasn't yet + // reached its CB. + abs_descendants.push(Rawlink::some(wrapper_flow)); + } + } + FlowConstructionResult(wrapper_flow, abs_descendants, fixed_descendants) + } + + /// Builds a flow for a node with `display: table-caption`. This yields a `TableCaptionFlow` + /// with possibly other `BlockFlow`s or `InlineFlow`s underneath it. + fn build_flow_for_table_caption(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult { + let flow = ~TableCaptionFlow::from_node(self, node) as ~Flow; + self.build_flow_using_children(flow, node) + } + + /// Builds a flow for a node with `display: table-row-group`. This yields a `TableRowGroupFlow` + /// with possibly other `TableRowFlow`s underneath it. + fn build_flow_for_table_rowgroup(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult { + let box_ = Box::new_from_specific_info(node, TableRowBox); + let flow = ~TableRowGroupFlow::from_node_and_box(node, box_) as ~Flow; + self.build_flow_using_children(flow, node) + } + + /// Builds a flow for a node with `display: table-row`. This yields a `TableRowFlow` with + /// possibly other `TableCellFlow`s underneath it. + fn build_flow_for_table_row(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult { + let box_ = Box::new_from_specific_info(node, TableRowBox); + let flow = ~TableRowFlow::from_node_and_box(node, box_) as ~Flow; + self.build_flow_using_children(flow, node) + } + + /// Builds a flow for a node with `display: table-cell`. This yields a `TableCellFlow` with + /// possibly other `BlockFlow`s or `InlineFlow`s underneath it. + fn build_flow_for_table_cell(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult { + let box_ = Box::new_from_specific_info(node, TableCellBox); + let flow = ~TableCellFlow::from_node_and_box(node, box_) as ~Flow; + self.build_flow_using_children(flow, node) + } + + /// Creates a box for a node with `display: table-column`. + fn build_boxes_for_table_column(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult { + // CSS 2.1 § 17.2.1. Treat all child boxes of a `table-column` as `display: none`. + for kid in node.children() { + kid.set_flow_construction_result(NoConstructionResult) + } + + let specific = TableColumnBox(TableColumnBoxInfo::new(node)); + let construction_item = TableColumnBoxConstructionItem( + Box::new_from_specific_info(node, specific) + ); + ConstructionItemConstructionResult(construction_item) + } + + /// Builds a flow for a node with `display: table-column-group`. + /// This yields a `TableColGroupFlow`. + fn build_flow_for_table_colgroup(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult { + let box_ = Box::new_from_specific_info(node, + TableColumnBox(TableColumnBoxInfo::new(node))); + let mut col_boxes = ~[]; + for kid in node.children() { + // CSS 2.1 § 17.2.1. Treat all non-column child boxes of `table-column-group` + // as `display: none`. + match kid.swap_out_construction_result() { + ConstructionItemConstructionResult(TableColumnBoxConstructionItem(box_)) => { + col_boxes.push(box_); + } + _ => {} + } + } + if col_boxes.is_empty() { + debug!("add TableColumnBox for empty colgroup"); + let specific = TableColumnBox(TableColumnBoxInfo::new(node)); + col_boxes.push( Box::new_from_specific_info(node, specific) ); + } + let mut flow = ~TableColGroupFlow::from_node_and_boxes(node, box_, col_boxes) as ~Flow; + flow.finish(self.layout_context); + + FlowConstructionResult(flow, Descendants::new(), Descendants::new()) + } } impl<'a> PostorderNodeMutTraversal for FlowConstructor<'a> { @@ -725,6 +947,12 @@ impl<'a> PostorderNodeMutTraversal for FlowConstructor<'a> { } } + // Table items contribute table flow construction results. + (display::table, _, _) => { + let construction_result = self.build_flow_for_table_wrapper(node); + node.set_flow_construction_result(construction_result) + } + // Absolutely positioned elements will have computed value of // `float` as 'none' and `display` as per the table. // Currently, for original `display` value of 'inline', the new @@ -739,6 +967,43 @@ impl<'a> PostorderNodeMutTraversal for FlowConstructor<'a> { node.set_flow_construction_result(construction_result) } + // Table items contribute table flow construction results. + (display::table_caption, _, _) => { + let construction_result = self.build_flow_for_table_caption(node); + node.set_flow_construction_result(construction_result) + } + + // Table items contribute table flow construction results. + (display::table_column_group, _, _) => { + let construction_result = self.build_flow_for_table_colgroup(node); + node.set_flow_construction_result(construction_result) + } + + // Table items contribute table flow construction results. + (display::table_column, _, _) => { + let construction_result = self.build_boxes_for_table_column(node); + node.set_flow_construction_result(construction_result) + } + + // Table items contribute table flow construction results. + (display::table_row_group, _, _) | (display::table_header_group, _, _) | + (display::table_footer_group, _, _) => { + let construction_result = self.build_flow_for_table_rowgroup(node); + node.set_flow_construction_result(construction_result) + } + + // Table items contribute table flow construction results. + (display::table_row, _, _) => { + let construction_result = self.build_flow_for_table_row(node); + node.set_flow_construction_result(construction_result) + } + + // Table items contribute table flow construction results. + (display::table_cell, _, _) => { + let construction_result = self.build_flow_for_table_cell(node); + node.set_flow_construction_result(construction_result) + } + // Block flows that are not floated contribute block flow construction results. // // TODO(pcwalton): Make this only trigger for blocks and handle the other `display` diff --git a/src/components/main/layout/flow.rs b/src/components/main/layout/flow.rs index 890d2fecd68..099f79c8e81 100644 --- a/src/components/main/layout/flow.rs +++ b/src/components/main/layout/flow.rs @@ -27,7 +27,7 @@ use css::node_style::StyledNode; use layout::block::{BlockFlow}; -use layout::box_::Box; +use layout::box_::{Box, TableRowBox, TableCellBox}; use layout::context::LayoutContext; use layout::construct::OptVector; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; @@ -36,6 +36,13 @@ use layout::incremental::RestyleDamage; use layout::inline::InlineFlow; use layout::parallel::FlowParallelInfo; use layout::parallel; +use layout::table_wrapper::TableWrapperFlow; +use layout::table::TableFlow; +use layout::table_colgroup::TableColGroupFlow; +use layout::table_rowgroup::TableRowGroupFlow; +use layout::table_row::TableRowFlow; +use layout::table_caption::TableCaptionFlow; +use layout::table_cell::TableCellFlow; use layout::wrapper::ThreadSafeLayoutNode; use layout::flow_list::{FlowList, Link, Rawlink, FlowListIterator, MutFlowListIterator}; @@ -84,6 +91,41 @@ pub trait Flow { fail!("called as_inline() on a non-inline flow") } + /// If this is a table wrapper flow, returns the underlying object. Fails otherwise. + fn as_table_wrapper<'a>(&'a mut self) -> &'a mut TableWrapperFlow { + fail!("called as_table_wrapper() on a non-tablewrapper flow") + } + + /// If this is a table flow, returns the underlying object. Fails otherwise. + fn as_table<'a>(&'a mut self) -> &'a mut TableFlow { + fail!("called as_table() on a non-table flow") + } + + /// If this is a table colgroup flow, returns the underlying object. Fails otherwise. + fn as_table_colgroup<'a>(&'a mut self) -> &'a mut TableColGroupFlow { + fail!("called as_table_colgroup() on a non-tablecolgroup flow") + } + + /// If this is a table rowgroup flow, returns the underlying object. Fails otherwise. + fn as_table_rowgroup<'a>(&'a mut self) -> &'a mut TableRowGroupFlow { + fail!("called as_table_rowgroup() on a non-tablerowgroup flow") + } + + /// If this is a table row flow, returns the underlying object. Fails otherwise. + fn as_table_row<'a>(&'a mut self) -> &'a mut TableRowFlow { + fail!("called as_table_row() on a non-tablerow flow") + } + + /// If this is a table cell flow, returns the underlying object. Fails otherwise. + fn as_table_caption<'a>(&'a mut self) -> &'a mut TableCaptionFlow { + fail!("called as_table_caption() on a non-tablecaption flow") + } + + /// If this is a table cell flow, returns the underlying object. Fails otherwise. + fn as_table_cell<'a>(&'a mut self) -> &'a mut TableCellFlow { + fail!("called as_table_cell() on a non-tablecell flow") + } + // Main methods /// Pass 1 of reflow: computes minimum and preferred widths. @@ -222,6 +264,36 @@ pub trait ImmutableFlowUtils { /// Returns true if this flow is a block or a float flow. fn is_block_like(self) -> bool; + /// Returns true if this flow is a table flow. + fn is_table(self) -> bool; + + /// Returns true if this flow is a table caption flow. + fn is_table_caption(self) -> bool; + + /// Returns true if this flow is a proper table child. + fn is_proper_table_child(self) -> bool; + + /// Returns true if this flow is a table row flow. + fn is_table_row(self) -> bool; + + /// Returns true if this flow is a table cell flow. + fn is_table_cell(self) -> bool; + + /// Returns true if this flow is a table colgroup flow. + fn is_table_colgroup(self) -> bool; + + /// Returns true if this flow is a table rowgroup flow. + fn is_table_rowgroup(self) -> bool; + + /// Returns true if this flow is one of table-related flows. + fn is_table_kind(self) -> bool; + + /// Returns true if anonymous flow is needed between this flow and child flow. + fn need_anonymous_flow(self, child: &Flow) -> bool; + + /// Generates missing child flow of this flow. + fn generate_missing_child_flow(self, node: &ThreadSafeLayoutNode) -> ~Flow; + /// Returns true if this flow has no children. fn is_leaf(self) -> bool; @@ -309,6 +381,13 @@ pub trait MutableOwnedFlowUtils { pub enum FlowClass { BlockFlowClass, InlineFlowClass, + TableWrapperFlowClass, + TableFlowClass, + TableColGroupFlowClass, + TableRowGroupFlowClass, + TableRowFlowClass, + TableCaptionFlowClass, + TableCellFlowClass, } /// A top-down traversal. @@ -753,7 +832,104 @@ impl<'a> ImmutableFlowUtils for &'a Flow { fn is_block_like(self) -> bool { match self.class() { BlockFlowClass => true, - InlineFlowClass => false, + _ => false, + } + } + + /// Returns true if this flow is a proper table child. + /// 'Proper table child' is defined as table-row flow, table-rowgroup flow, + /// table-column-group flow, or table-caption flow. + fn is_proper_table_child(self) -> bool { + match self.class() { + TableRowFlowClass | TableRowGroupFlowClass | + TableColGroupFlowClass | TableCaptionFlowClass => true, + _ => false, + } + } + + /// Returns true if this flow is a table row flow. + fn is_table_row(self) -> bool { + match self.class() { + TableRowFlowClass => true, + _ => false, + } + } + + /// Returns true if this flow is a table cell flow. + fn is_table_cell(self) -> bool { + match self.class() { + TableCellFlowClass => true, + _ => false, + } + } + + /// Returns true if this flow is a table colgroup flow. + fn is_table_colgroup(self) -> bool { + match self.class() { + TableColGroupFlowClass => true, + _ => false, + } + } + + /// Returns true if this flow is a table flow. + fn is_table(self) -> bool { + match self.class() { + TableFlowClass => true, + _ => false, + } + } + + /// Returns true if this flow is a table caption flow. + fn is_table_caption(self) -> bool { + match self.class() { + TableCaptionFlowClass => true, + _ => false, + } + } + + /// Returns true if this flow is a table rowgroup flow. + fn is_table_rowgroup(self) -> bool { + match self.class() { + TableRowGroupFlowClass => true, + _ => false, + } + } + + /// Returns true if this flow is one of table-related flows. + fn is_table_kind(self) -> bool { + match self.class() { + TableWrapperFlowClass | TableFlowClass | + TableColGroupFlowClass | TableRowGroupFlowClass | + TableRowFlowClass | TableCaptionFlowClass | TableCellFlowClass => true, + _ => false, + } + } + + /// Returns true if anonymous flow is needed between this flow and child flow. + /// Spec: http://www.w3.org/TR/CSS21/tables.html#anonymous-boxes + fn need_anonymous_flow(self, child: &Flow) -> bool { + match self.class() { + TableFlowClass => !child.is_proper_table_child(), + TableRowGroupFlowClass => !child.is_table_row(), + TableRowFlowClass => !child.is_table_cell(), + _ => false + } + } + + /// Generates missing child flow of this flow. + fn generate_missing_child_flow(self, node: &ThreadSafeLayoutNode) -> ~Flow { + match self.class() { + TableFlowClass | TableRowGroupFlowClass => { + let box_ = Box::new_anonymous_table_box(node, TableRowBox); + ~TableRowFlow::from_node_and_box(node, box_) as ~Flow + }, + TableRowFlowClass => { + let box_ = Box::new_anonymous_table_box(node, TableCellBox); + ~TableCellFlow::from_node_and_box(node, box_) as ~Flow + }, + _ => { + fail!("no need to generate a missing child") + } } } @@ -776,11 +952,11 @@ impl<'a> ImmutableFlowUtils for &'a Flow { fn is_block_container(self) -> bool { match self.class() { // TODO: Change this when inline-blocks are supported. - InlineFlowClass => false, - BlockFlowClass => { + BlockFlowClass | TableCaptionFlowClass | TableCellFlowClass => { // FIXME: Actually check the type of the node self.child_count() != 0 } + _ => false, } } @@ -788,7 +964,7 @@ impl<'a> ImmutableFlowUtils for &'a Flow { fn is_block_flow(self) -> bool { match self.class() { BlockFlowClass => true, - InlineFlowClass => false, + _ => false, } } @@ -796,7 +972,7 @@ impl<'a> ImmutableFlowUtils for &'a Flow { fn is_inline_flow(self) -> bool { match self.class() { InlineFlowClass => true, - BlockFlowClass => false, + _ => false, } } @@ -948,13 +1124,50 @@ impl<'a> MutableFlowUtils for &'a mut Flow { index, lists), InlineFlowClass => self.as_inline().build_display_list_inline(builder, container_block_size, dirty, index, lists), + TableWrapperFlowClass => self.as_table_wrapper().build_display_list_table_wrapper(builder, + container_block_size, + absolute_cb_abs_position, + dirty, + index, + lists), + TableFlowClass => self.as_table().build_display_list_table(builder, + container_block_size, + absolute_cb_abs_position, + dirty, + index, + lists), + TableRowGroupFlowClass => self.as_table_rowgroup().build_display_list_table_rowgroup(builder, + container_block_size, + absolute_cb_abs_position, + dirty, + index, + lists), + TableRowFlowClass => self.as_table_row().build_display_list_table_row(builder, + container_block_size, + absolute_cb_abs_position, + dirty, + index, + lists), + TableCaptionFlowClass => self.as_table_caption().build_display_list_table_caption(builder, + container_block_size, + absolute_cb_abs_position, + dirty, + index, + lists), + TableCellFlowClass => self.as_table_cell().build_display_list_table_cell(builder, + container_block_size, + absolute_cb_abs_position, + dirty, + index, + lists), + TableColGroupFlowClass => index, }; if lists.with_mut(|lists| lists.lists[index].list.len() == 0) { return true; } - if self.is_block_container() { + if self.is_block_container() || self.is_table_kind() { let block = self.as_block(); let mut child_lists = DisplayListCollection::new(); child_lists.add_list(DisplayList::new()); diff --git a/src/components/main/layout/inline.rs b/src/components/main/layout/inline.rs index c6e0c06d82b..dc05d359ed7 100644 --- a/src/components/main/layout/inline.rs +++ b/src/components/main/layout/inline.rs @@ -5,6 +5,7 @@ use css::node_style::StyledNode; use layout::box_::{Box, CannotSplit, GenericBox, IframeBox, ImageBox, ScannedTextBox, SplitDidFit}; use layout::box_::{SplitDidNotFit, UnscannedTextBox, InlineInfo}; +use layout::box_::{TableColumnBox, TableRowBox, TableWrapperBox, TableCellBox, TableBox}; use layout::context::LayoutContext; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; use layout::floats::{FloatLeft, Floats, PlacementInfo}; @@ -761,10 +762,12 @@ impl Flow for InlineFlow { (text_offset, line_height - text_offset, text_ascent) }, - GenericBox | IframeBox(_) => { + GenericBox | IframeBox(_) | TableBox | TableCellBox | TableRowBox | + TableWrapperBox => { let height = cur_box.border_box.get().size.height; (height, Au::new(0), height) }, + TableColumnBox(_) => fail!("Table column boxes do not have height"), UnscannedTextBox(_) => { fail!("Unscanned text boxes should have been scanned by now.") } diff --git a/src/components/main/layout/table.rs b/src/components/main/layout/table.rs new file mode 100644 index 00000000000..55ce4de5844 --- /dev/null +++ b/src/components/main/layout/table.rs @@ -0,0 +1,327 @@ +/* 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/. */ + +//! CSS table formatting contexts. + +use layout::box_::Box; +use layout::block::BlockFlow; +use layout::block::{WidthAndMarginsComputer, WidthConstraintInput, WidthConstraintSolution}; +use layout::construct::FlowConstructor; +use layout::context::LayoutContext; +use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; +use layout::floats::{FloatKind}; +use layout::flow::{TableFlowClass, FlowClass, Flow, ImmutableFlowUtils}; +use layout::flow; +use layout::table_wrapper::{TableLayout, FixedLayout, AutoLayout}; +use layout::wrapper::ThreadSafeLayoutNode; + +use std::cell::RefCell; +use style::computed_values::table_layout; +use geom::{Point2D, Rect, Size2D}; +use gfx::display_list::DisplayListCollection; +use servo_util::geometry::Au; + +/// A table flow corresponded to the table's internal table box under a table wrapper flow. +/// The properties `position`, `float`, and `margin-*` are used on the table wrapper box, +/// not table box per CSS 2.1 § 10.5. +pub struct TableFlow { + block_flow: BlockFlow, + + /// Column widths + col_widths: ~[Au], + + /// Table-layout property + table_layout: TableLayout, +} + +impl TableFlow { + pub fn from_node_and_box(node: &ThreadSafeLayoutNode, + box_: Box) + -> TableFlow { + let mut block_flow = BlockFlow::from_node_and_box(node, box_); + let table_layout = if block_flow.box_().style().Table.get().table_layout == + table_layout::fixed { + FixedLayout + } else { + AutoLayout + }; + TableFlow { + block_flow: block_flow, + col_widths: ~[], + table_layout: table_layout + } + } + + pub fn from_node(constructor: &mut FlowConstructor, + node: &ThreadSafeLayoutNode) + -> TableFlow { + let mut block_flow = BlockFlow::from_node(constructor, node); + let table_layout = if block_flow.box_().style().Table.get().table_layout == + table_layout::fixed { + FixedLayout + } else { + AutoLayout + }; + TableFlow { + block_flow: block_flow, + col_widths: ~[], + table_layout: table_layout + } + } + + pub fn float_from_node(constructor: &mut FlowConstructor, + node: &ThreadSafeLayoutNode, + float_kind: FloatKind) + -> TableFlow { + let mut block_flow = BlockFlow::float_from_node(constructor, node, float_kind); + let table_layout = if block_flow.box_().style().Table.get().table_layout == + table_layout::fixed { + FixedLayout + } else { + AutoLayout + }; + TableFlow { + block_flow: block_flow, + col_widths: ~[], + table_layout: table_layout + } + } + + pub fn teardown(&mut self) { + self.block_flow.teardown(); + self.col_widths = ~[]; + } + + /// Assign height for table flow. + /// + /// inline(always) because this is only ever called by in-order or non-in-order top-level + /// methods + #[inline(always)] + fn assign_height_table_base(&mut self, ctx: &mut LayoutContext, inorder: bool) { + + let (_, top_offset, bottom_offset, left_offset) = self.block_flow.initialize_offsets(true); + + self.block_flow.handle_children_floats_if_necessary(ctx, inorder, + left_offset, top_offset); + + let mut cur_y = top_offset; + for kid in self.block_flow.base.child_iter() { + let child_node = flow::mut_base(kid); + child_node.position.origin.y = cur_y; + cur_y = cur_y + child_node.position.size.height; + } + + let height = cur_y - top_offset; + + let mut noncontent_height = Au::new(0); + for box_ in self.block_flow.box_.iter() { + let mut position = box_.border_box.get(); + + // noncontent_height = border_top/bottom + padding_top/bottom of box + noncontent_height = box_.noncontent_height(); + + position.origin.y = Au(0); + position.size.height = height + noncontent_height; + + box_.border_box.set(position); + } + + self.block_flow.base.position.size.height = height + noncontent_height; + + self.block_flow.set_floats_out_if_inorder(inorder, height, cur_y, + top_offset, bottom_offset, left_offset); + } + + pub fn build_display_list_table<E:ExtraDisplayListData>( + &mut self, + builder: &DisplayListBuilder, + container_block_size: &Size2D<Au>, + absolute_cb_abs_position: Point2D<Au>, + dirty: &Rect<Au>, + index: uint, + lists: &RefCell<DisplayListCollection<E>>) + -> uint { + debug!("build_display_list_table: same process as block flow"); + self.block_flow.build_display_list_block(builder, container_block_size, + absolute_cb_abs_position, + dirty, index, lists) + } +} + +impl Flow for TableFlow { + fn class(&self) -> FlowClass { + TableFlowClass + } + + fn as_table<'a>(&'a mut self) -> &'a mut TableFlow { + self + } + + fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow { + &mut self.block_flow + } + + /// This function finds the specified column widths from column group and the first row. + /// Those are used in fixed table layout calculation. + /* FIXME: automatic table layout calculation */ + fn bubble_widths(&mut self, ctx: &mut LayoutContext) { + let mut did_first_row = false; + + /* find max width from child block contexts */ + for kid in self.block_flow.base.child_iter() { + assert!(kid.is_proper_table_child()); + + if kid.is_table_colgroup() { + self.col_widths.push_all(kid.as_table_colgroup().widths); + } else if kid.is_table_rowgroup() || kid.is_table_row() { + // read column widths from table-row-group/table-row, and assign + // width=0 for the columns not defined in column-group + // FIXME: need to read widths from either table-header-group OR + // first table-row + let kid_col_widths = if kid.is_table_rowgroup() { + &kid.as_table_rowgroup().col_widths + } else { + &kid.as_table_row().col_widths + }; + match self.table_layout { + FixedLayout if !did_first_row => { + did_first_row = true; + let mut child_widths = kid_col_widths.iter(); + for col_width in self.col_widths.mut_iter() { + match child_widths.next() { + Some(child_width) => { + if *col_width == Au::new(0) { + *col_width = *child_width; + } + }, + None => break + } + } + }, + _ => {} + } + let num_child_cols = kid_col_widths.len(); + let num_cols = self.col_widths.len(); + debug!("colgroup has {} column(s) and child has {} column(s)", num_cols, num_child_cols); + for i in range(num_cols, num_child_cols) { + self.col_widths.push( kid_col_widths[i] ); + } + } + } + self.block_flow.bubble_widths(ctx); + } + + /// 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. + fn assign_widths(&mut self, ctx: &mut LayoutContext) { + debug!("assign_widths({}): assigning width for flow", "table"); + + // The position was set to the containing block by the flow's parent. + let containing_block_width = self.block_flow.base.position.size.width; + let mut left_content_edge = Au::new(0); + let mut content_width = containing_block_width; + + let mut num_unspecified_widths = 0; + let mut total_column_width = Au::new(0); + for col_width in self.col_widths.iter() { + if *col_width == Au::new(0) { + num_unspecified_widths += 1; + } else { + total_column_width = total_column_width.add(col_width); + } + } + + let width_computer = InternalTable; + width_computer.compute_used_width(&mut self.block_flow, ctx, containing_block_width); + + for box_ in self.block_flow.box_.iter() { + left_content_edge = box_.padding.get().left + box_.border.get().left; + let padding_and_borders = box_.padding.get().left + box_.padding.get().right + + box_.border.get().left + box_.border.get().right; + content_width = box_.border_box.get().size.width - padding_and_borders; + } + + // In fixed table layout, we distribute extra space among the unspecified columns if there are + // any, or among all the columns if all are specified. + if (total_column_width < content_width) && (num_unspecified_widths == 0) { + let ratio = content_width.to_f64().unwrap() / total_column_width.to_f64().unwrap(); + for col_width in self.col_widths.mut_iter() { + *col_width = (*col_width).scale_by(ratio); + } + } else if num_unspecified_widths != 0 { + let extra_column_width = (content_width - total_column_width) / Au::new(num_unspecified_widths); + for col_width in self.col_widths.mut_iter() { + if *col_width == Au(0) { + *col_width = extra_column_width; + } + } + } + + self.block_flow.propagate_assigned_width_to_children(left_content_edge, content_width, Some(self.col_widths.clone())); + } + + /// This is called on kid flows by a parent. + /// + /// Hence, we can assume that assign_height has already been called on the + /// kid (because of the bottom-up traversal). + fn assign_height_inorder(&mut self, ctx: &mut LayoutContext) { + debug!("assign_height_inorder: assigning height for table"); + self.assign_height_table_base(ctx, true); + } + + fn assign_height(&mut self, ctx: &mut LayoutContext) { + debug!("assign_height: assigning height for table"); + self.assign_height_table_base(ctx, false); + } + + // CSS Section 8.3.1 - Collapsing Margins + // Since `margin` is not used on table box, `collapsing` and `collapsible` are set to 0 + fn collapse_margins(&mut self, + _: bool, + _: &mut bool, + _: &mut Au, + _: &mut Au, + collapsing: &mut Au, + collapsible: &mut Au) { + // `margin` is not used on table box. + *collapsing = Au::new(0); + *collapsible = Au::new(0); + } + + fn debug_str(&self) -> ~str { + let txt = ~"TableFlow: "; + txt.append(match self.block_flow.box_ { + Some(ref rb) => rb.debug_str(), + None => ~"", + }) + } +} + +/// Table, TableRowGroup, TableRow, TableCell types. +/// Their widths are calculated in the same way and do not have margins. +pub struct InternalTable; +impl WidthAndMarginsComputer for InternalTable { + + /// Compute the used value of width, taking care of min-width and max-width. + /// + /// CSS Section 10.4: Minimum and Maximum widths + fn compute_used_width(&self, + block: &mut BlockFlow, + ctx: &mut LayoutContext, + parent_flow_width: Au) { + let input = self.compute_width_constraint_inputs(block, parent_flow_width, ctx); + + let solution = self.solve_width_constraints(block, input); + + self.set_width_constraint_solutions(block, solution); + } + + /// Solve the width and margins constraints for this block flow. + fn solve_width_constraints(&self, + _: &mut BlockFlow, + input: WidthConstraintInput) + -> WidthConstraintSolution { + WidthConstraintSolution::new(input.available_width, Au::new(0), Au::new(0)) + } +} diff --git a/src/components/main/layout/table_caption.rs b/src/components/main/layout/table_caption.rs new file mode 100644 index 00000000000..b9ca7bd211a --- /dev/null +++ b/src/components/main/layout/table_caption.rs @@ -0,0 +1,103 @@ +/* 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/. */ + +//! CSS table formatting contexts. + +use layout::block::BlockFlow; +use layout::construct::FlowConstructor; +use layout::context::LayoutContext; +use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; +use layout::flow::{TableCaptionFlowClass, FlowClass, Flow}; +use layout::wrapper::ThreadSafeLayoutNode; + +use std::cell::RefCell; +use geom::{Point2D, Rect, Size2D}; +use gfx::display_list::DisplayListCollection; +use servo_util::geometry::Au; + +/// A table formatting context. +pub struct TableCaptionFlow { + block_flow: BlockFlow, +} + +impl TableCaptionFlow { + pub fn from_node(constructor: &mut FlowConstructor, + node: &ThreadSafeLayoutNode) + -> TableCaptionFlow { + TableCaptionFlow { + block_flow: BlockFlow::from_node(constructor, node) + } + } + + pub fn teardown(&mut self) { + self.block_flow.teardown(); + } + + pub fn build_display_list_table_caption<E:ExtraDisplayListData>( + &mut self, + builder: &DisplayListBuilder, + container_block_size: &Size2D<Au>, + absolute_cb_abs_position: Point2D<Au>, + dirty: &Rect<Au>, + index: uint, + lists: &RefCell<DisplayListCollection<E>>) + -> uint { + debug!("build_display_list_table_caption: same process as block flow"); + self.block_flow.build_display_list_block(builder, container_block_size, + absolute_cb_abs_position, + dirty, index, lists) + } +} + +impl Flow for TableCaptionFlow { + fn class(&self) -> FlowClass { + TableCaptionFlowClass + } + + fn as_table_caption<'a>(&'a mut self) -> &'a mut TableCaptionFlow { + self + } + + fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow { + &mut self.block_flow + } + + fn bubble_widths(&mut self, ctx: &mut LayoutContext) { + self.block_flow.bubble_widths(ctx); + } + + fn assign_widths(&mut self, ctx: &mut LayoutContext) { + debug!("assign_widths({}): assigning width for flow", "table_caption"); + self.block_flow.assign_widths(ctx); + } + + /// This is called on kid flows by a parent. + /// + /// Hence, we can assume that assign_height has already been called on the + /// kid (because of the bottom-up traversal). + fn assign_height_inorder(&mut self, ctx: &mut LayoutContext) { + debug!("assign_height_inorder: assigning height for table_caption"); + self.block_flow.assign_height_inorder(ctx); + } + + fn assign_height(&mut self, ctx: &mut LayoutContext) { + debug!("assign_height: assigning height for table_caption"); + self.block_flow.assign_height(ctx); + } + + /// table-caption has margins but is not collapsed with a sibling(table) + /// or its parents(table-wrapper). + /// Therefore, margins to be collapsed do not exist. + fn collapse_margins(&mut self, _: bool, _: &mut bool, _: &mut Au, + _: &mut Au, _: &mut Au, _: &mut Au) { + } + + fn debug_str(&self) -> ~str { + let txt = ~"TableCaptionFlow: "; + txt.append(match self.block_flow.box_ { + Some(ref rb) => rb.debug_str(), + None => ~"", + }) + } +} diff --git a/src/components/main/layout/table_cell.rs b/src/components/main/layout/table_cell.rs new file mode 100644 index 00000000000..624f0d8deb4 --- /dev/null +++ b/src/components/main/layout/table_cell.rs @@ -0,0 +1,173 @@ +/* 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/. */ + +//! CSS table formatting contexts. + +use layout::box_::Box; +use layout::block::{BlockFlow, WidthAndMarginsComputer}; +use layout::context::LayoutContext; +use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; +use layout::flow::{TableCellFlowClass, FlowClass, Flow}; +use layout::table::InternalTable; +use layout::wrapper::ThreadSafeLayoutNode; + +use std::cell::RefCell; +use geom::{Point2D, Rect, Size2D}; +use gfx::display_list::{DisplayListCollection}; +use servo_util::geometry::Au; + +/// A table formatting context. +pub struct TableCellFlow { + /// Data common to all flows. + block_flow: BlockFlow, +} + +impl TableCellFlow { + pub fn from_node_and_box(node: &ThreadSafeLayoutNode, + box_: Box) + -> TableCellFlow { + TableCellFlow { + block_flow: BlockFlow::from_node_and_box(node, box_) + } + } + + pub fn teardown(&mut self) { + self.block_flow.teardown() + } + + pub fn box_<'a>(&'a mut self) -> &'a Option<Box>{ + &self.block_flow.box_ + } + + /// Assign height for table-cell flow. + /// + /// inline(always) because this is only ever called by in-order or non-in-order top-level + /// methods + #[inline(always)] + fn assign_height_table_cell_base(&mut self, ctx: &mut LayoutContext, inorder: bool) { + let (_, mut top_offset, bottom_offset, left_offset) = self.block_flow + .initialize_offsets(true); + + self.block_flow.handle_children_floats_if_necessary(ctx, inorder, + left_offset, top_offset); + let mut cur_y = top_offset; + // Since table cell does not have `margin`, the first child's top margin and + // the last child's bottom margin do not collapse. + self.block_flow.compute_margin_collapse(&mut cur_y, + &mut top_offset, + &mut Au(0), + &mut Au(0), + false, + false); + + // CSS 2.1 § 17.5.3. Table cell box height is the minimum height required by the content. + let height = cur_y - top_offset; + + // TODO(june0cho): vertical-align of table-cell should be calculated. + let mut noncontent_height = Au::new(0); + for box_ in self.block_flow.box_.iter() { + let mut position = box_.border_box.get(); + + // noncontent_height = border_top/bottom + padding_top/bottom of box + noncontent_height = box_.noncontent_height(); + + position.origin.y = Au(0); + position.size.height = height + noncontent_height; + + box_.border_box.set(position); + } + + self.block_flow.base.position.size.height = height + noncontent_height; + + self.block_flow.set_floats_out_if_inorder(inorder, height, cur_y, top_offset, + bottom_offset, left_offset); + self.block_flow.assign_height_absolute_flows(ctx); + } + + pub fn build_display_list_table_cell<E:ExtraDisplayListData>( + &mut self, + builder: &DisplayListBuilder, + container_block_size: &Size2D<Au>, + absolute_cb_abs_position: Point2D<Au>, + dirty: &Rect<Au>, + index: uint, + lists: &RefCell<DisplayListCollection<E>>) + -> uint { + debug!("build_display_list_table_cell: same process as block flow"); + self.block_flow.build_display_list_block(builder, container_block_size, + absolute_cb_abs_position, + dirty, index, lists) + } +} + +impl Flow for TableCellFlow { + fn class(&self) -> FlowClass { + TableCellFlowClass + } + + fn as_table_cell<'a>(&'a mut self) -> &'a mut TableCellFlow { + self + } + + fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow { + &mut self.block_flow + } + + /// Minimum/preferred widths set by this function are used in automatic table layout calculation. + fn bubble_widths(&mut self, ctx: &mut LayoutContext) { + self.block_flow.bubble_widths(ctx); + } + + /// 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 table row. + fn assign_widths(&mut self, ctx: &mut LayoutContext) { + debug!("assign_widths({}): assigning width for flow", "table_cell"); + + // The position was set to the column width by the parent flow, table row flow. + let containing_block_width = self.block_flow.base.position.size.width; + let mut left_content_edge = Au::new(0); + let mut content_width = containing_block_width; + + let width_computer = InternalTable; + width_computer.compute_used_width(&mut self.block_flow, ctx, containing_block_width); + + for box_ in self.block_flow.box_.iter() { + left_content_edge = box_.border_box.get().origin.x + box_.padding.get().left + box_.border.get().left; + let padding_and_borders = box_.padding.get().left + box_.padding.get().right + + box_.border.get().left + box_.border.get().right; + content_width = box_.border_box.get().size.width - padding_and_borders; + } + + self.block_flow.propagate_assigned_width_to_children(left_content_edge, content_width, None); + } + + /// This is called on kid flows by a parent. + /// + /// Hence, we can assume that assign_height has already been called on the + /// kid (because of the bottom-up traversal). + fn assign_height_inorder(&mut self, ctx: &mut LayoutContext) { + debug!("assign_height_inorder: assigning height for table_cell"); + self.assign_height_table_cell_base(ctx, true); + } + + fn assign_height(&mut self, ctx: &mut LayoutContext) { + debug!("assign_height: assigning height for table_cell"); + self.assign_height_table_cell_base(ctx, false); + } + + /// TableCellBox and their parents(TableRowBox) do not have margins. + /// Therefore, margins to be collapsed do not exist. + fn collapse_margins(&mut self, _: bool, _: &mut bool, _: &mut Au, + _: &mut Au, _: &mut Au, _: &mut Au) { + } + + fn debug_str(&self) -> ~str { + let txt = ~"TableCellFlow: "; + txt.append(match self.block_flow.box_ { + Some(ref rb) => rb.debug_str(), + None => ~"", + }) + } +} + diff --git a/src/components/main/layout/table_colgroup.rs b/src/components/main/layout/table_colgroup.rs new file mode 100644 index 00000000000..8d9e1d98870 --- /dev/null +++ b/src/components/main/layout/table_colgroup.rs @@ -0,0 +1,98 @@ +/* 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/. */ + +//! CSS table formatting contexts. + +use layout::box_::{Box, TableColumnBox}; +use layout::context::LayoutContext; +use layout::flow::{BaseFlow, TableColGroupFlowClass, FlowClass, Flow}; +use layout::model::{MaybeAuto}; +use layout::wrapper::ThreadSafeLayoutNode; +use servo_util::geometry::Au; + +/// A table formatting context. +pub struct TableColGroupFlow { + /// Data common to all flows. + base: BaseFlow, + + /// The associated box. + box_: Option<Box>, + + /// The table column boxes + cols: ~[Box], + + /// The specified widths of table columns + widths: ~[Au], +} + +impl TableColGroupFlow { + pub fn from_node_and_boxes(node: &ThreadSafeLayoutNode, + box_: Box, + boxes: ~[Box]) -> TableColGroupFlow { + TableColGroupFlow { + base: BaseFlow::new((*node).clone()), + box_: Some(box_), + cols: boxes, + widths: ~[], + } + } + + pub fn teardown(&mut self) { + for box_ in self.box_.iter() { + box_.teardown(); + } + self.box_ = None; + self.cols = ~[]; + self.widths = ~[]; + } +} + +impl Flow for TableColGroupFlow { + fn class(&self) -> FlowClass { + TableColGroupFlowClass + } + + fn as_table_colgroup<'a>(&'a mut self) -> &'a mut TableColGroupFlow { + self + } + + fn bubble_widths(&mut self, _: &mut LayoutContext) { + for box_ in self.cols.iter() { + // get the specified value from width property + let width = MaybeAuto::from_style(box_.style().Box.get().width, + Au::new(0)).specified_or_zero(); + + let span: int = match box_.specific { + TableColumnBox(col_box) => col_box.span.unwrap_or(1), + _ => fail!("Other box come out in TableColGroupFlow. {:?}", box_.specific) + }; + for _ in range(0, span) { + self.widths.push(width); + } + } + } + + /// Table column widths are assigned in table flow and propagated to table row or rowgroup flow. + /// Therefore, table colgroup flow does not need to assign its width. + fn assign_widths(&mut self, _ctx: &mut LayoutContext) { + } + + /// Table column do not have height. + fn assign_height(&mut self, _ctx: &mut LayoutContext) { + } + + /// TableColumnBox and their parents(TableBox) do not have margins. + /// Therefore, margins to be collapsed do not exist. + fn collapse_margins(&mut self, _: bool, _: &mut bool, _: &mut Au, + _: &mut Au, _: &mut Au, _: &mut Au) { + } + + fn debug_str(&self) -> ~str { + let txt = ~"TableColGroupFlow: "; + txt.append(match self.box_ { + Some(ref rb) => rb.debug_str(), + None => ~"", + }) + } +} diff --git a/src/components/main/layout/table_row.rs b/src/components/main/layout/table_row.rs new file mode 100644 index 00000000000..17ebaded351 --- /dev/null +++ b/src/components/main/layout/table_row.rs @@ -0,0 +1,222 @@ +/* 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/. */ + +//! CSS table formatting contexts. + +use layout::box_::Box; +use layout::block::BlockFlow; +use layout::block::WidthAndMarginsComputer; +use layout::construct::FlowConstructor; +use layout::context::LayoutContext; +use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; +use layout::flow::{TableRowFlowClass, FlowClass, Flow, ImmutableFlowUtils}; +use layout::flow; +use layout::table::InternalTable; +use layout::model::{MaybeAuto, Specified, Auto}; +use layout::wrapper::ThreadSafeLayoutNode; + +use std::cell::RefCell; +use geom::{Point2D, Rect, Size2D}; +use gfx::display_list::DisplayListCollection; +use servo_util::geometry::Au; +use servo_util::geometry; + +/// A table formatting context. +pub struct TableRowFlow { + block_flow: BlockFlow, + + /// Column widths. + col_widths: ~[Au], +} + +impl TableRowFlow { + pub fn from_node_and_box(node: &ThreadSafeLayoutNode, + box_: Box) + -> TableRowFlow { + TableRowFlow { + block_flow: BlockFlow::from_node_and_box(node, box_), + col_widths: ~[], + } + } + + pub fn from_node(constructor: &mut FlowConstructor, + node: &ThreadSafeLayoutNode) + -> TableRowFlow { + TableRowFlow { + block_flow: BlockFlow::from_node(constructor, node), + col_widths: ~[], + } + } + + pub fn teardown(&mut self) { + self.block_flow.teardown(); + self.col_widths = ~[]; + } + + pub fn box_<'a>(&'a mut self) -> &'a Option<Box>{ + &self.block_flow.box_ + } + + fn initialize_offsets(&mut self) -> (Au, Au, Au) { + // TODO: If border-collapse: collapse, top_offset, bottom_offset, and left_offset + // should be updated. Currently, they are set as Au(0). + (Au(0), Au(0), Au(0)) + } + + /// Assign height for table-row flow. + /// + /// inline(always) because this is only ever called by in-order or non-in-order top-level + /// methods + #[inline(always)] + fn assign_height_table_row_base(&mut self, ctx: &mut LayoutContext, inorder: bool) { + let (top_offset, bottom_offset, left_offset) = self.initialize_offsets(); + + self.block_flow.handle_children_floats_if_necessary(ctx, inorder, + left_offset, top_offset); + let mut cur_y = top_offset; + + // Per CSS 2.1 § 17.5.3, find max_y = max( computed `height`, minimum height of all cells ) + let mut max_y = Au::new(0); + for kid in self.block_flow.base.child_iter() { + for child_box in kid.as_table_cell().box_().iter() { + // TODO: Percentage height + let child_specified_height = MaybeAuto::from_style(child_box.style().Box.get().height, + Au::new(0)).specified_or_zero(); + max_y = geometry::max(max_y, child_specified_height + child_box.noncontent_height()); + } + let child_node = flow::mut_base(kid); + child_node.position.origin.y = cur_y; + max_y = geometry::max(max_y, child_node.position.size.height); + } + + let mut height = max_y; + for box_ in self.block_flow.box_.iter() { + // TODO: Percentage height + height = match MaybeAuto::from_style(box_.style().Box.get().height, Au(0)) { + Auto => height, + Specified(value) => geometry::max(value, height) + }; + } + cur_y = cur_y + height; + + // Assign the height of own box + for box_ in self.block_flow.box_.iter() { + let mut position = box_.border_box.get(); + position.size.height = height; + box_.border_box.set(position); + } + self.block_flow.base.position.size.height = height; + + // Assign the height of kid boxes, which is the same value as own height. + for kid in self.block_flow.base.child_iter() { + for kid_box_ in kid.as_table_cell().box_().iter() { + let mut position = kid_box_.border_box.get(); + position.size.height = height; + kid_box_.border_box.set(position); + } + let child_node = flow::mut_base(kid); + child_node.position.size.height = height; + } + + self.block_flow.set_floats_out_if_inorder(inorder, height, cur_y, + top_offset, bottom_offset, left_offset); + } + + pub fn build_display_list_table_row<E:ExtraDisplayListData>( + &mut self, + builder: &DisplayListBuilder, + container_block_size: &Size2D<Au>, + absolute_cb_abs_position: Point2D<Au>, + dirty: &Rect<Au>, + index: uint, + lists: &RefCell<DisplayListCollection<E>>) + -> uint { + debug!("build_display_list_table_row: same process as block flow"); + self.block_flow.build_display_list_block(builder, container_block_size, + absolute_cb_abs_position, + dirty, index, lists) + } +} + +impl Flow for TableRowFlow { + fn class(&self) -> FlowClass { + TableRowFlowClass + } + + fn as_table_row<'a>(&'a mut self) -> &'a mut TableRowFlow { + self + } + + fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow { + &mut self.block_flow + } + + /// Recursively (bottom-up) determines the context's preferred and minimum widths. When called + /// on this context, all child contexts have had their min/pref widths set. This function must + /// decide min/pref widths based on child context widths and dimensions of any boxes it is + /// responsible for flowing. + /// Min/pref widths set by this function are used in automatic table layout calculation. + /// Also, this function collects the specified column widths of children cells. Those are used + /// in fixed table layout calculation + fn bubble_widths(&mut self, ctx: &mut LayoutContext) { + /* find the specified widths from child table-cell contexts */ + for kid in self.block_flow.base.child_iter() { + assert!(kid.is_table_cell()); + + for child_box in kid.as_table_cell().box_().iter() { + let child_specified_width = MaybeAuto::from_style(child_box.style().Box.get().width, + Au::new(0)).specified_or_zero(); + self.col_widths.push(child_specified_width); + } + } + + // TODO: calculate min_width & pref_width for automatic table layout calculation + self.block_flow.bubble_widths(ctx); + } + + /// 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. + fn assign_widths(&mut self, ctx: &mut LayoutContext) { + debug!("assign_widths({}): assigning width for flow", "table_row"); + + // The position was set to the containing block by the flow's parent. + let containing_block_width = self.block_flow.base.position.size.width; + // FIXME: In case of border-collapse: collapse, left_content_edge should be border-left + let left_content_edge = Au::new(0); + + let width_computer = InternalTable; + width_computer.compute_used_width(&mut self.block_flow, ctx, containing_block_width); + + self.block_flow.propagate_assigned_width_to_children(left_content_edge, Au(0), Some(self.col_widths.clone())); + } + + /// This is called on kid flows by a parent. + /// + /// Hence, we can assume that assign_height has already been called on the + /// kid (because of the bottom-up traversal). + fn assign_height_inorder(&mut self, ctx: &mut LayoutContext) { + debug!("assign_height_inorder: assigning height for table_row"); + self.assign_height_table_row_base(ctx, true); + } + + fn assign_height(&mut self, ctx: &mut LayoutContext) { + debug!("assign_height: assigning height for table_row"); + self.assign_height_table_row_base(ctx, false); + } + + /// TableRowBox and their parents(TableBox) do not have margins. + /// Therefore, margins to be collapsed do not exist. + fn collapse_margins(&mut self, _: bool, _: &mut bool, _: &mut Au, + _: &mut Au, _: &mut Au, _: &mut Au) { + } + + fn debug_str(&self) -> ~str { + let txt = ~"TableRowFlow: "; + txt.append(match self.block_flow.box_ { + Some(ref rb) => rb.debug_str(), + None => ~"", + }) + } +} + diff --git a/src/components/main/layout/table_rowgroup.rs b/src/components/main/layout/table_rowgroup.rs new file mode 100644 index 00000000000..92bb505d1e9 --- /dev/null +++ b/src/components/main/layout/table_rowgroup.rs @@ -0,0 +1,197 @@ +/* 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/. */ + +//! CSS table formatting contexts. + +use layout::box_::Box; +use layout::block::BlockFlow; +use layout::block::WidthAndMarginsComputer; +use layout::construct::FlowConstructor; +use layout::context::LayoutContext; +use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; +use layout::flow::{TableRowGroupFlowClass, FlowClass, Flow, ImmutableFlowUtils}; +use layout::flow; +use layout::table::InternalTable; +use layout::wrapper::ThreadSafeLayoutNode; + +use std::cell::RefCell; +use geom::{Point2D, Rect, Size2D}; +use gfx::display_list::DisplayListCollection; +use servo_util::geometry::Au; + +/// A table formatting context. +pub struct TableRowGroupFlow { + block_flow: BlockFlow, + + /// Column widths + col_widths: ~[Au], +} + +impl TableRowGroupFlow { + pub fn from_node_and_box(node: &ThreadSafeLayoutNode, + box_: Box) + -> TableRowGroupFlow { + TableRowGroupFlow { + block_flow: BlockFlow::from_node_and_box(node, box_), + col_widths: ~[], + } + } + + pub fn from_node(constructor: &mut FlowConstructor, + node: &ThreadSafeLayoutNode) + -> TableRowGroupFlow { + TableRowGroupFlow { + block_flow: BlockFlow::from_node(constructor, node), + col_widths: ~[], + } + } + + pub fn teardown(&mut self) { + self.block_flow.teardown(); + self.col_widths = ~[]; + } + + pub fn box_<'a>(&'a mut self) -> &'a Option<Box>{ + &self.block_flow.box_ + } + + fn initialize_offsets(&mut self) -> (Au, Au, Au) { + // TODO: If border-collapse: collapse, top_offset, bottom_offset, and left_offset + // should be updated. Currently, they are set as Au(0). + (Au(0), Au(0), Au(0)) + } + + /// Assign height for table-rowgroup flow. + /// + /// inline(always) because this is only ever called by in-order or non-in-order top-level + /// methods + #[inline(always)] + fn assign_height_table_rowgroup_base(&mut self, ctx: &mut LayoutContext, inorder: bool) { + let (top_offset, bottom_offset, left_offset) = self.initialize_offsets(); + + self.block_flow.handle_children_floats_if_necessary(ctx, inorder, + left_offset, top_offset); + let mut cur_y = top_offset; + + for kid in self.block_flow.base.child_iter() { + let child_node = flow::mut_base(kid); + child_node.position.origin.y = cur_y; + cur_y = cur_y + child_node.position.size.height; + } + + let height = cur_y - top_offset; + + for box_ in self.block_flow.box_.iter() { + let mut position = box_.border_box.get(); + position.size.height = height; + box_.border_box.set(position); + } + self.block_flow.base.position.size.height = height; + + self.block_flow.set_floats_out_if_inorder(inorder, height, cur_y, + top_offset, bottom_offset, left_offset); + } + + pub fn build_display_list_table_rowgroup<E:ExtraDisplayListData>( + &mut self, + builder: &DisplayListBuilder, + container_block_size: &Size2D<Au>, + absolute_cb_abs_position: Point2D<Au>, + dirty: &Rect<Au>, + index: uint, + lists: &RefCell<DisplayListCollection<E>>) + -> uint { + debug!("build_display_list_table_rowgroup: same process as block flow"); + self.block_flow.build_display_list_block(builder, container_block_size, + absolute_cb_abs_position, + dirty, index, lists) + } +} + +impl Flow for TableRowGroupFlow { + fn class(&self) -> FlowClass { + TableRowGroupFlowClass + } + + fn as_table_rowgroup<'a>(&'a mut self) -> &'a mut TableRowGroupFlow { + self + } + + fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow { + &mut self.block_flow + } + + /// Recursively (bottom-up) determines the context's preferred and minimum widths. When called + /// on this context, all child contexts have had their min/pref widths set. This function must + /// decide min/pref widths based on child context widths and dimensions of any boxes it is + /// responsible for flowing. + /// Min/pref widths set by this function are used in automatic table layout calculation. + /// Also, this function finds the specified column widths from the first row. + /// Those are used in fixed table layout calculation + fn bubble_widths(&mut self, ctx: &mut LayoutContext) { + /* find the specified column widths from the first table-row. + update the number of column widths from other table-rows. */ + for kid in self.block_flow.base.child_iter() { + assert!(kid.is_table_row()); + if self.col_widths.is_empty() { + self.col_widths = kid.as_table_row().col_widths.clone(); + } else { + let num_cols = self.col_widths.len(); + let num_child_cols = kid.as_table_row().col_widths.len(); + for _ in range(num_cols, num_child_cols) { + self.col_widths.push(Au::new(0)); + } + } + } + + // TODO: calculate min_width & pref_width for automatic table layout calculation + self.block_flow.bubble_widths(ctx); + } + + /// 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. + fn assign_widths(&mut self, ctx: &mut LayoutContext) { + debug!("assign_widths({}): assigning width for flow", "table_rowgroup"); + + // The position was set to the containing block by the flow's parent. + let containing_block_width = self.block_flow.base.position.size.width; + // FIXME: In case of border-collapse: collapse, left_content_edge should be border-left + let left_content_edge = Au::new(0); + let content_width = containing_block_width; + + let width_computer = InternalTable; + width_computer.compute_used_width(&mut self.block_flow, ctx, containing_block_width); + + self.block_flow.propagate_assigned_width_to_children(left_content_edge, content_width, Some(self.col_widths.clone())); + } + + /// This is called on kid flows by a parent. + /// + /// Hence, we can assume that assign_height has already been called on the + /// kid (because of the bottom-up traversal). + fn assign_height_inorder(&mut self, ctx: &mut LayoutContext) { + debug!("assign_height_inorder: assigning height for table_rowgroup"); + self.assign_height_table_rowgroup_base(ctx, true); + } + + fn assign_height(&mut self, ctx: &mut LayoutContext) { + debug!("assign_height: assigning height for table_rowgroup"); + self.assign_height_table_rowgroup_base(ctx, false); + } + + /// TableRowBox and their parents(TableBox) do not have margins. + /// Therefore, margins to be collapsed do not exist. + fn collapse_margins(&mut self, _: bool, _: &mut bool, _: &mut Au, + _: &mut Au, _: &mut Au, _: &mut Au) { + } + + fn debug_str(&self) -> ~str { + let txt = ~"TableRowGroupFlow: "; + txt.append(match self.block_flow.box_ { + Some(ref rb) => rb.debug_str(), + None => ~"", + }) + } +} + diff --git a/src/components/main/layout/table_wrapper.rs b/src/components/main/layout/table_wrapper.rs new file mode 100644 index 00000000000..d0ddfa11f92 --- /dev/null +++ b/src/components/main/layout/table_wrapper.rs @@ -0,0 +1,368 @@ +/* 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/. */ + +//! CSS table formatting contexts. + +use layout::box_::Box; +use layout::block::BlockFlow; +use layout::block::{WidthAndMarginsComputer, WidthConstraintInput, WidthConstraintSolution}; +use layout::construct::FlowConstructor; +use layout::context::LayoutContext; +use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; +use layout::floats::{FloatKind}; +use layout::flow::{TableWrapperFlowClass, FlowClass, Flow, ImmutableFlowUtils}; +use layout::flow; +use layout::model::{MaybeAuto, Specified, Auto, specified}; +use layout::wrapper::ThreadSafeLayoutNode; + +use std::cell::RefCell; +use style::computed_values::table_layout; +use geom::{Point2D, Rect, Size2D}; +use gfx::display_list::DisplayListCollection; +use servo_util::geometry::Au; +use servo_util::geometry; + +pub enum TableLayout { + FixedLayout, + AutoLayout +} + +/// A table wrapper flow based on a block formatting context. +pub struct TableWrapperFlow { + block_flow: BlockFlow, + + /// Column widths + col_widths: ~[Au], + + /// Table-layout property + table_layout: TableLayout, +} + +impl TableWrapperFlow { + pub fn from_node_and_box(node: &ThreadSafeLayoutNode, + box_: Box) + -> TableWrapperFlow { + let mut block_flow = BlockFlow::from_node_and_box(node, box_); + let table_layout = if block_flow.box_().style().Table.get().table_layout == + table_layout::fixed { + FixedLayout + } else { + AutoLayout + }; + TableWrapperFlow { + block_flow: block_flow, + col_widths: ~[], + table_layout: table_layout + } + } + + pub fn from_node(constructor: &mut FlowConstructor, + node: &ThreadSafeLayoutNode) + -> TableWrapperFlow { + let mut block_flow = BlockFlow::from_node(constructor, node); + let table_layout = if block_flow.box_().style().Table.get().table_layout == + table_layout::fixed { + FixedLayout + } else { + AutoLayout + }; + TableWrapperFlow { + block_flow: block_flow, + col_widths: ~[], + table_layout: table_layout + } + } + + pub fn float_from_node(constructor: &mut FlowConstructor, + node: &ThreadSafeLayoutNode, + float_kind: FloatKind) + -> TableWrapperFlow { + let mut block_flow = BlockFlow::float_from_node(constructor, node, float_kind); + let table_layout = if block_flow.box_().style().Table.get().table_layout == + table_layout::fixed { + FixedLayout + } else { + AutoLayout + }; + TableWrapperFlow { + block_flow: block_flow, + col_widths: ~[], + table_layout: table_layout + } + } + + pub fn is_float(&self) -> bool { + self.block_flow.float.is_some() + } + + pub fn teardown(&mut self) { + self.block_flow.teardown(); + self.col_widths = ~[]; + } + + /// Assign height for table-wrapper flow. + /// `Assign height` of table-wrapper flow follows a similar process to that of block flow. + /// However, table-wrapper flow doesn't consider collapsing margins for flow's children + /// and calculating padding/border. + /// + /// inline(always) because this is only ever called by in-order or non-in-order top-level + /// methods + #[inline(always)] + fn assign_height_table_wrapper_base(&mut self, ctx: &mut LayoutContext, inorder: bool) { + + // Note: Ignoring clearance for absolute flows as of now. + let ignore_clear = self.is_absolutely_positioned(); + let (clearance, top_offset, bottom_offset, left_offset) = self.block_flow.initialize_offsets(ignore_clear); + + self.block_flow.handle_children_floats_if_necessary(ctx, inorder, + left_offset, top_offset); + + // Table wrapper flow has margin but is not collapsed with kids(table caption and table). + let (margin_top, margin_bottom, _, _) = self.block_flow.precompute_margin(); + + let mut cur_y = top_offset; + + for kid in self.block_flow.base.child_iter() { + let child_node = flow::mut_base(kid); + child_node.position.origin.y = cur_y; + cur_y = cur_y + child_node.position.size.height; + } + + // top_offset: top margin-edge of the topmost child. + // hence, height = content height + let mut height = cur_y - top_offset; + + // For an absolutely positioned element, store the content height and stop the function. + if self.block_flow.store_content_height_if_absolutely_positioned(height) { + return; + } + + for box_ in self.block_flow.box_.iter() { + let style = box_.style(); + + // At this point, `height` is the height of the containing block, so passing `height` + // as the second argument here effectively makes percentages relative to the containing + // block per CSS 2.1 § 10.5. + height = match MaybeAuto::from_style(style.Box.get().height, height) { + Auto => height, + Specified(value) => geometry::max(value, height) + }; + } + + self.block_flow.compute_height_position(&mut height, + Au(0), + margin_top, + margin_bottom, + clearance); + + self.block_flow.set_floats_out_if_inorder(inorder, height, cur_y, + top_offset, bottom_offset, left_offset); + self.block_flow.assign_height_absolute_flows(ctx); + } + + pub fn build_display_list_table_wrapper<E:ExtraDisplayListData>( + &mut self, + builder: &DisplayListBuilder, + container_block_size: &Size2D<Au>, + absolute_cb_abs_position: Point2D<Au>, + dirty: &Rect<Au>, + index: uint, + lists: &RefCell<DisplayListCollection<E>>) + -> uint { + debug!("build_display_list_table_wrapper: same process as block flow"); + self.block_flow.build_display_list_block(builder, container_block_size, + absolute_cb_abs_position, + dirty, index, lists) + } +} + +impl Flow for TableWrapperFlow { + fn class(&self) -> FlowClass { + TableWrapperFlowClass + } + + fn as_table_wrapper<'a>(&'a mut self) -> &'a mut TableWrapperFlow { + self + } + + fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow { + &mut self.block_flow + } + + /* Recursively (bottom-up) determine the context's preferred and + minimum widths. When called on this context, all child contexts + have had their min/pref widths set. This function must decide + min/pref widths based on child context widths and dimensions of + any boxes it is responsible for flowing. */ + + fn bubble_widths(&mut self, ctx: &mut LayoutContext) { + /* find max width from child block contexts */ + for kid in self.block_flow.base.child_iter() { + assert!(kid.is_table_caption() || kid.is_table()); + + if kid.is_table() { + self.col_widths.push_all(kid.as_table().col_widths); + } + } + + self.block_flow.bubble_widths(ctx); + } + + /// 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. + /// + /// Dual boxes consume some width first, and the remainder is assigned to all child (block) + /// contexts. + fn assign_widths(&mut self, ctx: &mut LayoutContext) { + debug!("assign_widths({}): assigning width for flow", + if self.is_float() { + "floated table_wrapper" + } else { + "table_wrapper" + }); + + // The position was set to the containing block by the flow's parent. + let containing_block_width = self.block_flow.base.position.size.width; + let mut left_content_edge = Au::new(0); + let mut content_width = containing_block_width; + + self.block_flow.set_containing_width_if_float(containing_block_width); + + let width_computer = TableWrapper; + width_computer.compute_used_width_table_wrapper(self, ctx, containing_block_width); + + for box_ in self.block_flow.box_.iter() { + left_content_edge = box_.border_box.get().origin.x; + content_width = box_.border_box.get().size.width; + } + + match self.table_layout { + FixedLayout | _ if self.is_float() => + self.block_flow.base.position.size.width = content_width, + _ => {} + } + + self.block_flow.propagate_assigned_width_to_children(left_content_edge, content_width, None); + } + + /// This is called on kid flows by a parent. + /// + /// Hence, we can assume that assign_height has already been called on the + /// kid (because of the bottom-up traversal). + fn assign_height_inorder(&mut self, ctx: &mut LayoutContext) { + if self.is_float() { + debug!("assign_height_inorder_float: assigning height for floated table_wrapper"); + self.block_flow.assign_height_float_inorder(); + } else { + debug!("assign_height_inorder: assigning height for table_wrapper"); + self.assign_height_table_wrapper_base(ctx, true); + } + } + + fn assign_height(&mut self, ctx: &mut LayoutContext) { + if self.is_float() { + debug!("assign_height_float: assigning height for floated table_wrapper"); + self.block_flow.assign_height_float(ctx); + } else { + debug!("assign_height: assigning height for table_wrapper"); + self.assign_height_table_wrapper_base(ctx, false); + } + } + + // CSS Section 8.3.1 - Collapsing Margins + // `self`: the Flow whose margins we want to collapse. + // `collapsing`: value to be set by this function. This tells us how much + // of the top margin has collapsed with a previous margin. + // `collapsible`: Potential collapsible margin at the bottom of this flow's box. + fn collapse_margins(&mut self, + top_margin_collapsible: bool, + first_in_flow: &mut bool, + margin_top: &mut Au, + top_offset: &mut Au, + collapsing: &mut Au, + collapsible: &mut Au) { + self.block_flow.collapse_margins(top_margin_collapsible, + first_in_flow, + margin_top, + top_offset, + collapsing, + collapsible); + } + + fn debug_str(&self) -> ~str { + let txt = if self.is_float() { + ~"TableWrapperFlow(Float): " + } else { + ~"TableWrapperFlow: " + }; + txt.append(match self.block_flow.box_ { + Some(ref rb) => rb.debug_str(), + None => ~"", + }) + } +} + +struct TableWrapper; +impl TableWrapper { + fn compute_used_width_table_wrapper(&self, + table_wrapper: &mut TableWrapperFlow, + ctx: &mut LayoutContext, + parent_flow_width: Au) { + let input = self.compute_width_constraint_inputs_table_wrapper(table_wrapper, + parent_flow_width, + ctx); + + let solution = self.solve_width_constraints(&mut table_wrapper.block_flow, input); + + self.set_width_constraint_solutions(&mut table_wrapper.block_flow, solution); + self.set_flow_x_coord_if_necessary(&mut table_wrapper.block_flow, solution); + } + + fn compute_width_constraint_inputs_table_wrapper(&self, + table_wrapper: &mut TableWrapperFlow, + parent_flow_width: Au, + ctx: &mut LayoutContext) + -> WidthConstraintInput { + let mut input = self.compute_width_constraint_inputs(&mut table_wrapper.block_flow, + parent_flow_width, + ctx); + match table_wrapper.table_layout { + FixedLayout => { + let fixed_cells_width = table_wrapper.col_widths.iter().fold(Au(0), + |sum, width| sum.add(width)); + for box_ in table_wrapper.block_flow.box_.iter() { + let style = box_.style(); + + // Get left and right paddings, borders for table. + // We get these values from the box's style since table_wrapper doesn't have it's own border or padding. + // input.available_width is same as containing_block_width in table_wrapper. + let padding_left = specified(style.Padding.get().padding_left, + input.available_width); + let padding_right = specified(style.Padding.get().padding_right, + input.available_width); + let border_left = style.Border.get().border_left_width; + let border_right = style.Border.get().border_right_width; + let padding_and_borders = padding_left + padding_right + border_left + border_right; + let mut computed_width = input.computed_width.specified_or_zero(); + // Compare border-edge widths. Because fixed_cells_width indicates content-width, + // padding and border values are added to fixed_cells_width. + computed_width = geometry::max(fixed_cells_width + padding_and_borders, computed_width); + input.computed_width = Specified(computed_width); + } + }, + _ => {} + } + input + } +} + +impl WidthAndMarginsComputer for TableWrapper { + /// Solve the width and margins constraints for this block flow. + fn solve_width_constraints(&self, + block: &mut BlockFlow, + input: WidthConstraintInput) + -> WidthConstraintSolution { + self.solve_block_width_constraints(block, input) + } +} diff --git a/src/components/main/servo.rs b/src/components/main/servo.rs index 645c8794eab..3a056f9ef9f 100755 --- a/src/components/main/servo.rs +++ b/src/components/main/servo.rs @@ -101,6 +101,13 @@ pub mod layout { pub mod inline; pub mod model; pub mod parallel; + pub mod table_wrapper; + pub mod table; + pub mod table_caption; + pub mod table_colgroup; + pub mod table_rowgroup; + pub mod table_row; + pub mod table_cell; pub mod text; pub mod util; pub mod incremental; diff --git a/src/components/style/properties.rs.mako b/src/components/style/properties.rs.mako index 599eaf7bb8d..db8cf392722 100644 --- a/src/components/style/properties.rs.mako +++ b/src/components/style/properties.rs.mako @@ -284,11 +284,11 @@ pub mod longhands { // } if context.positioned || context.floated || context.is_root_element { match value { -// inline_table => table, + inline_table => table, inline | inline_block -// | table_row_group | table_column | table_column_group -// | table_header_group | table_footer_group | table_row -// | table_cell | table_caption + | table_row_group | table_column | table_column_group + | table_header_group | table_footer_group | table_row + | table_cell | table_caption => block, _ => value, } @@ -808,6 +808,9 @@ pub mod longhands { ${single_keyword("white-space", "normal pre")} // CSS 2.1, Section 17 - Tables + ${new_style_struct("Table", is_inherited=False)} + + ${single_keyword("table-layout", "auto fixed")} // CSS 2.1, Section 18 - User interface } diff --git a/src/test/html/anonymous_table.html b/src/test/html/anonymous_table.html new file mode 100644 index 00000000000..90363d43d28 --- /dev/null +++ b/src/test/html/anonymous_table.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<html> + <head> + <title>Fixed Table</title> + <style> + .table { + display: table; + table-layout: fixed; + width: 600px; + border: solid black 2px; + } + .colgroup { + display: table-column-group; + } + .column { + display: table-column; + } + .row { + display: table-row; + } + .cell { + display: table-cell; + border: solid red 1px; + } + </style> + </head> + <body> + <p> This test checks Anonymous table objects(CSS 2.1, Section 17.2.1) </p> + <p> 1. Remove irrelevant boxes</p> + <p> 2. Generate missing child wrappers: `table-row`, `table-cell` </p> + <div class="table"> + <span class="column"> inline child box of table-column. NOT Shown </span> + <span class="colgroup"> + <span>inline child box of table-column-group</span> NOT Shown + </span> + <span class="cell">Cell1</span> + <span class="cell">Cell2</span> + <span class="row"> + 2nd Row + <span>Cell4</span> + <span class="cell">Cell3</span> + Cell5 + </span> + </div> + </body> +<html> diff --git a/src/test/html/fixed_table.html b/src/test/html/fixed_table.html new file mode 100644 index 00000000000..d6b7e678a9b --- /dev/null +++ b/src/test/html/fixed_table.html @@ -0,0 +1,36 @@ +<!-- This test creates one table, one caption, three rows, three header cells, and five data cells. + The table uses the fixed table layout algorithm and the table's width specified to 600px. + Each column's width will be assigned as follows: + - 1st column: 200px (because it is defined in col element) + - 2nd column: 100px (because it is defined in first row) + - 3rd column: remaining width (becuase it is not defined so the remaining width is assigned) + And table, caption, td, th elements have border. --> +<!DOCTYPE html> +<html> + <head> + <title>Fixed Table</title> + <style> + table { + table-layout: fixed; + width: 600px; + border: solid black 2px; + } + caption { border: solid blue 1px; } + td { border: solid red 1px; } + th { border: solid red 1px; } + </style> + </head> + <body> + <table> + <caption>This is a 3x3 fixed table</caption> + <colgroup> + <col style="width: 200px" /> + </colgroup> + <tbody> + <tr><th style="width: 100px">Header 1</th><td style="width: 100px">Cell 1</td><td>Cell 2</td></tr> + <tr><th style="width: 300px">Header 2</th><td style="width: 300px">Cell 3</th><td>Cell 4</td></tr> + <tr><th>Header 3</th><td>Cell 5</th></tr> + </tbody> + </table> + </body> +<html> diff --git a/src/test/html/fixed_table_2.html b/src/test/html/fixed_table_2.html new file mode 100644 index 00000000000..0a5d28d6ad6 --- /dev/null +++ b/src/test/html/fixed_table_2.html @@ -0,0 +1,36 @@ +<!-- This test creates one table, one caption, three rows, three header cells, and five data cells. + The table uses the fixed table layout algorithm and the table's width specified to 600px. + Each column's width will be assigned according to their ratio of column's widths + which are defined in col elements. + And table, caption, td, th elements have border. --> +<!DOCTYPE html> +<html> + <head> + <title>Fixed Table</title> + <style> + table { + table-layout: fixed; + width: 600px; + border: solid black 2px; + } + caption { border: solid blue 1px; } + td { border: solid red 1px; } + th { border: solid red 1px; } + </style> + </head> + <body> + <table> + <caption>This is a 3x3 fixed table</caption> + <colgroup> + <col style="width: 10px" /> + <col style="width: 20px" /> + <col style="width: 30px" /> + </colgroup> + <tbody> + <tr><th style="width: 100px">Header 1</th><td style="width: 100px">Cell 1</td><td>Cell 2</td></tr> + <tr><th style="width: 300px">Header 2</th><td style="width: 300px">Cell 3</th><td>Cell 4</td></tr> + <tr><th>Header 3</th><td>Cell 5</th></tr> + </tbody> + </table> + </body> +<html> diff --git a/src/test/html/fixed_table_additional_cols.html b/src/test/html/fixed_table_additional_cols.html new file mode 100644 index 00000000000..cca70af353f --- /dev/null +++ b/src/test/html/fixed_table_additional_cols.html @@ -0,0 +1,39 @@ +<!-- This test creates one table, one caption, three rows, three header cells, and five data cells. + The table uses the fixed table layout algorithm and the table's width specified to 600px. + Each column's width will be assigned as follows: + - 1st & 2nd column: 200px (because it is defined in col element) + - 3rd & 4th column: remaining width / 2 + (becuase it is not defined so the remaining width is equally divided) + And table, caption, td, th elements have border. --> +<!DOCTYPE html> +<html> + <head> + <title>Fixed Table</title> + <style> + table { + table-layout: fixed; + width: 600px; + border: solid black 2px; + } + caption { border: solid blue 1px; } + td { border: solid red 1px; } + th { border: solid red 1px; } + </style> + </head> + <body> + <table> + <caption>This is a 3x4 fixed table</caption> + <colgroup> + <col style="width: 200px" /> + <col style="width: 200px" /> + <col /> + <col /> + </colgroup> + <tbody> + <tr><th>Header 1</th><td>Cell 1</td><td>Cell 2</td></tr> + <tr><th>Header 2</th><td>Cell 3</th><td>Cell 4</td></tr> + <tr><th>Header 3</th><td>Cell 5</th></tr> + </tbody> + </table> + </body> +<html> diff --git a/src/test/html/fixed_table_basic_height.html b/src/test/html/fixed_table_basic_height.html new file mode 100644 index 00000000000..2e2712b01c5 --- /dev/null +++ b/src/test/html/fixed_table_basic_height.html @@ -0,0 +1,40 @@ +<!-- This test creates one table, three rows, three header cells, and six data cells. + The table uses the fixed table layout algorithm and the table's width specified to 600px. + Each column's width will be assigned as 200px. + Each table row height is decided as max(specified row height, specified cells' heights, cells' minimum content heights). + As a result, each table row height will be assigned as followings: + - 1st row: 30px (specified cell height) + - 2nd row: 50px (specified row height) + - 3rd row: minimum content height +--> +<!DOCTYPE html> +<html> + <head> + <title>Table Height Test</title> + <style> + table { + table-layout: fixed; + width: 600px; + border: solid black 2px; + } + caption { + border: solid blue 1px; + } + td, th { + border: solid red 1px; + padding: 0px; + } + </style> + </head> + <body> + <table> + <caption>This test checks table height algorithm (CSS 2.1, Section 17.5.3), + excluding `vertical-align` and percentage height</caption> + <tbody> + <tr style="height:10px"><th>Header 1</th><td style="height: 30px">Cell 1</td><td>Cell 2</td></tr> + <tr style="height:50px"><th>Header 2</th><td>Cell 3</td><td style="height:10px">Cell 4</td></tr> + <tr style="height:20px"><th>Header 3</th><td style="height:10px">Cell 5</td><td><div>Cell6</div><p>Cell6</td></tr> + </tbody> + </table> + </body> +<html> diff --git a/src/test/html/fixed_table_simple.html b/src/test/html/fixed_table_simple.html new file mode 100644 index 00000000000..811d6a0bb75 --- /dev/null +++ b/src/test/html/fixed_table_simple.html @@ -0,0 +1,30 @@ +<!-- This test creates one table, one caption, three rows, three header cells, and six data cells. + The table uses fixed table layout algorithm and the table's width specified to 600px. + Each column should have same width because the column's widths are not defined here. + And table, caption, td, th elements have border. --> +<!DOCTYPE html> +<html> + <head> + <title>Simple Fixed Table</title> + <style> + table { + table-layout: fixed; + width: 600px; + border: solid black 2px; + } + caption { border: solid blue 1px; } + td { border: solid red 1px; } + th { border: solid red 1px; } + </style> + </head> + <body> + <table> + <caption>This is a 3x3 fixed table</caption> + <tbody> + <tr><th>Header 1</th><td>Cell 1</td><td>Cell 2</td></tr> + <tr><th>Header 2</th><td>Cell 3</td><td>Cell 4</td></tr> + <tr><th>Header 3</th><td>Cell 5</td><td>Cell 6</td></tr> + </tbody> + </table> + </body> +<html> diff --git a/src/test/html/fixed_table_with_margin_padding.html b/src/test/html/fixed_table_with_margin_padding.html new file mode 100644 index 00000000000..8dff2cc96bf --- /dev/null +++ b/src/test/html/fixed_table_with_margin_padding.html @@ -0,0 +1,50 @@ +<!-- This test creates one table, one caption, three rows, three header cells, and five data cells. + The table uses the fixed table layout algorithm and the table's width specified to 600px. + Each column's width will be assigned as follows: + - 1st column: 200px (because it is defined in col element) + - 2nd column: 100px (because it is defined in first row) + - 3rd column: remaining width (becuase it is not defined so the remaining width is assigned) + The table and caption elements have border, margin, and padding. + The td and th elements have border and padding. --> +<!DOCTYPE html> +<html> + <head> + <title>Fixed Table with margin, border, and padding</title> + <style> + table { + table-layout: fixed; + width: 600px; + border: solid black 2px; + margin: 10px; + padding: 10px; + } + caption { + border: solid blue 1px; + margin: 5px; + padding: 5px; + } + td { + border: solid red 1px; + padding: 5px; + } + th { + border: solid red 1px; + padding: 5px; + } + </style> + </head> + <body> + <table> + <caption>This is a 3x3 fixed table with margin, border, and padding</caption> + <colgroup> + <col style="width: 200px" /> + <col /> + </colgroup> + <tbody> + <tr><th style="width: 100px">Header 1</th><td style="width: 100px">Cell 1</td><td>Cell 2</td></tr> + <tr><th>Header 2</th><td>Cell 3</td><td>Cell 4</td></tr> + <tr><th>Header 3</th><td>Cell 5</td></tr> + </tbody> + </table> + </body> +<html> |