aboutsummaryrefslogtreecommitdiffstats
path: root/components/layout/table_wrapper.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/layout/table_wrapper.rs')
-rw-r--r--components/layout/table_wrapper.rs448
1 files changed, 330 insertions, 118 deletions
diff --git a/components/layout/table_wrapper.rs b/components/layout/table_wrapper.rs
index 8938bd190ee..646a8a58913 100644
--- a/components/layout/table_wrapper.rs
+++ b/components/layout/table_wrapper.rs
@@ -3,22 +3,30 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! 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)]
use block::{BlockFlow, BlockNonReplaced, FloatNonReplaced, ISizeAndMarginsComputer};
-use block::{ISizeConstraintInput, MarginsMayNotCollapse};
+use block::{MarginsMayNotCollapse};
use construct::FlowConstructor;
use context::LayoutContext;
use floats::FloatKind;
use flow::{TableWrapperFlowClass, FlowClass, Flow, ImmutableFlowUtils};
use fragment::Fragment;
-use model::{Specified, Auto, specified};
+use table::ColumnInlineSize;
use wrapper::ThreadSafeLayoutNode;
use servo_util::geometry::Au;
-use std::cmp::max;
+use std::cmp::{max, min};
use std::fmt;
+use style::CSSFloat;
use style::computed_values::{clear, float, table_layout};
#[deriving(Encodable)]
@@ -32,8 +40,8 @@ pub enum TableLayout {
pub struct TableWrapperFlow {
pub block_flow: BlockFlow,
- /// Column inline-sizes
- pub col_inline_sizes: Vec<Au>,
+ /// Inline-size information for each column.
+ pub column_inline_sizes: Vec<ColumnInlineSize>,
/// Table-layout property
pub table_layout: TableLayout,
@@ -52,7 +60,7 @@ impl TableWrapperFlow {
};
TableWrapperFlow {
block_flow: block_flow,
- col_inline_sizes: vec!(),
+ column_inline_sizes: vec!(),
table_layout: table_layout
}
}
@@ -69,7 +77,7 @@ impl TableWrapperFlow {
};
TableWrapperFlow {
block_flow: block_flow,
- col_inline_sizes: vec!(),
+ column_inline_sizes: vec!(),
table_layout: table_layout
}
}
@@ -87,7 +95,7 @@ impl TableWrapperFlow {
};
TableWrapperFlow {
block_flow: block_flow,
- col_inline_sizes: vec!(),
+ column_inline_sizes: vec!(),
table_layout: table_layout
}
}
@@ -97,113 +105,87 @@ impl TableWrapperFlow {
self.block_flow.build_display_list_block(layout_context);
}
- fn calculate_table_column_sizes(&mut self, mut input: ISizeConstraintInput)
- -> ISizeConstraintInput {
- let style = self.block_flow.fragment.style();
-
- // Get inline-start and inline-end paddings, borders for table.
- // We get these values from the fragment's style since table_wrapper doesn't have its own
- // border or padding. input.available_inline_size is same as containing_block_inline_size
- // in table_wrapper.
- let padding = style.logical_padding();
- let border = style.logical_border_width();
- let padding_and_borders =
- specified(padding.inline_start, input.available_inline_size) +
- specified(padding.inline_end, input.available_inline_size) +
- border.inline_start +
- border.inline_end;
-
- let computed_inline_size = match self.table_layout {
- FixedLayout => {
- 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
+ /// Calculates table column sizes for automatic layout per INTRINSIC § 4.3.
+ fn calculate_table_column_sizes_for_automatic_layout(&mut self) {
+ // Find the padding and border of our first child, which is the table itself.
+ //
+ // This is a little weird because we're computing border/padding/margins for our child,
+ // when normally the child computes it itself. But it has to be this way because the
+ // padding will affect where we place the child. This is an odd artifact of the way that
+ // tables are separated into table flows and table wrapper flows.
+ let available_inline_size = self.block_flow.fragment.border_box.size.inline;
+ let mut table_border_padding = Au(0);
+ for kid in self.block_flow.base.child_iter() {
+ if kid.is_table() {
+ let kid_block = kid.as_block();
+ kid_block.fragment.compute_border_and_padding(available_inline_size);
+ kid_block.fragment.compute_block_direction_margins(available_inline_size);
+ kid_block.fragment.compute_inline_direction_margins(available_inline_size);
+ table_border_padding = kid_block.fragment.border_padding.inline_start_end();
+ break
}
- };
- 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,
layout_context: &LayoutContext,
parent_flow_inline_size: Au) {
// 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,
parent_flow_inline_size,
layout_context)
@@ -213,9 +195,6 @@ impl TableWrapperFlow {
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.
if self.is_float() {
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) {
- // 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() {
- assert!(kid.is_table_caption() || kid.is_table());
-
+ debug_assert!(kid.is_table_caption() || 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);
+ 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 content_inline_size = self.block_flow.fragment.border_box.size.inline;
// 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,
- 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,
content_inline_size,
- assigned_col_inline_sizes);
+ assigned_column_inline_sizes);
}
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
+ }
+}
+