diff options
author | Junyoung Cho <june0.cho@samsung.com> | 2014-03-20 20:01:08 +0900 |
---|---|---|
committer | Junyoung Cho <june0.cho@samsung.com> | 2014-03-24 16:14:27 +0900 |
commit | 008be170d4b2c01e60baeb5827c189f4c7ed0041 (patch) | |
tree | bf611fd6191f257892b20104a30f5a5f05bd8263 /src | |
parent | e5333fca2da8b86ea11d43f04280187fd12b09f5 (diff) | |
download | servo-008be170d4b2c01e60baeb5827c189f4c7ed0041.tar.gz servo-008be170d4b2c01e60baeb5827c189f4c7ed0041.zip |
Construct table-related flow and calculate fixed layout
Diffstat (limited to 'src')
-rw-r--r-- | src/components/main/layout/block.rs | 93 | ||||
-rw-r--r-- | src/components/main/layout/box_.rs | 265 | ||||
-rw-r--r-- | src/components/main/layout/construct.rs | 272 | ||||
-rw-r--r-- | src/components/main/layout/flow.rs | 191 | ||||
-rw-r--r-- | src/components/main/layout/inline.rs | 5 | ||||
-rw-r--r-- | src/components/main/layout/table.rs | 327 | ||||
-rw-r--r-- | src/components/main/layout/table_caption.rs | 103 | ||||
-rw-r--r-- | src/components/main/layout/table_cell.rs | 173 | ||||
-rw-r--r-- | src/components/main/layout/table_colgroup.rs | 98 | ||||
-rw-r--r-- | src/components/main/layout/table_row.rs | 222 | ||||
-rw-r--r-- | src/components/main/layout/table_rowgroup.rs | 197 | ||||
-rw-r--r-- | src/components/main/layout/table_wrapper.rs | 368 | ||||
-rwxr-xr-x | src/components/main/servo.rs | 7 | ||||
-rw-r--r-- | src/components/style/properties.rs.mako | 11 |
14 files changed, 2199 insertions, 133 deletions
diff --git a/src/components/main/layout/block.rs b/src/components/main/layout/block.rs index a71e0bdf0b8..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") @@ -636,12 +648,10 @@ impl BlockFlow { _ => Au::new(0) }; - // Offset to content edge of box_ - let top_offset = clearance + box_.margin.get().top + box_.border.get().top + - box_.padding.get().top; - let bottom_offset = box_.margin.get().bottom + box_.border.get().bottom + - box_.padding.get().bottom; - let 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) } @@ -975,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); @@ -1019,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 { @@ -1085,7 +1095,8 @@ impl BlockFlow { /// 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) { + content_width: Au, + opt_col_widths: Option<~[Au]>) { let has_inorder_children = if self.is_float() { self.base.num_floats > 0 } else { @@ -1113,8 +1124,34 @@ impl BlockFlow { // 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()); + + // 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(); @@ -1122,10 +1159,8 @@ impl BlockFlow { 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.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() { @@ -1415,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); @@ -1491,7 +1526,7 @@ impl Flow for BlockFlow { self.base.position.size.width = content_width; } - self.propagate_assigned_width_to_children(left_content_edge, content_width); + self.propagate_assigned_width_to_children(left_content_edge, content_width, None); } /// This is called on kid flows by a parent. @@ -1644,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, @@ -1655,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, @@ -1676,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, @@ -1685,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), @@ -1714,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..7cf067f5423 100644 --- a/src/components/main/layout/box_.rs +++ b/src/components/main/layout/box_.rs @@ -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,22 @@ 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 from an opaque node. pub fn from_opaque_node_and_style(node: OpaqueNode, style: Arc<ComputedValues>, @@ -521,20 +565,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 +616,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 +661,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 +860,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 +1167,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 +1257,8 @@ impl Box { }); }); }, - GenericBox | IframeBox(..) => { + GenericBox | IframeBox(..) | TableBox | TableCellBox | TableRowBox | + TableWrapperBox => { lists.with_mut(|lists| { let item = ~ClipDisplayItem { base: BaseDisplayItem { @@ -1246,7 +1363,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 +1372,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 +1399,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 +1409,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 +1417,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 +1429,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 +1444,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 +1483,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 +1604,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 +1644,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 +1654,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 +1693,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 +1729,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..857b7c82530 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,6 +302,12 @@ 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, } @@ -332,10 +352,10 @@ 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 first_box = true; @@ -346,25 +366,29 @@ 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 - } - - // 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); - abs_descendants.push_descendants(kid_abs_descendants); - fixed_descendants.push_descendants(kid_fixed_descendants); + // 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, kid_abs_descendants, kid_fixed_descendants)) + } 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); + abs_descendants.push_descendants(kid_abs_descendants); + fixed_descendants.push_descendants(kid_fixed_descendants); + } } ConstructionItemConstructionResult(InlineBoxesConstructionItem( InlineBoxesConstructionResult { @@ -419,6 +443,10 @@ 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 + } } } @@ -456,7 +484,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 +492,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 +564,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 +645,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 +716,149 @@ impl<'a> FlowConstructor<'a> { self.build_boxes_for_replaced_inline_content(node) } } + + fn build_table_wrapper_flow_using_children(&mut self, + table_wrapper_flow: &mut ~Flow, + node: &ThreadSafeLayoutNode) + -> (Descendants, Descendants) { + // List of absolute descendants, in tree order. + let mut abs_descendants = Descendants::new(); + let mut fixed_descendants = Descendants::new(); + for kid in node.children() { + match kid.swap_out_construction_result() { + NoConstructionResult | ConstructionItemConstructionResult(_) => {} + FlowConstructionResult(kid_flow, kid_abs_descendants, kid_fixed_descendants) => { + // Only kid flows with table-caption are matched here. + assert!(kid_flow.is_table_caption()); + table_wrapper_flow.add_new_child(kid_flow); + abs_descendants.push_descendants(kid_abs_descendants); + fixed_descendants.push_descendants(kid_fixed_descendants); + } + } + } + (abs_descendants, fixed_descendants) + } + + /// 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); + let (mut abs_descendants, mut fixed_descendants) + = self.build_table_wrapper_flow_using_children(&mut wrapper_flow, node); + + // 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 +900,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 +920,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..cc4db0cb350 100644 --- a/src/components/main/layout/flow.rs +++ b/src/components/main/layout/flow.rs @@ -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,30 @@ 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 this flow has no children. fn is_leaf(self) -> bool; @@ -309,6 +375,13 @@ pub trait MutableOwnedFlowUtils { pub enum FlowClass { BlockFlowClass, InlineFlowClass, + TableWrapperFlowClass, + TableFlowClass, + TableColGroupFlowClass, + TableRowGroupFlowClass, + TableRowFlowClass, + TableCaptionFlowClass, + TableCellFlowClass, } /// A top-down traversal. @@ -753,7 +826,76 @@ 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, } } @@ -776,11 +918,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 +930,7 @@ impl<'a> ImmutableFlowUtils for &'a Flow { fn is_block_flow(self) -> bool { match self.class() { BlockFlowClass => true, - InlineFlowClass => false, + _ => false, } } @@ -796,7 +938,7 @@ impl<'a> ImmutableFlowUtils for &'a Flow { fn is_inline_flow(self) -> bool { match self.class() { InlineFlowClass => true, - BlockFlowClass => false, + _ => false, } } @@ -948,13 +1090,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 e5f919c5ccb..ba35b4ad28f 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 } |