mirror of
https://github.com/servo/servo.git
synced 2025-06-24 17:14:33 +01:00
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:
commit
fd5e5cd18b
21 changed files with 2861 additions and 316 deletions
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
};
|
||||
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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.")
|
||||
}
|
||||
|
|
327
src/components/main/layout/table.rs
Normal file
327
src/components/main/layout/table.rs
Normal 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))
|
||||
}
|
||||
}
|
103
src/components/main/layout/table_caption.rs
Normal file
103
src/components/main/layout/table_caption.rs
Normal 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 => ~"",
|
||||
})
|
||||
}
|
||||
}
|
173
src/components/main/layout/table_cell.rs
Normal file
173
src/components/main/layout/table_cell.rs
Normal 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 => ~"",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
98
src/components/main/layout/table_colgroup.rs
Normal file
98
src/components/main/layout/table_colgroup.rs
Normal 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 => ~"",
|
||||
})
|
||||
}
|
||||
}
|
222
src/components/main/layout/table_row.rs
Normal file
222
src/components/main/layout/table_row.rs
Normal 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 => ~"",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
197
src/components/main/layout/table_rowgroup.rs
Normal file
197
src/components/main/layout/table_rowgroup.rs
Normal 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 => ~"",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
368
src/components/main/layout/table_wrapper.rs
Normal file
368
src/components/main/layout/table_wrapper.rs
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
46
src/test/html/anonymous_table.html
Normal file
46
src/test/html/anonymous_table.html
Normal 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>
|
36
src/test/html/fixed_table.html
Normal file
36
src/test/html/fixed_table.html
Normal 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>
|
36
src/test/html/fixed_table_2.html
Normal file
36
src/test/html/fixed_table_2.html
Normal 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>
|
39
src/test/html/fixed_table_additional_cols.html
Normal file
39
src/test/html/fixed_table_additional_cols.html
Normal 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>
|
40
src/test/html/fixed_table_basic_height.html
Normal file
40
src/test/html/fixed_table_basic_height.html
Normal 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>
|
30
src/test/html/fixed_table_simple.html
Normal file
30
src/test/html/fixed_table_simple.html
Normal 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>
|
50
src/test/html/fixed_table_with_margin_padding.html
Normal file
50
src/test/html/fixed_table_with_margin_padding.html
Normal 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>
|
Loading…
Add table
Add a link
Reference in a new issue