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

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

Column spans are not yet supported.

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

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

View file

@ -2,15 +2,28 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* 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,
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>>) {
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,12 +1417,12 @@ 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(),
*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,
// 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),
@ -2411,9 +2437,10 @@ impl ISizeAndMarginsComputer for AbsoluteReplaced {
fn initial_computed_inline_size(&self,
block: &mut BlockFlow,
_: Au,
ctx: &LayoutContext)
layout_context: &LayoutContext)
-> MaybeAuto {
let containing_block_inline_size = block.containing_block_size(ctx.shared.screen_size).inline;
let containing_block_inline_size =
block.containing_block_size(layout_context.shared.screen_size).inline;
let fragment = block.fragment();
fragment.assign_replaced_inline_size_if_necessary(containing_block_inline_size);
// For replaced absolute flow, the rest of the constraint solving will
@ -2530,33 +2557,32 @@ 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],
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.
// 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;
}
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();
// 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();
let (margin_inline_start, margin_inline_end) = if use_margins {
(MaybeAuto::from_style(margin.inline_start, Au(0)).specified_or_zero(),
(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)
};
// 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();
let (padding_inline_start, padding_inline_end) = if use_padding {
(model::specified(padding.inline_start, Au(0)),
(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;
/// 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;
}
},
None => {}
}
sum_inline_sizes = sum_inline_sizes + *self_inline_size;
}
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>) {
@ -343,7 +342,9 @@ impl ISizeAndMarginsComputer for InternalTable {
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 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)
}
@ -97,8 +98,9 @@ impl Flow for TableCellFlow {
}
}
/// 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,12 +110,16 @@ 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,

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(),
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());
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,56 +150,64 @@ 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());
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());
@ -210,13 +215,19 @@ impl Flow for TableRowFlow {
// 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
@ -145,47 +127,45 @@ impl Flow for TableRowGroupFlow {
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);
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!();
/// 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_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();
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
}
}
// '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)
// 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
}
inline_size + padding_and_borders
// 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)
}
};
input.computed_inline_size = Specified(computed_inline_size);
input
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>