layout: Rewrite intrinsic inline-size and automatic table layout to

match L. David Baron's work-in-progress specification.

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

Column spans are not yet supported.

This effectively adds support for percentage widths, and it also fixes
many bugs, improving the layout of Google and Wikipedia.
This commit is contained in:
Patrick Walton 2014-10-14 14:17:57 -07:00
parent c9ce56a85d
commit c7e619dfe7
20 changed files with 1081 additions and 618 deletions

View file

@ -2,15 +2,28 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* 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
}
}

View file

@ -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);

View file

@ -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

View file

@ -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,
}
}

View file

@ -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,

View file

@ -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,
}
impl IntrinsicISizesContribution {
/// Creates and initializes an inline size computation with all sizes set to zero.
pub fn new() -> IntrinsicISizesContribution {
IntrinsicISizesContribution {
content_intrinsic_sizes: IntrinsicISizes::new(),
surrounding_size: Au(0),
}
}
pub fn total_minimum_inline_size(&self) -> Au {
self.minimum_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,
}
}
pub fn total_preferred_inline_size(&self) -> Au {
self.preferred_inline_size + self.surround_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 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)
}
}

View file

@ -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());
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);
// 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;
// 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
}
}
}

View file

@ -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>) {

View file

@ -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) {}

View file

@ -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 column_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<ColumnInlineSize> {
&mut self.column_inline_sizes
}
fn col_min_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
&self.col_min_inline_sizes
}
fn col_pref_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
&self.col_pref_inline_sizes
}
/// Recursively (bottom-up) determines the context's preferred and minimum inline-sizes. When 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);
inline_size_computer.compute_used_inline_size(&mut self.block_flow,
ctx,
containing_block_inline_size);
self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, Au(0), Some(self.col_inline_sizes.clone()));
self.block_flow
.propagate_assigned_inline_size_to_children(inline_start_content_edge,
containing_block_inline_size,
Some(self.column_inline_sizes.as_slice()));
}
fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
@ -242,3 +253,4 @@ impl fmt::Show for TableRowFlow {
write!(f, "TableRowFlow: {}", self.block_flow.fragment)
}
}

View file

@ -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);
inline_size_computer.compute_used_inline_size(&mut self.block_flow,
ctx,
containing_block_inline_size);
self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, content_inline_size, Some(self.col_inline_sizes.clone()));
self.block_flow.propagate_assigned_inline_size_to_children(
inline_start_content_edge,
content_inline_size,
Some(self.column_inline_sizes.as_slice()));
}
fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {

View file

@ -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
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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