auto merge of #1973 : june0cho/servo/table_rebase, r=larsbergstrom,metajack

This is a rebase of #1548 on recent master.

There have been many changes since #1548 is first uploaded, so I'm creating new PR.
This PR includes:
 - construction of table-* flows (table-wrapper, table-caption, table, table-rowgroup, table-row, table-cell)
 - fixed-layout table calculation
 - a part of anonymous table object implementation 
[CSS 2.1, 17.2.1](http://www.w3.org/TR/CSS21/tables.html#anonymous-boxes) (Step 1-1, 1-2, Step 2)
This commit is contained in:
bors-servo 2014-03-25 13:40:47 -04:00
commit fd5e5cd18b
21 changed files with 2861 additions and 316 deletions

View file

@ -362,6 +362,18 @@ impl BlockFlow {
}
}
pub fn from_node_and_box(node: &ThreadSafeLayoutNode,
box_: Box)
-> BlockFlow {
BlockFlow {
base: BaseFlow::new((*node).clone()),
box_: Some(box_),
is_root: false,
static_y_offset: Au::new(0),
float: None
}
}
pub fn float_from_node(constructor: &mut FlowConstructor,
node: &ThreadSafeLayoutNode,
float_kind: FloatKind)
@ -433,7 +445,7 @@ impl BlockFlow {
}
/// Return this flow's box.
fn box_<'a>(&'a mut self) -> &'a mut Box {
pub fn box_<'a>(&'a mut self) -> &'a mut Box {
match self.box_ {
Some(ref mut box_) => box_,
None => fail!("BlockFlow: no principal box found")
@ -625,47 +637,36 @@ impl BlockFlow {
self.base.fixed_descendants.static_y_offsets = fixed_descendant_y_offsets;
}
/// Assign height for current flow.
///
/// + Collapse margins for flow's children and set in-flow child flows'
/// y-coordinates now that we know their heights.
/// + Calculate and set the height of the current flow.
/// + Calculate height, vertical margins, and y-coordinate for the flow's
/// box. Ideally, this should be calculated using CSS Section 10.6.7
///
/// For absolute flows, store the calculated content height for the flow.
/// Defer the calculation of the other values till a later traversal.
///
/// inline(always) because this is only ever called by in-order or non-in-order top-level
/// methods
#[inline(always)]
fn assign_height_block_base(&mut self, ctx: &mut LayoutContext, inorder: bool) {
let mut cur_y = Au::new(0);
let mut clearance = Au::new(0);
// Offset to content edge of box_
let mut top_offset = Au::new(0);
let mut bottom_offset = Au::new(0);
let mut left_offset = Au::new(0);
for box_ in self.box_.iter() {
// Note: Ignoring clearance for absolute flows as of now.
if !self.is_absolutely_positioned() {
clearance = match box_.clear() {
None => Au::new(0),
Some(clear) => {
self.base.floats.clearance(clear)
}
/// Calculate clearance, top_offset, bottom_offset, and left_offset for the box.
/// If `ignore_clear` is true, clearance does not need to be calculated.
pub fn initialize_offsets(&mut self, ignore_clear: bool) -> (Au, Au, Au, Au) {
match self.box_ {
None => (Au(0), Au(0), Au(0), Au(0)),
Some(ref box_) => {
let clearance = match box_.clear() {
Some(clear) if !ignore_clear => self.base.floats.clearance(clear),
_ => Au::new(0)
};
// 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)
}
top_offset = clearance + box_.margin.get().top + box_.border.get().top +
box_.padding.get().top;
cur_y = cur_y + top_offset;
bottom_offset = box_.margin.get().bottom + box_.border.get().bottom +
box_.padding.get().bottom;
left_offset = box_.offset();
}
}
/// In case of inorder assign_height traversal and not absolute flow,
/// 'assign_height's of all children are visited
/// and Float info is shared between adjacent children.
/// Float info of the last child is saved in parent flow.
pub fn handle_children_floats_if_necessary(&mut self,
ctx: &mut LayoutContext,
inorder: bool,
left_offset: Au,
top_offset: Au) {
// Note: Ignoring floats for absolute flow as of now.
if inorder && !self.is_absolutely_positioned() {
// Floats for blocks work like this:
@ -684,9 +685,41 @@ impl BlockFlow {
}
self.base.floats = floats;
}
}
// The amount of margin that we can potentially collapse with
let mut collapsible = Au::new(0);
/// Compute margin_top and margin_bottom. Also, it is decided whether top margin and
/// bottom margin are collapsible according to CSS 2.1 § 8.3.1.
pub fn precompute_margin(&mut self) -> (Au, Au, bool, bool) {
match self.box_ {
// Margins for an absolutely positioned element do not collapse with
// its children.
Some(ref box_) if !self.is_absolutely_positioned() => {
let top_margin_collapsible = !self.is_root &&
box_.border.get().top == Au(0) &&
box_.padding.get().top == Au(0);
let bottom_margin_collapsible = !self.is_root &&
box_.border.get().bottom == Au(0) &&
box_.padding.get().bottom == Au(0);
let margin_top = box_.margin.get().top;
let margin_bottom = box_.margin.get().bottom;
(margin_top, margin_bottom, top_margin_collapsible, bottom_margin_collapsible)
},
_ => (Au(0), Au(0), false, false)
}
}
/// Compute collapsed margins between adjacent children or between the first/last child and parent
/// according to CSS 2.1 § 8.3.1. Current y position(cur_y) is continually updated for collapsing result.
pub fn compute_margin_collapse(&mut self,
cur_y: &mut Au,
top_offset: &mut Au,
margin_top: &mut Au,
margin_bottom: &mut Au,
top_margin_collapsible: bool,
bottom_margin_collapsible: bool) -> Au {
// How much to move up by to get to the beginning of
// current kid flow.
// Example: if previous sibling's margin-bottom is 20px and your
@ -694,55 +727,38 @@ impl BlockFlow {
// will be at the bottom margin edge of the previous sibling, we have
// to move up by 12px to get to our top margin edge. So, `collapsing`
// will be set to 12px
let mut collapsing = Au::new(0);
let mut margin_top = Au::new(0);
let mut margin_bottom = Au::new(0);
let mut top_margin_collapsible = false;
let mut bottom_margin_collapsible = false;
let mut first_in_flow = true;
// Margins for an absolutely positioned element do not collapse with
// its children.
if !self.is_absolutely_positioned() {
for box_ in self.box_.iter() {
if !self.is_root() && box_.border.get().top == Au(0)
&& box_.padding.get().top == Au(0) {
collapsible = box_.margin.get().top;
top_margin_collapsible = true;
}
if !self.is_root() && box_.border.get().bottom == Au(0) &&
box_.padding.get().bottom == Au(0) {
bottom_margin_collapsible = true;
}
margin_top = box_.margin.get().top;
margin_bottom = box_.margin.get().bottom;
}
}
let mut collapsing = Au::new(0);
// The amount of margin that we can potentially collapse with
let mut collapsible = if top_margin_collapsible {
*margin_top
} else {
Au(0)
};
// At this point, cur_y is at the content edge of the flow's box_
for kid in self.base.child_iter() {
// At this point, cur_y is at bottom margin edge of previous kid
if kid.is_absolutely_positioned() {
// Assume that the `hypothetical box` for an absolute flow
// starts immediately after the bottom margin edge of the
// previous flow.
kid.as_block().base.position.origin.y = cur_y;
kid.as_block().base.position.origin.y = *cur_y;
// Skip the collapsing for absolute flow kids and continue
// with the next flow.
} else {
kid.collapse_margins(top_margin_collapsible,
&mut first_in_flow,
&mut margin_top,
&mut top_offset,
margin_top,
top_offset,
&mut collapsing,
&mut collapsible);
let child_node = flow::mut_base(kid);
cur_y = cur_y - collapsing;
*cur_y = *cur_y - collapsing;
// At this point, after moving up by `collapsing`, cur_y is at the
// top margin edge of kid
child_node.position.origin.y = cur_y;
cur_y = cur_y + child_node.position.size.height;
child_node.position.origin.y = *cur_y;
*cur_y = *cur_y + child_node.position.size.height;
// At this point, cur_y is at the bottom margin edge of kid
}
}
@ -754,14 +770,128 @@ impl BlockFlow {
// The bottom margin for an absolutely positioned element does not
// collapse even with its children.
collapsing = if bottom_margin_collapsible && !self.is_absolutely_positioned() {
if margin_bottom < collapsible {
margin_bottom = collapsible;
if *margin_bottom < collapsible {
*margin_bottom = collapsible;
}
collapsible
} else {
Au::new(0)
};
collapsing
}
/// For an absolutely positioned element, store the content height for use in calculating
/// the absolute flow's dimensions later.
pub fn store_content_height_if_absolutely_positioned(&mut self,
height: Au) -> bool {
if self.is_absolutely_positioned() {
for box_ in self.box_.iter() {
let mut temp_position = box_.border_box.get();
temp_position.size.height = height;
box_.border_box.set(temp_position);
}
return true;
}
false
}
/// Compute the box height and set border_box and margin of the box.
pub fn compute_height_position(&mut self,
height: &mut Au,
border_and_padding: Au,
margin_top: Au,
margin_bottom: Au,
clearance: Au) {
// Here, height is content height of box_
let mut noncontent_height = border_and_padding;
for box_ in self.box_.iter() {
let mut position = box_.border_box.get();
let mut margin = box_.margin.get();
// The associated box is the border box of this flow.
// Margin after collapse
margin.top = margin_top;
margin.bottom = margin_bottom;
position.origin.y = clearance + margin.top;
// Border box height
position.size.height = *height + noncontent_height;
noncontent_height = noncontent_height + clearance + margin.top + margin.bottom;
box_.border_box.set(position);
box_.margin.set(margin);
}
// Height of margin box + clearance
self.base.position.size.height = *height + noncontent_height;
}
/// Set floats_out at the last step of the assign height calculation.
pub fn set_floats_out_if_inorder(&mut self,
inorder: bool,
height: Au,
cur_y: Au,
top_offset: Au,
bottom_offset: Au,
left_offset: Au) {
if inorder {
let extra_height = height - (cur_y - top_offset) + bottom_offset;
self.base.floats.translate(Point2D(left_offset, -extra_height));
}
}
/// Assign heights for all flows in absolute flow tree and store overflow for all
/// absolute descendants.
pub fn assign_height_absolute_flows(&mut self, ctx: &mut LayoutContext) {
if self.is_root_of_absolute_flow_tree() {
// Assign heights for all flows in this Absolute flow tree.
// This is preorder because the height of an absolute flow may depend on
// the height of its CB, which may also be an absolute flow.
self.traverse_preorder_absolute_flows(&mut AbsoluteAssignHeightsTraversal(ctx));
// Store overflow for all absolute descendants.
self.traverse_postorder_absolute_flows(&mut AbsoluteStoreOverflowTraversal {
layout_context: ctx,
});
}
}
/// Assign height for current flow.
///
/// + Collapse margins for flow's children and set in-flow child flows'
/// y-coordinates now that we know their heights.
/// + Calculate and set the height of the current flow.
/// + Calculate height, vertical margins, and y-coordinate for the flow's
/// box. Ideally, this should be calculated using CSS Section 10.6.7
///
/// For absolute flows, store the calculated content height for the flow.
/// Defer the calculation of the other values till a later traversal.
///
/// inline(always) because this is only ever called by in-order or non-in-order top-level
/// methods
#[inline(always)]
fn assign_height_block_base(&mut self, ctx: &mut LayoutContext, inorder: bool) {
// Note: Ignoring clearance for absolute flows as of now.
let ignore_clear = self.is_absolutely_positioned();
let (clearance, mut top_offset, bottom_offset, left_offset) =
self.initialize_offsets(ignore_clear);
self.handle_children_floats_if_necessary(ctx, inorder,
left_offset, top_offset);
let (mut margin_top, mut margin_bottom,
top_margin_collapsible, bottom_margin_collapsible) = self.precompute_margin();
let mut cur_y = top_offset;
let collapsing = self.compute_margin_collapse(&mut cur_y,
&mut top_offset,
&mut margin_top,
&mut margin_bottom,
top_margin_collapsible,
bottom_margin_collapsible);
// TODO: A box's own margins collapse if the 'min-height' property is zero, and it has neither
// top or bottom borders nor top or bottom padding, and it has a 'height' of either 0 or 'auto',
// and it does not contain a line box, and all of its in-flow children's margins (if any) collapse.
@ -782,17 +912,12 @@ impl BlockFlow {
cur_y - top_offset - collapsing
};
if self.is_absolutely_positioned() {
// Store the content height for use in calculating the absolute
// flow's dimensions later.
for box_ in self.box_.iter() {
let mut temp_position = box_.border_box.get();
temp_position.size.height = height;
box_.border_box.set(temp_position);
}
// For an absolutely positioned element, store the content height and stop the function.
if self.store_content_height_if_absolutely_positioned(height) {
return;
}
let mut border_and_padding = Au::new(0);
for box_ in self.box_.iter() {
let style = box_.style();
@ -803,51 +928,19 @@ impl BlockFlow {
Auto => height,
Specified(value) => value
};
border_and_padding = box_.padding.get().top + box_.padding.get().bottom +
box_.border.get().top + box_.border.get().bottom;
}
// Here, height is content height of box_
self.compute_height_position(&mut height,
border_and_padding,
margin_top,
margin_bottom,
clearance);
let mut noncontent_height = Au::new(0);
for box_ in self.box_.iter() {
let mut position = box_.border_box.get();
let mut margin = box_.margin.get();
// The associated box is the border box of this flow.
// Margin after collapse
margin.top = margin_top;
margin.bottom = margin_bottom;
noncontent_height = box_.padding.get().top + box_.padding.get().bottom +
box_.border.get().top + box_.border.get().bottom;
position.origin.y = clearance + margin.top;
// Border box height
position.size.height = height + noncontent_height;
noncontent_height = noncontent_height + clearance + margin.top + margin.bottom;
box_.border_box.set(position);
box_.margin.set(margin);
}
// Height of margin box + clearance
self.base.position.size.height = height + noncontent_height;
if inorder {
let extra_height = height - (cur_y - top_offset) + bottom_offset;
self.base.floats.translate(Point2D(left_offset, -extra_height));
}
if self.is_root_of_absolute_flow_tree() {
// Assign heights for all flows in this Absolute flow tree.
// This is preorder because the height of an absolute flow may depend on
// the height of its CB, which may also be an absolute flow.
self.traverse_preorder_absolute_flows(&mut AbsoluteAssignHeightsTraversal(ctx));
// Store overflow for all absolute descendants.
self.traverse_postorder_absolute_flows(&mut AbsoluteStoreOverflowTraversal {
layout_context: ctx,
});
}
self.set_floats_out_if_inorder(inorder, height, cur_y, top_offset, bottom_offset, left_offset);
self.assign_height_absolute_flows(ctx);
if self.is_root() {
self.assign_height_store_overflow_fixed_flows(ctx);
}
@ -892,7 +985,7 @@ impl BlockFlow {
/// This function is called on a kid flow by a parent.
/// Therefore, assign_height_float was already called on this kid flow by
/// the traversal function. So, the values used are well-defined.
fn assign_height_float_inorder(&mut self) {
pub fn assign_height_float_inorder(&mut self) {
let mut height = Au(0);
let mut clearance = Au(0);
let mut full_noncontent_width = Au(0);
@ -936,7 +1029,7 @@ impl BlockFlow {
/// should be calculated using CSS Section 10.6.7
///
/// It does not calculate the height of the flow itself.
fn assign_height_float(&mut self, ctx: &mut LayoutContext) {
pub fn assign_height_float(&mut self, ctx: &mut LayoutContext) {
// Now that we've determined our height, propagate that out.
let has_inorder_children = self.base.num_floats > 0;
if has_inorder_children {
@ -990,6 +1083,99 @@ impl BlockFlow {
box_.border_box.set(position);
}
/// In case of float, initialize containing_width at the beginning step of assign_width.
pub fn set_containing_width_if_float(&mut self, containing_block_width: Au) {
if self.is_float() {
self.float.get_mut_ref().containing_width = containing_block_width;
// Parent usually sets this, but floats are never inorder
self.base.flags_info.flags.set_inorder(false);
}
}
/// Assign the computed left_content_edge and content_width to children.
pub fn propagate_assigned_width_to_children(&mut self, left_content_edge: Au,
content_width: Au,
opt_col_widths: Option<~[Au]>) {
let has_inorder_children = if self.is_float() {
self.base.num_floats > 0
} else {
self.base.flags_info.flags.inorder() || self.base.num_floats > 0
};
let kid_abs_cb_x_offset;
if self.is_positioned() {
match self.box_ {
Some(ref box_) => {
// Pass yourself as a new Containing Block
// The static x offset for any immediate kid flows will be the
// left padding
kid_abs_cb_x_offset = box_.padding.get().left;
}
None => fail!("BlockFlow: no principal box found"),
}
} else {
// For kids, the left margin edge will be at our left content edge.
// The current static offset is at our left margin
// edge. So move in to the left content edge.
kid_abs_cb_x_offset = self.base.absolute_static_x_offset + left_content_edge;
}
let kid_fixed_cb_x_offset = self.base.fixed_static_x_offset + left_content_edge;
// FIXME(ksh8281): avoid copy
let flags_info = self.base.flags_info.clone();
// Left margin edge of kid flow is at our left content edge
let mut kid_left_margin_edge = left_content_edge;
// Width of kid flow is our content width
let mut kid_width = content_width;
for (i, kid) in self.base.child_iter().enumerate() {
assert!(kid.is_block_flow() || kid.is_inline_flow() || kid.is_table_kind());
match opt_col_widths {
Some(ref col_widths) => {
// If kid is table_rowgroup or table_row, the column widths info should be
// copied from its parent.
if kid.is_table_rowgroup() {
kid.as_table_rowgroup().col_widths = col_widths.clone()
} else if kid.is_table_row() {
kid.as_table_row().col_widths = col_widths.clone()
} else if kid.is_table_cell() {
// If kid is table_cell, the x offset and width for each cell should be
// calculated from parent's column widths info.
kid_left_margin_edge = if i == 0 {
Au(0)
} else {
kid_left_margin_edge + col_widths[i-1]
};
kid_width = col_widths[i]
}
}
None => {}
}
if kid.is_block_flow() {
let kid_block = kid.as_block();
kid_block.base.absolute_static_x_offset = kid_abs_cb_x_offset;
kid_block.base.fixed_static_x_offset = kid_fixed_cb_x_offset;
}
let child_base = flow::mut_base(kid);
child_base.position.origin.x = kid_left_margin_edge;
child_base.position.size.width = kid_width;
child_base.flags_info.flags.set_inorder(has_inorder_children);
if !child_base.flags_info.flags.inorder() {
child_base.floats = Floats::new();
}
// Per CSS 2.1 § 16.3.1, text decoration propagates to all children in flow.
//
// TODO(pcwalton): When we have out-of-flow children, don't unconditionally propagate.
child_base.flags_info.propagate_text_decoration_from_parent(&flags_info);
child_base.flags_info.propagate_text_alignment_from_parent(&flags_info)
}
}
/// Add display items for current block.
///
/// Set the absolute position for children after doing any offsetting for
@ -1264,7 +1450,7 @@ impl Flow for BlockFlow {
/* find max width from child block contexts */
for child_ctx in self.base.child_iter() {
assert!(child_ctx.is_block_flow() || child_ctx.is_inline_flow());
assert!(child_ctx.is_block_flow() || child_ctx.is_inline_flow() || child_ctx.is_table_kind());
let child_base = flow::mut_base(child_ctx);
min_width = geometry::max(min_width, child_base.min_width);
@ -1323,12 +1509,7 @@ impl Flow for BlockFlow {
let mut left_content_edge = Au::new(0);
let mut content_width = containing_block_width;
if self.is_float() {
self.float.get_mut_ref().containing_width = containing_block_width;
// Parent usually sets this, but floats are never inorder
self.base.flags_info.flags.set_inorder(false);
}
self.set_containing_width_if_float(containing_block_width);
self.compute_used_width(ctx, containing_block_width);
@ -1345,59 +1526,7 @@ impl Flow for BlockFlow {
self.base.position.size.width = content_width;
}
let has_inorder_children = if self.is_float() {
self.base.num_floats > 0
} else {
self.base.flags_info.flags.inorder() || self.base.num_floats > 0
};
let kid_abs_cb_x_offset;
if self.is_positioned() {
match self.box_ {
Some(ref box_) => {
// Pass yourself as a new Containing Block
// The static x offset for any immediate kid flows will be the
// left padding
kid_abs_cb_x_offset = box_.padding.get().left;
}
None => fail!("BlockFlow: no principal box found"),
}
} else {
// For kids, the left margin edge will be at our left content edge.
// The current static offset is at our left margin
// edge. So move in to the left content edge.
kid_abs_cb_x_offset = self.base.absolute_static_x_offset + left_content_edge;
}
let kid_fixed_cb_x_offset = self.base.fixed_static_x_offset + left_content_edge;
// FIXME(ksh8281): avoid copy
let flags_info = self.base.flags_info.clone();
for kid in self.base.child_iter() {
assert!(kid.is_block_flow() || kid.is_inline_flow());
if kid.is_block_flow() {
let kid_block = kid.as_block();
kid_block.base.absolute_static_x_offset = kid_abs_cb_x_offset;
kid_block.base.fixed_static_x_offset = kid_fixed_cb_x_offset;
}
let child_base = flow::mut_base(kid);
// Left margin edge of kid flow is at our left content edge
child_base.position.origin.x = left_content_edge;
// Width of kid flow is our content width
child_base.position.size.width = content_width;
child_base.flags_info.flags.set_inorder(has_inorder_children);
if !child_base.flags_info.flags.inorder() {
child_base.floats = Floats::new();
}
// Per CSS 2.1 § 16.3.1, text decoration propagates to all children in flow.
//
// TODO(pcwalton): When we have out-of-flow children, don't unconditionally propagate.
child_base.flags_info.propagate_text_decoration_from_parent(&flags_info);
child_base.flags_info.propagate_text_alignment_from_parent(&flags_info)
}
self.propagate_assigned_width_to_children(left_content_edge, content_width, None);
}
/// This is called on kid flows by a parent.
@ -1550,7 +1679,7 @@ impl Flow for BlockFlow {
}
/// The inputs for the widths-and-margins constraint equation.
struct WidthConstraintInput {
pub struct WidthConstraintInput {
computed_width: MaybeAuto,
left_margin: MaybeAuto,
right_margin: MaybeAuto,
@ -1561,13 +1690,13 @@ struct WidthConstraintInput {
}
impl WidthConstraintInput {
fn new(computed_width: MaybeAuto,
left_margin: MaybeAuto,
right_margin: MaybeAuto,
left: MaybeAuto,
right: MaybeAuto,
available_width: Au,
static_x_offset: Au)
pub fn new(computed_width: MaybeAuto,
left_margin: MaybeAuto,
right_margin: MaybeAuto,
left: MaybeAuto,
right: MaybeAuto,
available_width: Au,
static_x_offset: Au)
-> WidthConstraintInput {
WidthConstraintInput {
computed_width: computed_width,
@ -1582,7 +1711,7 @@ impl WidthConstraintInput {
}
/// The solutions for the widths-and-margins constraint equation.
struct WidthConstraintSolution {
pub struct WidthConstraintSolution {
left: Au,
right: Au,
width: Au,
@ -1591,7 +1720,7 @@ struct WidthConstraintSolution {
}
impl WidthConstraintSolution {
fn new(width: Au, margin_left: Au, margin_right: Au) -> WidthConstraintSolution {
pub fn new(width: Au, margin_left: Au, margin_right: Au) -> WidthConstraintSolution {
WidthConstraintSolution {
left: Au(0),
right: Au(0),
@ -1620,7 +1749,7 @@ impl WidthConstraintSolution {
// Trait to encapsulate the Width and Margin calculation.
//
// CSS Section 10.3
trait WidthAndMarginsComputer {
pub trait WidthAndMarginsComputer {
/// Compute the inputs for the Width constraint equation.
///
/// This is called only once to compute the initial inputs. For

View file

@ -29,7 +29,7 @@ use servo_util::str::is_whitespace;
use std::cast;
use std::cell::RefCell;
use std::num::Zero;
use style::{ComputedValues, TElement, TNode};
use style::{ComputedValues, TElement, TNode, cascade, initial_values};
use style::computed_values::{LengthOrPercentage, LengthOrPercentageOrAuto, overflow, LPA_Auto};
use style::computed_values::{border_style, clear, font_family, line_height, position};
use style::computed_values::{text_align, text_decoration, vertical_align, visibility, white_space};
@ -108,6 +108,11 @@ pub enum SpecificBoxInfo {
ImageBox(ImageBoxInfo),
IframeBox(IframeBoxInfo),
ScannedTextBox(ScannedTextBoxInfo),
TableBox,
TableCellBox,
TableColumnBox(TableColumnBoxInfo),
TableRowBox,
TableWrapperBox,
UnscannedTextBox(UnscannedTextBoxInfo),
}
@ -310,6 +315,29 @@ pub struct InlineParentInfo {
node: OpaqueNode,
}
/// A box that represents a table column.
#[deriving(Clone)]
pub struct TableColumnBoxInfo {
/// the number of columns a <col> element should span
span: Option<int>,
}
impl TableColumnBoxInfo {
/// Create the information specific to an table column box.
pub fn new(node: &ThreadSafeLayoutNode) -> TableColumnBoxInfo {
let span = {
let element = node.as_element();
element.get_attr(&namespace::Null, "span").and_then(|string| {
let n: Option<int> = FromStr::from_str(string);
n
})
};
TableColumnBoxInfo {
span: span,
}
}
}
// FIXME: Take just one parameter and use concat_ident! (mozilla/rust#12249)
macro_rules! def_noncontent( ($side:ident, $get:ident, $inline_get:ident) => (
impl Box {
@ -402,6 +430,49 @@ impl Box {
}
}
/// Constructs a new `Box` instance from a specific info.
pub fn new_from_specific_info(node: &ThreadSafeLayoutNode, specific: SpecificBoxInfo) -> Box {
Box {
node: OpaqueNode::from_thread_safe_layout_node(node),
style: node.style().clone(),
border_box: RefCell::new(Au::zero_rect()),
border: RefCell::new(Zero::zero()),
padding: RefCell::new(Zero::zero()),
margin: RefCell::new(Zero::zero()),
specific: specific,
position_offsets: RefCell::new(Zero::zero()),
inline_info: RefCell::new(None),
new_line_pos: ~[],
}
}
/// Constructs a new `Box` instance for an anonymous table object.
pub fn new_anonymous_table_box(node: &ThreadSafeLayoutNode, specific: SpecificBoxInfo) -> Box {
// CSS 2.1 § 17.2.1 This is for non-inherited properties on anonymous table boxes
// example:
//
// <div style="display: table">
// Foo
// </div>
//
// Anonymous table boxes, TableRowBox and TableCellBox, are generated around `Foo`, but it shouldn't inherit the border.
let (node_style, _) = cascade(&[], false, Some(node.style().get()),
&initial_values(), None);
Box {
node: OpaqueNode::from_thread_safe_layout_node(node),
style: Arc::new(node_style),
border_box: RefCell::new(Au::zero_rect()),
border: RefCell::new(Zero::zero()),
padding: RefCell::new(Zero::zero()),
margin: RefCell::new(Zero::zero()),
specific: specific,
position_offsets: RefCell::new(Zero::zero()),
inline_info: RefCell::new(None),
new_line_pos: ~[],
}
}
/// Constructs a new `Box` instance from an opaque node.
pub fn from_opaque_node_and_style(node: OpaqueNode,
style: Arc<ComputedValues>,
@ -521,20 +592,40 @@ impl Box {
/// Returns the shared part of the width for computation of minimum and preferred width per
/// CSS 2.1.
fn guess_width(&self) -> Au {
let style = self.style();
let mut margin_left = Au::new(0);
let mut margin_right = Au::new(0);
let mut padding_left = Au::new(0);
let mut padding_right = Au::new(0);
match self.specific {
GenericBox | IframeBox(_) | ImageBox(_) => {}
ScannedTextBox(_) | UnscannedTextBox(_) => return Au(0),
GenericBox | IframeBox(_) | ImageBox(_) => {
margin_left = MaybeAuto::from_style(style.Margin.get().margin_left,
Au::new(0)).specified_or_zero();
margin_right = MaybeAuto::from_style(style.Margin.get().margin_right,
Au::new(0)).specified_or_zero();
padding_left = self.compute_padding_length(style.Padding.get().padding_left,
Au::new(0));
padding_right = self.compute_padding_length(style.Padding.get().padding_right,
Au::new(0));
}
TableBox | TableCellBox => {
padding_left = self.compute_padding_length(style.Padding.get().padding_left,
Au::new(0));
padding_right = self.compute_padding_length(style.Padding.get().padding_right,
Au::new(0));
}
TableWrapperBox => {
margin_left = MaybeAuto::from_style(style.Margin.get().margin_left,
Au::new(0)).specified_or_zero();
margin_right = MaybeAuto::from_style(style.Margin.get().margin_right,
Au::new(0)).specified_or_zero();
}
TableRowBox => {}
ScannedTextBox(_) | TableColumnBox(_) | UnscannedTextBox(_) => return Au(0),
}
let style = self.style();
let width = MaybeAuto::from_style(style.Box.get().width, Au::new(0)).specified_or_zero();
let margin_left = MaybeAuto::from_style(style.Margin.get().margin_left,
Au::new(0)).specified_or_zero();
let margin_right = MaybeAuto::from_style(style.Margin.get().margin_right,
Au::new(0)).specified_or_zero();
let padding_left = self.compute_padding_length(style.Padding.get().padding_left, Au(0));
let padding_right = self.compute_padding_length(style.Padding.get().padding_right, Au(0));
width + margin_left + margin_right + padding_left + padding_right +
self.border.get().left + self.border.get().right
@ -552,23 +643,31 @@ impl Box {
///
/// FIXME(pcwalton): This should not be necessary. Just go to the style.
pub fn compute_borders(&self, style: &ComputedValues) {
#[inline]
fn width(width: Au, style: border_style::T) -> Au {
if style == border_style::none {
Au(0)
} else {
width
}
}
let border = match self.specific {
TableWrapperBox => {
SideOffsets2D::new(Au(0), Au(0), Au(0), Au(0))
},
_ => {
#[inline]
fn width(width: Au, style: border_style::T) -> Au {
if style == border_style::none {
Au(0)
} else {
width
}
}
self.border.set(SideOffsets2D::new(width(style.Border.get().border_top_width,
style.Border.get().border_top_style),
width(style.Border.get().border_right_width,
style.Border.get().border_right_style),
width(style.Border.get().border_bottom_width,
style.Border.get().border_bottom_style),
width(style.Border.get().border_left_width,
style.Border.get().border_left_style)))
SideOffsets2D::new(width(style.Border.get().border_top_width,
style.Border.get().border_top_style),
width(style.Border.get().border_right_width,
style.Border.get().border_right_style),
width(style.Border.get().border_bottom_width,
style.Border.get().border_bottom_style),
width(style.Border.get().border_left_width,
style.Border.get().border_left_style))
}
};
self.border.set(border)
}
pub fn compute_positioned_offsets(&self, style: &ComputedValues, containing_width: Au, containing_height: Au) {
@ -589,36 +688,51 @@ impl Box {
/// If it is auto, it is up to assign-height to ignore this value and
/// calculate the correct margin values.
pub fn compute_margin_top_bottom(&self, containing_block_width: Au) {
let style = self.style();
// Note: CSS 2.1 defines margin % values wrt CB *width* (not height).
let margin_top = MaybeAuto::from_style(style.Margin.get().margin_top,
containing_block_width).specified_or_zero();
let margin_bottom = MaybeAuto::from_style(style.Margin.get().margin_bottom,
containing_block_width).specified_or_zero();
let mut margin = self.margin.get();
margin.top = margin_top;
margin.bottom = margin_bottom;
self.margin.set(margin);
match self.specific {
TableBox | TableCellBox | TableRowBox | TableColumnBox(_) => {
self.margin.set(SideOffsets2D::new(Au(0), Au(0), Au(0), Au(0)))
},
_ => {
let style = self.style();
// Note: CSS 2.1 defines margin % values wrt CB *width* (not height).
let margin_top = MaybeAuto::from_style(style.Margin.get().margin_top,
containing_block_width).specified_or_zero();
let margin_bottom = MaybeAuto::from_style(style.Margin.get().margin_bottom,
containing_block_width).specified_or_zero();
let mut margin = self.margin.get();
margin.top = margin_top;
margin.bottom = margin_bottom;
self.margin.set(margin);
}
}
}
/// Populates the box model padding parameters from the given computed style.
pub fn compute_padding(&self, style: &ComputedValues, containing_block_width: Au) {
let padding = SideOffsets2D::new(self.compute_padding_length(style.Padding
.get()
.padding_top,
containing_block_width),
self.compute_padding_length(style.Padding
.get()
.padding_right,
containing_block_width),
self.compute_padding_length(style.Padding
.get()
.padding_bottom,
containing_block_width),
self.compute_padding_length(style.Padding
.get()
.padding_left,
containing_block_width));
let padding = match self.specific {
TableColumnBox(_) | TableRowBox | TableWrapperBox => {
SideOffsets2D::new(Au(0), Au(0), Au(0), Au(0))
},
GenericBox | IframeBox(_) | ImageBox(_) | TableBox | TableCellBox |
ScannedTextBox(_) | UnscannedTextBox(_) => {
SideOffsets2D::new(self.compute_padding_length(style.Padding
.get()
.padding_top,
containing_block_width),
self.compute_padding_length(style.Padding
.get()
.padding_right,
containing_block_width),
self.compute_padding_length(style.Padding
.get()
.padding_bottom,
containing_block_width),
self.compute_padding_length(style.Padding
.get()
.padding_left,
containing_block_width))
}
};
self.padding.set(padding)
}
@ -773,9 +887,37 @@ impl Box {
self.style().Text.get().text_decoration
}
/// Returns the sum of margin, border, and padding on the left.
pub fn offset(&self) -> Au {
self.margin.get().left + self.border.get().left + self.padding.get().left
/// Returns the left offset from margin edge to content edge.
pub fn left_offset(&self) -> Au {
match self.specific {
TableWrapperBox => self.margin.get().left,
TableBox | TableCellBox => self.border.get().left + self.padding.get().left,
TableRowBox => self.border.get().left,
TableColumnBox(_) => Au(0),
_ => self.margin.get().left + self.border.get().left + self.padding.get().left
}
}
/// Returns the top offset from margin edge to content edge.
pub fn top_offset(&self) -> Au {
match self.specific {
TableWrapperBox => self.margin.get().top,
TableBox | TableCellBox => self.border.get().top + self.padding.get().top,
TableRowBox => self.border.get().top,
TableColumnBox(_) => Au(0),
_ => self.margin.get().top + self.border.get().top + self.padding.get().top
}
}
/// Returns the bottom offset from margin edge to content edge.
pub fn bottom_offset(&self) -> Au {
match self.specific {
TableWrapperBox => self.margin.get().bottom,
TableBox | TableCellBox => self.border.get().bottom + self.padding.get().bottom,
TableRowBox => self.border.get().bottom,
TableColumnBox(_) => Au(0),
_ => self.margin.get().bottom + self.border.get().bottom + self.padding.get().bottom
}
}
/// Returns true if this element is replaced content. This is true for images, form elements,
@ -1052,6 +1194,7 @@ impl Box {
match self.specific {
UnscannedTextBox(_) => fail!("Shouldn't see unscanned boxes here."),
TableColumnBox(_) => fail!("Shouldn't see table column boxes here."),
ScannedTextBox(ref text_box) => {
let text_color = self.style().Color.get().color.to_gfx_color();
@ -1141,7 +1284,8 @@ impl Box {
});
});
},
GenericBox | IframeBox(..) => {
GenericBox | IframeBox(..) | TableBox | TableCellBox | TableRowBox |
TableWrapperBox => {
lists.with_mut(|lists| {
let item = ~ClipDisplayItem {
base: BaseDisplayItem {
@ -1246,7 +1390,7 @@ impl Box {
IframeBox(ref iframe_box) => {
self.finalize_position_and_size_of_iframe(iframe_box, flow_origin, builder.ctx)
}
GenericBox | ImageBox(_) | ScannedTextBox(_) | UnscannedTextBox(_) => {}
_ => {}
}
}
@ -1255,7 +1399,8 @@ impl Box {
pub fn minimum_and_preferred_widths(&self) -> (Au, Au) {
let guessed_width = self.guess_width();
let (additional_minimum, additional_preferred) = match self.specific {
GenericBox | IframeBox(_) => (Au(0), Au(0)),
GenericBox | IframeBox(_) | TableBox | TableCellBox | TableColumnBox(_) |
TableRowBox | TableWrapperBox => (Au(0), Au(0)),
ImageBox(ref image_box_info) => {
let image_width = image_box_info.image_width();
(image_width, image_width)
@ -1281,7 +1426,8 @@ impl Box {
/// TODO: What exactly does this function return? Why is it Au(0) for GenericBox?
pub fn content_width(&self) -> Au {
match self.specific {
GenericBox | IframeBox(_) => Au(0),
GenericBox | IframeBox(_) | TableBox | TableCellBox | TableRowBox |
TableWrapperBox => Au(0),
ImageBox(ref image_box_info) => {
image_box_info.computed_width()
}
@ -1290,6 +1436,7 @@ impl Box {
let text_bounds = run.get().metrics_for_range(range).bounding_box;
text_bounds.size.width
}
TableColumnBox(_) => fail!("Table column boxes do not have width"),
UnscannedTextBox(_) => fail!("Unscanned text boxes should have been scanned by now!"),
}
}
@ -1297,7 +1444,8 @@ impl Box {
///
pub fn content_height(&self) -> Au {
match self.specific {
GenericBox | IframeBox(_) => Au(0),
GenericBox | IframeBox(_) | TableBox | TableCellBox | TableRowBox |
TableWrapperBox => Au(0),
ImageBox(ref image_box_info) => {
image_box_info.computed_height()
}
@ -1308,6 +1456,7 @@ impl Box {
let em_size = text_bounds.size.height;
self.calculate_line_height(em_size)
}
TableColumnBox(_) => fail!("Table column boxes do not have height"),
UnscannedTextBox(_) => fail!("Unscanned text boxes should have been scanned by now!"),
}
}
@ -1322,7 +1471,9 @@ impl Box {
/// Split box which includes new-line character
pub fn split_by_new_line(&self) -> SplitBoxResult {
match self.specific {
GenericBox | IframeBox(_) | ImageBox(_) => CannotSplit,
GenericBox | IframeBox(_) | ImageBox(_) | TableBox | TableCellBox |
TableRowBox | TableWrapperBox => CannotSplit,
TableColumnBox(_) => fail!("Table column boxes do not need to split"),
UnscannedTextBox(_) => fail!("Unscanned text boxes should have been scanned by now!"),
ScannedTextBox(ref text_box_info) => {
let mut new_line_pos = self.new_line_pos.clone();
@ -1359,7 +1510,9 @@ impl Box {
/// Attempts to split this box so that its width is no more than `max_width`.
pub fn split_to_width(&self, max_width: Au, starts_line: bool) -> SplitBoxResult {
match self.specific {
GenericBox | IframeBox(_) | ImageBox(_) => CannotSplit,
GenericBox | IframeBox(_) | ImageBox(_) | TableBox | TableCellBox |
TableRowBox | TableWrapperBox => CannotSplit,
TableColumnBox(_) => fail!("Table column boxes do not have width"),
UnscannedTextBox(_) => fail!("Unscanned text boxes should have been scanned by now!"),
ScannedTextBox(ref text_box_info) => {
let mut pieces_processed_count: uint = 0;
@ -1478,8 +1631,8 @@ impl Box {
/// CSS 2.1 § 10.3.2.
pub fn assign_replaced_width_if_necessary(&self,container_width: Au) {
match self.specific {
GenericBox | IframeBox(_) => {
}
GenericBox | IframeBox(_) | TableBox | TableCellBox | TableRowBox |
TableWrapperBox => {}
ImageBox(ref image_box_info) => {
// TODO(ksh8281): compute border,margin,padding
let width = ImageBoxInfo::style_length(self.style().Box.get().width,
@ -1518,6 +1671,7 @@ impl Box {
position.get().size.width = position.get().size.width + self.noncontent_width() +
self.noncontent_inline_left() + self.noncontent_inline_right();
}
TableColumnBox(_) => fail!("Table column boxes do not have width"),
UnscannedTextBox(_) => fail!("Unscanned text boxes should have been scanned by now!"),
}
}
@ -1527,8 +1681,8 @@ impl Box {
/// Ideally, this should follow CSS 2.1 § 10.6.2
pub fn assign_replaced_height_if_necessary(&self) {
match self.specific {
GenericBox | IframeBox(_) => {
}
GenericBox | IframeBox(_) | TableBox | TableCellBox | TableRowBox |
TableWrapperBox => {}
ImageBox(ref image_box_info) => {
// TODO(ksh8281): compute border,margin,padding
let width = image_box_info.computed_width();
@ -1566,6 +1720,7 @@ impl Box {
position.get().size.height
= position.get().size.height + self.noncontent_height()
}
TableColumnBox(_) => fail!("Table column boxes do not have height"),
UnscannedTextBox(_) => fail!("Unscanned text boxes should have been scanned by now!"),
}
}
@ -1601,6 +1756,11 @@ impl Box {
IframeBox(_) => "IframeBox",
ImageBox(_) => "ImageBox",
ScannedTextBox(_) => "ScannedTextBox",
TableBox => "TableBox",
TableCellBox => "TableCellBox",
TableColumnBox(_) => "TableColumnBox",
TableRowBox => "TableRowBox",
TableWrapperBox => "TableWrapperBox",
UnscannedTextBox(_) => "UnscannedTextBox",
};

View file

@ -22,15 +22,23 @@
use css::node_style::StyledNode;
use layout::block::BlockFlow;
use layout::box_::{Box, GenericBox, IframeBox, IframeBoxInfo, ImageBox, ImageBoxInfo};
use layout::box_::{Box, GenericBox, IframeBox, IframeBoxInfo, ImageBox, ImageBoxInfo, TableBox};
use layout::box_::{TableCellBox, TableColumnBox, TableColumnBoxInfo, TableRowBox, TableWrapperBox};
use layout::box_::{InlineInfo, InlineParentInfo, SpecificBoxInfo, UnscannedTextBox};
use layout::box_::{UnscannedTextBoxInfo};
use layout::context::LayoutContext;
use layout::floats::FloatKind;
use layout::flow::{Flow, MutableOwnedFlowUtils};
use layout::flow::{Flow, ImmutableFlowUtils, MutableOwnedFlowUtils};
use layout::flow::{Descendants, AbsDescendants, FixedDescendants};
use layout::flow_list::{Rawlink};
use layout::inline::InlineFlow;
use layout::table_wrapper::TableWrapperFlow;
use layout::table::TableFlow;
use layout::table_caption::TableCaptionFlow;
use layout::table_colgroup::TableColGroupFlow;
use layout::table_rowgroup::TableRowGroupFlow;
use layout::table_row::TableRowFlow;
use layout::table_cell::TableCellFlow;
use layout::text::TextRunScanner;
use layout::util::{LayoutDataAccess, OpaqueNode};
use layout::wrapper::{PostorderNodeMutTraversal, TLayoutNode, ThreadSafeLayoutNode};
@ -39,6 +47,9 @@ use gfx::font_context::FontContext;
use script::dom::bindings::codegen::InheritTypes::TextCast;
use script::dom::bindings::js::JS;
use script::dom::element::{HTMLIFrameElementTypeId, HTMLImageElementTypeId, HTMLObjectElementTypeId};
use script::dom::element::{HTMLTableElementTypeId, HTMLTableSectionElementTypeId};
use script::dom::element::{HTMLTableDataCellElementTypeId, HTMLTableHeaderCellElementTypeId};
use script::dom::element::{HTMLTableColElementTypeId, HTMLTableRowElementTypeId};
use script::dom::node::{CommentNodeTypeId, DoctypeNodeTypeId, DocumentFragmentNodeTypeId};
use script::dom::node::{DocumentNodeTypeId, ElementNodeTypeId, ProcessingInstructionNodeTypeId};
use script::dom::node::{TextNodeTypeId};
@ -89,6 +100,8 @@ enum ConstructionItem {
InlineBoxesConstructionItem(InlineBoxesConstructionResult),
/// Potentially ignorable whitespace.
WhitespaceConstructionItem(OpaqueNode, Arc<ComputedValues>),
/// TableColumn Box
TableColumnBoxConstructionItem(Box),
}
impl ConstructionItem {
@ -102,6 +115,7 @@ impl ConstructionItem {
}
}
WhitespaceConstructionItem(..) => {}
TableColumnBoxConstructionItem(_) => {}
}
}
}
@ -288,20 +302,28 @@ impl<'a> FlowConstructor<'a> {
let data = node.get_object_data(&self.layout_context.url);
self.build_box_info_for_image(node, data)
}
ElementNodeTypeId(HTMLTableElementTypeId) => TableWrapperBox,
ElementNodeTypeId(HTMLTableColElementTypeId) => TableColumnBox(TableColumnBoxInfo::new(node)),
ElementNodeTypeId(HTMLTableDataCellElementTypeId) |
ElementNodeTypeId(HTMLTableHeaderCellElementTypeId) => TableCellBox,
ElementNodeTypeId(HTMLTableRowElementTypeId) |
ElementNodeTypeId(HTMLTableSectionElementTypeId) => TableRowBox,
TextNodeTypeId => UnscannedTextBox(UnscannedTextBoxInfo::new(node)),
_ => GenericBox,
}
}
/// Creates an inline flow from a set of inline boxes and adds it as a child of the given flow.
/// Creates an inline flow from a set of inline boxes, then adds it as a child of the given flow
/// or pushes it onto the given flow list.
///
/// `#[inline(always)]` because this is performance critical and LLVM will not inline it
/// otherwise.
#[inline(always)]
fn flush_inline_boxes_to_flow(&mut self,
boxes: ~[Box],
flow: &mut ~Flow,
node: &ThreadSafeLayoutNode) {
fn flush_inline_boxes_to_flow_or_list(&mut self,
boxes: ~[Box],
flow: &mut ~Flow,
flow_list: &mut ~[~Flow],
node: &ThreadSafeLayoutNode) {
if boxes.len() == 0 {
return
}
@ -310,18 +332,23 @@ impl<'a> FlowConstructor<'a> {
TextRunScanner::new().scan_for_runs(self.font_context(), inline_flow);
inline_flow.finish(self.layout_context);
flow.add_new_child(inline_flow)
if flow.need_anonymous_flow(inline_flow) {
flow_list.push(inline_flow)
} else {
flow.add_new_child(inline_flow)
}
}
/// Creates an inline flow from a set of inline boxes, if present, and adds it as a child of
/// the given flow.
fn flush_inline_boxes_to_flow_if_necessary(&mut self,
opt_boxes: &mut Option<~[Box]>,
flow: &mut ~Flow,
node: &ThreadSafeLayoutNode) {
/// the given flow or pushes it onto the given flow list.
fn flush_inline_boxes_to_flow_or_list_if_necessary(&mut self,
opt_boxes: &mut Option<~[Box]>,
flow: &mut ~Flow,
flow_list: &mut ~[~Flow],
node: &ThreadSafeLayoutNode) {
let opt_boxes = mem::replace(opt_boxes, None);
if opt_boxes.len() > 0 {
self.flush_inline_boxes_to_flow(opt_boxes.to_vec(), flow, node)
self.flush_inline_boxes_to_flow_or_list(opt_boxes.to_vec(), flow, flow_list, node)
}
}
@ -332,12 +359,13 @@ impl<'a> FlowConstructor<'a> {
/// this block flow.
/// Also, deal with the absolute and fixed descendants bubbled up by
/// children nodes.
fn build_block_flow_using_children(&mut self,
mut flow: ~Flow,
node: &ThreadSafeLayoutNode)
-> ConstructionResult {
fn build_flow_using_children(&mut self,
mut flow: ~Flow,
node: &ThreadSafeLayoutNode)
-> ConstructionResult {
// Gather up boxes for the inline flows we might need to create.
let mut opt_boxes_for_inline_flow = None;
let mut consecutive_siblings = ~[];
let mut first_box = true;
// List of absolute descendants, in tree order.
let mut abs_descendants = Descendants::new();
@ -346,25 +374,38 @@ impl<'a> FlowConstructor<'a> {
match kid.swap_out_construction_result() {
NoConstructionResult => {}
FlowConstructionResult(kid_flow, kid_abs_descendants, kid_fixed_descendants) => {
// Strip ignorable whitespace from the start of this flow per CSS 2.1 §
// 9.2.1.1.
if first_box {
strip_ignorable_whitespace_from_start(&mut opt_boxes_for_inline_flow);
first_box = false
}
// If kid_flow is TableCaptionFlow, kid_flow should be added under TableWrapperFlow.
if flow.is_table() && kid_flow.is_table_caption() {
kid.set_flow_construction_result(FlowConstructionResult(kid_flow,
Descendants::new(),
Descendants::new()))
} else if flow.need_anonymous_flow(kid_flow) {
consecutive_siblings.push(kid_flow)
} else {
// Strip ignorable whitespace from the start of this flow per CSS 2.1 §
// 9.2.1.1.
if flow.is_table_kind() || first_box {
strip_ignorable_whitespace_from_start(&mut opt_boxes_for_inline_flow);
first_box = false
}
// Flush any inline boxes that we were gathering up. This allows us to handle
// {ib} splits.
debug!("flushing {} inline box(es) to flow A",
opt_boxes_for_inline_flow.as_ref()
.map_or(0, |boxes| boxes.len()));
self.flush_inline_boxes_to_flow_if_necessary(&mut opt_boxes_for_inline_flow,
&mut flow,
node);
flow.add_new_child(kid_flow);
// Flush any inline boxes that we were gathering up. This allows us to handle
// {ib} splits.
debug!("flushing {} inline box(es) to flow A",
opt_boxes_for_inline_flow.as_ref()
.map_or(0, |boxes| boxes.len()));
self.flush_inline_boxes_to_flow_or_list_if_necessary(&mut opt_boxes_for_inline_flow,
&mut flow,
&mut consecutive_siblings,
node);
if !consecutive_siblings.is_empty() {
self.generate_anonymous_missing_child(consecutive_siblings, &mut flow, node);
consecutive_siblings = ~[];
}
flow.add_new_child(kid_flow);
}
abs_descendants.push_descendants(kid_abs_descendants);
fixed_descendants.push_descendants(kid_fixed_descendants);
}
ConstructionItemConstructionResult(InlineBoxesConstructionItem(
InlineBoxesConstructionResult {
@ -399,14 +440,19 @@ impl<'a> FlowConstructor<'a> {
opt_boxes_for_inline_flow.as_ref()
.map_or(0,
|boxes| boxes.len()));
self.flush_inline_boxes_to_flow_if_necessary(
self.flush_inline_boxes_to_flow_or_list_if_necessary(
&mut opt_boxes_for_inline_flow,
&mut flow,
&mut consecutive_siblings,
node);
// Push the flow generated by the {ib} split onto our list of
// flows.
flow.add_new_child(kid_flow)
if flow.need_anonymous_flow(kid_flow) {
consecutive_siblings.push(kid_flow)
} else {
flow.add_new_child(kid_flow)
}
}
}
}
@ -419,15 +465,23 @@ impl<'a> FlowConstructor<'a> {
ConstructionItemConstructionResult(WhitespaceConstructionItem(..)) => {
// Nothing to do here.
}
ConstructionItemConstructionResult(TableColumnBoxConstructionItem(_)) => {
// TODO: Implement anonymous table objects for missing parents
// CSS 2.1 § 17.2.1, step 3-2
}
}
}
// Perform a final flush of any inline boxes that we were gathering up to handle {ib}
// splits, after stripping ignorable whitespace.
strip_ignorable_whitespace_from_end(&mut opt_boxes_for_inline_flow);
self.flush_inline_boxes_to_flow_if_necessary(&mut opt_boxes_for_inline_flow,
&mut flow,
node);
self.flush_inline_boxes_to_flow_or_list_if_necessary(&mut opt_boxes_for_inline_flow,
&mut flow,
&mut consecutive_siblings,
node);
if !consecutive_siblings.is_empty() {
self.generate_anonymous_missing_child(consecutive_siblings, &mut flow, node);
}
// The flow is done.
flow.finish(self.layout_context);
@ -456,7 +510,7 @@ impl<'a> FlowConstructor<'a> {
/// to happen.
fn build_flow_for_block(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
let flow = ~BlockFlow::from_node(self, node) as ~Flow;
self.build_block_flow_using_children(flow, node)
self.build_flow_using_children(flow, node)
}
/// Builds the flow for a node with `float: {left|right}`. This yields a float `BlockFlow` with
@ -464,7 +518,7 @@ impl<'a> FlowConstructor<'a> {
fn build_flow_for_floated_block(&mut self, node: &ThreadSafeLayoutNode, float_kind: FloatKind)
-> ConstructionResult {
let flow = ~BlockFlow::float_from_node(self, node, float_kind) as ~Flow;
self.build_block_flow_using_children(flow, node)
self.build_flow_using_children(flow, node)
}
@ -536,6 +590,10 @@ impl<'a> FlowConstructor<'a> {
whitespace_style,
UnscannedTextBox(UnscannedTextBoxInfo::from_text(~" "))))
}
ConstructionItemConstructionResult(TableColumnBoxConstructionItem(_)) => {
// TODO: Implement anonymous table objects for missing parents
// CSS 2.1 § 17.2.1, step 3-2
}
}
}
@ -613,7 +671,7 @@ impl<'a> FlowConstructor<'a> {
let boxes_len = boxes.len();
parent_box.compute_borders(parent_box.style());
for (i,box_) in boxes.iter().enumerate() {
for (i, box_) in boxes.iter().enumerate() {
if box_.inline_info.with( |data| data.is_none() ) {
box_.inline_info.set(Some(InlineInfo::new()));
}
@ -684,6 +742,170 @@ impl<'a> FlowConstructor<'a> {
self.build_boxes_for_replaced_inline_content(node)
}
}
/// TableCaptionFlow is populated underneath TableWrapperFlow
fn place_table_caption_under_table_wrapper(&mut self,
table_wrapper_flow: &mut ~Flow,
node: &ThreadSafeLayoutNode) {
for kid in node.children() {
match kid.swap_out_construction_result() {
NoConstructionResult | ConstructionItemConstructionResult(_) => {}
FlowConstructionResult(kid_flow, _, _) => {
// Only kid flows with table-caption are matched here.
assert!(kid_flow.is_table_caption());
table_wrapper_flow.add_new_child(kid_flow);
}
}
}
}
/// Generates an anonymous table flow according to CSS 2.1 § 17.2.1, step 2.
/// If necessary, generate recursively another anonymous table flow.
fn generate_anonymous_missing_child(&mut self, child_flows: ~[~Flow],
flow: &mut ~Flow, node: &ThreadSafeLayoutNode) {
let mut anonymous_flow = flow.generate_missing_child_flow(node);
let mut consecutive_siblings = ~[];
for kid_flow in child_flows.move_iter() {
if anonymous_flow.need_anonymous_flow(kid_flow) {
consecutive_siblings.push(kid_flow);
continue;
}
if !consecutive_siblings.is_empty() {
self.generate_anonymous_missing_child(consecutive_siblings, &mut anonymous_flow, node);
consecutive_siblings = ~[];
}
anonymous_flow.add_new_child(kid_flow);
}
if !consecutive_siblings.is_empty() {
self.generate_anonymous_missing_child(consecutive_siblings, &mut anonymous_flow, node);
}
// The flow is done.
anonymous_flow.finish(self.layout_context);
flow.add_new_child(anonymous_flow);
}
/// Builds a flow for a node with `display: table`. This yields a `TableWrapperFlow` with possibly
/// other `TableCaptionFlow`s or `TableFlow`s underneath it.
fn build_flow_for_table_wrapper(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
let box_ = Box::new_from_specific_info(node, TableWrapperBox);
let mut wrapper_flow = ~TableWrapperFlow::from_node_and_box(node, box_) as ~Flow;
let table_box_ = Box::new_from_specific_info(node, TableBox);
let table_flow = ~TableFlow::from_node_and_box(node, table_box_) as ~Flow;
// We first populate the TableFlow with other flows than TableCaptionFlow.
// We then populate the TableWrapperFlow with TableCaptionFlow, and attach
// the TableFlow to the TableWrapperFlow
let construction_result = self.build_flow_using_children(table_flow, node);
self.place_table_caption_under_table_wrapper(&mut wrapper_flow, node);
let mut abs_descendants = Descendants::new();
let mut fixed_descendants = Descendants::new();
// NOTE: The order of captions and table are not the same order as in the DOM tree.
// All caption blocks are placed before the table flow
match construction_result {
FlowConstructionResult(table_flow, table_abs_descendants, table_fixed_descendants) => {
wrapper_flow.add_new_child(table_flow);
abs_descendants.push_descendants(table_abs_descendants);
fixed_descendants.push_descendants(table_fixed_descendants);
}
_ => {}
}
// The flow is done.
wrapper_flow.finish(self.layout_context);
let is_positioned = wrapper_flow.as_block().is_positioned();
let is_fixed_positioned = wrapper_flow.as_block().is_fixed();
let is_absolutely_positioned = wrapper_flow.as_block().is_absolutely_positioned();
if is_positioned {
// This is the CB for all the absolute descendants.
wrapper_flow.set_abs_descendants(abs_descendants);
abs_descendants = Descendants::new();
if is_fixed_positioned {
// Send itself along with the other fixed descendants.
fixed_descendants.push(Rawlink::some(wrapper_flow));
} else if is_absolutely_positioned {
// This is now the only absolute flow in the subtree which hasn't yet
// reached its CB.
abs_descendants.push(Rawlink::some(wrapper_flow));
}
}
FlowConstructionResult(wrapper_flow, abs_descendants, fixed_descendants)
}
/// Builds a flow for a node with `display: table-caption`. This yields a `TableCaptionFlow`
/// with possibly other `BlockFlow`s or `InlineFlow`s underneath it.
fn build_flow_for_table_caption(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
let flow = ~TableCaptionFlow::from_node(self, node) as ~Flow;
self.build_flow_using_children(flow, node)
}
/// Builds a flow for a node with `display: table-row-group`. This yields a `TableRowGroupFlow`
/// with possibly other `TableRowFlow`s underneath it.
fn build_flow_for_table_rowgroup(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
let box_ = Box::new_from_specific_info(node, TableRowBox);
let flow = ~TableRowGroupFlow::from_node_and_box(node, box_) as ~Flow;
self.build_flow_using_children(flow, node)
}
/// Builds a flow for a node with `display: table-row`. This yields a `TableRowFlow` with
/// possibly other `TableCellFlow`s underneath it.
fn build_flow_for_table_row(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
let box_ = Box::new_from_specific_info(node, TableRowBox);
let flow = ~TableRowFlow::from_node_and_box(node, box_) as ~Flow;
self.build_flow_using_children(flow, node)
}
/// Builds a flow for a node with `display: table-cell`. This yields a `TableCellFlow` with
/// possibly other `BlockFlow`s or `InlineFlow`s underneath it.
fn build_flow_for_table_cell(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
let box_ = Box::new_from_specific_info(node, TableCellBox);
let flow = ~TableCellFlow::from_node_and_box(node, box_) as ~Flow;
self.build_flow_using_children(flow, node)
}
/// Creates a box for a node with `display: table-column`.
fn build_boxes_for_table_column(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
// CSS 2.1 § 17.2.1. Treat all child boxes of a `table-column` as `display: none`.
for kid in node.children() {
kid.set_flow_construction_result(NoConstructionResult)
}
let specific = TableColumnBox(TableColumnBoxInfo::new(node));
let construction_item = TableColumnBoxConstructionItem(
Box::new_from_specific_info(node, specific)
);
ConstructionItemConstructionResult(construction_item)
}
/// Builds a flow for a node with `display: table-column-group`.
/// This yields a `TableColGroupFlow`.
fn build_flow_for_table_colgroup(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
let box_ = Box::new_from_specific_info(node,
TableColumnBox(TableColumnBoxInfo::new(node)));
let mut col_boxes = ~[];
for kid in node.children() {
// CSS 2.1 § 17.2.1. Treat all non-column child boxes of `table-column-group`
// as `display: none`.
match kid.swap_out_construction_result() {
ConstructionItemConstructionResult(TableColumnBoxConstructionItem(box_)) => {
col_boxes.push(box_);
}
_ => {}
}
}
if col_boxes.is_empty() {
debug!("add TableColumnBox for empty colgroup");
let specific = TableColumnBox(TableColumnBoxInfo::new(node));
col_boxes.push( Box::new_from_specific_info(node, specific) );
}
let mut flow = ~TableColGroupFlow::from_node_and_boxes(node, box_, col_boxes) as ~Flow;
flow.finish(self.layout_context);
FlowConstructionResult(flow, Descendants::new(), Descendants::new())
}
}
impl<'a> PostorderNodeMutTraversal for FlowConstructor<'a> {
@ -725,6 +947,12 @@ impl<'a> PostorderNodeMutTraversal for FlowConstructor<'a> {
}
}
// Table items contribute table flow construction results.
(display::table, _, _) => {
let construction_result = self.build_flow_for_table_wrapper(node);
node.set_flow_construction_result(construction_result)
}
// Absolutely positioned elements will have computed value of
// `float` as 'none' and `display` as per the table.
// Currently, for original `display` value of 'inline', the new
@ -739,6 +967,43 @@ impl<'a> PostorderNodeMutTraversal for FlowConstructor<'a> {
node.set_flow_construction_result(construction_result)
}
// Table items contribute table flow construction results.
(display::table_caption, _, _) => {
let construction_result = self.build_flow_for_table_caption(node);
node.set_flow_construction_result(construction_result)
}
// Table items contribute table flow construction results.
(display::table_column_group, _, _) => {
let construction_result = self.build_flow_for_table_colgroup(node);
node.set_flow_construction_result(construction_result)
}
// Table items contribute table flow construction results.
(display::table_column, _, _) => {
let construction_result = self.build_boxes_for_table_column(node);
node.set_flow_construction_result(construction_result)
}
// Table items contribute table flow construction results.
(display::table_row_group, _, _) | (display::table_header_group, _, _) |
(display::table_footer_group, _, _) => {
let construction_result = self.build_flow_for_table_rowgroup(node);
node.set_flow_construction_result(construction_result)
}
// Table items contribute table flow construction results.
(display::table_row, _, _) => {
let construction_result = self.build_flow_for_table_row(node);
node.set_flow_construction_result(construction_result)
}
// Table items contribute table flow construction results.
(display::table_cell, _, _) => {
let construction_result = self.build_flow_for_table_cell(node);
node.set_flow_construction_result(construction_result)
}
// Block flows that are not floated contribute block flow construction results.
//
// TODO(pcwalton): Make this only trigger for blocks and handle the other `display`

View file

@ -27,7 +27,7 @@
use css::node_style::StyledNode;
use layout::block::{BlockFlow};
use layout::box_::Box;
use layout::box_::{Box, TableRowBox, TableCellBox};
use layout::context::LayoutContext;
use layout::construct::OptVector;
use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData};
@ -36,6 +36,13 @@ use layout::incremental::RestyleDamage;
use layout::inline::InlineFlow;
use layout::parallel::FlowParallelInfo;
use layout::parallel;
use layout::table_wrapper::TableWrapperFlow;
use layout::table::TableFlow;
use layout::table_colgroup::TableColGroupFlow;
use layout::table_rowgroup::TableRowGroupFlow;
use layout::table_row::TableRowFlow;
use layout::table_caption::TableCaptionFlow;
use layout::table_cell::TableCellFlow;
use layout::wrapper::ThreadSafeLayoutNode;
use layout::flow_list::{FlowList, Link, Rawlink, FlowListIterator, MutFlowListIterator};
@ -84,6 +91,41 @@ pub trait Flow {
fail!("called as_inline() on a non-inline flow")
}
/// If this is a table wrapper flow, returns the underlying object. Fails otherwise.
fn as_table_wrapper<'a>(&'a mut self) -> &'a mut TableWrapperFlow {
fail!("called as_table_wrapper() on a non-tablewrapper flow")
}
/// If this is a table flow, returns the underlying object. Fails otherwise.
fn as_table<'a>(&'a mut self) -> &'a mut TableFlow {
fail!("called as_table() on a non-table flow")
}
/// If this is a table colgroup flow, returns the underlying object. Fails otherwise.
fn as_table_colgroup<'a>(&'a mut self) -> &'a mut TableColGroupFlow {
fail!("called as_table_colgroup() on a non-tablecolgroup flow")
}
/// If this is a table rowgroup flow, returns the underlying object. Fails otherwise.
fn as_table_rowgroup<'a>(&'a mut self) -> &'a mut TableRowGroupFlow {
fail!("called as_table_rowgroup() on a non-tablerowgroup flow")
}
/// If this is a table row flow, returns the underlying object. Fails otherwise.
fn as_table_row<'a>(&'a mut self) -> &'a mut TableRowFlow {
fail!("called as_table_row() on a non-tablerow flow")
}
/// If this is a table cell flow, returns the underlying object. Fails otherwise.
fn as_table_caption<'a>(&'a mut self) -> &'a mut TableCaptionFlow {
fail!("called as_table_caption() on a non-tablecaption flow")
}
/// If this is a table cell flow, returns the underlying object. Fails otherwise.
fn as_table_cell<'a>(&'a mut self) -> &'a mut TableCellFlow {
fail!("called as_table_cell() on a non-tablecell flow")
}
// Main methods
/// Pass 1 of reflow: computes minimum and preferred widths.
@ -222,6 +264,36 @@ pub trait ImmutableFlowUtils {
/// Returns true if this flow is a block or a float flow.
fn is_block_like(self) -> bool;
/// Returns true if this flow is a table flow.
fn is_table(self) -> bool;
/// Returns true if this flow is a table caption flow.
fn is_table_caption(self) -> bool;
/// Returns true if this flow is a proper table child.
fn is_proper_table_child(self) -> bool;
/// Returns true if this flow is a table row flow.
fn is_table_row(self) -> bool;
/// Returns true if this flow is a table cell flow.
fn is_table_cell(self) -> bool;
/// Returns true if this flow is a table colgroup flow.
fn is_table_colgroup(self) -> bool;
/// Returns true if this flow is a table rowgroup flow.
fn is_table_rowgroup(self) -> bool;
/// Returns true if this flow is one of table-related flows.
fn is_table_kind(self) -> bool;
/// Returns true if anonymous flow is needed between this flow and child flow.
fn need_anonymous_flow(self, child: &Flow) -> bool;
/// Generates missing child flow of this flow.
fn generate_missing_child_flow(self, node: &ThreadSafeLayoutNode) -> ~Flow;
/// Returns true if this flow has no children.
fn is_leaf(self) -> bool;
@ -309,6 +381,13 @@ pub trait MutableOwnedFlowUtils {
pub enum FlowClass {
BlockFlowClass,
InlineFlowClass,
TableWrapperFlowClass,
TableFlowClass,
TableColGroupFlowClass,
TableRowGroupFlowClass,
TableRowFlowClass,
TableCaptionFlowClass,
TableCellFlowClass,
}
/// A top-down traversal.
@ -753,7 +832,104 @@ impl<'a> ImmutableFlowUtils for &'a Flow {
fn is_block_like(self) -> bool {
match self.class() {
BlockFlowClass => true,
InlineFlowClass => false,
_ => false,
}
}
/// Returns true if this flow is a proper table child.
/// 'Proper table child' is defined as table-row flow, table-rowgroup flow,
/// table-column-group flow, or table-caption flow.
fn is_proper_table_child(self) -> bool {
match self.class() {
TableRowFlowClass | TableRowGroupFlowClass |
TableColGroupFlowClass | TableCaptionFlowClass => true,
_ => false,
}
}
/// Returns true if this flow is a table row flow.
fn is_table_row(self) -> bool {
match self.class() {
TableRowFlowClass => true,
_ => false,
}
}
/// Returns true if this flow is a table cell flow.
fn is_table_cell(self) -> bool {
match self.class() {
TableCellFlowClass => true,
_ => false,
}
}
/// Returns true if this flow is a table colgroup flow.
fn is_table_colgroup(self) -> bool {
match self.class() {
TableColGroupFlowClass => true,
_ => false,
}
}
/// Returns true if this flow is a table flow.
fn is_table(self) -> bool {
match self.class() {
TableFlowClass => true,
_ => false,
}
}
/// Returns true if this flow is a table caption flow.
fn is_table_caption(self) -> bool {
match self.class() {
TableCaptionFlowClass => true,
_ => false,
}
}
/// Returns true if this flow is a table rowgroup flow.
fn is_table_rowgroup(self) -> bool {
match self.class() {
TableRowGroupFlowClass => true,
_ => false,
}
}
/// Returns true if this flow is one of table-related flows.
fn is_table_kind(self) -> bool {
match self.class() {
TableWrapperFlowClass | TableFlowClass |
TableColGroupFlowClass | TableRowGroupFlowClass |
TableRowFlowClass | TableCaptionFlowClass | TableCellFlowClass => true,
_ => false,
}
}
/// Returns true if anonymous flow is needed between this flow and child flow.
/// Spec: http://www.w3.org/TR/CSS21/tables.html#anonymous-boxes
fn need_anonymous_flow(self, child: &Flow) -> bool {
match self.class() {
TableFlowClass => !child.is_proper_table_child(),
TableRowGroupFlowClass => !child.is_table_row(),
TableRowFlowClass => !child.is_table_cell(),
_ => false
}
}
/// Generates missing child flow of this flow.
fn generate_missing_child_flow(self, node: &ThreadSafeLayoutNode) -> ~Flow {
match self.class() {
TableFlowClass | TableRowGroupFlowClass => {
let box_ = Box::new_anonymous_table_box(node, TableRowBox);
~TableRowFlow::from_node_and_box(node, box_) as ~Flow
},
TableRowFlowClass => {
let box_ = Box::new_anonymous_table_box(node, TableCellBox);
~TableCellFlow::from_node_and_box(node, box_) as ~Flow
},
_ => {
fail!("no need to generate a missing child")
}
}
}
@ -776,11 +952,11 @@ impl<'a> ImmutableFlowUtils for &'a Flow {
fn is_block_container(self) -> bool {
match self.class() {
// TODO: Change this when inline-blocks are supported.
InlineFlowClass => false,
BlockFlowClass => {
BlockFlowClass | TableCaptionFlowClass | TableCellFlowClass => {
// FIXME: Actually check the type of the node
self.child_count() != 0
}
_ => false,
}
}
@ -788,7 +964,7 @@ impl<'a> ImmutableFlowUtils for &'a Flow {
fn is_block_flow(self) -> bool {
match self.class() {
BlockFlowClass => true,
InlineFlowClass => false,
_ => false,
}
}
@ -796,7 +972,7 @@ impl<'a> ImmutableFlowUtils for &'a Flow {
fn is_inline_flow(self) -> bool {
match self.class() {
InlineFlowClass => true,
BlockFlowClass => false,
_ => false,
}
}
@ -948,13 +1124,50 @@ impl<'a> MutableFlowUtils for &'a mut Flow {
index,
lists),
InlineFlowClass => self.as_inline().build_display_list_inline(builder, container_block_size, dirty, index, lists),
TableWrapperFlowClass => self.as_table_wrapper().build_display_list_table_wrapper(builder,
container_block_size,
absolute_cb_abs_position,
dirty,
index,
lists),
TableFlowClass => self.as_table().build_display_list_table(builder,
container_block_size,
absolute_cb_abs_position,
dirty,
index,
lists),
TableRowGroupFlowClass => self.as_table_rowgroup().build_display_list_table_rowgroup(builder,
container_block_size,
absolute_cb_abs_position,
dirty,
index,
lists),
TableRowFlowClass => self.as_table_row().build_display_list_table_row(builder,
container_block_size,
absolute_cb_abs_position,
dirty,
index,
lists),
TableCaptionFlowClass => self.as_table_caption().build_display_list_table_caption(builder,
container_block_size,
absolute_cb_abs_position,
dirty,
index,
lists),
TableCellFlowClass => self.as_table_cell().build_display_list_table_cell(builder,
container_block_size,
absolute_cb_abs_position,
dirty,
index,
lists),
TableColGroupFlowClass => index,
};
if lists.with_mut(|lists| lists.lists[index].list.len() == 0) {
return true;
}
if self.is_block_container() {
if self.is_block_container() || self.is_table_kind() {
let block = self.as_block();
let mut child_lists = DisplayListCollection::new();
child_lists.add_list(DisplayList::new());

View file

@ -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.")
}

View file

@ -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))
}
}

View file

@ -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 => ~"",
})
}
}

View file

@ -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 => ~"",
})
}
}

View file

@ -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 => ~"",
})
}
}

View file

@ -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 => ~"",
})
}
}

View file

@ -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 => ~"",
})
}
}

View file

@ -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)
}
}

View file

@ -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;

View file

@ -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
}

View file

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html>
<head>
<title>Fixed Table</title>
<style>
.table {
display: table;
table-layout: fixed;
width: 600px;
border: solid black 2px;
}
.colgroup {
display: table-column-group;
}
.column {
display: table-column;
}
.row {
display: table-row;
}
.cell {
display: table-cell;
border: solid red 1px;
}
</style>
</head>
<body>
<p> This test checks Anonymous table objects(CSS 2.1, Section 17.2.1) </p>
<p> 1. Remove irrelevant boxes</p>
<p> 2. Generate missing child wrappers: `table-row`, `table-cell` </p>
<div class="table">
<span class="column"> inline child box of table-column. NOT Shown </span>
<span class="colgroup">
<span>inline child box of table-column-group</span> NOT Shown
</span>
<span class="cell">Cell1</span>
<span class="cell">Cell2</span>
<span class="row">
2nd Row
<span>Cell4</span>
<span class="cell">Cell3</span>
Cell5
</span>
</div>
</body>
<html>

View file

@ -0,0 +1,36 @@
<!-- This test creates one table, one caption, three rows, three header cells, and five data cells.
The table uses the fixed table layout algorithm and the table's width specified to 600px.
Each column's width will be assigned as follows:
- 1st column: 200px (because it is defined in col element)
- 2nd column: 100px (because it is defined in first row)
- 3rd column: remaining width (becuase it is not defined so the remaining width is assigned)
And table, caption, td, th elements have border. -->
<!DOCTYPE html>
<html>
<head>
<title>Fixed Table</title>
<style>
table {
table-layout: fixed;
width: 600px;
border: solid black 2px;
}
caption { border: solid blue 1px; }
td { border: solid red 1px; }
th { border: solid red 1px; }
</style>
</head>
<body>
<table>
<caption>This is a 3x3 fixed table</caption>
<colgroup>
<col style="width: 200px" />
</colgroup>
<tbody>
<tr><th style="width: 100px">Header 1</th><td style="width: 100px">Cell 1</td><td>Cell 2</td></tr>
<tr><th style="width: 300px">Header 2</th><td style="width: 300px">Cell 3</th><td>Cell 4</td></tr>
<tr><th>Header 3</th><td>Cell 5</th></tr>
</tbody>
</table>
</body>
<html>

View file

@ -0,0 +1,36 @@
<!-- This test creates one table, one caption, three rows, three header cells, and five data cells.
The table uses the fixed table layout algorithm and the table's width specified to 600px.
Each column's width will be assigned according to their ratio of column's widths
which are defined in col elements.
And table, caption, td, th elements have border. -->
<!DOCTYPE html>
<html>
<head>
<title>Fixed Table</title>
<style>
table {
table-layout: fixed;
width: 600px;
border: solid black 2px;
}
caption { border: solid blue 1px; }
td { border: solid red 1px; }
th { border: solid red 1px; }
</style>
</head>
<body>
<table>
<caption>This is a 3x3 fixed table</caption>
<colgroup>
<col style="width: 10px" />
<col style="width: 20px" />
<col style="width: 30px" />
</colgroup>
<tbody>
<tr><th style="width: 100px">Header 1</th><td style="width: 100px">Cell 1</td><td>Cell 2</td></tr>
<tr><th style="width: 300px">Header 2</th><td style="width: 300px">Cell 3</th><td>Cell 4</td></tr>
<tr><th>Header 3</th><td>Cell 5</th></tr>
</tbody>
</table>
</body>
<html>

View file

@ -0,0 +1,39 @@
<!-- This test creates one table, one caption, three rows, three header cells, and five data cells.
The table uses the fixed table layout algorithm and the table's width specified to 600px.
Each column's width will be assigned as follows:
- 1st & 2nd column: 200px (because it is defined in col element)
- 3rd & 4th column: remaining width / 2
(becuase it is not defined so the remaining width is equally divided)
And table, caption, td, th elements have border. -->
<!DOCTYPE html>
<html>
<head>
<title>Fixed Table</title>
<style>
table {
table-layout: fixed;
width: 600px;
border: solid black 2px;
}
caption { border: solid blue 1px; }
td { border: solid red 1px; }
th { border: solid red 1px; }
</style>
</head>
<body>
<table>
<caption>This is a 3x4 fixed table</caption>
<colgroup>
<col style="width: 200px" />
<col style="width: 200px" />
<col />
<col />
</colgroup>
<tbody>
<tr><th>Header 1</th><td>Cell 1</td><td>Cell 2</td></tr>
<tr><th>Header 2</th><td>Cell 3</th><td>Cell 4</td></tr>
<tr><th>Header 3</th><td>Cell 5</th></tr>
</tbody>
</table>
</body>
<html>

View file

@ -0,0 +1,40 @@
<!-- This test creates one table, three rows, three header cells, and six data cells.
The table uses the fixed table layout algorithm and the table's width specified to 600px.
Each column's width will be assigned as 200px.
Each table row height is decided as max(specified row height, specified cells' heights, cells' minimum content heights).
As a result, each table row height will be assigned as followings:
- 1st row: 30px (specified cell height)
- 2nd row: 50px (specified row height)
- 3rd row: minimum content height
-->
<!DOCTYPE html>
<html>
<head>
<title>Table Height Test</title>
<style>
table {
table-layout: fixed;
width: 600px;
border: solid black 2px;
}
caption {
border: solid blue 1px;
}
td, th {
border: solid red 1px;
padding: 0px;
}
</style>
</head>
<body>
<table>
<caption>This test checks table height algorithm (CSS 2.1, Section 17.5.3),
excluding `vertical-align` and percentage height</caption>
<tbody>
<tr style="height:10px"><th>Header 1</th><td style="height: 30px">Cell 1</td><td>Cell 2</td></tr>
<tr style="height:50px"><th>Header 2</th><td>Cell 3</td><td style="height:10px">Cell 4</td></tr>
<tr style="height:20px"><th>Header 3</th><td style="height:10px">Cell 5</td><td><div>Cell6</div><p>Cell6</td></tr>
</tbody>
</table>
</body>
<html>

View file

@ -0,0 +1,30 @@
<!-- This test creates one table, one caption, three rows, three header cells, and six data cells.
The table uses fixed table layout algorithm and the table's width specified to 600px.
Each column should have same width because the column's widths are not defined here.
And table, caption, td, th elements have border. -->
<!DOCTYPE html>
<html>
<head>
<title>Simple Fixed Table</title>
<style>
table {
table-layout: fixed;
width: 600px;
border: solid black 2px;
}
caption { border: solid blue 1px; }
td { border: solid red 1px; }
th { border: solid red 1px; }
</style>
</head>
<body>
<table>
<caption>This is a 3x3 fixed table</caption>
<tbody>
<tr><th>Header 1</th><td>Cell 1</td><td>Cell 2</td></tr>
<tr><th>Header 2</th><td>Cell 3</td><td>Cell 4</td></tr>
<tr><th>Header 3</th><td>Cell 5</td><td>Cell 6</td></tr>
</tbody>
</table>
</body>
<html>

View file

@ -0,0 +1,50 @@
<!-- This test creates one table, one caption, three rows, three header cells, and five data cells.
The table uses the fixed table layout algorithm and the table's width specified to 600px.
Each column's width will be assigned as follows:
- 1st column: 200px (because it is defined in col element)
- 2nd column: 100px (because it is defined in first row)
- 3rd column: remaining width (becuase it is not defined so the remaining width is assigned)
The table and caption elements have border, margin, and padding.
The td and th elements have border and padding. -->
<!DOCTYPE html>
<html>
<head>
<title>Fixed Table with margin, border, and padding</title>
<style>
table {
table-layout: fixed;
width: 600px;
border: solid black 2px;
margin: 10px;
padding: 10px;
}
caption {
border: solid blue 1px;
margin: 5px;
padding: 5px;
}
td {
border: solid red 1px;
padding: 5px;
}
th {
border: solid red 1px;
padding: 5px;
}
</style>
</head>
<body>
<table>
<caption>This is a 3x3 fixed table with margin, border, and padding</caption>
<colgroup>
<col style="width: 200px" />
<col />
</colgroup>
<tbody>
<tr><th style="width: 100px">Header 1</th><td style="width: 100px">Cell 1</td><td>Cell 2</td></tr>
<tr><th>Header 2</th><td>Cell 3</td><td>Cell 4</td></tr>
<tr><th>Header 3</th><td>Cell 5</td></tr>
</tbody>
</table>
</body>
<html>