aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJunyoung Cho <june0.cho@samsung.com>2014-03-20 20:01:08 +0900
committerJunyoung Cho <june0.cho@samsung.com>2014-03-24 16:14:27 +0900
commit008be170d4b2c01e60baeb5827c189f4c7ed0041 (patch)
treebf611fd6191f257892b20104a30f5a5f05bd8263 /src
parente5333fca2da8b86ea11d43f04280187fd12b09f5 (diff)
downloadservo-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.rs93
-rw-r--r--src/components/main/layout/box_.rs265
-rw-r--r--src/components/main/layout/construct.rs272
-rw-r--r--src/components/main/layout/flow.rs191
-rw-r--r--src/components/main/layout/inline.rs5
-rw-r--r--src/components/main/layout/table.rs327
-rw-r--r--src/components/main/layout/table_caption.rs103
-rw-r--r--src/components/main/layout/table_cell.rs173
-rw-r--r--src/components/main/layout/table_colgroup.rs98
-rw-r--r--src/components/main/layout/table_row.rs222
-rw-r--r--src/components/main/layout/table_rowgroup.rs197
-rw-r--r--src/components/main/layout/table_wrapper.rs368
-rwxr-xr-xsrc/components/main/servo.rs7
-rw-r--r--src/components/style/properties.rs.mako11
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
}