diff options
Diffstat (limited to 'components/layout')
-rw-r--r-- | components/layout/block.rs | 238 | ||||
-rw-r--r-- | components/layout/construct.rs | 4 | ||||
-rw-r--r-- | components/layout/flow.rs | 18 | ||||
-rw-r--r-- | components/layout/fragment.rs | 217 | ||||
-rw-r--r-- | components/layout/inline.rs | 27 | ||||
-rw-r--r-- | components/layout/model.rs | 66 | ||||
-rw-r--r-- | components/layout/table.rs | 291 | ||||
-rw-r--r-- | components/layout/table_cell.rs | 24 | ||||
-rw-r--r-- | components/layout/table_colgroup.rs | 31 | ||||
-rw-r--r-- | components/layout/table_row.rs | 146 | ||||
-rw-r--r-- | components/layout/table_rowgroup.rs | 107 | ||||
-rw-r--r-- | components/layout/table_wrapper.rs | 448 |
12 files changed, 1001 insertions, 616 deletions
diff --git a/components/layout/block.rs b/components/layout/block.rs index 2797245f03b..d5352d622dc 100644 --- a/components/layout/block.rs +++ b/components/layout/block.rs @@ -2,15 +2,28 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -//! CSS block formatting contexts. +//! Layout for CSS block-level elements. //! -//! Terminology Note: -//! As per the CSS Spec, the term 'absolute positioning' here refers to -//! elements with position = 'absolute' or 'fixed'. -//! The term 'positioned element' refers to elements with position = -//! 'relative', 'absolute', or 'fixed'. +//! As a terminology note, the term *absolute positioning* here refers to elements with position +//! `absolute` or `fixed`. The term *positioned element* refers to elements with position +//! `relative`, `absolute`, and `fixed`. The term *containing block* (occasionally abbreviated as +//! *CB*) is the containing block for the current flow, which differs from the static containing +//! 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)] @@ -22,11 +35,10 @@ use flow::{MutableFlowUtils, PreorderFlowTraversal, PostorderFlowTraversal, mut_ use flow; use fragment::{Fragment, ImageFragment, InlineBlockFragment, ScannedTextFragment}; use layout_debug; -use model::{Auto, IntrinsicISizes, MarginCollapseInfo, MarginsCollapse}; -use model::{MarginsCollapseThrough, MaybeAuto, NoCollapsibleMargins, Specified, specified}; -use model::{specified_or_none}; +use model::{Auto, IntrinsicISizes, MarginCollapseInfo, MarginsCollapse, MarginsCollapseThrough}; +use model::{MaybeAuto, NoCollapsibleMargins, Specified, specified, specified_or_none}; +use table::ColumnInlineSize; use wrapper::ThreadSafeLayoutNode; -use style::computed_values::{clear, position}; use collections::dlist::DList; use geom::{Size2D, Point2D, Rect}; @@ -43,8 +55,8 @@ use std::cmp::{max, min}; use std::fmt; use std::mem; 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::{display, float, overflow}; +use style::computed_values::{LPN_Percentage, LP_Length, LP_Percentage, box_sizing, clear}; +use style::computed_values::{display, float, overflow, position}; use sync::Arc; /// Information specific to floated blocks. @@ -762,9 +774,10 @@ impl BlockFlow { /// /// This is where we use the preferred inline-sizes and minimum inline-sizes /// calculated in the bubble-inline-sizes traversal. - fn get_shrink_to_fit_inline_size(&self, available_inline_size: Au) -> Au { - min(self.base.intrinsic_inline_sizes.preferred_inline_size, - max(self.base.intrinsic_inline_sizes.minimum_inline_size, available_inline_size)) + pub fn get_shrink_to_fit_inline_size(&self, available_inline_size: Au) -> Au { + let content_intrinsic_inline_sizes = self.content_intrinsic_inline_sizes(); + 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 @@ -1291,10 +1304,11 @@ impl BlockFlow { /// `#[inline(always)]` because this is called only from block or table inline-size assignment /// and the code for block layout is significantly simpler. #[inline(always)] - pub fn propagate_assigned_inline_size_to_children(&mut self, - inline_start_content_edge: Au, - content_inline_size: Au, - opt_col_inline_sizes: Option<Vec<Au>>) { + pub fn propagate_assigned_inline_size_to_children( + &mut self, + inline_start_content_edge: Au, + content_inline_size: Au, + optional_column_inline_sizes: Option<&[ColumnInlineSize]>) { // 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_end_floats_impact_child = self.base.flags.impacted_by_right_floats(); @@ -1403,13 +1417,13 @@ impl BlockFlow { } // Handle tables. - match opt_col_inline_sizes { - Some(ref col_inline_sizes) => { + match optional_column_inline_sizes { + Some(ref column_inline_sizes) => { propagate_column_inline_sizes_to_child(kid, - i, - content_inline_size, - col_inline_sizes.as_slice(), - &mut inline_start_margin_edge) + i, + content_inline_size, + *column_inline_sizes, + &mut inline_start_margin_edge) } None => {} } @@ -1469,6 +1483,23 @@ impl BlockFlow { // TODO(pcwalton): If the inline-size of this flow is different from the size we estimated // 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 { @@ -1515,35 +1546,31 @@ impl Flow for BlockFlow { }; // 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 right_float_width = Au(0); - for child_ctx in self.base.child_iter() { - assert!(child_ctx.is_block_flow() || - child_ctx.is_inline_flow() || - child_ctx.is_table_kind()); - - let float_kind = child_ctx.float_kind(); - let child_base = flow::mut_base(child_ctx); - - 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()); + for kid in self.base.child_iter() { + let is_absolutely_positioned = kid.is_absolutely_positioned(); + let float_kind = kid.float_kind(); + let child_base = flow::mut_base(kid); + if !is_absolutely_positioned && !fixed_width { + computation.content_intrinsic_sizes.minimum_inline_size = + max(computation.content_intrinsic_sizes.minimum_inline_size, + child_base.intrinsic_inline_sizes.minimum_inline_size); match float_kind { float::none => { - intrinsic_inline_sizes.preferred_inline_size = - max(intrinsic_inline_sizes.preferred_inline_size, - child_base.intrinsic_inline_sizes.total_preferred_inline_size()); + computation.content_intrinsic_sizes.preferred_inline_size = + max(computation.content_intrinsic_sizes.preferred_inline_size, + child_base.intrinsic_inline_sizes.preferred_inline_size); } float::left => { 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 => { 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); } - intrinsic_inline_sizes.preferred_inline_size = - max(intrinsic_inline_sizes.preferred_inline_size, - left_float_width + right_float_width); + // FIXME(pcwalton): This should consider all float descendants, not just children. + // FIXME(pcwalton): This is not well-spec'd; INTRINSIC specifies to do this, but CSS-SIZING + // 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(); - 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; + self.base.intrinsic_inline_sizes = computation.finish(); match self.fragment.style().get_box().float { float::none => {} @@ -1575,11 +1595,11 @@ impl Flow for BlockFlow { self.base.flags = flags } - /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When - /// called on this context, the context has had its inline-size set by the parent context. + /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. + /// 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) - /// contexts. + /// Dual fragments consume some inline-size first, and the remainder is assigned to all child + /// (block) contexts. fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) { 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. - 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 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 @@ -1686,7 +1709,7 @@ impl Flow for BlockFlow { self.base.block_container_explicit_block_size.unwrap_or(Au(0)); self.fragment.assign_replaced_block_size_if_necessary(containing_block_block_size); 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. debug!("assign_block_size: assigning block_size for root flow"); self.assign_block_size_block_base(ctx, MarginsMayNotCollapse); @@ -1882,6 +1905,7 @@ impl ISizeConstraintInput { } /// The solutions for the inline-size-and-margins constraint equation. +#[deriving(Show)] pub struct ISizeConstraintSolution { pub inline_start: Au, pub inline_end: Au, @@ -1923,9 +1947,8 @@ impl ISizeConstraintSolution { pub trait ISizeAndMarginsComputer { /// Compute the inputs for the ISize constraint equation. /// - /// This is called only once to compute the initial inputs. For - /// calculation involving min-inline-size and max-inline-size, we don't need to - /// recompute these. + /// This is called only once to compute the initial inputs. For calculations involving + /// minimum and maximum inline-size, we don't need to recompute these. fn compute_inline_size_constraint_inputs(&self, block: &mut BlockFlow, parent_flow_inline_size: Au, @@ -1934,7 +1957,9 @@ pub trait ISizeAndMarginsComputer { let containing_block_inline_size = 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, parent_flow_inline_size, @@ -1955,7 +1980,8 @@ pub trait ISizeAndMarginsComputer { let margin = style.logical_margin(); 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( computed_inline_size, MaybeAuto::from_style(margin.inline_start, containing_block_inline_size), @@ -2015,10 +2041,10 @@ pub trait ISizeAndMarginsComputer { -> ISizeConstraintSolution; fn initial_computed_inline_size(&self, - block: &mut BlockFlow, - parent_flow_inline_size: Au, - ctx: &LayoutContext) - -> MaybeAuto { + block: &mut BlockFlow, + parent_flow_inline_size: Au, + ctx: &LayoutContext) + -> MaybeAuto { MaybeAuto::from_style(block.fragment().style().content_inline_size(), self.containing_block_inline_size(block, 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. fn initial_computed_inline_size(&self, - block: &mut BlockFlow, - _: Au, - ctx: &LayoutContext) - -> MaybeAuto { - let containing_block_inline_size = block.containing_block_size(ctx.shared.screen_size).inline; + block: &mut BlockFlow, + _: Au, + layout_context: &LayoutContext) + -> MaybeAuto { + let containing_block_inline_size = + block.containing_block_size(layout_context.shared.screen_size).inline; let fragment = block.fragment(); fragment.assign_replaced_inline_size_if_necessary(containing_block_inline_size); // 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. fn initial_computed_inline_size(&self, - block: &mut BlockFlow, - parent_flow_inline_size: Au, - _: &LayoutContext) - -> MaybeAuto { + block: &mut BlockFlow, + parent_flow_inline_size: Au, + _: &LayoutContext) + -> MaybeAuto { let fragment = block.fragment(); fragment.assign_replaced_inline_size_if_necessary(parent_flow_inline_size); // 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. fn initial_computed_inline_size(&self, - block: &mut BlockFlow, - parent_flow_inline_size: Au, - _: &LayoutContext) - -> MaybeAuto { + block: &mut BlockFlow, + parent_flow_inline_size: Au, + _: &LayoutContext) + -> MaybeAuto { let fragment = block.fragment(); fragment.assign_replaced_inline_size_if_necessary(parent_flow_inline_size); // 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, - child_index: uint, - content_inline_size: Au, - column_inline_sizes: &[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 - // parent. + child_index: uint, + content_inline_size: Au, + column_inline_sizes: &[ColumnInlineSize], + inline_start_margin_edge: &mut Au) { + // If kid is table_rowgroup or table_row, the column inline-sizes info should be copied from + // its parent. // // FIXME(pcwalton): This seems inefficient. Reference count it instead? 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. content_inline_size } else if kid.is_table_cell() { - // If kid is table_cell, the x offset and inline-size for each cell should be - // 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] + column_inline_sizes[child_index].minimum_length } else { // ISize of kid flow is our content inline-size. content_inline_size }; - let kid_base = flow::mut_base(kid); - kid_base.position.start.i = *inline_start_margin_edge; - kid_base.block_container_inline_size = inline_size; + { + let kid_base = flow::mut_base(kid); + 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 + } } + diff --git a/components/layout/construct.rs b/components/layout/construct.rs index 4233c565dba..7d4b6d24b81 100644 --- a/components/layout/construct.rs +++ b/components/layout/construct.rs @@ -767,8 +767,8 @@ impl<'a> FlowConstructor<'a> { flow.add_new_child(anonymous_flow); } - /// Builds a flow for a node with `display: table`. This yields a `TableWrapperFlow` with possibly - /// other `TableCaptionFlow`s or `TableFlow`s underneath it. + /// Builds a flow for a node with `display: table`. This yields a `TableWrapperFlow` with + /// possibly other `TableCaptionFlow`s or `TableFlow`s underneath it. fn build_flow_for_table_wrapper(&mut self, node: &ThreadSafeLayoutNode, float_value: float::T) -> ConstructionResult { let fragment = Fragment::new_from_specific_info(node, TableWrapperFragment); diff --git a/components/layout/flow.rs b/components/layout/flow.rs index c33f7528c6b..8c75cb27eec 100644 --- a/components/layout/flow.rs +++ b/components/layout/flow.rs @@ -36,7 +36,7 @@ use incremental::RestyleDamage; use inline::InlineFlow; use model::{CollapsibleMargins, IntrinsicISizes, MarginCollapseInfo}; use parallel::FlowParallelInfo; -use table::TableFlow; +use table::{ColumnInlineSize, TableFlow}; use table_caption::TableCaptionFlow; use table_cell::TableCellFlow; 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. /// Fails otherwise. - fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> { - fail!("called col_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_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") + fn column_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<ColumnInlineSize> { + fail!("called column_inline_sizes() on non-table flow") } // Main methods diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index e9bd51b3ece..b2c9c23293c 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -14,7 +14,7 @@ use flow::Flow; use flow_ref::FlowRef; use inline::{InlineFragmentContext, InlineMetrics}; use layout_debug; -use model::{Auto, IntrinsicISizes, MaybeAuto, Specified, specified}; +use model::{Auto, IntrinsicISizes, IntrinsicISizesContribution, MaybeAuto, Specified, specified}; use model; use text; use util::{OpaqueNodeMethods, ToGfxColor}; @@ -529,51 +529,93 @@ impl Fragment { 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 - /// or replaced elements. - fn style_specified_intrinsic_inline_size(&self) -> IntrinsicISizes { - let (use_margins, use_padding) = match self.specific { + /// Determines which quantities (border/padding/margin/specified) should be included in the + /// intrinsic inline size of this fragment. + fn quantities_included_in_intrinsic_inline_size(&self) + -> QuantitiesIncludedInIntrinsicInlineSizes { + match self.specific { GenericFragment | IframeFragment(_) | ImageFragment(_) | InlineBlockFragment(_) | - InputFragment => (true, true), - TableFragment | TableCellFragment => (false, true), - TableWrapperFragment => (true, false), - TableRowFragment => (false, false), + InputFragment => QuantitiesIncludedInIntrinsicInlineSizes::all(), + TableFragment | TableCellFragment => { + IntrinsicInlineSizeIncludesPadding | + IntrinsicInlineSizeIncludesBorder | + IntrinsicInlineSizeIncludesSpecified + } + TableWrapperFragment => { + IntrinsicInlineSizeIncludesMargins | + IntrinsicInlineSizeIncludesBorder | + IntrinsicInlineSizeIncludesSpecified + } + TableRowFragment => { + IntrinsicInlineSizeIncludesBorder | + IntrinsicInlineSizeIncludesSpecified + } ScannedTextFragment(_) | TableColumnFragment(_) | UnscannedTextFragment(_) | InlineAbsoluteHypotheticalFragment(_) => { - // Styles are irrelevant for these kinds of fragments. - return IntrinsicISizes::new() + QuantitiesIncludedInIntrinsicInlineSizes::empty() } - }; + } + } + /// 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 inline_size = MaybeAuto::from_style(style.content_inline_size(), - Au(0)).specified_or_zero(); - let margin = style.logical_margin(); - let (margin_inline_start, margin_inline_end) = if use_margins { - (MaybeAuto::from_style(margin.inline_start, Au(0)).specified_or_zero(), + // FIXME(pcwalton): Percentages should be relative to any definite size per CSS-SIZING. + // This will likely need to be done by pushing down definite sizes during selector + // 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()) } else { - (Au(0), Au(0)) + Au(0) }; - let padding = style.logical_padding(); - let (padding_inline_start, padding_inline_end) = if use_padding { - (model::specified(padding.inline_start, Au(0)), + // FIXME(pcwalton): Percentages should be relative to any definite size per CSS-SIZING. + // This will likely need to be done by pushing down definite sizes during selector + // 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))) } 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? - let border = self.border_width(); - let surround_inline_size = margin_inline_start + margin_inline_end + padding_inline_start + padding_inline_end + - border.inline_start_end(); + let surrounding_inline_size = self.surrounding_intrinsic_inline_size(); - IntrinsicISizes { - minimum_inline_size: inline_size, - preferred_inline_size: inline_size, - surround_inline_size: surround_inline_size, + IntrinsicISizesContribution { + content_intrinsic_sizes: IntrinsicISizes { + minimum_inline_size: specified, + 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 - /// style. After this call, the `border_padding` and the vertical direction of the `margin` - /// field will be correct. - pub fn compute_border_padding_margins(&mut self, - containing_block_inline_size: Au) { - // Compute vertical margins. Note that this value will be ignored by layout if the style - // specifies `auto`. + /// Computes the margins in the inline direction from the containing block inline-size and the + /// style. After this call, the inline direction of the `margin` field will be correct. + /// + /// Do not use this method if the inline direction margins are to be computed some other way + /// (for example, via constraint solving for blocks). + pub fn compute_inline_direction_margins(&mut self, containing_block_inline_size: Au) { + 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 { TableFragment | TableCellFragment | TableRowFragment | TableColumnFragment(_) => { self.margin.block_start = 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(); - 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(); - 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. let border = self.border_width(); @@ -1241,28 +1313,23 @@ impl Fragment { } } - /// Returns the intrinsic inline-sizes of this fragment. - pub fn intrinsic_inline_sizes(&mut self) -> IntrinsicISizes { + /// Computes the intrinsic inline-sizes of this fragment. + pub fn compute_intrinsic_inline_sizes(&mut self) -> IntrinsicISizesContribution { let mut result = self.style_specified_intrinsic_inline_size(); - match self.specific { GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableColumnFragment(_) | TableRowFragment | TableWrapperFragment | InlineAbsoluteHypotheticalFragment(_) | InputFragment => {} InlineBlockFragment(ref mut info) => { let block_flow = info.flow_ref.get_mut().as_block(); - result.minimum_inline_size = max(result.minimum_inline_size, - 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); + result.union_block(&block_flow.base.intrinsic_inline_sizes) } ImageFragment(ref mut image_fragment_info) => { let image_inline_size = image_fragment_info.image_inline_size(); - result.minimum_inline_size = max(result.minimum_inline_size, image_inline_size); - result.preferred_inline_size = max(result.preferred_inline_size, - image_inline_size); + result.union_block(&IntrinsicISizes { + minimum_inline_size: image_inline_size, + preferred_inline_size: image_inline_size, + }) } ScannedTextFragment(ref text_fragment_info) => { let range = &text_fragment_info.range; @@ -1274,10 +1341,10 @@ impl Fragment { .metrics_for_range(range) .advance_width; - result.minimum_inline_size = max(result.minimum_inline_size, - min_line_inline_size); - result.preferred_inline_size = max(result.preferred_inline_size, - max_line_inline_size); + result.union_block(&IntrinsicISizes { + minimum_inline_size: min_line_inline_size, + preferred_inline_size: max_line_inline_size, + }) } UnscannedTextFragment(..) => { 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 padding_inline_size = 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; - result.preferred_inline_size = result.preferred_inline_size + - border_width + padding_inline_size; } } } @@ -1477,7 +1542,9 @@ impl Fragment { } else { 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())) } @@ -1502,8 +1569,7 @@ impl Fragment { /// Assigns replaced inline-size, padding, and margins for this fragment only if it is replaced /// content per CSS 2.1 § 10.3.2. - pub fn assign_replaced_inline_size_if_necessary(&mut self, - container_inline_size: Au) { + pub fn assign_replaced_inline_size_if_necessary(&mut self, container_inline_size: Au) { match self.specific { GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableRowFragment | TableWrapperFragment | InputFragment => return, @@ -1515,8 +1581,6 @@ impl Fragment { InlineAbsoluteHypotheticalFragment(_) => {} }; - self.compute_border_padding_margins(container_inline_size); - let style_inline_size = self.style().content_inline_size(); let style_block_size = self.style().content_block_size(); let style_min_inline_size = self.style().min_inline_size(); @@ -1528,17 +1592,16 @@ impl Fragment { match self.specific { InlineAbsoluteHypotheticalFragment(ref mut info) => { let block_flow = info.flow_ref.get_mut().as_block(); - block_flow.base.block_container_inline_size = - block_flow.base.intrinsic_inline_sizes.preferred_inline_size + - block_flow.base.intrinsic_inline_sizes.surround_inline_size; + block_flow.base.position.size.inline = + block_flow.base.intrinsic_inline_sizes.preferred_inline_size; // This is a hypothetical box, so it takes up no space. self.border_box.size.inline = Au(0); } InlineBlockFragment(ref mut info) => { let block_flow = info.flow_ref.get_mut().as_block(); - self.border_box.size.inline = block_flow.base.intrinsic_inline_sizes.preferred_inline_size + - block_flow.base.intrinsic_inline_sizes.surround_inline_size; + self.border_box.size.inline = + block_flow.base.intrinsic_inline_sizes.preferred_inline_size; block_flow.base.block_container_inline_size = self.border_box.size.inline; } ScannedTextFragment(_) => { @@ -1652,7 +1715,8 @@ impl Fragment { InlineBlockFragment(ref mut info) => { // Not the primary fragment, so we do not take the noncontent size into account. 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) => { // 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_metrics = text::font_metrics_for_style(layout_context.font_context(), &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(_) => { // 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, + } +} + diff --git a/components/layout/inline.rs b/components/layout/inline.rs index 5c55bc600c2..26956155015 100644 --- a/components/layout/inline.rs +++ b/components/layout/inline.rs @@ -12,16 +12,15 @@ use flow; use fragment::{Fragment, InlineAbsoluteHypotheticalFragment, InlineBlockFragment}; use fragment::{ScannedTextFragment, ScannedTextFragmentInfo, SplitInfo}; use layout_debug; -use model::IntrinsicISizes; +use model::IntrinsicISizesContribution; use text; use wrapper::ThreadSafeLayoutNode; use collections::{Deque, RingBuf}; -use geom::Rect; +use geom::{Rect, Size2D}; use gfx::display_list::{ContentLevel, DisplayList}; use gfx::font::FontMetrics; use gfx::font_context::FontContext; -use geom::Size2D; use gfx::text::glyph::CharIndex; use servo_util::geometry::Au; use servo_util::logical_geometry::{LogicalRect, LogicalSize}; @@ -922,24 +921,12 @@ impl Flow for InlineFlow { 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() { debug!("Flow: measuring {}", *fragment); - - 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; + computation.union_inline(&fragment.compute_intrinsic_inline_sizes().finish()) } - - self.base.intrinsic_inline_sizes = intrinsic_inline_sizes; + self.base.intrinsic_inline_sizes = computation.finish() } /// 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 this = &mut *self; 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); } } @@ -1130,6 +1120,7 @@ impl Flow for InlineFlow { let block_flow = info.flow_ref.get_mut().as_block(); // FIXME(#2795): Get the real container size let container_size = Size2D::zero(); + block_flow.base.abs_position = self.base.abs_position + fragment.border_box.start.to_physical(self.base.writing_mode, diff --git a/components/layout/model.rs b/components/layout/model.rs index fca00da5994..e6b916a9545 100644 --- a/components/layout/model.rs +++ b/components/layout/model.rs @@ -249,14 +249,11 @@ pub struct IntrinsicISizes { pub minimum_inline_size: Au, /// The *preferred inline-size* of the content. 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 { 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 { minimum_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, +} - pub fn total_minimum_inline_size(&self) -> Au { - self.minimum_inline_size + self.surround_inline_size +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_preferred_inline_size(&self) -> Au { - self.preferred_inline_size + self.surround_inline_size + /// Adds the content intrinsic sizes and the surrounding size together to yield the final + /// 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, + } + } + + /// Updates the computation so that the minimum is the maximum of the current minimum and the + /// 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. +#[deriving(PartialEq)] pub enum MaybeAuto { Auto, Specified(Au), @@ -290,7 +334,9 @@ impl MaybeAuto { -> MaybeAuto { match length { 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) } } diff --git a/components/layout/table.rs b/components/layout/table.rs index 7aca57f7fc4..e066e7adc6a 100644 --- a/components/layout/table.rs +++ b/components/layout/table.rs @@ -14,6 +14,7 @@ use floats::FloatKind; use flow::{TableFlowClass, FlowClass, Flow, ImmutableFlowUtils}; use fragment::Fragment; use layout_debug; +use model::{IntrinsicISizes, IntrinsicISizesContribution}; use table_wrapper::{TableLayout, FixedLayout, AutoLayout}; use wrapper::ThreadSafeLayoutNode; @@ -21,7 +22,8 @@ use servo_util::geometry::Au; use servo_util::logical_geometry::LogicalRect; use std::cmp::max; 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. /// 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 block_flow: BlockFlow, - /// Column inline-sizes - pub col_inline_sizes: Vec<Au>, - - /// Column min inline-sizes. - pub col_min_inline_sizes: Vec<Au>, - - /// Column pref inline-sizes. - pub col_pref_inline_sizes: Vec<Au>, + /// Information about the inline-sizes of each column. + pub column_inline_sizes: Vec<ColumnInlineSize>, /// Table-layout property pub table_layout: TableLayout, @@ -56,9 +52,7 @@ impl TableFlow { }; TableFlow { block_flow: block_flow, - col_inline_sizes: vec!(), - col_min_inline_sizes: vec!(), - col_pref_inline_sizes: vec!(), + column_inline_sizes: Vec::new(), table_layout: table_layout } } @@ -75,9 +69,7 @@ impl TableFlow { }; TableFlow { block_flow: block_flow, - col_inline_sizes: vec!(), - col_min_inline_sizes: vec!(), - col_pref_inline_sizes: vec!(), + column_inline_sizes: Vec::new(), table_layout: table_layout } } @@ -95,30 +87,33 @@ impl TableFlow { }; TableFlow { block_flow: block_flow, - col_inline_sizes: vec!(), - col_min_inline_sizes: vec!(), - col_pref_inline_sizes: vec!(), + column_inline_sizes: Vec::new(), table_layout: table_layout } } - /// Update the corresponding value of self_inline-sizes if a value of kid_inline-sizes has larger value - /// than one of self_inline-sizes. - pub fn update_col_inline_sizes(self_inline_sizes: &mut Vec<Au>, kid_inline_sizes: &Vec<Au>) -> Au { - let mut sum_inline_sizes = Au(0); - let mut kid_inline_sizes_it = kid_inline_sizes.iter(); - for self_inline_size in self_inline_sizes.iter_mut() { - match kid_inline_sizes_it.next() { - Some(kid_inline_size) => { - if *self_inline_size < *kid_inline_size { - *self_inline_size = *kid_inline_size; - } - }, - None => {} - } - sum_inline_sizes = sum_inline_sizes + *self_inline_size; + /// Update the corresponding value of `self_inline_sizes` if a value of `kid_inline_sizes` has + /// a larger value than one of `self_inline_sizes`. Returns the minimum and preferred inline + /// sizes. + pub fn update_column_inline_sizes(parent_inline_sizes: &mut Vec<ColumnInlineSize>, + child_inline_sizes: &Vec<ColumnInlineSize>) + -> IntrinsicISizes { + let mut total_inline_sizes = IntrinsicISizes::new(); + for (parent_sizes, child_sizes) in parent_inline_sizes.iter_mut() + .zip(child_inline_sizes.iter()) { + *parent_sizes = ColumnInlineSize { + minimum_length: max(parent_sizes.minimum_length, child_sizes.minimum_length), + percentage: parent_sizes.greatest_percentage(child_sizes), + preferred: max(parent_sizes.preferred, child_sizes.preferred), + constrained: parent_sizes.constrained || child_sizes.constrained + }; + + 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. @@ -155,16 +150,8 @@ impl Flow for TableFlow { &mut self.block_flow } - fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> { - &mut self.col_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 + fn column_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<ColumnInlineSize> { + &mut self.column_inline_sizes } /// 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}", 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(); let mut did_first_row = false; - 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() { - self.col_inline_sizes.push_all(kid.as_table_colgroup().inline_sizes.as_slice()); - self.col_min_inline_sizes = self.col_inline_sizes.clone(); - self.col_pref_inline_sizes = self.col_inline_sizes.clone(); + for specified_inline_size in kid.as_table_colgroup().inline_sizes.iter() { + self.column_inline_sizes.push(ColumnInlineSize { + 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() { - // read column inline-sizes from table-row-group/table-row, and assign - // inline-size=0 for the columns not defined in column-group - // FIXME: need to read inline-sizes from either table-header-group OR - // first table-row + // Read column inline-sizes from the table-row-group/table-row, and assign + // inline-size=0 for the columns not defined in the column group. + // FIXME: Need to read inline-sizes from either table-header-group OR the first + // table-row. match self.table_layout { FixedLayout => { - let kid_col_inline_sizes = kid.col_inline_sizes(); + // Fixed table layout only looks at the first row. if !did_first_row { did_first_row = true; - let mut child_inline_sizes = kid_col_inline_sizes.iter(); - for col_inline_size in self.col_inline_sizes.iter_mut() { - match child_inline_sizes.next() { - Some(child_inline_size) => { - if *col_inline_size == Au::new(0) { - *col_inline_size = *child_inline_size; - } - }, - None => break - } + for child_column_inline_size in kid.column_inline_sizes().iter() { + self.column_inline_sizes.push(*child_column_inline_size); } } - 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 => { - min_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_min_inline_sizes, kid.col_min_inline_sizes()); - pref_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_pref_inline_sizes, kid.col_pref_inline_sizes()); - - // update the number of column inline-sizes from table-rows. - let num_cols = self.col_min_inline_sizes.len(); - let num_child_cols = kid.col_min_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(Au::new(0)); - let new_kid_min = kid.col_min_inline_sizes()[i]; - self.col_min_inline_sizes.push( new_kid_min ); - let new_kid_pref = kid.col_pref_inline_sizes()[i]; - self.col_pref_inline_sizes.push( new_kid_pref ); - min_inline_size = min_inline_size + new_kid_min; - pref_inline_size = pref_inline_size + new_kid_pref; + let child_column_inline_sizes = kid.column_inline_sizes(); + let mut child_intrinsic_sizes = + TableFlow::update_column_inline_sizes(&mut self.column_inline_sizes, + child_column_inline_sizes); + + // Add new columns if processing this row caused us to discover them. + let child_column_count = child_column_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)", + parent_column_count, + child_column_count); + self.column_inline_sizes.reserve(child_column_count); + for i in range(parent_column_count, child_column_count) { + let inline_size_for_new_column = (*child_column_inline_sizes)[i]; + child_intrinsic_sizes.minimum_inline_size = + child_intrinsic_sizes.minimum_inline_size + + 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.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; + self.block_flow.base.intrinsic_inline_sizes = computation.finish() } - /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. 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) { + /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. + /// When called on this context, the context has had its inline-size set by the parent context. + fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) { let _scope = layout_debug_scope!("table::assign_inline_sizes {:s}", self.block_flow.base.debug_id()); 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 mut num_unspecified_inline_sizes = 0; - let mut total_column_inline_size = Au::new(0); - for col_inline_size in self.col_inline_sizes.iter() { - if *col_inline_size == Au::new(0) { - num_unspecified_inline_sizes += 1; + let mut total_column_inline_size = Au(0); + for column_inline_size in self.column_inline_sizes.iter() { + let this_column_inline_size = column_inline_size.minimum_length; + if this_column_inline_size == Au(0) { + num_unspecified_inline_sizes += 1 } 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; - 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 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 { FixedLayout => { - // In fixed table layout, we distribute extra space among the unspecified columns if there are - // any, or among all the columns if all are specified. - if (total_column_inline_size < content_inline_size) && (num_unspecified_inline_sizes == 0) { - let ratio = content_inline_size.to_f64().unwrap() / total_column_inline_size.to_f64().unwrap(); - for col_inline_size in self.col_inline_sizes.iter_mut() { - *col_inline_size = (*col_inline_size).scale_by(ratio); + // In fixed table layout, we distribute extra space among the unspecified columns + // if there are any, or among all the columns if all are specified. + if total_column_inline_size < content_inline_size && + num_unspecified_inline_sizes == 0 { + let extra_column_inline_size = content_inline_size; + (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 { - let extra_column_inline_size = (content_inline_size - total_column_inline_size) / num_unspecified_inline_sizes; - for col_inline_size in self.col_inline_sizes.iter_mut() { - if *col_inline_size == Au(0) { - *col_inline_size = extra_column_inline_size; + let extra_column_inline_size = + (content_inline_size - total_column_inline_size) / + num_unspecified_inline_sizes; + 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_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>) { @@ -340,10 +339,12 @@ impl ISizeAndMarginsComputer for InternalTable { /// /// CSS Section 10.4: Minimum and Maximum inline-sizes fn compute_used_inline_size(&self, - block: &mut BlockFlow, - ctx: &LayoutContext, - parent_flow_inline_size: Au) { - let input = self.compute_inline_size_constraint_inputs(block, parent_flow_inline_size, ctx); + block: &mut BlockFlow, + ctx: &LayoutContext, + parent_flow_inline_size: Au) { + let input = self.compute_inline_size_constraint_inputs(block, + parent_flow_inline_size, + ctx); let solution = self.solve_inline_size_constraints(block, &input); 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. fn solve_inline_size_constraints(&self, _: &mut BlockFlow, input: &ISizeConstraintInput) -> 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 + } } } + diff --git a/components/layout/table_cell.rs b/components/layout/table_cell.rs index e1581e2d642..0cda4fb922a 100644 --- a/components/layout/table_cell.rs +++ b/components/layout/table_cell.rs @@ -26,7 +26,8 @@ pub struct 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 { 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 } 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.minimum_inline_size; } } - /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When - /// called on this context, the context has had its inline-size set by the parent table row. + /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. + /// 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) { let _scope = layout_debug_scope!("table_cell::assign_inline_sizes {:s}", 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 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; 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, - content_inline_size, - None); + content_inline_size, + None); } fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) { diff --git a/components/layout/table_colgroup.rs b/components/layout/table_colgroup.rs index 427cd6f5f1f..664d28e8100 100644 --- a/components/layout/table_colgroup.rs +++ b/components/layout/table_colgroup.rs @@ -10,11 +10,11 @@ use context::LayoutContext; use flow::{BaseFlow, TableColGroupFlowClass, FlowClass, Flow}; use fragment::{Fragment, TableColumnFragment}; use layout_debug; -use model::{MaybeAuto}; use wrapper::ThreadSafeLayoutNode; use servo_util::geometry::Au; use std::fmt; +use style::computed_values::LengthOrPercentageOrAuto; /// A table formatting context. pub struct TableColGroupFlow { @@ -27,14 +27,17 @@ pub struct TableColGroupFlow { /// The table column fragments pub cols: Vec<Fragment>, - /// The specified inline-sizes of table columns - pub inline_sizes: Vec<Au>, + /// The specified inline-sizes of table columns. (We use `LengthOrPercentageOrAuto` here in + /// lieu of `ColumnInlineSize` because column groups do not establish minimum or preferred + /// inline sizes.) + pub inline_sizes: Vec<LengthOrPercentageOrAuto>, } impl TableColGroupFlow { pub fn from_node_and_fragments(node: &ThreadSafeLayoutNode, fragment: Fragment, - fragments: Vec<Fragment>) -> TableColGroupFlow { + fragments: Vec<Fragment>) + -> TableColGroupFlow { TableColGroupFlow { base: BaseFlow::new((*node).clone()), fragment: Some(fragment), @@ -58,27 +61,25 @@ impl Flow for TableColGroupFlow { self.base.debug_id()); for fragment in self.cols.iter() { - // get the specified value from inline-size property - let inline_size = MaybeAuto::from_style(fragment.style().content_inline_size(), - Au::new(0)).specified_or_zero(); - + // Retrieve the specified value from the appropriate CSS property. + let inline_size = fragment.style().content_inline_size(); let span: int = match fragment.specific { 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) { - 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. - /// Therefore, table colgroup flow does not need to assign its inline-size. - fn assign_inline_sizes(&mut self, _ctx: &LayoutContext) { + /// Table column inline-sizes are assigned in the table flow and propagated to table row flows + /// and/or rowgroup flows. Therefore, table colgroup flows do not need to assign inline-sizes. + fn assign_inline_sizes(&mut self, _: &LayoutContext) { } - /// Table column do not have block-size. - fn assign_block_size(&mut self, _ctx: &LayoutContext) { + /// Table columns do not have block-size. + fn assign_block_size(&mut self, _: &LayoutContext) { } fn update_late_computed_inline_position_if_necessary(&mut self, _: Au) {} diff --git a/components/layout/table_row.rs b/components/layout/table_row.rs index d7236c5c41b..0ee0441e3ca 100644 --- a/components/layout/table_row.rs +++ b/components/layout/table_row.rs @@ -14,27 +14,22 @@ use flow::{TableRowFlowClass, FlowClass, Flow, ImmutableFlowUtils}; use flow; use fragment::Fragment; use layout_debug; -use table::InternalTable; +use table::{ColumnInlineSize, InternalTable}; use model::{MaybeAuto, Specified, Auto}; use wrapper::ThreadSafeLayoutNode; use servo_util::geometry::Au; use std::cmp::max; 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)] pub struct TableRowFlow { pub block_flow: BlockFlow, - /// Column inline-sizes. - pub col_inline_sizes: Vec<Au>, - - /// Column min inline-sizes. - pub col_min_inline_sizes: Vec<Au>, - - /// Column pref inline-sizes. - pub col_pref_inline_sizes: Vec<Au>, + /// Information about the inline-sizes of each column. + pub column_inline_sizes: Vec<ColumnInlineSize>, } impl TableRowFlow { @@ -43,9 +38,7 @@ impl TableRowFlow { -> TableRowFlow { TableRowFlow { block_flow: BlockFlow::from_node_and_fragment(node, fragment), - col_inline_sizes: vec!(), - col_min_inline_sizes: vec!(), - col_pref_inline_sizes: vec!(), + column_inline_sizes: Vec::new() } } @@ -54,9 +47,7 @@ impl TableRowFlow { -> TableRowFlow { TableRowFlow { block_flow: BlockFlow::from_node(constructor, node), - col_inline_sizes: vec!(), - col_min_inline_sizes: vec!(), - col_pref_inline_sizes: vec!(), + column_inline_sizes: Vec::new() } } @@ -65,8 +56,8 @@ impl TableRowFlow { } fn initialize_offsets(&mut self) -> (Au, Au, Au) { - // TODO: If border-collapse: collapse, block-start_offset, block-end_offset, and inline-start_offset - // should be updated. Currently, they are set as Au(0). + // TODO: If border-collapse: collapse, block_start_offset, block_end_offset, and + // inline_start_offset should be updated. Currently, they are set as Au(0). (Au(0), Au(0), Au(0)) } @@ -82,19 +73,21 @@ impl TableRowFlow { 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 ) - let mut max_y = Au::new(0); + // Per CSS 2.1 § 17.5.3, find max_y = max(computed `block-size`, minimum block-size of all + // cells). + let mut max_y = Au(0); for kid in self.block_flow.base.child_iter() { kid.assign_block_size_for_inorder_child_if_necessary(layout_context); { let child_fragment = kid.as_table_cell().fragment(); // TODO: Percentage block-size - let child_specified_block_size = MaybeAuto::from_style(child_fragment.style().content_block_size(), - Au::new(0)).specified_or_zero(); - max_y = - max(max_y, - child_specified_block_size + child_fragment.border_padding.block_start_end()); + let child_specified_block_size = + MaybeAuto::from_style(child_fragment.style().content_block_size(), + Au::new(0)).specified_or_zero(); + max_y = max(max_y, + child_specified_block_size + + child_fragment.border_padding.block_start_end()); } let child_node = flow::mut_base(kid); child_node.position.start.b = cur_y; @@ -103,7 +96,11 @@ impl TableRowFlow { let mut block_size = max_y; // 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, Specified(value) => max(value, block_size) }; @@ -153,70 +150,84 @@ impl Flow for TableRowFlow { &mut self.block_flow } - fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> { - &mut self.col_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 + fn column_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<ColumnInlineSize> { + &mut self.column_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. + /// 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. - /// 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) { 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); - let mut pref_inline_size = Au(0); - /* find the specified inline_sizes from child table-cell contexts */ + // Bubble up the specified inline-sizes from child table cells. + let (mut min_inline_size, mut pref_inline_size) = (Au(0), Au(0)); for kid in self.block_flow.base.child_iter() { assert!(kid.is_table_cell()); - // collect the specified column inline-sizes of cells. These are used in fixed table layout calculation. - { - let child_fragment = kid.as_table_cell().fragment(); - let child_specified_inline_size = MaybeAuto::from_style(child_fragment.style().content_inline_size(), - Au::new(0)).specified_or_zero(); - self.col_inline_sizes.push(child_specified_inline_size); - } + // Collect the specified column inline-size of the cell. This is used in both fixed and + // automatic table layout calculation. + let child_specified_inline_size = kid.as_table_cell() + .fragment() + .style() + .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); - self.col_min_inline_sizes.push(child_base.intrinsic_inline_sizes.minimum_inline_size); - self.col_pref_inline_sizes.push(child_base.intrinsic_inline_sizes.preferred_inline_size); - min_inline_size = min_inline_size + child_base.intrinsic_inline_sizes.minimum_inline_size; - pref_inline_size = pref_inline_size + child_base.intrinsic_inline_sizes.preferred_inline_size; + let child_column_inline_size = ColumnInlineSize { + minimum_length: match child_specified_inline_size { + LPA_Auto | LPA_Percentage(_) => { + 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.preferred_inline_size = max( - min_inline_size, pref_inline_size); + 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 called - /// on this context, the context has had its inline-size set by the parent context. + /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. + /// 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) { 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"); // 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; - // FIXME: In case of border-collapse: collapse, inline-start_content_edge should be border-inline-start - let inline_start_content_edge = Au::new(0); + // FIXME: In case of border-collapse: collapse, inline_start_content_edge should be + // border_inline_start. + let inline_start_content_edge = Au(0); let inline_size_computer = InternalTable; - 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())); + 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, + containing_block_inline_size, + Some(self.column_inline_sizes.as_slice())); } 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) } } + diff --git a/components/layout/table_rowgroup.rs b/components/layout/table_rowgroup.rs index 7efb2e96d09..1a4b1ce374a 100644 --- a/components/layout/table_rowgroup.rs +++ b/components/layout/table_rowgroup.rs @@ -14,11 +14,11 @@ use flow::{TableRowGroupFlowClass, FlowClass, Flow, ImmutableFlowUtils}; use flow; use fragment::Fragment; use layout_debug; -use table::{InternalTable, TableFlow}; +use model::IntrinsicISizesContribution; +use table::{ColumnInlineSize, InternalTable, TableFlow}; use wrapper::ThreadSafeLayoutNode; use servo_util::geometry::Au; -use std::cmp::max; use std::fmt; /// A table formatting context. @@ -26,14 +26,8 @@ use std::fmt; pub struct TableRowGroupFlow { pub block_flow: BlockFlow, - /// Column inline-sizes - pub col_inline_sizes: Vec<Au>, - - /// Column min inline-sizes. - pub col_min_inline_sizes: Vec<Au>, - - /// Column pref inline-sizes. - pub col_pref_inline_sizes: Vec<Au>, + /// Information about the inline-sizes of each column. + pub column_inline_sizes: Vec<ColumnInlineSize>, } impl TableRowGroupFlow { @@ -42,9 +36,7 @@ impl TableRowGroupFlow { -> TableRowGroupFlow { TableRowGroupFlow { block_flow: BlockFlow::from_node_and_fragment(node, fragment), - col_inline_sizes: vec!(), - col_min_inline_sizes: vec!(), - col_pref_inline_sizes: vec!(), + column_inline_sizes: Vec::new(), } } @@ -53,9 +45,7 @@ impl TableRowGroupFlow { -> TableRowGroupFlow { TableRowGroupFlow { block_flow: BlockFlow::from_node(constructor, node), - col_inline_sizes: vec!(), - col_min_inline_sizes: vec!(), - col_pref_inline_sizes: vec!(), + column_inline_sizes: Vec::new(), } } @@ -64,8 +54,8 @@ impl TableRowGroupFlow { } fn initialize_offsets(&mut self) -> (Au, Au, Au) { - // TODO: If border-collapse: collapse, block-start_offset, block-end_offset, and inline-start_offset - // should be updated. Currently, they are set as Au(0). + // TODO: If border-collapse: collapse, block-start_offset, block-end_offset, and + // inline-start_offset should be updated. Currently, they are set as Au(0). (Au(0), Au(0), Au(0)) } @@ -120,16 +110,8 @@ impl Flow for TableRowGroupFlow { &mut self.block_flow } - fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> { - &mut self.col_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 + fn column_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<ColumnInlineSize> { + &mut self.column_inline_sizes } /// 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. fn bubble_inline_sizes(&mut self) { let _scope = layout_debug_scope!("table_rowgroup::bubble_inline_sizes {:s}", - self.block_flow.base.debug_id()); - - let mut min_inline_size = Au(0); - let mut pref_inline_size = Au(0); + self.block_flow.base.debug_id()); + let mut computation = IntrinsicISizesContribution::new(); for kid in self.block_flow.base.child_iter() { assert!(kid.is_table_row()); - // calculate min_inline-size & pref_inline-size for automatic table layout calculation - // 'self.col_min_inline-sizes' collects the maximum value of cells' min-inline-sizes for each column. - // 'self.col_pref_inline-sizes' collects the maximum value of cells' pref-inline-sizes for each column. - if self.col_inline_sizes.is_empty() { // First Row - assert!(self.col_min_inline_sizes.is_empty() && self.col_pref_inline_sizes.is_empty()); - // '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(); + // Calculate minimum and preferred inline sizes for automatic table layout. + if self.column_inline_sizes.is_empty() { + // We're the first row. + debug_assert!(self.column_inline_sizes.is_empty()); + self.column_inline_sizes = kid.column_inline_sizes().clone(); } else { - min_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_min_inline_sizes, kid.col_min_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, + kid.column_inline_sizes()); // update the number of column inline-sizes from table-rows. - let num_cols = self.col_inline_sizes.len(); - let num_child_cols = kid.col_min_inline_sizes().len(); - for i in range(num_cols, num_child_cols) { - self.col_inline_sizes.push(Au::new(0)); - let new_kid_min = kid.col_min_inline_sizes()[i]; - self.col_min_inline_sizes.push(kid.col_min_inline_sizes()[i]); - let new_kid_pref = kid.col_pref_inline_sizes()[i]; - self.col_pref_inline_sizes.push(kid.col_pref_inline_sizes()[i]); - min_inline_size = min_inline_size + new_kid_min; - pref_inline_size = pref_inline_size + new_kid_pref; + let column_count = self.column_inline_sizes.len(); + let child_column_count = kid.column_inline_sizes().len(); + for i in range(column_count, child_column_count) { + let this_column_inline_size = (*kid.column_inline_sizes())[i]; + + // FIXME(pcwalton): Ignoring the percentage here seems dubious. + child_intrinsic_sizes.minimum_inline_size = + child_intrinsic_sizes.minimum_inline_size + + this_column_inline_size.minimum_length; + 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.preferred_inline_size = max( - min_inline_size, pref_inline_size); + self.block_flow.base.intrinsic_inline_sizes = computation.finish() } - /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When - /// called on this context, the context has had its inline-size set by the parent context. + /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. + /// 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) { let _scope = layout_debug_scope!("table_rowgroup::assign_inline_sizes {:s}", self.block_flow.base.debug_id()); @@ -199,9 +179,14 @@ impl Flow for TableRowGroupFlow { let content_inline_size = containing_block_inline_size; let inline_size_computer = InternalTable; - 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())); + 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.column_inline_sizes.as_slice())); } fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) { 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 + } +} + |