Refactor assign_height and assign_width for block flow.

This commit is contained in:
Junyoung Cho 2014-03-18 20:16:15 +09:00
parent 3401a568f2
commit 8dbec50178

View file

@ -625,47 +625,38 @@ impl BlockFlow {
self.base.fixed_descendants.static_y_offsets = fixed_descendant_y_offsets; self.base.fixed_descendants.static_y_offsets = fixed_descendant_y_offsets;
} }
/// Assign height for current flow. /// Calculate clearance, top_offset, bottom_offset, and left_offset for the box.
/// /// If `ignore_clear` is true, clearance does not need to be calculated.
/// + Collapse margins for flow's children and set in-flow child flows' pub fn initialize_offsets(&mut self, ignore_clear: bool) -> (Au, Au, Au, Au) {
/// y-coordinates now that we know their heights. match self.box_ {
/// + Calculate and set the height of the current flow. None => (Au(0), Au(0), Au(0), Au(0)),
/// + Calculate height, vertical margins, and y-coordinate for the flow's Some(ref box_) => {
/// box. Ideally, this should be calculated using CSS Section 10.6.7 let clearance = match box_.clear() {
/// Some(clear) if !ignore_clear => self.base.floats.clearance(clear),
/// For absolute flows, store the calculated content height for the flow. _ => Au::new(0)
/// 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)
}
}; };
// Offset to content edge of box_
let top_offset = clearance + box_.margin.get().top + box_.border.get().top +
box_.padding.get().top;
let bottom_offset = box_.margin.get().bottom + box_.border.get().bottom +
box_.padding.get().bottom;
let left_offset = box_.offset();
(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. // Note: Ignoring floats for absolute flow as of now.
if inorder && !self.is_absolutely_positioned() { if inorder && !self.is_absolutely_positioned() {
// Floats for blocks work like this: // Floats for blocks work like this:
@ -684,9 +675,41 @@ impl BlockFlow {
} }
self.base.floats = floats; self.base.floats = floats;
} }
}
// The amount of margin that we can potentially collapse with /// Compute margin_top and margin_bottom. Also, it is decided whether top margin and
let mut collapsible = Au::new(0); /// 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 // How much to move up by to get to the beginning of
// current kid flow. // current kid flow.
// Example: if previous sibling's margin-bottom is 20px and your // Example: if previous sibling's margin-bottom is 20px and your
@ -694,55 +717,38 @@ impl BlockFlow {
// will be at the bottom margin edge of the previous sibling, we have // 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` // to move up by 12px to get to our top margin edge. So, `collapsing`
// will be set to 12px // 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; let mut first_in_flow = true;
// Margins for an absolutely positioned element do not collapse with let mut collapsing = Au::new(0);
// its children. // The amount of margin that we can potentially collapse with
if !self.is_absolutely_positioned() { let mut collapsible = if top_margin_collapsible {
for box_ in self.box_.iter() { *margin_top
if !self.is_root() && box_.border.get().top == Au(0) } else {
&& box_.padding.get().top == Au(0) { 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;
}
}
// At this point, cur_y is at the content edge of the flow's box_ // At this point, cur_y is at the content edge of the flow's box_
for kid in self.base.child_iter() { for kid in self.base.child_iter() {
// At this point, cur_y is at bottom margin edge of previous kid // At this point, cur_y is at bottom margin edge of previous kid
if kid.is_absolutely_positioned() { if kid.is_absolutely_positioned() {
// Assume that the `hypothetical box` for an absolute flow // Assume that the `hypothetical box` for an absolute flow
// starts immediately after the bottom margin edge of the // starts immediately after the bottom margin edge of the
// previous flow. // 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 // Skip the collapsing for absolute flow kids and continue
// with the next flow. // with the next flow.
} else { } else {
kid.collapse_margins(top_margin_collapsible, kid.collapse_margins(top_margin_collapsible,
&mut first_in_flow, &mut first_in_flow,
&mut margin_top, margin_top,
&mut top_offset, top_offset,
&mut collapsing, &mut collapsing,
&mut collapsible); &mut collapsible);
let child_node = flow::mut_base(kid); 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 // At this point, after moving up by `collapsing`, cur_y is at the
// top margin edge of kid // top margin edge of kid
child_node.position.origin.y = cur_y; child_node.position.origin.y = *cur_y;
cur_y = cur_y + child_node.position.size.height; *cur_y = *cur_y + child_node.position.size.height;
// At this point, cur_y is at the bottom margin edge of kid // At this point, cur_y is at the bottom margin edge of kid
} }
} }
@ -754,14 +760,128 @@ impl BlockFlow {
// The bottom margin for an absolutely positioned element does not // The bottom margin for an absolutely positioned element does not
// collapse even with its children. // collapse even with its children.
collapsing = if bottom_margin_collapsible && !self.is_absolutely_positioned() { collapsing = if bottom_margin_collapsible && !self.is_absolutely_positioned() {
if margin_bottom < collapsible { if *margin_bottom < collapsible {
margin_bottom = collapsible; *margin_bottom = collapsible;
} }
collapsible collapsible
} else { } else {
Au::new(0) 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 // 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', // 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. // and it does not contain a line box, and all of its in-flow children's margins (if any) collapse.
@ -782,17 +902,12 @@ impl BlockFlow {
cur_y - top_offset - collapsing cur_y - top_offset - collapsing
}; };
if self.is_absolutely_positioned() { // For an absolutely positioned element, store the content height and stop the function.
// Store the content height for use in calculating the absolute if self.store_content_height_if_absolutely_positioned(height) {
// flow's dimensions later.
for box_ in self.box_.iter() {
let mut temp_position = box_.border_box.get();
temp_position.size.height = height;
box_.border_box.set(temp_position);
}
return; return;
} }
let mut border_and_padding = Au::new(0);
for box_ in self.box_.iter() { for box_ in self.box_.iter() {
let style = box_.style(); let style = box_.style();
@ -803,51 +918,19 @@ impl BlockFlow {
Auto => height, Auto => height,
Specified(value) => value 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); self.set_floats_out_if_inorder(inorder, height, cur_y, top_offset, bottom_offset, left_offset);
for box_ in self.box_.iter() { self.assign_height_absolute_flows(ctx);
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,
});
}
if self.is_root() { if self.is_root() {
self.assign_height_store_overflow_fixed_flows(ctx); self.assign_height_store_overflow_fixed_flows(ctx);
} }
@ -990,6 +1073,74 @@ impl BlockFlow {
box_.border_box.set(position); 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) {
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)
}
}
/// Add display items for current block. /// Add display items for current block.
/// ///
/// Set the absolute position for children after doing any offsetting for /// Set the absolute position for children after doing any offsetting for
@ -1323,12 +1474,7 @@ impl Flow for BlockFlow {
let mut left_content_edge = Au::new(0); let mut left_content_edge = Au::new(0);
let mut content_width = containing_block_width; let mut content_width = containing_block_width;
if self.is_float() { self.set_containing_width_if_float(containing_block_width);
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.compute_used_width(ctx, containing_block_width); self.compute_used_width(ctx, containing_block_width);
@ -1345,59 +1491,7 @@ impl Flow for BlockFlow {
self.base.position.size.width = content_width; self.base.position.size.width = content_width;
} }
let has_inorder_children = if self.is_float() { self.propagate_assigned_width_to_children(left_content_edge, content_width);
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)
}
} }
/// This is called on kid flows by a parent. /// This is called on kid flows by a parent.