auto merge of #3609 : pcwalton/servo/tables, r=SimonSapin

http://dbaron.org/css/intrinsic/

Column spans are not yet supported.

This effectively adds support for percentage widths, and it also fixes
many bugs, improving the layout of Google and Wikipedia.

r? @SimonSapin
This commit is contained in:
bors-servo 2014-10-14 15:42:32 -06:00
commit e2d7777c41
20 changed files with 1081 additions and 618 deletions

View file

@ -2,15 +2,28 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! CSS block formatting contexts. //! Layout for CSS block-level elements.
//! //!
//! Terminology Note: //! As a terminology note, the term *absolute positioning* here refers to elements with position
//! As per the CSS Spec, the term 'absolute positioning' here refers to //! `absolute` or `fixed`. The term *positioned element* refers to elements with position
//! elements with position = 'absolute' or 'fixed'. //! `relative`, `absolute`, and `fixed`. The term *containing block* (occasionally abbreviated as
//! The term 'positioned element' refers to elements with position = //! *CB*) is the containing block for the current flow, which differs from the static containing
//! 'relative', 'absolute', or 'fixed'. //! block if the flow is absolutely-positioned.
//! //!
//! CB: Containing Block of the current flow. //! "CSS 2.1" or "CSS 2.2" refers to the editor's draft of the W3C "Cascading Style Sheets Level 2
//! Revision 2 (CSS 2.2) Specification" available here:
//!
//! http://dev.w3.org/csswg/css2/
//!
//! "INTRINSIC" refers to L. David Baron's "More Precise Definitions of Inline Layout and Table
//! Layout" available here:
//!
//! http://dbaron.org/css/intrinsic/
//!
//! "CSS-SIZING" refers to the W3C "CSS Intrinsic & Extrinsic Sizing Module Level 3" document
//! available here:
//!
//! http://dev.w3.org/csswg/css-sizing/
#![deny(unsafe_block)] #![deny(unsafe_block)]
@ -22,11 +35,10 @@ use flow::{MutableFlowUtils, PreorderFlowTraversal, PostorderFlowTraversal, mut_
use flow; use flow;
use fragment::{Fragment, ImageFragment, InlineBlockFragment, ScannedTextFragment}; use fragment::{Fragment, ImageFragment, InlineBlockFragment, ScannedTextFragment};
use layout_debug; use layout_debug;
use model::{Auto, IntrinsicISizes, MarginCollapseInfo, MarginsCollapse}; use model::{Auto, IntrinsicISizes, MarginCollapseInfo, MarginsCollapse, MarginsCollapseThrough};
use model::{MarginsCollapseThrough, MaybeAuto, NoCollapsibleMargins, Specified, specified}; use model::{MaybeAuto, NoCollapsibleMargins, Specified, specified, specified_or_none};
use model::{specified_or_none}; use table::ColumnInlineSize;
use wrapper::ThreadSafeLayoutNode; use wrapper::ThreadSafeLayoutNode;
use style::computed_values::{clear, position};
use collections::dlist::DList; use collections::dlist::DList;
use geom::{Size2D, Point2D, Rect}; use geom::{Size2D, Point2D, Rect};
@ -43,8 +55,8 @@ use std::cmp::{max, min};
use std::fmt; use std::fmt;
use std::mem; use std::mem;
use style::computed_values::{LPA_Auto, LPA_Length, LPA_Percentage, LPN_Length, LPN_None}; use style::computed_values::{LPA_Auto, LPA_Length, LPA_Percentage, LPN_Length, LPN_None};
use style::computed_values::{LPN_Percentage, LP_Length, LP_Percentage, box_sizing}; use style::computed_values::{LPN_Percentage, LP_Length, LP_Percentage, box_sizing, clear};
use style::computed_values::{display, float, overflow}; use style::computed_values::{display, float, overflow, position};
use sync::Arc; use sync::Arc;
/// Information specific to floated blocks. /// Information specific to floated blocks.
@ -762,9 +774,10 @@ impl BlockFlow {
/// ///
/// This is where we use the preferred inline-sizes and minimum inline-sizes /// This is where we use the preferred inline-sizes and minimum inline-sizes
/// calculated in the bubble-inline-sizes traversal. /// calculated in the bubble-inline-sizes traversal.
fn get_shrink_to_fit_inline_size(&self, available_inline_size: Au) -> Au { pub fn get_shrink_to_fit_inline_size(&self, available_inline_size: Au) -> Au {
min(self.base.intrinsic_inline_sizes.preferred_inline_size, let content_intrinsic_inline_sizes = self.content_intrinsic_inline_sizes();
max(self.base.intrinsic_inline_sizes.minimum_inline_size, available_inline_size)) min(content_intrinsic_inline_sizes.preferred_inline_size,
max(content_intrinsic_inline_sizes.minimum_inline_size, available_inline_size))
} }
/// If this is the root flow, shifts all kids down and adjusts our size to account for /// If this is the root flow, shifts all kids down and adjusts our size to account for
@ -1291,10 +1304,11 @@ impl BlockFlow {
/// `#[inline(always)]` because this is called only from block or table inline-size assignment /// `#[inline(always)]` because this is called only from block or table inline-size assignment
/// and the code for block layout is significantly simpler. /// and the code for block layout is significantly simpler.
#[inline(always)] #[inline(always)]
pub fn propagate_assigned_inline_size_to_children(&mut self, pub fn propagate_assigned_inline_size_to_children(
inline_start_content_edge: Au, &mut self,
content_inline_size: Au, inline_start_content_edge: Au,
opt_col_inline_sizes: Option<Vec<Au>>) { content_inline_size: Au,
optional_column_inline_sizes: Option<&[ColumnInlineSize]>) {
// Keep track of whether floats could impact each child. // Keep track of whether floats could impact each child.
let mut inline_start_floats_impact_child = self.base.flags.impacted_by_left_floats(); let mut inline_start_floats_impact_child = self.base.flags.impacted_by_left_floats();
let mut inline_end_floats_impact_child = self.base.flags.impacted_by_right_floats(); let mut inline_end_floats_impact_child = self.base.flags.impacted_by_right_floats();
@ -1403,13 +1417,13 @@ impl BlockFlow {
} }
// Handle tables. // Handle tables.
match opt_col_inline_sizes { match optional_column_inline_sizes {
Some(ref col_inline_sizes) => { Some(ref column_inline_sizes) => {
propagate_column_inline_sizes_to_child(kid, propagate_column_inline_sizes_to_child(kid,
i, i,
content_inline_size, content_inline_size,
col_inline_sizes.as_slice(), *column_inline_sizes,
&mut inline_start_margin_edge) &mut inline_start_margin_edge)
} }
None => {} None => {}
} }
@ -1469,6 +1483,23 @@ impl BlockFlow {
// TODO(pcwalton): If the inline-size of this flow is different from the size we estimated // TODO(pcwalton): If the inline-size of this flow is different from the size we estimated
// earlier, lay it out again. // earlier, lay it out again.
} }
fn is_inline_block(&self) -> bool {
self.fragment.style().get_box().display == display::inline_block
}
/// Computes the content portion (only) of the intrinsic inline sizes of this flow. This is
/// used for calculating shrink-to-fit width. Assumes that intrinsic sizes have already been
/// computed for this flow.
fn content_intrinsic_inline_sizes(&self) -> IntrinsicISizes {
let surrounding_inline_size = self.fragment.surrounding_intrinsic_inline_size();
IntrinsicISizes {
minimum_inline_size: self.base.intrinsic_inline_sizes.minimum_inline_size -
surrounding_inline_size,
preferred_inline_size: self.base.intrinsic_inline_sizes.preferred_inline_size -
surrounding_inline_size,
}
}
} }
impl Flow for BlockFlow { impl Flow for BlockFlow {
@ -1515,35 +1546,31 @@ impl Flow for BlockFlow {
}; };
// Find the maximum inline-size from children. // Find the maximum inline-size from children.
let mut intrinsic_inline_sizes = IntrinsicISizes::new(); let mut computation = self.fragment.compute_intrinsic_inline_sizes();
let mut left_float_width = Au(0); let mut left_float_width = Au(0);
let mut right_float_width = Au(0); let mut right_float_width = Au(0);
for child_ctx in self.base.child_iter() { for kid in self.base.child_iter() {
assert!(child_ctx.is_block_flow() || let is_absolutely_positioned = kid.is_absolutely_positioned();
child_ctx.is_inline_flow() || let float_kind = kid.float_kind();
child_ctx.is_table_kind()); let child_base = flow::mut_base(kid);
if !is_absolutely_positioned && !fixed_width {
let float_kind = child_ctx.float_kind(); computation.content_intrinsic_sizes.minimum_inline_size =
let child_base = flow::mut_base(child_ctx); max(computation.content_intrinsic_sizes.minimum_inline_size,
child_base.intrinsic_inline_sizes.minimum_inline_size);
if !fixed_width {
intrinsic_inline_sizes.minimum_inline_size =
max(intrinsic_inline_sizes.minimum_inline_size,
child_base.intrinsic_inline_sizes.total_minimum_inline_size());
match float_kind { match float_kind {
float::none => { float::none => {
intrinsic_inline_sizes.preferred_inline_size = computation.content_intrinsic_sizes.preferred_inline_size =
max(intrinsic_inline_sizes.preferred_inline_size, max(computation.content_intrinsic_sizes.preferred_inline_size,
child_base.intrinsic_inline_sizes.total_preferred_inline_size()); child_base.intrinsic_inline_sizes.preferred_inline_size);
} }
float::left => { float::left => {
left_float_width = left_float_width + left_float_width = left_float_width +
child_base.intrinsic_inline_sizes.total_preferred_inline_size(); child_base.intrinsic_inline_sizes.preferred_inline_size;
} }
float::right => { float::right => {
right_float_width = right_float_width + right_float_width = right_float_width +
child_base.intrinsic_inline_sizes.total_preferred_inline_size(); child_base.intrinsic_inline_sizes.preferred_inline_size;
} }
} }
} }
@ -1551,21 +1578,14 @@ impl Flow for BlockFlow {
flags.union_floated_descendants_flags(child_base.flags); flags.union_floated_descendants_flags(child_base.flags);
} }
intrinsic_inline_sizes.preferred_inline_size = // FIXME(pcwalton): This should consider all float descendants, not just children.
max(intrinsic_inline_sizes.preferred_inline_size, // FIXME(pcwalton): This is not well-spec'd; INTRINSIC specifies to do this, but CSS-SIZING
left_float_width + right_float_width); // says not to. In practice, Gecko and WebKit both do this.
computation.content_intrinsic_sizes.preferred_inline_size =
max(computation.content_intrinsic_sizes.preferred_inline_size,
left_float_width + right_float_width);
let fragment_intrinsic_inline_sizes = self.fragment.intrinsic_inline_sizes(); self.base.intrinsic_inline_sizes = computation.finish();
intrinsic_inline_sizes.minimum_inline_size =
max(intrinsic_inline_sizes.minimum_inline_size,
fragment_intrinsic_inline_sizes.minimum_inline_size);
intrinsic_inline_sizes.preferred_inline_size =
max(intrinsic_inline_sizes.preferred_inline_size,
fragment_intrinsic_inline_sizes.preferred_inline_size);
intrinsic_inline_sizes.surround_inline_size =
intrinsic_inline_sizes.surround_inline_size +
fragment_intrinsic_inline_sizes.surround_inline_size;
self.base.intrinsic_inline_sizes = intrinsic_inline_sizes;
match self.fragment.style().get_box().float { match self.fragment.style().get_box().float {
float::none => {} float::none => {}
@ -1575,11 +1595,11 @@ impl Flow for BlockFlow {
self.base.flags = flags self.base.flags = flags
} }
/// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When /// Recursively (top-down) determines the actual inline-size of child contexts and fragments.
/// called on this context, the context has had its inline-size set by the parent context. /// When called on this context, the context has had its inline-size set by the parent context.
/// ///
/// Dual fragments consume some inline-size first, and the remainder is assigned to all child (block) /// Dual fragments consume some inline-size first, and the remainder is assigned to all child
/// contexts. /// (block) contexts.
fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) { fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
let _scope = layout_debug_scope!("block::assign_inline_sizes {:s}", self.base.debug_id()); let _scope = layout_debug_scope!("block::assign_inline_sizes {:s}", self.base.debug_id());
@ -1632,11 +1652,14 @@ impl Flow for BlockFlow {
} }
// Move in from the inline-start border edge. // Move in from the inline-start border edge.
let inline_start_content_edge = self.fragment.border_box.start.i + self.fragment.border_padding.inline_start; let inline_start_content_edge = self.fragment.border_box.start.i +
self.fragment.border_padding.inline_start;
let padding_and_borders = self.fragment.border_padding.inline_start_end(); let padding_and_borders = self.fragment.border_padding.inline_start_end();
let content_inline_size = self.fragment.border_box.size.inline - padding_and_borders; let content_inline_size = self.fragment.border_box.size.inline - padding_and_borders;
self.propagate_assigned_inline_size_to_children(inline_start_content_edge, content_inline_size, None); self.propagate_assigned_inline_size_to_children(inline_start_content_edge,
content_inline_size,
None);
} }
/// Assigns block-sizes in-order; or, if this is a float, places the float. The default /// Assigns block-sizes in-order; or, if this is a float, places the float. The default
@ -1686,7 +1709,7 @@ impl Flow for BlockFlow {
self.base.block_container_explicit_block_size.unwrap_or(Au(0)); self.base.block_container_explicit_block_size.unwrap_or(Au(0));
self.fragment.assign_replaced_block_size_if_necessary(containing_block_block_size); self.fragment.assign_replaced_block_size_if_necessary(containing_block_block_size);
self.base.position.size.block = self.fragment.border_box.size.block; self.base.position.size.block = self.fragment.border_box.size.block;
} else if self.is_root() || self.is_float() { } else if self.is_root() || self.is_float() || self.is_inline_block() {
// Root element margins should never be collapsed according to CSS § 8.3.1. // Root element margins should never be collapsed according to CSS § 8.3.1.
debug!("assign_block_size: assigning block_size for root flow"); debug!("assign_block_size: assigning block_size for root flow");
self.assign_block_size_block_base(ctx, MarginsMayNotCollapse); self.assign_block_size_block_base(ctx, MarginsMayNotCollapse);
@ -1882,6 +1905,7 @@ impl ISizeConstraintInput {
} }
/// The solutions for the inline-size-and-margins constraint equation. /// The solutions for the inline-size-and-margins constraint equation.
#[deriving(Show)]
pub struct ISizeConstraintSolution { pub struct ISizeConstraintSolution {
pub inline_start: Au, pub inline_start: Au,
pub inline_end: Au, pub inline_end: Au,
@ -1923,9 +1947,8 @@ impl ISizeConstraintSolution {
pub trait ISizeAndMarginsComputer { pub trait ISizeAndMarginsComputer {
/// Compute the inputs for the ISize constraint equation. /// Compute the inputs for the ISize constraint equation.
/// ///
/// This is called only once to compute the initial inputs. For /// This is called only once to compute the initial inputs. For calculations involving
/// calculation involving min-inline-size and max-inline-size, we don't need to /// minimum and maximum inline-size, we don't need to recompute these.
/// recompute these.
fn compute_inline_size_constraint_inputs(&self, fn compute_inline_size_constraint_inputs(&self,
block: &mut BlockFlow, block: &mut BlockFlow,
parent_flow_inline_size: Au, parent_flow_inline_size: Au,
@ -1934,7 +1957,9 @@ pub trait ISizeAndMarginsComputer {
let containing_block_inline_size = let containing_block_inline_size =
self.containing_block_inline_size(block, parent_flow_inline_size, layout_context); self.containing_block_inline_size(block, parent_flow_inline_size, layout_context);
block.fragment.compute_border_padding_margins(containing_block_inline_size); block.fragment.compute_block_direction_margins(containing_block_inline_size);
block.fragment.compute_inline_direction_margins(containing_block_inline_size);
block.fragment.compute_border_and_padding(containing_block_inline_size);
let mut computed_inline_size = self.initial_computed_inline_size(block, let mut computed_inline_size = self.initial_computed_inline_size(block,
parent_flow_inline_size, parent_flow_inline_size,
@ -1955,7 +1980,8 @@ pub trait ISizeAndMarginsComputer {
let margin = style.logical_margin(); let margin = style.logical_margin();
let position = style.logical_position(); let position = style.logical_position();
let available_inline_size = containing_block_inline_size - block.fragment.border_padding.inline_start_end(); let available_inline_size = containing_block_inline_size -
block.fragment.border_padding.inline_start_end();
return ISizeConstraintInput::new( return ISizeConstraintInput::new(
computed_inline_size, computed_inline_size,
MaybeAuto::from_style(margin.inline_start, containing_block_inline_size), MaybeAuto::from_style(margin.inline_start, containing_block_inline_size),
@ -2015,10 +2041,10 @@ pub trait ISizeAndMarginsComputer {
-> ISizeConstraintSolution; -> ISizeConstraintSolution;
fn initial_computed_inline_size(&self, fn initial_computed_inline_size(&self,
block: &mut BlockFlow, block: &mut BlockFlow,
parent_flow_inline_size: Au, parent_flow_inline_size: Au,
ctx: &LayoutContext) ctx: &LayoutContext)
-> MaybeAuto { -> MaybeAuto {
MaybeAuto::from_style(block.fragment().style().content_inline_size(), MaybeAuto::from_style(block.fragment().style().content_inline_size(),
self.containing_block_inline_size(block, self.containing_block_inline_size(block,
parent_flow_inline_size, parent_flow_inline_size,
@ -2409,11 +2435,12 @@ impl ISizeAndMarginsComputer for AbsoluteReplaced {
/// Calculate used value of inline-size just like we do for inline replaced elements. /// Calculate used value of inline-size just like we do for inline replaced elements.
fn initial_computed_inline_size(&self, fn initial_computed_inline_size(&self,
block: &mut BlockFlow, block: &mut BlockFlow,
_: Au, _: Au,
ctx: &LayoutContext) layout_context: &LayoutContext)
-> MaybeAuto { -> MaybeAuto {
let containing_block_inline_size = block.containing_block_size(ctx.shared.screen_size).inline; let containing_block_inline_size =
block.containing_block_size(layout_context.shared.screen_size).inline;
let fragment = block.fragment(); let fragment = block.fragment();
fragment.assign_replaced_inline_size_if_necessary(containing_block_inline_size); fragment.assign_replaced_inline_size_if_necessary(containing_block_inline_size);
// For replaced absolute flow, the rest of the constraint solving will // For replaced absolute flow, the rest of the constraint solving will
@ -2459,10 +2486,10 @@ impl ISizeAndMarginsComputer for BlockReplaced {
/// Calculate used value of inline-size just like we do for inline replaced elements. /// Calculate used value of inline-size just like we do for inline replaced elements.
fn initial_computed_inline_size(&self, fn initial_computed_inline_size(&self,
block: &mut BlockFlow, block: &mut BlockFlow,
parent_flow_inline_size: Au, parent_flow_inline_size: Au,
_: &LayoutContext) _: &LayoutContext)
-> MaybeAuto { -> MaybeAuto {
let fragment = block.fragment(); let fragment = block.fragment();
fragment.assign_replaced_inline_size_if_necessary(parent_flow_inline_size); fragment.assign_replaced_inline_size_if_necessary(parent_flow_inline_size);
// For replaced block flow, the rest of the constraint solving will // For replaced block flow, the rest of the constraint solving will
@ -2515,10 +2542,10 @@ impl ISizeAndMarginsComputer for FloatReplaced {
/// Calculate used value of inline-size just like we do for inline replaced elements. /// Calculate used value of inline-size just like we do for inline replaced elements.
fn initial_computed_inline_size(&self, fn initial_computed_inline_size(&self,
block: &mut BlockFlow, block: &mut BlockFlow,
parent_flow_inline_size: Au, parent_flow_inline_size: Au,
_: &LayoutContext) _: &LayoutContext)
-> MaybeAuto { -> MaybeAuto {
let fragment = block.fragment(); let fragment = block.fragment();
fragment.assign_replaced_inline_size_if_necessary(parent_flow_inline_size); fragment.assign_replaced_inline_size_if_necessary(parent_flow_inline_size);
// For replaced block flow, the rest of the constraint solving will // For replaced block flow, the rest of the constraint solving will
@ -2528,35 +2555,34 @@ impl ISizeAndMarginsComputer for FloatReplaced {
} }
fn propagate_column_inline_sizes_to_child(kid: &mut Flow, fn propagate_column_inline_sizes_to_child(kid: &mut Flow,
child_index: uint, child_index: uint,
content_inline_size: Au, content_inline_size: Au,
column_inline_sizes: &[Au], column_inline_sizes: &[ColumnInlineSize],
inline_start_margin_edge: &mut Au) { inline_start_margin_edge: &mut Au) {
// If kid is table_rowgroup or table_row, the column inline-sizes info should be copied from its // If kid is table_rowgroup or table_row, the column inline-sizes info should be copied from
// parent. // its parent.
// //
// FIXME(pcwalton): This seems inefficient. Reference count it instead? // FIXME(pcwalton): This seems inefficient. Reference count it instead?
let inline_size = if kid.is_table() || kid.is_table_rowgroup() || kid.is_table_row() { let inline_size = if kid.is_table() || kid.is_table_rowgroup() || kid.is_table_row() {
*kid.col_inline_sizes() = column_inline_sizes.iter().map(|&x| x).collect(); *kid.column_inline_sizes() = column_inline_sizes.iter().map(|&x| x).collect();
// ISize of kid flow is our content inline-size. // ISize of kid flow is our content inline-size.
content_inline_size content_inline_size
} else if kid.is_table_cell() { } else if kid.is_table_cell() {
// If kid is table_cell, the x offset and inline-size for each cell should be column_inline_sizes[child_index].minimum_length
// calculated from parent's column inline-sizes info.
*inline_start_margin_edge = if child_index == 0 {
Au(0)
} else {
*inline_start_margin_edge + column_inline_sizes[child_index - 1]
};
column_inline_sizes[child_index]
} else { } else {
// ISize of kid flow is our content inline-size. // ISize of kid flow is our content inline-size.
content_inline_size content_inline_size
}; };
let kid_base = flow::mut_base(kid); {
kid_base.position.start.i = *inline_start_margin_edge; let kid_base = flow::mut_base(kid);
kid_base.block_container_inline_size = inline_size; kid_base.position.start.i = *inline_start_margin_edge;
kid_base.block_container_inline_size = inline_size;
}
if kid.is_table_cell() {
*inline_start_margin_edge = *inline_start_margin_edge + inline_size
}
} }

View file

@ -767,8 +767,8 @@ impl<'a> FlowConstructor<'a> {
flow.add_new_child(anonymous_flow); flow.add_new_child(anonymous_flow);
} }
/// Builds a flow for a node with `display: table`. This yields a `TableWrapperFlow` with possibly /// Builds a flow for a node with `display: table`. This yields a `TableWrapperFlow` with
/// other `TableCaptionFlow`s or `TableFlow`s underneath it. /// possibly other `TableCaptionFlow`s or `TableFlow`s underneath it.
fn build_flow_for_table_wrapper(&mut self, node: &ThreadSafeLayoutNode, fn build_flow_for_table_wrapper(&mut self, node: &ThreadSafeLayoutNode,
float_value: float::T) -> ConstructionResult { float_value: float::T) -> ConstructionResult {
let fragment = Fragment::new_from_specific_info(node, TableWrapperFragment); let fragment = Fragment::new_from_specific_info(node, TableWrapperFragment);

View file

@ -36,7 +36,7 @@ use incremental::RestyleDamage;
use inline::InlineFlow; use inline::InlineFlow;
use model::{CollapsibleMargins, IntrinsicISizes, MarginCollapseInfo}; use model::{CollapsibleMargins, IntrinsicISizes, MarginCollapseInfo};
use parallel::FlowParallelInfo; use parallel::FlowParallelInfo;
use table::TableFlow; use table::{ColumnInlineSize, TableFlow};
use table_caption::TableCaptionFlow; use table_caption::TableCaptionFlow;
use table_cell::TableCellFlow; use table_cell::TableCellFlow;
use table_colgroup::TableColGroupFlow; use table_colgroup::TableColGroupFlow;
@ -164,20 +164,8 @@ pub trait Flow: fmt::Show + ToString + Sync {
/// If this is a table row or table rowgroup or table flow, returns column inline-sizes. /// If this is a table row or table rowgroup or table flow, returns column inline-sizes.
/// Fails otherwise. /// Fails otherwise.
fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> { fn column_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<ColumnInlineSize> {
fail!("called col_inline_sizes() on an other flow than table-row/table-rowgroup/table") fail!("called column_inline_sizes() on non-table flow")
}
/// If this is a table row flow or table rowgroup flow or table flow, returns column min
/// inline-sizes. Fails otherwise.
fn col_min_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
fail!("called col_min_inline_sizes() on an other flow than table-row/table-rowgroup/table")
}
/// If this is a table row flow or table rowgroup flow or table flow, returns column min
/// inline-sizes. Fails otherwise.
fn col_pref_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
fail!("called col_pref_inline_sizes() on an other flow than table-row/table-rowgroup/table")
} }
// Main methods // Main methods

View file

@ -14,7 +14,7 @@ use flow::Flow;
use flow_ref::FlowRef; use flow_ref::FlowRef;
use inline::{InlineFragmentContext, InlineMetrics}; use inline::{InlineFragmentContext, InlineMetrics};
use layout_debug; use layout_debug;
use model::{Auto, IntrinsicISizes, MaybeAuto, Specified, specified}; use model::{Auto, IntrinsicISizes, IntrinsicISizesContribution, MaybeAuto, Specified, specified};
use model; use model;
use text; use text;
use util::{OpaqueNodeMethods, ToGfxColor}; use util::{OpaqueNodeMethods, ToGfxColor};
@ -529,51 +529,93 @@ impl Fragment {
self.inline_context.as_mut().unwrap().styles.push(style.clone()); self.inline_context.as_mut().unwrap().styles.push(style.clone());
} }
/// Uses the style only to estimate the intrinsic inline-sizes. These may be modified for text /// Determines which quantities (border/padding/margin/specified) should be included in the
/// or replaced elements. /// intrinsic inline size of this fragment.
fn style_specified_intrinsic_inline_size(&self) -> IntrinsicISizes { fn quantities_included_in_intrinsic_inline_size(&self)
let (use_margins, use_padding) = match self.specific { -> QuantitiesIncludedInIntrinsicInlineSizes {
match self.specific {
GenericFragment | IframeFragment(_) | ImageFragment(_) | InlineBlockFragment(_) | GenericFragment | IframeFragment(_) | ImageFragment(_) | InlineBlockFragment(_) |
InputFragment => (true, true), InputFragment => QuantitiesIncludedInIntrinsicInlineSizes::all(),
TableFragment | TableCellFragment => (false, true), TableFragment | TableCellFragment => {
TableWrapperFragment => (true, false), IntrinsicInlineSizeIncludesPadding |
TableRowFragment => (false, false), IntrinsicInlineSizeIncludesBorder |
IntrinsicInlineSizeIncludesSpecified
}
TableWrapperFragment => {
IntrinsicInlineSizeIncludesMargins |
IntrinsicInlineSizeIncludesBorder |
IntrinsicInlineSizeIncludesSpecified
}
TableRowFragment => {
IntrinsicInlineSizeIncludesBorder |
IntrinsicInlineSizeIncludesSpecified
}
ScannedTextFragment(_) | TableColumnFragment(_) | UnscannedTextFragment(_) | ScannedTextFragment(_) | TableColumnFragment(_) | UnscannedTextFragment(_) |
InlineAbsoluteHypotheticalFragment(_) => { InlineAbsoluteHypotheticalFragment(_) => {
// Styles are irrelevant for these kinds of fragments. QuantitiesIncludedInIntrinsicInlineSizes::empty()
return IntrinsicISizes::new()
} }
}; }
}
/// Returns the portion of the intrinsic inline-size that consists of borders, padding, and/or
/// margins.
///
/// FIXME(#2261, pcwalton): This won't work well for inlines: is this OK?
pub fn surrounding_intrinsic_inline_size(&self) -> Au {
let flags = self.quantities_included_in_intrinsic_inline_size();
let style = self.style(); let style = self.style();
let inline_size = MaybeAuto::from_style(style.content_inline_size(),
Au(0)).specified_or_zero();
let margin = style.logical_margin(); // FIXME(pcwalton): Percentages should be relative to any definite size per CSS-SIZING.
let (margin_inline_start, margin_inline_end) = if use_margins { // This will likely need to be done by pushing down definite sizes during selector
(MaybeAuto::from_style(margin.inline_start, Au(0)).specified_or_zero(), // cascading.
let margin = if flags.contains(IntrinsicInlineSizeIncludesMargins) {
let margin = style.logical_margin();
(MaybeAuto::from_style(margin.inline_start, Au(0)).specified_or_zero() +
MaybeAuto::from_style(margin.inline_end, Au(0)).specified_or_zero()) MaybeAuto::from_style(margin.inline_end, Au(0)).specified_or_zero())
} else { } else {
(Au(0), Au(0)) Au(0)
}; };
let padding = style.logical_padding(); // FIXME(pcwalton): Percentages should be relative to any definite size per CSS-SIZING.
let (padding_inline_start, padding_inline_end) = if use_padding { // This will likely need to be done by pushing down definite sizes during selector
(model::specified(padding.inline_start, Au(0)), // cascading.
let padding = if flags.contains(IntrinsicInlineSizeIncludesPadding) {
let padding = style.logical_padding();
(model::specified(padding.inline_start, Au(0)) +
model::specified(padding.inline_end, Au(0))) model::specified(padding.inline_end, Au(0)))
} else { } else {
(Au(0), Au(0)) Au(0)
};
let border = if flags.contains(IntrinsicInlineSizeIncludesBorder) {
self.border_width().inline_start_end()
} else {
Au(0)
};
margin + padding + border
}
/// Uses the style only to estimate the intrinsic inline-sizes. These may be modified for text
/// or replaced elements.
fn style_specified_intrinsic_inline_size(&self) -> IntrinsicISizesContribution {
let flags = self.quantities_included_in_intrinsic_inline_size();
let style = self.style();
let specified = if flags.contains(IntrinsicInlineSizeIncludesSpecified) {
MaybeAuto::from_style(style.content_inline_size(), Au(0)).specified_or_zero()
} else {
Au(0)
}; };
// FIXME(#2261, pcwalton): This won't work well for inlines: is this OK? // FIXME(#2261, pcwalton): This won't work well for inlines: is this OK?
let border = self.border_width(); let surrounding_inline_size = self.surrounding_intrinsic_inline_size();
let surround_inline_size = margin_inline_start + margin_inline_end + padding_inline_start + padding_inline_end +
border.inline_start_end();
IntrinsicISizes { IntrinsicISizesContribution {
minimum_inline_size: inline_size, content_intrinsic_sizes: IntrinsicISizes {
preferred_inline_size: inline_size, minimum_inline_size: specified,
surround_inline_size: surround_inline_size, preferred_inline_size: specified,
},
surrounding_size: surrounding_inline_size,
} }
} }
@ -601,28 +643,58 @@ impl Fragment {
} }
} }
/// Computes the border, padding, and vertical margins from the containing block inline-size and the /// Computes the margins in the inline direction from the containing block inline-size and the
/// style. After this call, the `border_padding` and the vertical direction of the `margin` /// style. After this call, the inline direction of the `margin` field will be correct.
/// field will be correct. ///
pub fn compute_border_padding_margins(&mut self, /// Do not use this method if the inline direction margins are to be computed some other way
containing_block_inline_size: Au) { /// (for example, via constraint solving for blocks).
// Compute vertical margins. Note that this value will be ignored by layout if the style pub fn compute_inline_direction_margins(&mut self, containing_block_inline_size: Au) {
// specifies `auto`. match self.specific {
TableFragment | TableCellFragment | TableRowFragment | TableColumnFragment(_) => {
self.margin.inline_start = Au(0);
self.margin.inline_end = Au(0)
}
_ => {
let margin = self.style().logical_margin();
self.margin.inline_start =
MaybeAuto::from_style(margin.inline_start, containing_block_inline_size)
.specified_or_zero();
self.margin.inline_end =
MaybeAuto::from_style(margin.inline_end, containing_block_inline_size)
.specified_or_zero();
}
}
}
/// Computes the margins in the block direction from the containing block inline-size and the
/// style. After this call, the block direction of the `margin` field will be correct.
///
/// Do not use this method if the block direction margins are to be computed some other way
/// (for example, via constraint solving for absolutely-positioned flows).
pub fn compute_block_direction_margins(&mut self, containing_block_inline_size: Au) {
match self.specific { match self.specific {
TableFragment | TableCellFragment | TableRowFragment | TableColumnFragment(_) => { TableFragment | TableCellFragment | TableRowFragment | TableColumnFragment(_) => {
self.margin.block_start = Au(0); self.margin.block_start = Au(0);
self.margin.block_end = Au(0) self.margin.block_end = Au(0)
} }
_ => { _ => {
// NB: Percentages are relative to containing block inline-size (not block-size) per CSS 2.1. // NB: Percentages are relative to containing block inline-size (not block-size)
// per CSS 2.1.
let margin = self.style().logical_margin(); let margin = self.style().logical_margin();
self.margin.block_start = MaybeAuto::from_style(margin.block_start, containing_block_inline_size) self.margin.block_start =
MaybeAuto::from_style(margin.block_start, containing_block_inline_size)
.specified_or_zero();
self.margin.block_end =
MaybeAuto::from_style(margin.block_end, containing_block_inline_size)
.specified_or_zero(); .specified_or_zero();
self.margin.block_end = MaybeAuto::from_style(margin.block_end, containing_block_inline_size)
.specified_or_zero()
} }
} }
}
/// Computes the border and padding in both inline and block directions from the containing
/// block inline-size and the style. After this call, the `border_padding` field will be
/// correct.
pub fn compute_border_and_padding(&mut self, containing_block_inline_size: Au) {
// Compute border. // Compute border.
let border = self.border_width(); let border = self.border_width();
@ -1241,28 +1313,23 @@ impl Fragment {
} }
} }
/// Returns the intrinsic inline-sizes of this fragment. /// Computes the intrinsic inline-sizes of this fragment.
pub fn intrinsic_inline_sizes(&mut self) -> IntrinsicISizes { pub fn compute_intrinsic_inline_sizes(&mut self) -> IntrinsicISizesContribution {
let mut result = self.style_specified_intrinsic_inline_size(); let mut result = self.style_specified_intrinsic_inline_size();
match self.specific { match self.specific {
GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment |
TableColumnFragment(_) | TableRowFragment | TableWrapperFragment | TableColumnFragment(_) | TableRowFragment | TableWrapperFragment |
InlineAbsoluteHypotheticalFragment(_) | InputFragment => {} InlineAbsoluteHypotheticalFragment(_) | InputFragment => {}
InlineBlockFragment(ref mut info) => { InlineBlockFragment(ref mut info) => {
let block_flow = info.flow_ref.get_mut().as_block(); let block_flow = info.flow_ref.get_mut().as_block();
result.minimum_inline_size = max(result.minimum_inline_size, result.union_block(&block_flow.base.intrinsic_inline_sizes)
block_flow.base.intrinsic_inline_sizes.minimum_inline_size +
block_flow.base.intrinsic_inline_sizes.surround_inline_size);
result.preferred_inline_size = max(result.preferred_inline_size,
block_flow.base.intrinsic_inline_sizes.preferred_inline_size +
block_flow.base.intrinsic_inline_sizes.surround_inline_size);
} }
ImageFragment(ref mut image_fragment_info) => { ImageFragment(ref mut image_fragment_info) => {
let image_inline_size = image_fragment_info.image_inline_size(); let image_inline_size = image_fragment_info.image_inline_size();
result.minimum_inline_size = max(result.minimum_inline_size, image_inline_size); result.union_block(&IntrinsicISizes {
result.preferred_inline_size = max(result.preferred_inline_size, minimum_inline_size: image_inline_size,
image_inline_size); preferred_inline_size: image_inline_size,
})
} }
ScannedTextFragment(ref text_fragment_info) => { ScannedTextFragment(ref text_fragment_info) => {
let range = &text_fragment_info.range; let range = &text_fragment_info.range;
@ -1274,10 +1341,10 @@ impl Fragment {
.metrics_for_range(range) .metrics_for_range(range)
.advance_width; .advance_width;
result.minimum_inline_size = max(result.minimum_inline_size, result.union_block(&IntrinsicISizes {
min_line_inline_size); minimum_inline_size: min_line_inline_size,
result.preferred_inline_size = max(result.preferred_inline_size, preferred_inline_size: max_line_inline_size,
max_line_inline_size); })
} }
UnscannedTextFragment(..) => { UnscannedTextFragment(..) => {
fail!("Unscanned text fragments should have been scanned by now!") fail!("Unscanned text fragments should have been scanned by now!")
@ -1293,10 +1360,8 @@ impl Fragment {
let border_width = style.logical_border_width().inline_start_end(); let border_width = style.logical_border_width().inline_start_end();
let padding_inline_size = let padding_inline_size =
model::padding_from_style(&**style, Au(0)).inline_start_end(); model::padding_from_style(&**style, Au(0)).inline_start_end();
result.minimum_inline_size = result.minimum_inline_size + border_width + result.surrounding_size = result.surrounding_size + border_width +
padding_inline_size; padding_inline_size;
result.preferred_inline_size = result.preferred_inline_size +
border_width + padding_inline_size;
} }
} }
} }
@ -1477,7 +1542,9 @@ impl Fragment {
} else { } else {
None None
}; };
let inline_end = inline_end_range.map(|inline_end_range| SplitInfo::new(inline_end_range, text_fragment_info)); let inline_end = inline_end_range.map(|inline_end_range| {
SplitInfo::new(inline_end_range, text_fragment_info)
});
Some((inline_start, inline_end, text_fragment_info.run.clone())) Some((inline_start, inline_end, text_fragment_info.run.clone()))
} }
@ -1502,8 +1569,7 @@ impl Fragment {
/// Assigns replaced inline-size, padding, and margins for this fragment only if it is replaced /// Assigns replaced inline-size, padding, and margins for this fragment only if it is replaced
/// content per CSS 2.1 § 10.3.2. /// content per CSS 2.1 § 10.3.2.
pub fn assign_replaced_inline_size_if_necessary(&mut self, pub fn assign_replaced_inline_size_if_necessary(&mut self, container_inline_size: Au) {
container_inline_size: Au) {
match self.specific { match self.specific {
GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment |
TableRowFragment | TableWrapperFragment | InputFragment => return, TableRowFragment | TableWrapperFragment | InputFragment => return,
@ -1515,8 +1581,6 @@ impl Fragment {
InlineAbsoluteHypotheticalFragment(_) => {} InlineAbsoluteHypotheticalFragment(_) => {}
}; };
self.compute_border_padding_margins(container_inline_size);
let style_inline_size = self.style().content_inline_size(); let style_inline_size = self.style().content_inline_size();
let style_block_size = self.style().content_block_size(); let style_block_size = self.style().content_block_size();
let style_min_inline_size = self.style().min_inline_size(); let style_min_inline_size = self.style().min_inline_size();
@ -1528,17 +1592,16 @@ impl Fragment {
match self.specific { match self.specific {
InlineAbsoluteHypotheticalFragment(ref mut info) => { InlineAbsoluteHypotheticalFragment(ref mut info) => {
let block_flow = info.flow_ref.get_mut().as_block(); let block_flow = info.flow_ref.get_mut().as_block();
block_flow.base.block_container_inline_size = block_flow.base.position.size.inline =
block_flow.base.intrinsic_inline_sizes.preferred_inline_size + block_flow.base.intrinsic_inline_sizes.preferred_inline_size;
block_flow.base.intrinsic_inline_sizes.surround_inline_size;
// This is a hypothetical box, so it takes up no space. // This is a hypothetical box, so it takes up no space.
self.border_box.size.inline = Au(0); self.border_box.size.inline = Au(0);
} }
InlineBlockFragment(ref mut info) => { InlineBlockFragment(ref mut info) => {
let block_flow = info.flow_ref.get_mut().as_block(); let block_flow = info.flow_ref.get_mut().as_block();
self.border_box.size.inline = block_flow.base.intrinsic_inline_sizes.preferred_inline_size + self.border_box.size.inline =
block_flow.base.intrinsic_inline_sizes.surround_inline_size; block_flow.base.intrinsic_inline_sizes.preferred_inline_size;
block_flow.base.block_container_inline_size = self.border_box.size.inline; block_flow.base.block_container_inline_size = self.border_box.size.inline;
} }
ScannedTextFragment(_) => { ScannedTextFragment(_) => {
@ -1652,7 +1715,8 @@ impl Fragment {
InlineBlockFragment(ref mut info) => { InlineBlockFragment(ref mut info) => {
// Not the primary fragment, so we do not take the noncontent size into account. // Not the primary fragment, so we do not take the noncontent size into account.
let block_flow = info.flow_ref.get_mut().as_block(); let block_flow = info.flow_ref.get_mut().as_block();
self.border_box.size.block = block_flow.base.position.size.block; self.border_box.size.block = block_flow.base.position.size.block +
block_flow.fragment.margin.block_start_end()
} }
InlineAbsoluteHypotheticalFragment(ref mut info) => { InlineAbsoluteHypotheticalFragment(ref mut info) => {
// Not the primary fragment, so we do not take the noncontent size into account. // Not the primary fragment, so we do not take the noncontent size into account.
@ -1686,7 +1750,9 @@ impl Fragment {
let font_style = text::computed_style_to_font_style(&*self.style); let font_style = text::computed_style_to_font_style(&*self.style);
let font_metrics = text::font_metrics_for_style(layout_context.font_context(), let font_metrics = text::font_metrics_for_style(layout_context.font_context(),
&font_style); &font_style);
InlineMetrics::from_block_height(&font_metrics, block_flow.base.position.size.block) InlineMetrics::from_block_height(&font_metrics,
block_flow.base.position.size.block +
block_flow.fragment.margin.block_start_end())
} }
InlineAbsoluteHypotheticalFragment(_) => { InlineAbsoluteHypotheticalFragment(_) => {
// Hypothetical boxes take up no space. // Hypothetical boxes take up no space.
@ -1838,3 +1904,12 @@ impl fmt::Show for Fragment {
} }
} }
bitflags! {
flags QuantitiesIncludedInIntrinsicInlineSizes: u8 {
static IntrinsicInlineSizeIncludesMargins = 0x01,
static IntrinsicInlineSizeIncludesPadding = 0x02,
static IntrinsicInlineSizeIncludesBorder = 0x04,
static IntrinsicInlineSizeIncludesSpecified = 0x08,
}
}

View file

@ -12,16 +12,15 @@ use flow;
use fragment::{Fragment, InlineAbsoluteHypotheticalFragment, InlineBlockFragment}; use fragment::{Fragment, InlineAbsoluteHypotheticalFragment, InlineBlockFragment};
use fragment::{ScannedTextFragment, ScannedTextFragmentInfo, SplitInfo}; use fragment::{ScannedTextFragment, ScannedTextFragmentInfo, SplitInfo};
use layout_debug; use layout_debug;
use model::IntrinsicISizes; use model::IntrinsicISizesContribution;
use text; use text;
use wrapper::ThreadSafeLayoutNode; use wrapper::ThreadSafeLayoutNode;
use collections::{Deque, RingBuf}; use collections::{Deque, RingBuf};
use geom::Rect; use geom::{Rect, Size2D};
use gfx::display_list::{ContentLevel, DisplayList}; use gfx::display_list::{ContentLevel, DisplayList};
use gfx::font::FontMetrics; use gfx::font::FontMetrics;
use gfx::font_context::FontContext; use gfx::font_context::FontContext;
use geom::Size2D;
use gfx::text::glyph::CharIndex; use gfx::text::glyph::CharIndex;
use servo_util::geometry::Au; use servo_util::geometry::Au;
use servo_util::logical_geometry::{LogicalRect, LogicalSize}; use servo_util::logical_geometry::{LogicalRect, LogicalSize};
@ -922,24 +921,12 @@ impl Flow for InlineFlow {
flow::mut_base(kid).floats = Floats::new(writing_mode); flow::mut_base(kid).floats = Floats::new(writing_mode);
} }
let mut intrinsic_inline_sizes = IntrinsicISizes::new(); let mut computation = IntrinsicISizesContribution::new();
for fragment in self.fragments.fragments.iter_mut() { for fragment in self.fragments.fragments.iter_mut() {
debug!("Flow: measuring {}", *fragment); debug!("Flow: measuring {}", *fragment);
computation.union_inline(&fragment.compute_intrinsic_inline_sizes().finish())
let fragment_intrinsic_inline_sizes =
fragment.intrinsic_inline_sizes();
intrinsic_inline_sizes.minimum_inline_size = max(
intrinsic_inline_sizes.minimum_inline_size,
fragment_intrinsic_inline_sizes.minimum_inline_size);
intrinsic_inline_sizes.preferred_inline_size =
intrinsic_inline_sizes.preferred_inline_size +
fragment_intrinsic_inline_sizes.preferred_inline_size;
intrinsic_inline_sizes.surround_inline_size =
intrinsic_inline_sizes.surround_inline_size +
fragment_intrinsic_inline_sizes.surround_inline_size;
} }
self.base.intrinsic_inline_sizes = computation.finish()
self.base.intrinsic_inline_sizes = intrinsic_inline_sizes;
} }
/// Recursively (top-down) determines the actual inline-size of child contexts and fragments. /// Recursively (top-down) determines the actual inline-size of child contexts and fragments.
@ -960,6 +947,9 @@ impl Flow for InlineFlow {
let inline_size = self.base.position.size.inline; let inline_size = self.base.position.size.inline;
let this = &mut *self; let this = &mut *self;
for fragment in this.fragments.fragments.iter_mut() { for fragment in this.fragments.fragments.iter_mut() {
fragment.compute_border_and_padding(inline_size);
fragment.compute_block_direction_margins(inline_size);
fragment.compute_inline_direction_margins(inline_size);
fragment.assign_replaced_inline_size_if_necessary(inline_size); fragment.assign_replaced_inline_size_if_necessary(inline_size);
} }
} }
@ -1130,6 +1120,7 @@ impl Flow for InlineFlow {
let block_flow = info.flow_ref.get_mut().as_block(); let block_flow = info.flow_ref.get_mut().as_block();
// FIXME(#2795): Get the real container size // FIXME(#2795): Get the real container size
let container_size = Size2D::zero(); let container_size = Size2D::zero();
block_flow.base.abs_position = block_flow.base.abs_position =
self.base.abs_position + self.base.abs_position +
fragment.border_box.start.to_physical(self.base.writing_mode, fragment.border_box.start.to_physical(self.base.writing_mode,

View file

@ -249,14 +249,11 @@ pub struct IntrinsicISizes {
pub minimum_inline_size: Au, pub minimum_inline_size: Au,
/// The *preferred inline-size* of the content. /// The *preferred inline-size* of the content.
pub preferred_inline_size: Au, pub preferred_inline_size: Au,
/// The estimated sum of borders, padding, and margins. Some calculations use this information
/// when computing intrinsic inline-sizes.
pub surround_inline_size: Au,
} }
impl fmt::Show for IntrinsicISizes { impl fmt::Show for IntrinsicISizes {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "min={}, pref={}, surr={}", self.minimum_inline_size, self.preferred_inline_size, self.surround_inline_size) write!(f, "min={}, pref={}", self.minimum_inline_size, self.preferred_inline_size)
} }
} }
@ -265,20 +262,67 @@ impl IntrinsicISizes {
IntrinsicISizes { IntrinsicISizes {
minimum_inline_size: Au(0), minimum_inline_size: Au(0),
preferred_inline_size: Au(0), preferred_inline_size: Au(0),
surround_inline_size: Au(0), }
}
}
/// The temporary result of the computation of intrinsic inline-sizes.
pub struct IntrinsicISizesContribution {
/// Intrinsic sizes for the content only (not counting borders, padding, or margins).
pub content_intrinsic_sizes: IntrinsicISizes,
/// The inline size of borders and padding, as well as margins if appropriate.
pub surrounding_size: Au,
}
impl IntrinsicISizesContribution {
/// Creates and initializes an inline size computation with all sizes set to zero.
pub fn new() -> IntrinsicISizesContribution {
IntrinsicISizesContribution {
content_intrinsic_sizes: IntrinsicISizes::new(),
surrounding_size: Au(0),
} }
} }
pub fn total_minimum_inline_size(&self) -> Au { /// Adds the content intrinsic sizes and the surrounding size together to yield the final
self.minimum_inline_size + self.surround_inline_size /// intrinsic size computation.
pub fn finish(self) -> IntrinsicISizes {
IntrinsicISizes {
minimum_inline_size: self.content_intrinsic_sizes.minimum_inline_size +
self.surrounding_size,
preferred_inline_size: self.content_intrinsic_sizes.preferred_inline_size +
self.surrounding_size,
}
} }
pub fn total_preferred_inline_size(&self) -> Au { /// Updates the computation so that the minimum is the maximum of the current minimum and the
self.preferred_inline_size + self.surround_inline_size /// given minimum and the preferred is the sum of the current preferred and the given
/// preferred. This is used when laying out fragments in the inline direction.
///
/// FIXME(pcwalton): This is incorrect when the inline fragment contains forced line breaks
/// (e.g. `<br>` or `white-space: pre`).
pub fn union_inline(&mut self, sizes: &IntrinsicISizes) {
self.content_intrinsic_sizes.minimum_inline_size =
max(self.content_intrinsic_sizes.minimum_inline_size, sizes.minimum_inline_size);
self.content_intrinsic_sizes.preferred_inline_size =
self.content_intrinsic_sizes.preferred_inline_size + sizes.preferred_inline_size
}
/// Updates the computation so that the minimum is the maximum of the current minimum and the
/// given minimum and the preferred is the maximum of the current preferred and the given
/// preferred. This can be useful when laying out fragments in the block direction (but note
/// that it does not take floats into account, so `BlockFlow` does not use it).
///
/// This is used when contributing the intrinsic sizes for individual fragments.
pub fn union_block(&mut self, sizes: &IntrinsicISizes) {
self.content_intrinsic_sizes.minimum_inline_size =
max(self.content_intrinsic_sizes.minimum_inline_size, sizes.minimum_inline_size);
self.content_intrinsic_sizes.preferred_inline_size =
max(self.content_intrinsic_sizes.preferred_inline_size, sizes.preferred_inline_size)
} }
} }
/// Useful helper data type when computing values for blocks and positioned elements. /// Useful helper data type when computing values for blocks and positioned elements.
#[deriving(PartialEq)]
pub enum MaybeAuto { pub enum MaybeAuto {
Auto, Auto,
Specified(Au), Specified(Au),
@ -290,7 +334,9 @@ impl MaybeAuto {
-> MaybeAuto { -> MaybeAuto {
match length { match length {
computed::LPA_Auto => Auto, computed::LPA_Auto => Auto,
computed::LPA_Percentage(percent) => Specified(containing_length.scale_by(percent)), computed::LPA_Percentage(percent) => {
Specified(containing_length.scale_by(percent))
}
computed::LPA_Length(length) => Specified(length) computed::LPA_Length(length) => Specified(length)
} }
} }

View file

@ -14,6 +14,7 @@ use floats::FloatKind;
use flow::{TableFlowClass, FlowClass, Flow, ImmutableFlowUtils}; use flow::{TableFlowClass, FlowClass, Flow, ImmutableFlowUtils};
use fragment::Fragment; use fragment::Fragment;
use layout_debug; use layout_debug;
use model::{IntrinsicISizes, IntrinsicISizesContribution};
use table_wrapper::{TableLayout, FixedLayout, AutoLayout}; use table_wrapper::{TableLayout, FixedLayout, AutoLayout};
use wrapper::ThreadSafeLayoutNode; use wrapper::ThreadSafeLayoutNode;
@ -21,7 +22,8 @@ use servo_util::geometry::Au;
use servo_util::logical_geometry::LogicalRect; use servo_util::logical_geometry::LogicalRect;
use std::cmp::max; use std::cmp::max;
use std::fmt; use std::fmt;
use style::computed_values::table_layout; use style::computed_values::{LPA_Auto, LPA_Length, LPA_Percentage, table_layout};
use style::CSSFloat;
/// A table flow corresponded to the table's internal table fragment under a table wrapper flow. /// A table flow corresponded to the table's internal table fragment under a table wrapper flow.
/// The properties `position`, `float`, and `margin-*` are used on the table wrapper fragment, /// The properties `position`, `float`, and `margin-*` are used on the table wrapper fragment,
@ -30,14 +32,8 @@ use style::computed_values::table_layout;
pub struct TableFlow { pub struct TableFlow {
pub block_flow: BlockFlow, pub block_flow: BlockFlow,
/// Column inline-sizes /// Information about the inline-sizes of each column.
pub col_inline_sizes: Vec<Au>, pub column_inline_sizes: Vec<ColumnInlineSize>,
/// Column min inline-sizes.
pub col_min_inline_sizes: Vec<Au>,
/// Column pref inline-sizes.
pub col_pref_inline_sizes: Vec<Au>,
/// Table-layout property /// Table-layout property
pub table_layout: TableLayout, pub table_layout: TableLayout,
@ -56,9 +52,7 @@ impl TableFlow {
}; };
TableFlow { TableFlow {
block_flow: block_flow, block_flow: block_flow,
col_inline_sizes: vec!(), column_inline_sizes: Vec::new(),
col_min_inline_sizes: vec!(),
col_pref_inline_sizes: vec!(),
table_layout: table_layout table_layout: table_layout
} }
} }
@ -75,9 +69,7 @@ impl TableFlow {
}; };
TableFlow { TableFlow {
block_flow: block_flow, block_flow: block_flow,
col_inline_sizes: vec!(), column_inline_sizes: Vec::new(),
col_min_inline_sizes: vec!(),
col_pref_inline_sizes: vec!(),
table_layout: table_layout table_layout: table_layout
} }
} }
@ -95,30 +87,33 @@ impl TableFlow {
}; };
TableFlow { TableFlow {
block_flow: block_flow, block_flow: block_flow,
col_inline_sizes: vec!(), column_inline_sizes: Vec::new(),
col_min_inline_sizes: vec!(),
col_pref_inline_sizes: vec!(),
table_layout: table_layout table_layout: table_layout
} }
} }
/// Update the corresponding value of self_inline-sizes if a value of kid_inline-sizes has larger value /// Update the corresponding value of `self_inline_sizes` if a value of `kid_inline_sizes` has
/// than one of self_inline-sizes. /// a larger value than one of `self_inline_sizes`. Returns the minimum and preferred inline
pub fn update_col_inline_sizes(self_inline_sizes: &mut Vec<Au>, kid_inline_sizes: &Vec<Au>) -> Au { /// sizes.
let mut sum_inline_sizes = Au(0); pub fn update_column_inline_sizes(parent_inline_sizes: &mut Vec<ColumnInlineSize>,
let mut kid_inline_sizes_it = kid_inline_sizes.iter(); child_inline_sizes: &Vec<ColumnInlineSize>)
for self_inline_size in self_inline_sizes.iter_mut() { -> IntrinsicISizes {
match kid_inline_sizes_it.next() { let mut total_inline_sizes = IntrinsicISizes::new();
Some(kid_inline_size) => { for (parent_sizes, child_sizes) in parent_inline_sizes.iter_mut()
if *self_inline_size < *kid_inline_size { .zip(child_inline_sizes.iter()) {
*self_inline_size = *kid_inline_size; *parent_sizes = ColumnInlineSize {
} minimum_length: max(parent_sizes.minimum_length, child_sizes.minimum_length),
}, percentage: parent_sizes.greatest_percentage(child_sizes),
None => {} preferred: max(parent_sizes.preferred, child_sizes.preferred),
} constrained: parent_sizes.constrained || child_sizes.constrained
sum_inline_sizes = sum_inline_sizes + *self_inline_size; };
total_inline_sizes.minimum_inline_size = total_inline_sizes.minimum_inline_size +
parent_sizes.minimum_length;
total_inline_sizes.preferred_inline_size = total_inline_sizes.preferred_inline_size +
parent_sizes.preferred;
} }
sum_inline_sizes total_inline_sizes
} }
/// Assign block-size for table flow. /// Assign block-size for table flow.
@ -155,16 +150,8 @@ impl Flow for TableFlow {
&mut self.block_flow &mut self.block_flow
} }
fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> { fn column_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<ColumnInlineSize> {
&mut self.col_inline_sizes &mut self.column_inline_sizes
}
fn col_min_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
&self.col_min_inline_sizes
}
fn col_pref_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
&self.col_pref_inline_sizes
} }
/// The specified column inline-sizes are set from column group and the first row for the fixed /// The specified column inline-sizes are set from column group and the first row for the fixed
@ -175,81 +162,77 @@ impl Flow for TableFlow {
let _scope = layout_debug_scope!("table::bubble_inline_sizes {:s}", let _scope = layout_debug_scope!("table::bubble_inline_sizes {:s}",
self.block_flow.base.debug_id()); self.block_flow.base.debug_id());
let mut min_inline_size = Au(0); let mut computation = IntrinsicISizesContribution::new();
let mut pref_inline_size = Au(0);
let mut did_first_row = false; let mut did_first_row = false;
for kid in self.block_flow.base.child_iter() { for kid in self.block_flow.base.child_iter() {
assert!(kid.is_proper_table_child()); debug_assert!(kid.is_proper_table_child());
if kid.is_table_colgroup() { if kid.is_table_colgroup() {
self.col_inline_sizes.push_all(kid.as_table_colgroup().inline_sizes.as_slice()); for specified_inline_size in kid.as_table_colgroup().inline_sizes.iter() {
self.col_min_inline_sizes = self.col_inline_sizes.clone(); self.column_inline_sizes.push(ColumnInlineSize {
self.col_pref_inline_sizes = self.col_inline_sizes.clone(); minimum_length: match *specified_inline_size {
LPA_Auto | LPA_Percentage(_) => Au(0),
LPA_Length(length) => length,
},
percentage: match *specified_inline_size {
LPA_Auto | LPA_Length(_) => 0.0,
LPA_Percentage(percentage) => percentage,
},
preferred: Au(0),
constrained: false,
})
}
} else if kid.is_table_rowgroup() || kid.is_table_row() { } else if kid.is_table_rowgroup() || kid.is_table_row() {
// read column inline-sizes from table-row-group/table-row, and assign // Read column inline-sizes from the table-row-group/table-row, and assign
// inline-size=0 for the columns not defined in column-group // inline-size=0 for the columns not defined in the column group.
// FIXME: need to read inline-sizes from either table-header-group OR // FIXME: Need to read inline-sizes from either table-header-group OR the first
// first table-row // table-row.
match self.table_layout { match self.table_layout {
FixedLayout => { FixedLayout => {
let kid_col_inline_sizes = kid.col_inline_sizes(); // Fixed table layout only looks at the first row.
if !did_first_row { if !did_first_row {
did_first_row = true; did_first_row = true;
let mut child_inline_sizes = kid_col_inline_sizes.iter(); for child_column_inline_size in kid.column_inline_sizes().iter() {
for col_inline_size in self.col_inline_sizes.iter_mut() { self.column_inline_sizes.push(*child_column_inline_size);
match child_inline_sizes.next() {
Some(child_inline_size) => {
if *col_inline_size == Au::new(0) {
*col_inline_size = *child_inline_size;
}
},
None => break
}
} }
} }
let num_child_cols = kid_col_inline_sizes.len(); }
let num_cols = self.col_inline_sizes.len();
debug!("table until the previous row has {} column(s) and this row has {} column(s)",
num_cols, num_child_cols);
for i in range(num_cols, num_child_cols) {
self.col_inline_sizes.push((*kid_col_inline_sizes)[i]);
}
},
AutoLayout => { AutoLayout => {
min_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_min_inline_sizes, kid.col_min_inline_sizes()); let child_column_inline_sizes = kid.column_inline_sizes();
pref_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_pref_inline_sizes, kid.col_pref_inline_sizes()); let mut child_intrinsic_sizes =
TableFlow::update_column_inline_sizes(&mut self.column_inline_sizes,
child_column_inline_sizes);
// update the number of column inline-sizes from table-rows. // Add new columns if processing this row caused us to discover them.
let num_cols = self.col_min_inline_sizes.len(); let child_column_count = child_column_inline_sizes.len();
let num_child_cols = kid.col_min_inline_sizes().len(); let parent_column_count = self.column_inline_sizes.len();
debug!("table until the previous row has {} column(s) and this row has {} column(s)", debug!("table until the previous row has {} column(s) and this row has {} \
num_cols, num_child_cols); column(s)",
for i in range(num_cols, num_child_cols) { parent_column_count,
self.col_inline_sizes.push(Au::new(0)); child_column_count);
let new_kid_min = kid.col_min_inline_sizes()[i]; self.column_inline_sizes.reserve(child_column_count);
self.col_min_inline_sizes.push( new_kid_min ); for i in range(parent_column_count, child_column_count) {
let new_kid_pref = kid.col_pref_inline_sizes()[i]; let inline_size_for_new_column = (*child_column_inline_sizes)[i];
self.col_pref_inline_sizes.push( new_kid_pref ); child_intrinsic_sizes.minimum_inline_size =
min_inline_size = min_inline_size + new_kid_min; child_intrinsic_sizes.minimum_inline_size +
pref_inline_size = pref_inline_size + new_kid_pref; inline_size_for_new_column.minimum_length;
child_intrinsic_sizes.preferred_inline_size =
child_intrinsic_sizes.preferred_inline_size +
inline_size_for_new_column.preferred;
self.column_inline_sizes.push(inline_size_for_new_column);
} }
computation.union_block(&child_intrinsic_sizes)
} }
} }
} }
} }
let fragment_intrinsic_inline_sizes = self.block_flow.fragment.intrinsic_inline_sizes(); self.block_flow.base.intrinsic_inline_sizes = computation.finish()
self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size;
self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size =
max(min_inline_size, pref_inline_size);
self.block_flow.base.intrinsic_inline_sizes.surround_inline_size =
fragment_intrinsic_inline_sizes.surround_inline_size;
} }
/// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When /// Recursively (top-down) determines the actual inline-size of child contexts and fragments.
/// called on this context, the context has had its inline-size set by the parent context. /// When called on this context, the context has had its inline-size set by the parent context.
fn assign_inline_sizes(&mut self, ctx: &LayoutContext) { fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
let _scope = layout_debug_scope!("table::assign_inline_sizes {:s}", let _scope = layout_debug_scope!("table::assign_inline_sizes {:s}",
self.block_flow.base.debug_id()); self.block_flow.base.debug_id());
debug!("assign_inline_sizes({}): assigning inline_size for flow", "table"); debug!("assign_inline_sizes({}): assigning inline_size for flow", "table");
@ -258,37 +241,50 @@ impl Flow for TableFlow {
let containing_block_inline_size = self.block_flow.base.block_container_inline_size; let containing_block_inline_size = self.block_flow.base.block_container_inline_size;
let mut num_unspecified_inline_sizes = 0; let mut num_unspecified_inline_sizes = 0;
let mut total_column_inline_size = Au::new(0); let mut total_column_inline_size = Au(0);
for col_inline_size in self.col_inline_sizes.iter() { for column_inline_size in self.column_inline_sizes.iter() {
if *col_inline_size == Au::new(0) { let this_column_inline_size = column_inline_size.minimum_length;
num_unspecified_inline_sizes += 1; if this_column_inline_size == Au(0) {
num_unspecified_inline_sizes += 1
} else { } else {
total_column_inline_size = total_column_inline_size.add(col_inline_size); total_column_inline_size = total_column_inline_size + this_column_inline_size
} }
} }
let inline_size_computer = InternalTable; let inline_size_computer = InternalTable;
inline_size_computer.compute_used_inline_size(&mut self.block_flow, ctx, containing_block_inline_size); inline_size_computer.compute_used_inline_size(&mut self.block_flow,
layout_context,
containing_block_inline_size);
let inline_start_content_edge = self.block_flow.fragment.border_padding.inline_start; let inline_start_content_edge = self.block_flow.fragment.border_padding.inline_start;
let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end(); let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end();
let content_inline_size = self.block_flow.fragment.border_box.size.inline - padding_and_borders; let content_inline_size =
self.block_flow.fragment.border_box.size.inline - padding_and_borders;
match self.table_layout { match self.table_layout {
FixedLayout => { FixedLayout => {
// In fixed table layout, we distribute extra space among the unspecified columns if there are // In fixed table layout, we distribute extra space among the unspecified columns
// any, or among all the columns if all are specified. // if there are any, or among all the columns if all are specified.
if (total_column_inline_size < content_inline_size) && (num_unspecified_inline_sizes == 0) { if total_column_inline_size < content_inline_size &&
let ratio = content_inline_size.to_f64().unwrap() / total_column_inline_size.to_f64().unwrap(); num_unspecified_inline_sizes == 0 {
for col_inline_size in self.col_inline_sizes.iter_mut() { let extra_column_inline_size = content_inline_size;
*col_inline_size = (*col_inline_size).scale_by(ratio); (content_inline_size - total_column_inline_size) /
(self.column_inline_sizes.len() as i32);
for column_inline_size in self.column_inline_sizes.iter_mut() {
column_inline_size.minimum_length = column_inline_size.minimum_length +
extra_column_inline_size;
column_inline_size.percentage = 0.0;
} }
} else if num_unspecified_inline_sizes != 0 { } else if num_unspecified_inline_sizes != 0 {
let extra_column_inline_size = (content_inline_size - total_column_inline_size) / num_unspecified_inline_sizes; let extra_column_inline_size =
for col_inline_size in self.col_inline_sizes.iter_mut() { (content_inline_size - total_column_inline_size) /
if *col_inline_size == Au(0) { num_unspecified_inline_sizes;
*col_inline_size = extra_column_inline_size; for column_inline_size in self.column_inline_sizes.iter_mut() {
if column_inline_size.minimum_length == Au(0) &&
column_inline_size.percentage == 0.0 {
column_inline_size.minimum_length = extra_column_inline_size /
num_unspecified_inline_sizes
} }
column_inline_size.percentage = 0.0;
} }
} }
} }
@ -299,7 +295,10 @@ impl Flow for TableFlow {
self.block_flow.base.flags.set_impacted_by_left_floats(false); self.block_flow.base.flags.set_impacted_by_left_floats(false);
self.block_flow.base.flags.set_impacted_by_right_floats(false); self.block_flow.base.flags.set_impacted_by_right_floats(false);
self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, content_inline_size, Some(self.col_inline_sizes.clone())); self.block_flow.propagate_assigned_inline_size_to_children(
inline_start_content_edge,
content_inline_size,
Some(self.column_inline_sizes.as_slice()));
} }
fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) { fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
@ -340,10 +339,12 @@ impl ISizeAndMarginsComputer for InternalTable {
/// ///
/// CSS Section 10.4: Minimum and Maximum inline-sizes /// CSS Section 10.4: Minimum and Maximum inline-sizes
fn compute_used_inline_size(&self, fn compute_used_inline_size(&self,
block: &mut BlockFlow, block: &mut BlockFlow,
ctx: &LayoutContext, ctx: &LayoutContext,
parent_flow_inline_size: Au) { parent_flow_inline_size: Au) {
let input = self.compute_inline_size_constraint_inputs(block, parent_flow_inline_size, ctx); let input = self.compute_inline_size_constraint_inputs(block,
parent_flow_inline_size,
ctx);
let solution = self.solve_inline_size_constraints(block, &input); let solution = self.solve_inline_size_constraints(block, &input);
self.set_inline_size_constraint_solutions(block, solution); self.set_inline_size_constraint_solutions(block, solution);
} }
@ -351,6 +352,48 @@ impl ISizeAndMarginsComputer for InternalTable {
/// Solve the inline-size and margins constraints for this block flow. /// Solve the inline-size and margins constraints for this block flow.
fn solve_inline_size_constraints(&self, _: &mut BlockFlow, input: &ISizeConstraintInput) fn solve_inline_size_constraints(&self, _: &mut BlockFlow, input: &ISizeConstraintInput)
-> ISizeConstraintSolution { -> ISizeConstraintSolution {
ISizeConstraintSolution::new(input.available_inline_size, Au::new(0), Au::new(0)) ISizeConstraintSolution::new(input.available_inline_size, Au(0), Au(0))
} }
} }
/// Information about the inline sizes of columns within a table.
///
/// During table inline-size bubbling, we might need to store both a percentage constraint and a
/// specific width constraint. For instance, one cell might say that it wants to be 100 pixels wide
/// in the inline direction and another cell might say that it wants to take up 20% of the inline-
/// size of the table. Now because we bubble up these constraints during the bubble-inline-sizes
/// phase of layout, we don't know yet how wide the table is ultimately going to be in the inline
/// direction. As we need to pick the maximum width of all cells for a column (in this case, the
/// maximum of 100 pixels and 20% of the table), the preceding constraint means that we must
/// potentially store both a specified width *and* a specified percentage, so that the inline-size
/// assignment phase of layout will know which one to pick.
#[deriving(Clone, Encodable, Show)]
pub struct ColumnInlineSize {
/// The preferred intrinsic inline size.
pub preferred: Au,
/// The largest specified size of this column as a length.
pub minimum_length: Au,
/// The largest specified size of this column as a percentage (`width` property).
pub percentage: CSSFloat,
/// Whether the column inline size is *constrained* per INTRINSIC § 4.1.
pub constrained: bool,
}
impl ColumnInlineSize {
/// Returns the true minimum size of this column, given the containing block's inline size.
/// Beware that this is generally only correct for fixed table layout. (Compare CSS 2.1 §
/// 17.5.2.1 with the algorithm in INTRINSIC § 4.)
pub fn minimum(&self, containing_block_inline_size: Au) -> Au {
max(self.minimum_length, containing_block_inline_size.scale_by(self.percentage))
}
/// Returns the higher of the two percentages specified in `self` and `other`.
pub fn greatest_percentage(&self, other: &ColumnInlineSize) -> CSSFloat {
if self.percentage > other.percentage {
self.percentage
} else {
other.percentage
}
}
}

View file

@ -26,7 +26,8 @@ pub struct TableCellFlow {
} }
impl TableCellFlow { impl TableCellFlow {
pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode, fragment: Fragment) -> TableCellFlow { pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode, fragment: Fragment)
-> TableCellFlow {
TableCellFlow { TableCellFlow {
block_flow: BlockFlow::from_node_and_fragment(node, fragment) block_flow: BlockFlow::from_node_and_fragment(node, fragment)
} }
@ -91,14 +92,15 @@ impl Flow for TableCellFlow {
self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = specified_inline_size self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = specified_inline_size
} }
if self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size < if self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size <
self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size { self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size {
self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size = self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size =
self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size; self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size;
} }
} }
/// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When /// Recursively (top-down) determines the actual inline-size of child contexts and fragments.
/// called on this context, the context has had its inline-size set by the parent table row. /// When called on this context, the context has had its inline-size set by the parent table
/// row.
fn assign_inline_sizes(&mut self, ctx: &LayoutContext) { fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
let _scope = layout_debug_scope!("table_cell::assign_inline_sizes {:s}", let _scope = layout_debug_scope!("table_cell::assign_inline_sizes {:s}",
self.block_flow.base.debug_id()); self.block_flow.base.debug_id());
@ -108,16 +110,20 @@ impl Flow for TableCellFlow {
let containing_block_inline_size = self.block_flow.base.block_container_inline_size; let containing_block_inline_size = self.block_flow.base.block_container_inline_size;
let inline_size_computer = InternalTable; let inline_size_computer = InternalTable;
inline_size_computer.compute_used_inline_size(&mut self.block_flow, ctx, containing_block_inline_size); inline_size_computer.compute_used_inline_size(&mut self.block_flow,
ctx,
containing_block_inline_size);
let inline_start_content_edge = self.block_flow.fragment.border_box.start.i + let inline_start_content_edge =
self.block_flow.fragment.border_box.start.i +
self.block_flow.fragment.border_padding.inline_start; self.block_flow.fragment.border_padding.inline_start;
let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end(); let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end();
let content_inline_size = self.block_flow.fragment.border_box.size.inline - padding_and_borders; let content_inline_size =
self.block_flow.fragment.border_box.size.inline - padding_and_borders;
self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge,
content_inline_size, content_inline_size,
None); None);
} }
fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) { fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {

View file

@ -10,11 +10,11 @@ use context::LayoutContext;
use flow::{BaseFlow, TableColGroupFlowClass, FlowClass, Flow}; use flow::{BaseFlow, TableColGroupFlowClass, FlowClass, Flow};
use fragment::{Fragment, TableColumnFragment}; use fragment::{Fragment, TableColumnFragment};
use layout_debug; use layout_debug;
use model::{MaybeAuto};
use wrapper::ThreadSafeLayoutNode; use wrapper::ThreadSafeLayoutNode;
use servo_util::geometry::Au; use servo_util::geometry::Au;
use std::fmt; use std::fmt;
use style::computed_values::LengthOrPercentageOrAuto;
/// A table formatting context. /// A table formatting context.
pub struct TableColGroupFlow { pub struct TableColGroupFlow {
@ -27,14 +27,17 @@ pub struct TableColGroupFlow {
/// The table column fragments /// The table column fragments
pub cols: Vec<Fragment>, pub cols: Vec<Fragment>,
/// The specified inline-sizes of table columns /// The specified inline-sizes of table columns. (We use `LengthOrPercentageOrAuto` here in
pub inline_sizes: Vec<Au>, /// lieu of `ColumnInlineSize` because column groups do not establish minimum or preferred
/// inline sizes.)
pub inline_sizes: Vec<LengthOrPercentageOrAuto>,
} }
impl TableColGroupFlow { impl TableColGroupFlow {
pub fn from_node_and_fragments(node: &ThreadSafeLayoutNode, pub fn from_node_and_fragments(node: &ThreadSafeLayoutNode,
fragment: Fragment, fragment: Fragment,
fragments: Vec<Fragment>) -> TableColGroupFlow { fragments: Vec<Fragment>)
-> TableColGroupFlow {
TableColGroupFlow { TableColGroupFlow {
base: BaseFlow::new((*node).clone()), base: BaseFlow::new((*node).clone()),
fragment: Some(fragment), fragment: Some(fragment),
@ -58,27 +61,25 @@ impl Flow for TableColGroupFlow {
self.base.debug_id()); self.base.debug_id());
for fragment in self.cols.iter() { for fragment in self.cols.iter() {
// get the specified value from inline-size property // Retrieve the specified value from the appropriate CSS property.
let inline_size = MaybeAuto::from_style(fragment.style().content_inline_size(), let inline_size = fragment.style().content_inline_size();
Au::new(0)).specified_or_zero();
let span: int = match fragment.specific { let span: int = match fragment.specific {
TableColumnFragment(col_fragment) => col_fragment.span.unwrap_or(1), TableColumnFragment(col_fragment) => col_fragment.span.unwrap_or(1),
_ => fail!("Other fragment come out in TableColGroupFlow. {:?}", fragment.specific) _ => fail!("non-table-column fragment inside table column?!"),
}; };
for _ in range(0, span) { for _ in range(0, span) {
self.inline_sizes.push(inline_size); self.inline_sizes.push(inline_size)
} }
} }
} }
/// Table column inline-sizes are assigned in table flow and propagated to table row or rowgroup flow. /// Table column inline-sizes are assigned in the table flow and propagated to table row flows
/// Therefore, table colgroup flow does not need to assign its inline-size. /// and/or rowgroup flows. Therefore, table colgroup flows do not need to assign inline-sizes.
fn assign_inline_sizes(&mut self, _ctx: &LayoutContext) { fn assign_inline_sizes(&mut self, _: &LayoutContext) {
} }
/// Table column do not have block-size. /// Table columns do not have block-size.
fn assign_block_size(&mut self, _ctx: &LayoutContext) { fn assign_block_size(&mut self, _: &LayoutContext) {
} }
fn update_late_computed_inline_position_if_necessary(&mut self, _: Au) {} fn update_late_computed_inline_position_if_necessary(&mut self, _: Au) {}

View file

@ -14,27 +14,22 @@ use flow::{TableRowFlowClass, FlowClass, Flow, ImmutableFlowUtils};
use flow; use flow;
use fragment::Fragment; use fragment::Fragment;
use layout_debug; use layout_debug;
use table::InternalTable; use table::{ColumnInlineSize, InternalTable};
use model::{MaybeAuto, Specified, Auto}; use model::{MaybeAuto, Specified, Auto};
use wrapper::ThreadSafeLayoutNode; use wrapper::ThreadSafeLayoutNode;
use servo_util::geometry::Au; use servo_util::geometry::Au;
use std::cmp::max; use std::cmp::max;
use std::fmt; use std::fmt;
use style::computed_values::{LPA_Auto, LPA_Length, LPA_Percentage};
/// A table formatting context. /// A single row of a table.
#[deriving(Encodable)] #[deriving(Encodable)]
pub struct TableRowFlow { pub struct TableRowFlow {
pub block_flow: BlockFlow, pub block_flow: BlockFlow,
/// Column inline-sizes. /// Information about the inline-sizes of each column.
pub col_inline_sizes: Vec<Au>, pub column_inline_sizes: Vec<ColumnInlineSize>,
/// Column min inline-sizes.
pub col_min_inline_sizes: Vec<Au>,
/// Column pref inline-sizes.
pub col_pref_inline_sizes: Vec<Au>,
} }
impl TableRowFlow { impl TableRowFlow {
@ -43,9 +38,7 @@ impl TableRowFlow {
-> TableRowFlow { -> TableRowFlow {
TableRowFlow { TableRowFlow {
block_flow: BlockFlow::from_node_and_fragment(node, fragment), block_flow: BlockFlow::from_node_and_fragment(node, fragment),
col_inline_sizes: vec!(), column_inline_sizes: Vec::new()
col_min_inline_sizes: vec!(),
col_pref_inline_sizes: vec!(),
} }
} }
@ -54,9 +47,7 @@ impl TableRowFlow {
-> TableRowFlow { -> TableRowFlow {
TableRowFlow { TableRowFlow {
block_flow: BlockFlow::from_node(constructor, node), block_flow: BlockFlow::from_node(constructor, node),
col_inline_sizes: vec!(), column_inline_sizes: Vec::new()
col_min_inline_sizes: vec!(),
col_pref_inline_sizes: vec!(),
} }
} }
@ -65,8 +56,8 @@ impl TableRowFlow {
} }
fn initialize_offsets(&mut self) -> (Au, Au, Au) { fn initialize_offsets(&mut self) -> (Au, Au, Au) {
// TODO: If border-collapse: collapse, block-start_offset, block-end_offset, and inline-start_offset // TODO: If border-collapse: collapse, block_start_offset, block_end_offset, and
// should be updated. Currently, they are set as Au(0). // inline_start_offset should be updated. Currently, they are set as Au(0).
(Au(0), Au(0), Au(0)) (Au(0), Au(0), Au(0))
} }
@ -82,19 +73,21 @@ impl TableRowFlow {
let /* mut */ cur_y = block_start_offset; let /* mut */ cur_y = block_start_offset;
// Per CSS 2.1 § 17.5.3, find max_y = max( computed `block-size`, minimum block-size of all cells ) // Per CSS 2.1 § 17.5.3, find max_y = max(computed `block-size`, minimum block-size of all
let mut max_y = Au::new(0); // cells).
let mut max_y = Au(0);
for kid in self.block_flow.base.child_iter() { for kid in self.block_flow.base.child_iter() {
kid.assign_block_size_for_inorder_child_if_necessary(layout_context); kid.assign_block_size_for_inorder_child_if_necessary(layout_context);
{ {
let child_fragment = kid.as_table_cell().fragment(); let child_fragment = kid.as_table_cell().fragment();
// TODO: Percentage block-size // TODO: Percentage block-size
let child_specified_block_size = MaybeAuto::from_style(child_fragment.style().content_block_size(), let child_specified_block_size =
Au::new(0)).specified_or_zero(); MaybeAuto::from_style(child_fragment.style().content_block_size(),
max_y = Au::new(0)).specified_or_zero();
max(max_y, max_y = max(max_y,
child_specified_block_size + child_fragment.border_padding.block_start_end()); child_specified_block_size +
child_fragment.border_padding.block_start_end());
} }
let child_node = flow::mut_base(kid); let child_node = flow::mut_base(kid);
child_node.position.start.b = cur_y; child_node.position.start.b = cur_y;
@ -103,7 +96,11 @@ impl TableRowFlow {
let mut block_size = max_y; let mut block_size = max_y;
// TODO: Percentage block-size // TODO: Percentage block-size
block_size = match MaybeAuto::from_style(self.block_flow.fragment.style().content_block_size(), Au(0)) { block_size = match MaybeAuto::from_style(self.block_flow
.fragment
.style()
.content_block_size(),
Au(0)) {
Auto => block_size, Auto => block_size,
Specified(value) => max(value, block_size) Specified(value) => max(value, block_size)
}; };
@ -153,70 +150,84 @@ impl Flow for TableRowFlow {
&mut self.block_flow &mut self.block_flow
} }
fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> { fn column_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<ColumnInlineSize> {
&mut self.col_inline_sizes &mut self.column_inline_sizes
} }
fn col_min_inline_sizes<'a>(&'a self) -> &'a Vec<Au> { /// Recursively (bottom-up) determines the context's preferred and minimum inline-sizes. When
&self.col_min_inline_sizes /// called on this context, all child contexts have had their min/pref inline-sizes set. This
} /// function must decide min/pref inline-sizes based on child context inline-sizes and
/// dimensions of any fragments it is responsible for flowing.
fn col_pref_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
&self.col_pref_inline_sizes
}
/// Recursively (bottom-up) determines the context's preferred and minimum inline-sizes. When called
/// on this context, all child contexts have had their min/pref inline-sizes set. This function must
/// decide min/pref inline-sizes based on child context inline-sizes and dimensions of any fragments it is
/// responsible for flowing.
/// Min/pref inline-sizes set by this function are used in automatic table layout calculation. /// Min/pref inline-sizes set by this function are used in automatic table layout calculation.
/// The specified column inline-sizes of children cells are used in fixed table layout calculation. /// The specified column inline-sizes of children cells are used in fixed table layout
/// calculation.
fn bubble_inline_sizes(&mut self) { fn bubble_inline_sizes(&mut self) {
let _scope = layout_debug_scope!("table_row::bubble_inline_sizes {:s}", let _scope = layout_debug_scope!("table_row::bubble_inline_sizes {:s}",
self.block_flow.base.debug_id()); self.block_flow.base.debug_id());
let mut min_inline_size = Au(0); // Bubble up the specified inline-sizes from child table cells.
let mut pref_inline_size = Au(0); let (mut min_inline_size, mut pref_inline_size) = (Au(0), Au(0));
/* find the specified inline_sizes from child table-cell contexts */
for kid in self.block_flow.base.child_iter() { for kid in self.block_flow.base.child_iter() {
assert!(kid.is_table_cell()); assert!(kid.is_table_cell());
// collect the specified column inline-sizes of cells. These are used in fixed table layout calculation. // Collect the specified column inline-size of the cell. This is used in both fixed and
{ // automatic table layout calculation.
let child_fragment = kid.as_table_cell().fragment(); let child_specified_inline_size = kid.as_table_cell()
let child_specified_inline_size = MaybeAuto::from_style(child_fragment.style().content_inline_size(), .fragment()
Au::new(0)).specified_or_zero(); .style()
self.col_inline_sizes.push(child_specified_inline_size); .content_inline_size();
}
// collect min_inline-size & pref_inline-size of children cells for automatic table layout calculation. // Collect minimum and preferred inline-sizes of the cell for automatic table layout
// calculation.
let child_base = flow::mut_base(kid); let child_base = flow::mut_base(kid);
self.col_min_inline_sizes.push(child_base.intrinsic_inline_sizes.minimum_inline_size); let child_column_inline_size = ColumnInlineSize {
self.col_pref_inline_sizes.push(child_base.intrinsic_inline_sizes.preferred_inline_size); minimum_length: match child_specified_inline_size {
min_inline_size = min_inline_size + child_base.intrinsic_inline_sizes.minimum_inline_size; LPA_Auto | LPA_Percentage(_) => {
pref_inline_size = pref_inline_size + child_base.intrinsic_inline_sizes.preferred_inline_size; child_base.intrinsic_inline_sizes.minimum_inline_size
}
LPA_Length(length) => length,
},
percentage: match child_specified_inline_size {
LPA_Auto | LPA_Length(_) => 0.0,
LPA_Percentage(percentage) => percentage,
},
preferred: child_base.intrinsic_inline_sizes.preferred_inline_size,
constrained: match child_specified_inline_size {
LPA_Length(_) => true,
LPA_Auto | LPA_Percentage(_) => false,
},
};
min_inline_size = min_inline_size + child_column_inline_size.minimum_length;
pref_inline_size = pref_inline_size + child_column_inline_size.preferred;
self.column_inline_sizes.push(child_column_inline_size);
} }
self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size; self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size;
self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size = max( self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size = max(min_inline_size,
min_inline_size, pref_inline_size); pref_inline_size);
} }
/// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When called /// Recursively (top-down) determines the actual inline-size of child contexts and fragments.
/// on this context, the context has had its inline-size set by the parent context. /// When called on this context, the context has had its inline-size set by the parent context.
fn assign_inline_sizes(&mut self, ctx: &LayoutContext) { fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
let _scope = layout_debug_scope!("table_row::assign_inline_sizes {:s}", let _scope = layout_debug_scope!("table_row::assign_inline_sizes {:s}",
self.block_flow.base.debug_id()); self.block_flow.base.debug_id());
debug!("assign_inline_sizes({}): assigning inline_size for flow", "table_row"); debug!("assign_inline_sizes({}): assigning inline_size for flow", "table_row");
// The position was set to the containing block by the flow's parent. // The position was set to the containing block by the flow's parent.
let containing_block_inline_size = self.block_flow.base.block_container_inline_size; let containing_block_inline_size = self.block_flow.base.block_container_inline_size;
// FIXME: In case of border-collapse: collapse, inline-start_content_edge should be border-inline-start // FIXME: In case of border-collapse: collapse, inline_start_content_edge should be
let inline_start_content_edge = Au::new(0); // border_inline_start.
let inline_start_content_edge = Au(0);
let inline_size_computer = InternalTable; let inline_size_computer = InternalTable;
inline_size_computer.compute_used_inline_size(&mut self.block_flow, ctx, containing_block_inline_size); inline_size_computer.compute_used_inline_size(&mut self.block_flow,
ctx,
containing_block_inline_size);
self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, Au(0), Some(self.col_inline_sizes.clone())); self.block_flow
.propagate_assigned_inline_size_to_children(inline_start_content_edge,
containing_block_inline_size,
Some(self.column_inline_sizes.as_slice()));
} }
fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) { fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
@ -242,3 +253,4 @@ impl fmt::Show for TableRowFlow {
write!(f, "TableRowFlow: {}", self.block_flow.fragment) write!(f, "TableRowFlow: {}", self.block_flow.fragment)
} }
} }

View file

@ -14,11 +14,11 @@ use flow::{TableRowGroupFlowClass, FlowClass, Flow, ImmutableFlowUtils};
use flow; use flow;
use fragment::Fragment; use fragment::Fragment;
use layout_debug; use layout_debug;
use table::{InternalTable, TableFlow}; use model::IntrinsicISizesContribution;
use table::{ColumnInlineSize, InternalTable, TableFlow};
use wrapper::ThreadSafeLayoutNode; use wrapper::ThreadSafeLayoutNode;
use servo_util::geometry::Au; use servo_util::geometry::Au;
use std::cmp::max;
use std::fmt; use std::fmt;
/// A table formatting context. /// A table formatting context.
@ -26,14 +26,8 @@ use std::fmt;
pub struct TableRowGroupFlow { pub struct TableRowGroupFlow {
pub block_flow: BlockFlow, pub block_flow: BlockFlow,
/// Column inline-sizes /// Information about the inline-sizes of each column.
pub col_inline_sizes: Vec<Au>, pub column_inline_sizes: Vec<ColumnInlineSize>,
/// Column min inline-sizes.
pub col_min_inline_sizes: Vec<Au>,
/// Column pref inline-sizes.
pub col_pref_inline_sizes: Vec<Au>,
} }
impl TableRowGroupFlow { impl TableRowGroupFlow {
@ -42,9 +36,7 @@ impl TableRowGroupFlow {
-> TableRowGroupFlow { -> TableRowGroupFlow {
TableRowGroupFlow { TableRowGroupFlow {
block_flow: BlockFlow::from_node_and_fragment(node, fragment), block_flow: BlockFlow::from_node_and_fragment(node, fragment),
col_inline_sizes: vec!(), column_inline_sizes: Vec::new(),
col_min_inline_sizes: vec!(),
col_pref_inline_sizes: vec!(),
} }
} }
@ -53,9 +45,7 @@ impl TableRowGroupFlow {
-> TableRowGroupFlow { -> TableRowGroupFlow {
TableRowGroupFlow { TableRowGroupFlow {
block_flow: BlockFlow::from_node(constructor, node), block_flow: BlockFlow::from_node(constructor, node),
col_inline_sizes: vec!(), column_inline_sizes: Vec::new(),
col_min_inline_sizes: vec!(),
col_pref_inline_sizes: vec!(),
} }
} }
@ -64,8 +54,8 @@ impl TableRowGroupFlow {
} }
fn initialize_offsets(&mut self) -> (Au, Au, Au) { fn initialize_offsets(&mut self) -> (Au, Au, Au) {
// TODO: If border-collapse: collapse, block-start_offset, block-end_offset, and inline-start_offset // TODO: If border-collapse: collapse, block-start_offset, block-end_offset, and
// should be updated. Currently, they are set as Au(0). // inline-start_offset should be updated. Currently, they are set as Au(0).
(Au(0), Au(0), Au(0)) (Au(0), Au(0), Au(0))
} }
@ -120,16 +110,8 @@ impl Flow for TableRowGroupFlow {
&mut self.block_flow &mut self.block_flow
} }
fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> { fn column_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<ColumnInlineSize> {
&mut self.col_inline_sizes &mut self.column_inline_sizes
}
fn col_min_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
&self.col_min_inline_sizes
}
fn col_pref_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
&self.col_pref_inline_sizes
} }
/// Recursively (bottom-up) determines the context's preferred and minimum inline-sizes. When /// Recursively (bottom-up) determines the context's preferred and minimum inline-sizes. When
@ -143,49 +125,47 @@ impl Flow for TableRowGroupFlow {
/// used in fixed table layout calculation. /// used in fixed table layout calculation.
fn bubble_inline_sizes(&mut self) { fn bubble_inline_sizes(&mut self) {
let _scope = layout_debug_scope!("table_rowgroup::bubble_inline_sizes {:s}", let _scope = layout_debug_scope!("table_rowgroup::bubble_inline_sizes {:s}",
self.block_flow.base.debug_id()); self.block_flow.base.debug_id());
let mut min_inline_size = Au(0);
let mut pref_inline_size = Au(0);
let mut computation = IntrinsicISizesContribution::new();
for kid in self.block_flow.base.child_iter() { for kid in self.block_flow.base.child_iter() {
assert!(kid.is_table_row()); assert!(kid.is_table_row());
// calculate min_inline-size & pref_inline-size for automatic table layout calculation // Calculate minimum and preferred inline sizes for automatic table layout.
// 'self.col_min_inline-sizes' collects the maximum value of cells' min-inline-sizes for each column. if self.column_inline_sizes.is_empty() {
// 'self.col_pref_inline-sizes' collects the maximum value of cells' pref-inline-sizes for each column. // We're the first row.
if self.col_inline_sizes.is_empty() { // First Row debug_assert!(self.column_inline_sizes.is_empty());
assert!(self.col_min_inline_sizes.is_empty() && self.col_pref_inline_sizes.is_empty()); self.column_inline_sizes = kid.column_inline_sizes().clone();
// 'self.col_inline-sizes' collects the specified column inline-sizes from the first table-row for fixed table layout calculation.
self.col_inline_sizes = kid.col_inline_sizes().clone();
self.col_min_inline_sizes = kid.col_min_inline_sizes().clone();
self.col_pref_inline_sizes = kid.col_pref_inline_sizes().clone();
} else { } else {
min_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_min_inline_sizes, kid.col_min_inline_sizes()); let mut child_intrinsic_sizes =
pref_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_pref_inline_sizes, kid.col_pref_inline_sizes()); TableFlow::update_column_inline_sizes(&mut self.column_inline_sizes,
kid.column_inline_sizes());
// update the number of column inline-sizes from table-rows. // update the number of column inline-sizes from table-rows.
let num_cols = self.col_inline_sizes.len(); let column_count = self.column_inline_sizes.len();
let num_child_cols = kid.col_min_inline_sizes().len(); let child_column_count = kid.column_inline_sizes().len();
for i in range(num_cols, num_child_cols) { for i in range(column_count, child_column_count) {
self.col_inline_sizes.push(Au::new(0)); let this_column_inline_size = (*kid.column_inline_sizes())[i];
let new_kid_min = kid.col_min_inline_sizes()[i];
self.col_min_inline_sizes.push(kid.col_min_inline_sizes()[i]); // FIXME(pcwalton): Ignoring the percentage here seems dubious.
let new_kid_pref = kid.col_pref_inline_sizes()[i]; child_intrinsic_sizes.minimum_inline_size =
self.col_pref_inline_sizes.push(kid.col_pref_inline_sizes()[i]); child_intrinsic_sizes.minimum_inline_size +
min_inline_size = min_inline_size + new_kid_min; this_column_inline_size.minimum_length;
pref_inline_size = pref_inline_size + new_kid_pref; child_intrinsic_sizes.preferred_inline_size =
child_intrinsic_sizes.preferred_inline_size +
this_column_inline_size.preferred;
self.column_inline_sizes.push(this_column_inline_size);
} }
computation.union_block(&child_intrinsic_sizes)
} }
} }
self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size; self.block_flow.base.intrinsic_inline_sizes = computation.finish()
self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size = max(
min_inline_size, pref_inline_size);
} }
/// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When /// Recursively (top-down) determines the actual inline-size of child contexts and fragments.
/// called on this context, the context has had its inline-size set by the parent context. /// When called on this context, the context has had its inline-size set by the parent context.
fn assign_inline_sizes(&mut self, ctx: &LayoutContext) { fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
let _scope = layout_debug_scope!("table_rowgroup::assign_inline_sizes {:s}", let _scope = layout_debug_scope!("table_rowgroup::assign_inline_sizes {:s}",
self.block_flow.base.debug_id()); self.block_flow.base.debug_id());
@ -199,9 +179,14 @@ impl Flow for TableRowGroupFlow {
let content_inline_size = containing_block_inline_size; let content_inline_size = containing_block_inline_size;
let inline_size_computer = InternalTable; let inline_size_computer = InternalTable;
inline_size_computer.compute_used_inline_size(&mut self.block_flow, ctx, containing_block_inline_size); inline_size_computer.compute_used_inline_size(&mut self.block_flow,
ctx,
containing_block_inline_size);
self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, content_inline_size, Some(self.col_inline_sizes.clone())); self.block_flow.propagate_assigned_inline_size_to_children(
inline_start_content_edge,
content_inline_size,
Some(self.column_inline_sizes.as_slice()));
} }
fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) { fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {

View file

@ -3,22 +3,30 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! CSS tables. //! CSS tables.
//!
//! This follows the "More Precise Definitions of Inline Layout and Table Layout" proposal written
//! by L. David Baron (Mozilla) here:
//!
//! http://dbaron.org/css/intrinsic/
//!
//! Hereafter this document is referred to as INTRINSIC.
#![deny(unsafe_block)] #![deny(unsafe_block)]
use block::{BlockFlow, BlockNonReplaced, FloatNonReplaced, ISizeAndMarginsComputer}; use block::{BlockFlow, BlockNonReplaced, FloatNonReplaced, ISizeAndMarginsComputer};
use block::{ISizeConstraintInput, MarginsMayNotCollapse}; use block::{MarginsMayNotCollapse};
use construct::FlowConstructor; use construct::FlowConstructor;
use context::LayoutContext; use context::LayoutContext;
use floats::FloatKind; use floats::FloatKind;
use flow::{TableWrapperFlowClass, FlowClass, Flow, ImmutableFlowUtils}; use flow::{TableWrapperFlowClass, FlowClass, Flow, ImmutableFlowUtils};
use fragment::Fragment; use fragment::Fragment;
use model::{Specified, Auto, specified}; use table::ColumnInlineSize;
use wrapper::ThreadSafeLayoutNode; use wrapper::ThreadSafeLayoutNode;
use servo_util::geometry::Au; use servo_util::geometry::Au;
use std::cmp::max; use std::cmp::{max, min};
use std::fmt; use std::fmt;
use style::CSSFloat;
use style::computed_values::{clear, float, table_layout}; use style::computed_values::{clear, float, table_layout};
#[deriving(Encodable)] #[deriving(Encodable)]
@ -32,8 +40,8 @@ pub enum TableLayout {
pub struct TableWrapperFlow { pub struct TableWrapperFlow {
pub block_flow: BlockFlow, pub block_flow: BlockFlow,
/// Column inline-sizes /// Inline-size information for each column.
pub col_inline_sizes: Vec<Au>, pub column_inline_sizes: Vec<ColumnInlineSize>,
/// Table-layout property /// Table-layout property
pub table_layout: TableLayout, pub table_layout: TableLayout,
@ -52,7 +60,7 @@ impl TableWrapperFlow {
}; };
TableWrapperFlow { TableWrapperFlow {
block_flow: block_flow, block_flow: block_flow,
col_inline_sizes: vec!(), column_inline_sizes: vec!(),
table_layout: table_layout table_layout: table_layout
} }
} }
@ -69,7 +77,7 @@ impl TableWrapperFlow {
}; };
TableWrapperFlow { TableWrapperFlow {
block_flow: block_flow, block_flow: block_flow,
col_inline_sizes: vec!(), column_inline_sizes: vec!(),
table_layout: table_layout table_layout: table_layout
} }
} }
@ -87,7 +95,7 @@ impl TableWrapperFlow {
}; };
TableWrapperFlow { TableWrapperFlow {
block_flow: block_flow, block_flow: block_flow,
col_inline_sizes: vec!(), column_inline_sizes: vec!(),
table_layout: table_layout table_layout: table_layout
} }
} }
@ -97,113 +105,87 @@ impl TableWrapperFlow {
self.block_flow.build_display_list_block(layout_context); self.block_flow.build_display_list_block(layout_context);
} }
fn calculate_table_column_sizes(&mut self, mut input: ISizeConstraintInput) /// Calculates table column sizes for automatic layout per INTRINSIC § 4.3.
-> ISizeConstraintInput { fn calculate_table_column_sizes_for_automatic_layout(&mut self) {
let style = self.block_flow.fragment.style(); // Find the padding and border of our first child, which is the table itself.
//
// Get inline-start and inline-end paddings, borders for table. // This is a little weird because we're computing border/padding/margins for our child,
// We get these values from the fragment's style since table_wrapper doesn't have its own // when normally the child computes it itself. But it has to be this way because the
// border or padding. input.available_inline_size is same as containing_block_inline_size // padding will affect where we place the child. This is an odd artifact of the way that
// in table_wrapper. // tables are separated into table flows and table wrapper flows.
let padding = style.logical_padding(); let available_inline_size = self.block_flow.fragment.border_box.size.inline;
let border = style.logical_border_width(); let mut table_border_padding = Au(0);
let padding_and_borders = for kid in self.block_flow.base.child_iter() {
specified(padding.inline_start, input.available_inline_size) + if kid.is_table() {
specified(padding.inline_end, input.available_inline_size) + let kid_block = kid.as_block();
border.inline_start + kid_block.fragment.compute_border_and_padding(available_inline_size);
border.inline_end; kid_block.fragment.compute_block_direction_margins(available_inline_size);
kid_block.fragment.compute_inline_direction_margins(available_inline_size);
let computed_inline_size = match self.table_layout { table_border_padding = kid_block.fragment.border_padding.inline_start_end();
FixedLayout => { break
let fixed_cells_inline_size = self.col_inline_sizes
.iter()
.fold(Au(0), |sum, inline_size| {
sum.add(inline_size)
});
let mut computed_inline_size = input.computed_inline_size.specified_or_zero();
// Compare border-edge inline-sizes. Because fixed_cells_inline_size indicates
// content-inline-size, padding and border values are added to
// fixed_cells_inline_size.
computed_inline_size = max(
fixed_cells_inline_size + padding_and_borders, computed_inline_size);
computed_inline_size
},
AutoLayout => {
// Automatic table layout is calculated according to CSS 2.1 § 17.5.2.2.
let mut cap_min = Au(0);
let mut cols_min = Au(0);
let mut cols_max = Au(0);
let mut col_min_inline_sizes = &vec!();
let mut col_pref_inline_sizes = &vec!();
for kid in self.block_flow.base.child_iter() {
if kid.is_table_caption() {
cap_min = kid.as_block().base.intrinsic_inline_sizes.minimum_inline_size;
} else {
assert!(kid.is_table());
cols_min = kid.as_block().base.intrinsic_inline_sizes.minimum_inline_size;
cols_max = kid.as_block()
.base
.intrinsic_inline_sizes
.preferred_inline_size;
col_min_inline_sizes = kid.col_min_inline_sizes();
col_pref_inline_sizes = kid.col_pref_inline_sizes();
}
}
// 'extra_inline-size': difference between the calculated table inline-size and
// minimum inline-size required by all columns. It will be distributed over the
// columns.
let (inline_size, extra_inline_size) = match input.computed_inline_size {
Auto => {
if input.available_inline_size > max(cols_max, cap_min) {
if cols_max > cap_min {
self.col_inline_sizes = col_pref_inline_sizes.clone();
(cols_max, Au(0))
} else {
(cap_min, cap_min - cols_min)
}
} else {
let max = if cols_min >= input.available_inline_size &&
cols_min >= cap_min {
self.col_inline_sizes = col_min_inline_sizes.clone();
cols_min
} else {
max(input.available_inline_size, cap_min)
};
(max, max - cols_min)
}
},
Specified(inline_size) => {
let max = if cols_min >= inline_size && cols_min >= cap_min {
self.col_inline_sizes = col_min_inline_sizes.clone();
cols_min
} else {
max(inline_size, cap_min)
};
(max, max - cols_min)
}
};
// The extra inline-size is distributed over the columns
if extra_inline_size > Au(0) {
let cell_len = self.col_inline_sizes.len() as f64;
self.col_inline_sizes = col_min_inline_sizes.iter()
.map(|inline_size| {
inline_size + extra_inline_size.scale_by(1.0 / cell_len)
}).collect();
}
inline_size + padding_and_borders
} }
}; }
input.computed_inline_size = Specified(computed_inline_size);
input // FIXME(pcwalton, spec): INTRINSIC § 8 does not properly define how to compute this, but
// says "the basic idea is the same as the shrink-to-fit width that CSS2.1 defines". So we
// just use the shrink-to-fit inline size.
let available_inline_size =
self.block_flow.get_shrink_to_fit_inline_size(available_inline_size);
// Compute all the guesses for the column sizes, and sum them.
let mut total_guess = AutoLayoutCandidateGuess::new();
let guesses: Vec<AutoLayoutCandidateGuess> =
self.column_inline_sizes.iter().map(|column_inline_size| {
let guess = AutoLayoutCandidateGuess::from_column_inline_size(
column_inline_size,
available_inline_size);
total_guess = total_guess + guess;
guess
}).collect();
// Assign inline sizes.
let selection = SelectedAutoLayoutCandidateGuess::select(&total_guess,
available_inline_size);
let mut total_used_inline_size = Au(0);
for (column_inline_size, guess) in self.column_inline_sizes
.iter_mut()
.zip(guesses.iter()) {
column_inline_size.minimum_length = guess.calculate(selection);
column_inline_size.percentage = 0.0;
total_used_inline_size = total_used_inline_size + column_inline_size.minimum_length
}
// Distribute excess inline-size if necessary per INTRINSIC § 4.4.
//
// FIXME(pcwalton, spec): How do I deal with fractional excess?
let excess_inline_size = available_inline_size - total_used_inline_size;
if excess_inline_size > Au(0) &&
selection == UsePreferredGuessAndDistributeExcessInlineSize {
let mut info = ExcessInlineSizeDistributionInfo::new();
for column_inline_size in self.column_inline_sizes.iter() {
info.update(column_inline_size)
}
let mut total_distributed_excess_size = Au(0);
for column_inline_size in self.column_inline_sizes.iter_mut() {
info.distribute_excess_inline_size_to_column(column_inline_size,
excess_inline_size,
&mut total_distributed_excess_size)
}
total_used_inline_size = available_inline_size
}
self.block_flow.fragment.border_box.size.inline = total_used_inline_size +
table_border_padding;
self.block_flow.base.position.size.inline = total_used_inline_size +
table_border_padding + self.block_flow.fragment.margin.inline_start_end();
} }
fn compute_used_inline_size(&mut self, fn compute_used_inline_size(&mut self,
layout_context: &LayoutContext, layout_context: &LayoutContext,
parent_flow_inline_size: Au) { parent_flow_inline_size: Au) {
// Delegate to the appropriate inline size computer to find the constraint inputs. // Delegate to the appropriate inline size computer to find the constraint inputs.
let mut input = if self.is_float() { let input = if self.is_float() {
FloatNonReplaced.compute_inline_size_constraint_inputs(&mut self.block_flow, FloatNonReplaced.compute_inline_size_constraint_inputs(&mut self.block_flow,
parent_flow_inline_size, parent_flow_inline_size,
layout_context) layout_context)
@ -213,9 +195,6 @@ impl TableWrapperFlow {
layout_context) layout_context)
}; };
// Compute the inline sizes of the columns.
input = self.calculate_table_column_sizes(input);
// Delegate to the appropriate inline size computer to write the constraint solutions in. // Delegate to the appropriate inline size computer to write the constraint solutions in.
if self.is_float() { if self.is_float() {
let solution = FloatNonReplaced.solve_inline_size_constraints(&mut self.block_flow, let solution = FloatNonReplaced.solve_inline_size_constraints(&mut self.block_flow,
@ -261,12 +240,11 @@ impl Flow for TableWrapperFlow {
} }
fn bubble_inline_sizes(&mut self) { fn bubble_inline_sizes(&mut self) {
// get column inline-sizes info from table flow // Get the column inline-sizes info from the table flow.
for kid in self.block_flow.base.child_iter() { for kid in self.block_flow.base.child_iter() {
assert!(kid.is_table_caption() || kid.is_table()); debug_assert!(kid.is_table_caption() || kid.is_table());
if kid.is_table() { if kid.is_table() {
self.col_inline_sizes.push_all(kid.as_table().col_inline_sizes.as_slice()); self.column_inline_sizes = kid.column_inline_sizes().clone()
} }
} }
@ -296,18 +274,25 @@ impl Flow for TableWrapperFlow {
self.compute_used_inline_size(layout_context, containing_block_inline_size); self.compute_used_inline_size(layout_context, containing_block_inline_size);
match self.table_layout {
FixedLayout => {}
AutoLayout => {
self.calculate_table_column_sizes_for_automatic_layout()
}
}
let inline_start_content_edge = self.block_flow.fragment.border_box.start.i; let inline_start_content_edge = self.block_flow.fragment.border_box.start.i;
let content_inline_size = self.block_flow.fragment.border_box.size.inline; let content_inline_size = self.block_flow.fragment.border_box.size.inline;
// In case of fixed layout, column inline-sizes are calculated in table flow. // In case of fixed layout, column inline-sizes are calculated in table flow.
let assigned_col_inline_sizes = match self.table_layout { let assigned_column_inline_sizes = match self.table_layout {
FixedLayout => None, FixedLayout => None,
AutoLayout => Some(self.col_inline_sizes.clone()) AutoLayout => Some(self.column_inline_sizes.as_slice())
}; };
self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge,
content_inline_size, content_inline_size,
assigned_col_inline_sizes); assigned_column_inline_sizes);
} }
fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) { fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
@ -353,3 +338,230 @@ impl fmt::Show for TableWrapperFlow {
} }
} }
/// The layout "guesses" defined in INTRINSIC § 4.3.
struct AutoLayoutCandidateGuess {
/// The column inline-size assignment where each column is assigned its intrinsic minimum
/// inline-size.
minimum_guess: Au,
/// The column inline-size assignment where:
/// * A column with an intrinsic percentage inline-size greater than 0% is assigned the
/// larger of:
/// - Its intrinsic percentage inline-size times the assignable inline-size;
/// - Its intrinsic minimum inline-size;
/// * Other columns receive their intrinsic minimum inline-size.
minimum_percentage_guess: Au,
/// The column inline-size assignment where:
/// * Each column with an intrinsic percentage inline-size greater than 0% is assigned the
/// larger of:
/// - Its intrinsic percentage inline-size times the assignable inline-size;
/// - Its intrinsic minimum inline-size;
/// * Any other column that is constrained is assigned its intrinsic preferred inline-size;
/// * Other columns are assigned their intrinsic minimum inline-size.
minimum_specified_guess: Au,
/// The column inline-size assignment where:
/// * Each column with an intrinsic percentage inline-size greater than 0% is assigned the
/// larger of:
/// - Its intrinsic percentage inline-size times the assignable inline-size;
/// - Its intrinsic minimum inline-size;
/// * Other columns are assigned their intrinsic preferred inline-size.
preferred_guess: Au,
}
impl AutoLayoutCandidateGuess {
/// Creates a guess with all elements initialized to zero.
fn new() -> AutoLayoutCandidateGuess {
AutoLayoutCandidateGuess {
minimum_guess: Au(0),
minimum_percentage_guess: Au(0),
minimum_specified_guess: Au(0),
preferred_guess: Au(0),
}
}
/// Fills in the inline-size guesses for this column per INTRINSIC § 4.3.
fn from_column_inline_size(column_inline_size: &ColumnInlineSize, assignable_inline_size: Au)
-> AutoLayoutCandidateGuess {
let minimum_percentage_guess =
max(assignable_inline_size.scale_by(column_inline_size.percentage),
column_inline_size.minimum_length);
AutoLayoutCandidateGuess {
minimum_guess: column_inline_size.minimum_length,
minimum_percentage_guess: minimum_percentage_guess,
// FIXME(pcwalton): We need the notion of *constrainedness* per INTRINSIC § 4 to
// implement this one correctly.
minimum_specified_guess: if column_inline_size.percentage > 0.0 {
minimum_percentage_guess
} else if column_inline_size.constrained {
column_inline_size.preferred
} else {
column_inline_size.minimum_length
},
preferred_guess: if column_inline_size.percentage > 0.0 {
minimum_percentage_guess
} else {
column_inline_size.preferred
},
}
}
/// Calculates the inline-size, interpolating appropriately based on the value of `selection`.
///
/// This does *not* distribute excess inline-size. That must be done later if necessary.
fn calculate(&self, selection: SelectedAutoLayoutCandidateGuess) -> Au {
match selection {
UseMinimumGuess => self.minimum_guess,
InterpolateBetweenMinimumGuessAndMinimumPercentageGuess(weight) => {
interp(self.minimum_guess, self.minimum_percentage_guess, weight)
}
InterpolateBetweenMinimumPercentageGuessAndMinimumSpecifiedGuess(weight) => {
interp(self.minimum_percentage_guess, self.minimum_specified_guess, weight)
}
InterpolateBetweenMinimumSpecifiedGuessAndPreferredGuess(weight) => {
interp(self.minimum_specified_guess, self.preferred_guess, weight)
}
UsePreferredGuessAndDistributeExcessInlineSize => {
self.preferred_guess
}
}
}
}
impl Add<AutoLayoutCandidateGuess,AutoLayoutCandidateGuess> for AutoLayoutCandidateGuess {
#[inline]
fn add(&self, other: &AutoLayoutCandidateGuess) -> AutoLayoutCandidateGuess {
AutoLayoutCandidateGuess {
minimum_guess: self.minimum_guess + other.minimum_guess,
minimum_percentage_guess:
self.minimum_percentage_guess + other.minimum_percentage_guess,
minimum_specified_guess: self.minimum_specified_guess + other.minimum_specified_guess,
preferred_guess: self.preferred_guess + other.preferred_guess,
}
}
}
/// The `CSSFloat` member specifies the weight of the smaller of the two guesses, on a scale from
/// 0.0 to 1.0.
#[deriving(PartialEq, Show)]
enum SelectedAutoLayoutCandidateGuess {
UseMinimumGuess,
InterpolateBetweenMinimumGuessAndMinimumPercentageGuess(CSSFloat),
InterpolateBetweenMinimumPercentageGuessAndMinimumSpecifiedGuess(CSSFloat),
InterpolateBetweenMinimumSpecifiedGuessAndPreferredGuess(CSSFloat),
UsePreferredGuessAndDistributeExcessInlineSize,
}
impl SelectedAutoLayoutCandidateGuess {
/// See INTRINSIC § 4.3.
///
/// FIXME(pcwalton, INTRINSIC spec): INTRINSIC doesn't specify whether these are exclusive or
/// inclusive ranges.
fn select(guess: &AutoLayoutCandidateGuess, assignable_inline_size: Au)
-> SelectedAutoLayoutCandidateGuess {
if assignable_inline_size < guess.minimum_guess {
UseMinimumGuess
} else if assignable_inline_size < guess.minimum_percentage_guess {
let weight = weight(guess.minimum_guess,
assignable_inline_size,
guess.minimum_percentage_guess);
InterpolateBetweenMinimumGuessAndMinimumPercentageGuess(weight)
} else if assignable_inline_size < guess.minimum_specified_guess {
let weight = weight(guess.minimum_percentage_guess,
assignable_inline_size,
guess.minimum_specified_guess);
InterpolateBetweenMinimumPercentageGuessAndMinimumSpecifiedGuess(weight)
} else if assignable_inline_size < guess.preferred_guess {
let weight = weight(guess.minimum_specified_guess,
assignable_inline_size,
guess.preferred_guess);
InterpolateBetweenMinimumSpecifiedGuessAndPreferredGuess(weight)
} else {
UsePreferredGuessAndDistributeExcessInlineSize
}
}
}
/// Computes the weight needed to linearly interpolate `middle` between two guesses `low` and
/// `high` as specified by INTRINSIC § 4.3.
fn weight(low: Au, middle: Au, high: Au) -> CSSFloat {
(middle - low).to_subpx() / (high - low).to_subpx()
}
/// Linearly interpolates between two guesses, as specified by INTRINSIC § 4.3.
fn interp(low: Au, high: Au, weight: CSSFloat) -> Au {
low + (high - low).scale_by(weight)
}
struct ExcessInlineSizeDistributionInfo {
preferred_inline_size_of_nonconstrained_columns_with_no_percentage: Au,
count_of_nonconstrained_columns_with_no_percentage: u32,
preferred_inline_size_of_constrained_columns_with_no_percentage: Au,
total_percentage: CSSFloat,
column_count: u32,
}
impl ExcessInlineSizeDistributionInfo {
fn new() -> ExcessInlineSizeDistributionInfo {
ExcessInlineSizeDistributionInfo {
preferred_inline_size_of_nonconstrained_columns_with_no_percentage: Au(0),
count_of_nonconstrained_columns_with_no_percentage: 0,
preferred_inline_size_of_constrained_columns_with_no_percentage: Au(0),
total_percentage: 0.0,
column_count: 0,
}
}
fn update(&mut self, column_inline_size: &ColumnInlineSize) {
if !column_inline_size.constrained && column_inline_size.percentage == 0.0 {
self.preferred_inline_size_of_nonconstrained_columns_with_no_percentage =
self.preferred_inline_size_of_nonconstrained_columns_with_no_percentage +
column_inline_size.preferred;
self.count_of_nonconstrained_columns_with_no_percentage += 1
}
if column_inline_size.constrained && column_inline_size.percentage == 0.0 {
self.preferred_inline_size_of_constrained_columns_with_no_percentage =
self.preferred_inline_size_of_constrained_columns_with_no_percentage +
column_inline_size.preferred
}
self.total_percentage += column_inline_size.percentage;
self.column_count += 1
}
/// Based on the information here, distributes excess inline-size to the given column per
/// INTRINSIC § 4.4.
///
/// `#[inline]` so the compiler will hoist out the branch, which is loop-invariant.
#[inline]
fn distribute_excess_inline_size_to_column(&self,
column_inline_size: &mut ColumnInlineSize,
excess_inline_size: Au,
total_distributed_excess_size: &mut Au) {
let proportion =
if self.preferred_inline_size_of_nonconstrained_columns_with_no_percentage > Au(0) {
column_inline_size.preferred.to_subpx() /
self.preferred_inline_size_of_nonconstrained_columns_with_no_percentage
.to_subpx()
} else if self.count_of_nonconstrained_columns_with_no_percentage > 0 {
1.0 / (self.count_of_nonconstrained_columns_with_no_percentage as CSSFloat)
} else if self.preferred_inline_size_of_constrained_columns_with_no_percentage >
Au(0) {
column_inline_size.preferred.to_subpx() /
self.preferred_inline_size_of_constrained_columns_with_no_percentage.to_subpx()
} else if self.total_percentage > 0.0 {
column_inline_size.percentage / self.total_percentage
} else {
1.0 / (self.column_count as CSSFloat)
};
// The `min` here has the effect of throwing away fractional excess at the end of the
// table.
let amount_to_distribute = min(excess_inline_size.scale_by(proportion),
excess_inline_size - *total_distributed_excess_size);
*total_distributed_excess_size = *total_distributed_excess_size + amount_to_distribute;
column_inline_size.minimum_length = column_inline_size.minimum_length +
amount_to_distribute
}
}

View file

@ -172,6 +172,8 @@ fragment=top != ../html/acid2.html acid2_ref.html
!= input_height_a.html input_height_ref.html != input_height_a.html input_height_ref.html
== pre_ignorable_whitespace_a.html pre_ignorable_whitespace_ref.html == pre_ignorable_whitespace_a.html pre_ignorable_whitespace_ref.html
== many_brs_a.html many_brs_ref.html == many_brs_a.html many_brs_ref.html
== table_expansion_to_fit_a.html table_expansion_to_fit_ref.html
== table_percentage_width_a.html table_percentage_width_ref.html
== legacy_input_size_attribute_override_a.html legacy_input_size_attribute_override_ref.html == legacy_input_size_attribute_override_a.html legacy_input_size_attribute_override_ref.html
== legacy_td_width_attribute_a.html legacy_td_width_attribute_ref.html == legacy_td_width_attribute_a.html legacy_td_width_attribute_ref.html
== box_sizing_sanity_check_a.html box_sizing_sanity_check_ref.html == box_sizing_sanity_check_a.html box_sizing_sanity_check_ref.html

View file

@ -16,10 +16,14 @@
display: inline-block; display: inline-block;
color: yellow; color: yellow;
margin-left: 50px; margin-left: 50px;
margin-right: 50px;
background: yellow;
height: 100px;
width: 100px;
} }
</style> </style>
</head> </head>
<body> <body>
<div><span>X X</span></div> <div><span></span><span></span></div>
</body> </body>
</html> </html>

View file

@ -11,10 +11,11 @@
</style> </style>
</head> </head>
<body> <body>
<p>Don't crash!</p>
<table class="rel"> <table class="rel">
<tbody> <tbody>
<tr class="abs"> <tr class="abs">
<td style="padding: 0">Don't crash!</td> <td>&nbsp;</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View file

@ -1,12 +1,11 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head>
</head>
<body> <body>
<p>Don't crash!</p>
<table> <table>
<tbody> <tbody>
<tr> <tr>
<td style="padding: 0">Don't crash!</td> <td>&nbsp;</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View file

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<style>
* {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<table style="width: 500px; text-align: center;">
<tr><td style="">Expanding...</td></tr>
<tr><td style="">to...</td></tr>
<tr><td style="">fit!</td></tr>
</table>
</body>
</html>

View file

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<style>
* {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div style="text-align: center; width: 500px;">
<div>Expanding...</div>
<div>to...</div>
<div>fit!</div>
</div>
</body>
</html>

View file

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<style>
* {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<table style="width: 500px; text-align: center;" cellpadding=0 cellspacing=0>
<tr><td style="width: 30%; height: 50px; background: blue;"></td><td style="width: 70%; background: green;"></td></tr>
</table>
</body>
</html>

View file

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<style>
* {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div style="width: 500px; text-align: center;">
<span style="display: inline-block; width: 150px; height: 50px; background: blue;"></span><span style="display: inline-block; width: 350px; height: 50px; background: green;"></span>
</div>
</body>
</html>