layout: Compute intrinsic sizes for flex items and flex containers (#32854)

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Mukilan Thiyagarajan <mukilan@igalia.com>
Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Delan Azabani 2024-08-02 14:45:11 +08:00 committed by GitHub
parent 7495ba20a5
commit 974c9dc89a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 625 additions and 222 deletions

View file

@ -7,7 +7,7 @@ use std::borrow::Cow;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use style::values::specified::text::TextDecorationLine;
use super::{FlexContainer, FlexLevelBox};
use super::{FlexContainer, FlexItemBox, FlexLevelBox};
use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
use crate::dom::{BoxSlot, LayoutBox, NodeExt};
@ -176,9 +176,11 @@ where
),
};
Some(ArcRefCell::new(FlexLevelBox::FlexItem(
IndependentFormattingContext::NonReplaced(non_replaced),
)))
Some(ArcRefCell::new(FlexLevelBox::FlexItem(FlexItemBox {
independent_formatting_context: IndependentFormattingContext::NonReplaced(
non_replaced,
),
})))
},
FlexLevelJob::Element {
info,
@ -201,15 +203,15 @@ where
)),
))
} else {
ArcRefCell::new(FlexLevelBox::FlexItem(
IndependentFormattingContext::construct(
ArcRefCell::new(FlexLevelBox::FlexItem(FlexItemBox {
independent_formatting_context: IndependentFormattingContext::construct(
self.context,
&info,
display_inside,
contents,
self.text_decoration_line,
),
))
}))
};
box_slot.set(LayoutBox::FlexLevel(box_.clone()));
Some(box_)
@ -227,6 +229,9 @@ where
FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(_) => 0,
});
FlexContainer { children }
FlexContainer {
children,
style: self.info.style.clone(),
}
}
}

View file

@ -8,7 +8,7 @@ use style::properties::longhands::flex_direction::computed_value::T as FlexDirec
use crate::geom::{LogicalRect, LogicalSides, LogicalVec2};
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Default)]
pub(super) struct FlexRelativeVec2<T> {
pub main: T,
pub cross: T,

View file

@ -8,6 +8,7 @@ use std::cmp::Ordering;
use app_units::Au;
use atomic_refcell::AtomicRefMut;
use itertools::izip;
use style::logical_geometry::WritingMode;
use style::properties::longhands::align_content::computed_value::T as AlignContent;
use style::properties::longhands::align_items::computed_value::T as AlignItems;
use style::properties::longhands::align_self::computed_value::T as AlignSelf;
@ -19,14 +20,14 @@ use style::properties::ComputedValues;
use style::values::computed::length::Size;
use style::values::computed::Length;
use style::values::generics::flex::GenericFlexBasis as FlexBasis;
use style::values::generics::length::LengthPercentageOrNormal;
use style::values::generics::length::{GenericLengthPercentageOrAuto, LengthPercentageOrNormal};
use style::values::specified::align::AlignFlags;
use style::Zero;
use super::geom::{
FlexAxis, FlexRelativeRect, FlexRelativeSides, FlexRelativeVec2, MainStartCrossStart,
};
use super::{FlexContainer, FlexLevelBox};
use super::{FlexContainer, FlexItemBox, FlexLevelBox};
use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
use crate::formatting_contexts::{Baselines, IndependentFormattingContext, IndependentLayout};
@ -168,13 +169,166 @@ impl FlexContext<'_> {
}
}
#[derive(Default)]
struct DesiredFlexFractionAndGrowOrShrinkFactor {
desired_flex_fraction: f32,
flex_grow_or_shrink_factor: f32,
}
#[derive(Default)]
struct FlexItemBoxInlineContentSizesInfo {
outer_flex_base_size: Au,
content_min_size_no_auto: FlexRelativeVec2<Au>,
content_max_size: FlexRelativeVec2<Option<Au>>,
pbm_auto_is_zero: FlexRelativeVec2<Au>,
min_flex_factors: DesiredFlexFractionAndGrowOrShrinkFactor,
max_flex_factors: DesiredFlexFractionAndGrowOrShrinkFactor,
min_content_main_size_for_multiline_container: Au,
}
impl FlexContainer {
pub fn inline_content_sizes(&self) -> ContentSizes {
// FIXME: implement this. The spec for it is the same as for "normal" layout:
// https://drafts.csswg.org/css-flexbox/#layout-algorithm
// … except that the parts that say “the flex container is being sized
// under a min or max-content constraint” apply.
ContentSizes::zero() // Return an incorrect result rather than panic
pub fn inline_content_sizes(
&mut self,
layout_context: &LayoutContext,
writing_mode: WritingMode,
) -> ContentSizes {
// - TODO: calculate intrinsic cross sizes when container is a column
// (and check for writing-mode?)
// - TODO: Collapsed flex items need to be skipped for intrinsic size calculation.
// <https://drafts.csswg.org/css-flexbox/#intrinsic-cross-sizes>
// > It is calculated, considering only non-collapsed flex items, by:
// > 1. For each flex item, subtract its outer flex base size from its max-content
// > contribution size.
let mut chosen_max_flex_fraction = f32::NEG_INFINITY;
let mut chosen_min_flex_fraction = f32::NEG_INFINITY;
let mut sum_of_flex_grow_factors = 0.0;
let mut sum_of_flex_shrink_factors = 0.0;
let mut item_infos = vec![];
let container_is_horizontal = self.style.writing_mode.is_horizontal();
let flex_direction = used_flex_direction(&*self.style);
let flex_axis = FlexAxis::from(flex_direction);
let flex_wrap = self.style.get_position().flex_wrap;
let flex_wrap_reverse = match flex_wrap {
FlexWrap::Nowrap | FlexWrap::Wrap => false,
FlexWrap::WrapReverse => true,
};
let main_start_cross_start = MainStartCrossStart::from(flex_direction, flex_wrap_reverse);
for kid in self.children.iter() {
let kid = &mut *kid.borrow_mut();
match kid {
FlexLevelBox::FlexItem(item) => {
sum_of_flex_grow_factors += item.style().get_position().flex_grow.0;
sum_of_flex_shrink_factors += item.style().get_position().flex_shrink.0;
let info = item.inline_content_size_info(
layout_context,
writing_mode,
container_is_horizontal,
flex_axis,
main_start_cross_start,
);
// > 2. Place all flex items into lines of infinite length. Within
// > each line, find the greatest (most > positive) desired flex
// > fraction among all the flex items. This is the lines chosen flex
// > fraction.
chosen_max_flex_fraction =
chosen_max_flex_fraction.max(info.max_flex_factors.desired_flex_fraction);
chosen_min_flex_fraction =
chosen_min_flex_fraction.max(info.min_flex_factors.desired_flex_fraction);
item_infos.push(info)
},
FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(_) => {},
}
}
let normalize_flex_fraction = |chosen_flex_fraction| {
if chosen_flex_fraction > 0.0 && sum_of_flex_grow_factors < 1.0 {
// > 3. If the chosen flex fraction is positive, and the sum of the lines
// > flex grow factors is less than 1, > divide the chosen flex fraction by that
// > sum.
chosen_flex_fraction / sum_of_flex_grow_factors
} else if chosen_flex_fraction < 0.0 && sum_of_flex_shrink_factors < 1.0 {
// > If the chosen flex fraction is negative, and the sum of the lines flex
// > shrink factors is less than 1, > multiply the chosen flex fraction by that
// > sum.
chosen_flex_fraction * sum_of_flex_shrink_factors
} else {
chosen_flex_fraction
}
};
let chosen_min_flex_fraction = normalize_flex_fraction(chosen_min_flex_fraction);
let chosen_max_flex_fraction = normalize_flex_fraction(chosen_max_flex_fraction);
let column_gap = match self.style.clone_column_gap() {
LengthPercentageOrNormal::LengthPercentage(length_percentage) => {
length_percentage.to_used_value(Au::zero())
},
LengthPercentageOrNormal::Normal => Au::zero(),
};
let extra_space_from_column_gap = column_gap * (item_infos.len() as i32 - 1);
let mut container_max_content_size = extra_space_from_column_gap;
let mut container_min_content_size = if flex_wrap == FlexWrap::Nowrap {
extra_space_from_column_gap
} else {
Au::zero()
};
for FlexItemBoxInlineContentSizesInfo {
outer_flex_base_size,
content_min_size_no_auto,
content_max_size,
pbm_auto_is_zero,
min_flex_factors,
max_flex_factors,
min_content_main_size_for_multiline_container,
} in item_infos.iter()
{
// > 4. Add each items flex base size to the product of its flex grow factor (scaled flex shrink
// > factor, if shrinking) and the chosen flex fraction, then clamp that result by the max main size
// > floored by the min main size.
let outer_min_main_size = content_min_size_no_auto.main + pbm_auto_is_zero.main;
let outer_max_main_size = content_max_size.main.map(|v| v + pbm_auto_is_zero.main);
// > 5. The flex containers max-content size is the largest sum (among all the lines) of the
// > afore-calculated sizes of all items within a single line.
container_max_content_size += (*outer_flex_base_size +
Au::from_f32_px(
max_flex_factors.flex_grow_or_shrink_factor * chosen_max_flex_fraction,
))
.clamp_between_extremums(outer_min_main_size, outer_max_main_size);
// > The min-content main size of a single-line flex container is calculated
// > identically to the max-content main size, except that the flex items
// > min-content contributions are used instead of their max-content contributions.
//
// > However, for a multi-line container, the min-content main size is simply the
// > largest min-content contribution of all the non-collapsed flex items in the
// > flex container. For this purpose, each items contribution is capped by the
// > items flex base size if the item is not growable, floored by the items flex
// > base size if the item is not shrinkable, and then further clamped by the items
// > min and max main sizes.
if flex_wrap == FlexWrap::Nowrap {
container_min_content_size += (*outer_flex_base_size +
Au::from_f32_px(
min_flex_factors.flex_grow_or_shrink_factor * chosen_min_flex_fraction,
))
.clamp_between_extremums(outer_min_main_size, outer_max_main_size);
} else {
container_min_content_size
.max_assign(*min_content_main_size_for_multiline_container);
}
}
ContentSizes {
min_content: container_min_content_size,
max_content: container_max_content_size,
}
}
/// <https://drafts.csswg.org/css-flexbox/#layout-algorithm>
@ -216,14 +370,8 @@ impl FlexContainer {
})
.collect::<Vec<_>>();
// Column flex containers are not fully implemented yet,
// so give a different layout instead of panicking.
// FIXME: implement `todo!`s for FlexAxis::Column below, and remove this
let container_style = containing_block.style;
let flex_direction = match container_style.clone_flex_direction() {
FlexDirection::Row | FlexDirection::Column => FlexDirection::Row,
FlexDirection::RowReverse | FlexDirection::ColumnReverse => FlexDirection::RowReverse,
};
let flex_direction = used_flex_direction(container_style);
let flex_axis = FlexAxis::from(flex_direction);
let (container_min_cross_size, container_max_cross_size) = self
@ -280,7 +428,7 @@ impl FlexContainer {
let flex_item_boxes = flex_items.iter_mut().map(|child| &mut **child);
let mut flex_items = flex_item_boxes
.map(|box_| FlexItem::new(&flex_context, box_))
.map(|flex_item_box| FlexItem::new(&flex_context, flex_item_box))
.collect::<Vec<_>>();
// “Determine the main size of the flex container”
@ -576,7 +724,7 @@ impl FlexContainer {
}
impl<'a> FlexItem<'a> {
fn new(flex_context: &FlexContext, box_: &'a mut IndependentFormattingContext) -> Self {
fn new(flex_context: &FlexContext, box_: &'a mut FlexItemBox) -> Self {
let containing_block = flex_context.containing_block;
// https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
@ -588,9 +736,11 @@ impl<'a> FlexItem<'a> {
let container_is_horizontal = containing_block.style.writing_mode.is_horizontal();
let item_is_horizontal = box_.style().writing_mode.is_horizontal();
let item_is_orthogonal = item_is_horizontal != container_is_horizontal;
let container_is_row = flex_context.flex_axis == FlexAxis::Row;
let cross_axis_is_item_block_axis = container_is_row ^ item_is_orthogonal;
let cross_axis_is_item_block_axis = cross_axis_is_item_block_axis(
container_is_horizontal,
item_is_horizontal,
flex_context.flex_axis,
);
let pbm = box_.style().padding_border_margin(containing_block);
let content_box_size = box_
@ -606,75 +756,16 @@ impl<'a> FlexItem<'a> {
.content_min_box_size(containing_block, &pbm)
.map(|v| v.map(Au::from));
// https://drafts.csswg.org/css-flexbox/#min-size-auto
let automatic_min_size = || {
// FIXME(stshine): Consider more situations when auto min size is not needed.
if box_.style().get_box().overflow_x.is_scrollable() {
return Au::zero();
}
if cross_axis_is_item_block_axis {
let specified_size_suggestion = content_box_size.inline;
let transferred_size_suggestion = match box_ {
IndependentFormattingContext::NonReplaced(_) => None,
IndependentFormattingContext::Replaced(ref bfc) => {
match (
bfc.contents
.inline_size_over_block_size_intrinsic_ratio(box_.style()),
content_box_size.block,
) {
(Some(ratio), AuOrAuto::LengthPercentage(block_size)) => {
let block_size = block_size.clamp_between_extremums(
min_size.block.auto_is(Au::zero),
max_size.block,
);
Some(block_size.scale_by(ratio))
},
_ => None,
}
},
};
let inline_content_size = box_
.inline_content_sizes(flex_context.layout_context)
.min_content;
let content_size_suggestion = match box_ {
IndependentFormattingContext::NonReplaced(_) => inline_content_size,
IndependentFormattingContext::Replaced(ref replaced) => {
if let Some(ratio) = replaced
.contents
.inline_size_over_block_size_intrinsic_ratio(box_.style())
{
inline_content_size.clamp_between_extremums(
min_size.block.auto_is(Au::zero).scale_by(ratio),
max_size.block.map(|l| l.scale_by(ratio)),
)
} else {
inline_content_size
}
},
};
let result = match specified_size_suggestion {
AuOrAuto::LengthPercentage(l) => l.min(content_size_suggestion),
AuOrAuto::Auto => {
if let Some(l) = transferred_size_suggestion {
l.min(content_size_suggestion)
} else {
content_size_suggestion
}
},
};
result.clamp_below_max(max_size.inline)
} else {
// FIXME(stshine): Implement this when main axis is item's block axis.
Au::zero()
}
};
let min_size = LogicalVec2 {
inline: min_size.inline.auto_is(automatic_min_size),
inline: min_size.inline.auto_is(|| {
box_.automatic_min_size(
flex_context.layout_context,
cross_axis_is_item_block_axis,
content_box_size,
min_size,
max_size,
)
}),
block: min_size.block.auto_is(Au::zero),
};
let margin_auto_is_zero = pbm.margin.auto_is(Au::zero);
@ -694,8 +785,9 @@ impl<'a> FlexItem<'a> {
let align_self = flex_context.align_for(&box_.style().clone_align_self());
let flex_base_size = flex_base_size(
flex_context,
box_,
flex_context.layout_context,
flex_context.container_definite_inner_size,
&mut box_.independent_formatting_context,
cross_axis_is_item_block_axis,
content_box_size,
padding_border,
@ -706,7 +798,7 @@ impl<'a> FlexItem<'a> {
let margin: FlexRelativeSides<AuOrAuto> = flex_context.sides_to_flex_relative(pbm.margin);
Self {
box_,
box_: &mut box_.independent_formatting_context,
content_box_size,
content_min_size,
content_max_size,
@ -723,7 +815,8 @@ impl<'a> FlexItem<'a> {
/// <https://drafts.csswg.org/css-flexbox/#algo-main-item>
fn flex_base_size(
flex_context: &FlexContext,
layout_context: &LayoutContext,
container_definite_inner_size: FlexRelativeVec2<Option<Au>>,
flex_item: &mut IndependentFormattingContext,
cross_axis_is_item_block_axis: bool,
content_box_size: FlexRelativeVec2<AuOrAuto>,
@ -744,7 +837,7 @@ fn flex_base_size(
};
// “For example, percentage values of flex-basis are resolved
// against the flex items containing block (i.e. its flex container);”
match flex_context.container_definite_inner_size.main {
match container_definite_inner_size.main {
Some(container_definite_main_size) => {
let length = length_percentage
.0
@ -791,9 +884,7 @@ fn flex_base_size(
// “treating a value of content as max-content.”
if cross_axis_is_item_block_axis {
// The main axis is the inline axis
flex_item
.inline_content_sizes(flex_context.layout_context)
.max_content
flex_item.inline_content_sizes(layout_context).max_content
} else {
// FIXME: block-axis content sizing requires another pass
// of "full" layout
@ -805,6 +896,27 @@ fn flex_base_size(
}
}
fn cross_axis_is_item_block_axis(
container_is_horizontal: bool,
item_is_horizontal: bool,
flex_axis: FlexAxis,
) -> bool {
let item_is_orthogonal = item_is_horizontal != container_is_horizontal;
let container_is_row = flex_axis == FlexAxis::Row;
container_is_row ^ item_is_orthogonal
}
fn used_flex_direction(container_style: &ComputedValues) -> FlexDirection {
// Column flex containers are not fully implemented yet,
// so give a different layout instead of panicking.
// FIXME: implement `todo!`s for FlexAxis::Column below, and remove this
match container_style.clone_flex_direction() {
FlexDirection::Row | FlexDirection::Column => FlexDirection::Row,
FlexDirection::RowReverse | FlexDirection::ColumnReverse => FlexDirection::RowReverse,
}
}
// “Collect flex items into flex lines”
// https://drafts.csswg.org/css-flexbox/#algo-line-break
fn collect_flex_lines<'items>(
@ -1625,3 +1737,315 @@ impl FlexItem<'_> {
outer_cross_start + margin.cross_start + self.border.cross_start + self.padding.cross_start
}
}
impl FlexItemBox {
fn inline_content_size_info(
&mut self,
layout_context: &LayoutContext,
container_writing_mode: WritingMode,
container_is_horizontal: bool,
flex_axis: FlexAxis,
main_start_cross_start: MainStartCrossStart,
) -> FlexItemBoxInlineContentSizesInfo {
let style = self.style().clone();
let item_writing_mode = style.writing_mode;
let item_is_horizontal = item_writing_mode.is_horizontal();
let cross_axis_is_item_block_axis =
cross_axis_is_item_block_axis(container_is_horizontal, item_is_horizontal, flex_axis);
let pbm = style.padding_border_margin_for_intrinsic_size(item_writing_mode);
let box_size = style
.box_size(item_writing_mode)
.map(|v| v.percentage_relative_to(Length::zero()));
let content_box_size = style
.content_box_size_for_box_size(box_size, &pbm)
.map(|v| v.map(Au::from));
let min_size = style
.min_box_size(item_writing_mode)
.map(|v| v.percentage_relative_to(Length::zero()));
let content_min_size = style
.content_min_box_size_for_min_size(min_size, &pbm)
.map(|v| v.map(Au::from));
let max_size = style
.max_box_size(item_writing_mode)
.map(|v| v.map(|v| v.percentage_relative_to(Length::zero())));
let content_max_size = style
.content_max_box_size_for_max_size(max_size, &pbm)
.map(|v| v.map(Au::from));
let automatic_min_size = self.automatic_min_size(
layout_context,
cross_axis_is_item_block_axis,
content_box_size,
content_min_size,
content_max_size,
);
let content_box_size = flex_axis.vec2_to_flex_relative(content_box_size);
let content_min_size_no_auto = LogicalVec2 {
inline: content_min_size.inline.auto_is(|| automatic_min_size),
block: content_min_size.block.auto_is(Au::zero),
};
let content_min_size_no_auto = flex_axis.vec2_to_flex_relative(content_min_size_no_auto);
let content_max_size = flex_axis.vec2_to_flex_relative(content_max_size);
let padding = main_start_cross_start.sides_to_flex_relative(pbm.padding);
let border = main_start_cross_start.sides_to_flex_relative(pbm.border);
let padding_border = padding.sum_by_axis() + border.sum_by_axis();
let margin_auto_is_zero = pbm.margin.auto_is(Au::zero);
let margin_auto_is_zero =
main_start_cross_start.sides_to_flex_relative(margin_auto_is_zero);
let pbm_auto_is_zero = FlexRelativeVec2 {
main: padding_border.main,
cross: padding_border.cross,
} + margin_auto_is_zero.sum_by_axis();
let flex_base_size = flex_base_size(
layout_context,
FlexRelativeVec2 {
main: None,
cross: None,
},
&mut self.independent_formatting_context,
cross_axis_is_item_block_axis,
content_box_size,
padding_border,
);
let content_contribution_sizes = self.inline_content_sizes(
layout_context,
container_writing_mode,
content_box_size,
content_min_size_no_auto,
content_max_size,
pbm_auto_is_zero,
);
let outer_flex_base_size = flex_base_size + pbm_auto_is_zero.main;
let max_flex_factors = self.desired_flex_factors_for_preferred_width(
content_contribution_sizes.max_content,
flex_base_size,
outer_flex_base_size,
);
// > The min-content main size of a single-line flex container is calculated
// > identically to the max-content main size, except that the flex items
// > min-content contributions are used instead of their max-content contributions.
let min_flex_factors = self.desired_flex_factors_for_preferred_width(
content_contribution_sizes.min_content,
flex_base_size,
outer_flex_base_size,
);
// > However, for a multi-line container, the min-content main size is simply the
// > largest min-content contribution of all the non-collapsed flex items in the
// > flex container. For this purpose, each items contribution is capped by the
// > items flex base size if the item is not growable, floored by the items flex
// > base size if the item is not shrinkable, and then further clamped by the items
// > min and max main sizes.
let mut min_content_main_size_for_multiline_container =
content_contribution_sizes.min_content;
if style.get_position().flex_grow.is_zero() {
min_content_main_size_for_multiline_container.min_assign(flex_base_size);
}
if style.get_position().flex_shrink.is_zero() {
min_content_main_size_for_multiline_container.max_assign(flex_base_size);
}
min_content_main_size_for_multiline_container =
min_content_main_size_for_multiline_container
.clamp_between_extremums(content_min_size_no_auto.main, content_max_size.main);
FlexItemBoxInlineContentSizesInfo {
outer_flex_base_size,
content_min_size_no_auto,
content_max_size,
pbm_auto_is_zero,
min_flex_factors,
max_flex_factors,
min_content_main_size_for_multiline_container,
}
}
fn desired_flex_factors_for_preferred_width(
&self,
preferred_width: Au,
flex_base_size: Au,
outer_flex_base_size: Au,
) -> DesiredFlexFractionAndGrowOrShrinkFactor {
let difference = (preferred_width - outer_flex_base_size).to_f32_px();
let (flex_grow_or_scaled_flex_shrink_factor, desired_flex_fraction) = if difference > 0.0 {
// > If that result is positive, divide it by the items flex
// > grow factor if the flex grow > factor is ≥ 1, or multiply
// > it by the flex grow factor if the flex grow factor is < 1;
let flex_grow_factor = self.style().get_position().flex_grow.0;
(
flex_grow_factor,
if flex_grow_factor >= 1.0 {
difference / flex_grow_factor
} else {
difference * flex_grow_factor
},
)
} else if difference < 0.0 {
// > if the result is negative, divide it by the items scaled
// > flex shrink factor (if dividing > by zero, treat the result
// > as negative infinity).
let flex_shrink_factor = self.style().get_position().flex_shrink.0;
let scaled_flex_shrink_factor = flex_shrink_factor * flex_base_size.to_f32_px();
(
scaled_flex_shrink_factor,
if scaled_flex_shrink_factor != 0.0 {
difference / scaled_flex_shrink_factor
} else {
f32::NEG_INFINITY
},
)
} else {
(0.0, 0.0)
};
DesiredFlexFractionAndGrowOrShrinkFactor {
desired_flex_fraction,
flex_grow_or_shrink_factor: flex_grow_or_scaled_flex_shrink_factor,
}
}
fn inline_content_sizes(
&mut self,
layout_context: &LayoutContext,
writing_mode: WritingMode,
content_box_size: FlexRelativeVec2<GenericLengthPercentageOrAuto<Au>>,
content_min_size: FlexRelativeVec2<Au>,
content_max_size: FlexRelativeVec2<Option<Au>>,
pbm_auto_is_zero: FlexRelativeVec2<Au>,
) -> ContentSizes {
// TODO: use cross sizes when container is a column
// (and check for writing-mode?)
// <https://drafts.csswg.org/css-flexbox/#intrinsic-item-contributions>
let outer_inline_content_sizes = self
.independent_formatting_context
.outer_inline_content_sizes(layout_context, writing_mode);
let outer_preferred_size = content_box_size
.main
.non_auto()
.map(|preferred_size| preferred_size + pbm_auto_is_zero.main);
let outer_min_main_size = content_min_size.main + pbm_auto_is_zero.main;
let outer_max_main_size = content_max_size
.main
.map(|max_main_size| max_main_size + pbm_auto_is_zero.main);
// > The main-size min-content contribution of a flex item is the larger of its
// > outer min-content size and outer preferred size if that is not auto, clamped by
// > its min/max main size.
let min_content_contribution = outer_preferred_size
.map_or(
outer_inline_content_sizes.min_content,
|outer_preferred_size| {
outer_preferred_size.max(outer_inline_content_sizes.min_content)
},
)
.clamp_between_extremums(outer_min_main_size, outer_max_main_size);
// > The main-size max-content contribution of a flex item is the larger of its
// > outer max-content size and outer preferred size if that is not auto, clamped by
// > its min/max main size.
let max_content_contribution = outer_preferred_size
.map_or(
outer_inline_content_sizes.max_content,
|outer_preferred_size| {
outer_preferred_size.max(outer_inline_content_sizes.max_content)
},
)
.clamp_between_extremums(outer_min_main_size, outer_max_main_size);
ContentSizes {
min_content: min_content_contribution,
max_content: max_content_contribution,
}
}
/// This is an implementation of <https://drafts.csswg.org/css-flexbox/#min-size-auto>.
fn automatic_min_size(
&mut self,
layout_context: &LayoutContext,
cross_axis_is_item_block_axis: bool,
content_box_size: LogicalVec2<AuOrAuto>,
min_size: LogicalVec2<GenericLengthPercentageOrAuto<Au>>,
max_size: LogicalVec2<Option<Au>>,
) -> Au {
// FIXME(stshine): Consider more situations when auto min size is not needed.
if self
.independent_formatting_context
.style()
.get_box()
.overflow_x
.is_scrollable()
{
return Au::zero();
}
if cross_axis_is_item_block_axis {
let specified_size_suggestion = content_box_size.inline;
let transferred_size_suggestion = match self.independent_formatting_context {
IndependentFormattingContext::NonReplaced(_) => None,
IndependentFormattingContext::Replaced(ref bfc) => {
match (
bfc.contents.inline_size_over_block_size_intrinsic_ratio(
self.independent_formatting_context.style(),
),
content_box_size.block,
) {
(Some(ratio), AuOrAuto::LengthPercentage(block_size)) => {
let block_size = block_size.clamp_between_extremums(
min_size.block.auto_is(Au::zero),
max_size.block,
);
Some(block_size.scale_by(ratio))
},
_ => None,
}
},
};
let inline_content_size = self
.independent_formatting_context
.inline_content_sizes(layout_context)
.min_content;
let content_size_suggestion = match self.independent_formatting_context {
IndependentFormattingContext::NonReplaced(_) => inline_content_size,
IndependentFormattingContext::Replaced(ref replaced) => {
if let Some(ratio) = replaced
.contents
.inline_size_over_block_size_intrinsic_ratio(
self.independent_formatting_context.style(),
)
{
inline_content_size.clamp_between_extremums(
min_size.block.auto_is(Au::zero).scale_by(ratio),
max_size.block.map(|l| l.scale_by(ratio)),
)
} else {
inline_content_size
}
},
};
let result = match specified_size_suggestion {
AuOrAuto::LengthPercentage(l) => l.min(content_size_suggestion),
AuOrAuto::Auto => {
if let Some(l) = transferred_size_suggestion {
l.min(content_size_suggestion)
} else {
content_size_suggestion
}
},
};
result.clamp_below_max(max_size.inline)
} else {
// FIXME(stshine): Implement this when main axis is item's block axis.
Au::zero()
}
}
}

View file

@ -3,6 +3,8 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use serde::Serialize;
use servo_arc::Arc as ServoArc;
use style::properties::ComputedValues;
use crate::cell::ArcRefCell;
use crate::formatting_contexts::IndependentFormattingContext;
@ -15,10 +17,24 @@ mod layout;
#[derive(Debug, Serialize)]
pub(crate) struct FlexContainer {
children: Vec<ArcRefCell<FlexLevelBox>>,
#[serde(skip_serializing)]
style: ServoArc<ComputedValues>,
}
#[derive(Debug, Serialize)]
pub(crate) enum FlexLevelBox {
FlexItem(IndependentFormattingContext),
FlexItem(FlexItemBox),
OutOfFlowAbsolutelyPositionedBox(ArcRefCell<AbsolutelyPositionedBox>),
}
#[derive(Debug, Serialize)]
pub(crate) struct FlexItemBox {
independent_formatting_context: IndependentFormattingContext,
}
impl FlexItemBox {
fn style(&self) -> &ServoArc<ComputedValues> {
self.independent_formatting_context.style()
}
}

View file

@ -258,7 +258,7 @@ impl NonReplacedFormattingContextContents {
Self::Flow(inner) => inner
.contents
.inline_content_sizes(layout_context, writing_mode),
Self::Flex(inner) => inner.inline_content_sizes(),
Self::Flex(inner) => inner.inline_content_sizes(layout_context, writing_mode),
Self::Table(table) => table.inline_content_sizes(layout_context, writing_mode),
}
}

View file

@ -16,7 +16,7 @@ use style::Zero;
use crate::style_ext::{Clamp, ComputedValuesExt};
#[derive(Clone, Copy, Debug, Serialize)]
#[derive(Clone, Copy, Debug, Default, Serialize)]
pub(crate) struct ContentSizes {
pub min_content: Au,
pub max_content: Au,

View file

@ -204,17 +204,36 @@ pub(crate) trait ComputedValuesExt {
containing_block: &ContainingBlock,
pbm: &PaddingBorderMargin,
) -> LogicalVec2<LengthOrAuto>;
fn content_box_size_for_box_size(
&self,
box_size: LogicalVec2<LengthOrAuto>,
pbm: &PaddingBorderMargin,
) -> LogicalVec2<LengthOrAuto>;
fn content_min_box_size(
&self,
containing_block: &ContainingBlock,
pbm: &PaddingBorderMargin,
) -> LogicalVec2<LengthOrAuto>;
fn content_min_box_size_for_min_size(
&self,
box_size: LogicalVec2<LengthOrAuto>,
pbm: &PaddingBorderMargin,
) -> LogicalVec2<LengthOrAuto>;
fn content_max_box_size(
&self,
containing_block: &ContainingBlock,
pbm: &PaddingBorderMargin,
) -> LogicalVec2<Option<Length>>;
fn content_max_box_size_for_max_size(
&self,
box_size: LogicalVec2<Option<Length>>,
pbm: &PaddingBorderMargin,
) -> LogicalVec2<Option<Length>>;
fn padding_border_margin(&self, containing_block: &ContainingBlock) -> PaddingBorderMargin;
fn padding_border_margin_for_intrinsic_size(
&self,
writing_mode: WritingMode,
) -> PaddingBorderMargin;
fn padding(
&self,
containing_block_writing_mode: WritingMode,
@ -316,6 +335,14 @@ impl ComputedValuesExt for ComputedValues {
let box_size = self
.box_size(containing_block.style.writing_mode)
.percentages_relative_to(containing_block);
self.content_box_size_for_box_size(box_size, pbm)
}
fn content_box_size_for_box_size(
&self,
box_size: LogicalVec2<LengthOrAuto>,
pbm: &PaddingBorderMargin,
) -> LogicalVec2<LengthOrAuto> {
match self.get_position().box_sizing {
BoxSizing::ContentBox => box_size,
BoxSizing::BorderBox => LogicalVec2 {
@ -336,9 +363,17 @@ impl ComputedValuesExt for ComputedValues {
containing_block: &ContainingBlock,
pbm: &PaddingBorderMargin,
) -> LogicalVec2<LengthOrAuto> {
let min_box_size = self
let box_size = self
.min_box_size(containing_block.style.writing_mode)
.percentages_relative_to(containing_block);
self.content_min_box_size_for_min_size(box_size, pbm)
}
fn content_min_box_size_for_min_size(
&self,
min_box_size: LogicalVec2<LengthOrAuto>,
pbm: &PaddingBorderMargin,
) -> LogicalVec2<LengthOrAuto> {
match self.get_position().box_sizing {
BoxSizing::ContentBox => min_box_size,
BoxSizing::BorderBox => LogicalVec2 {
@ -361,6 +396,15 @@ impl ComputedValuesExt for ComputedValues {
let max_box_size = self
.max_box_size(containing_block.style.writing_mode)
.percentages_relative_to(containing_block);
self.content_max_box_size_for_max_size(max_box_size, pbm)
}
fn content_max_box_size_for_max_size(
&self,
max_box_size: LogicalVec2<Option<Length>>,
pbm: &PaddingBorderMargin,
) -> LogicalVec2<Option<Length>> {
match self.get_position().box_sizing {
BoxSizing::ContentBox => max_box_size,
BoxSizing::BorderBox => {
@ -398,6 +442,28 @@ impl ComputedValuesExt for ComputedValues {
}
}
fn padding_border_margin_for_intrinsic_size(
&self,
writing_mode: WritingMode,
) -> PaddingBorderMargin {
let padding = self
.padding(writing_mode)
.percentages_relative_to(Length::zero());
let border = self.border_width(writing_mode);
let margin = self
.margin(writing_mode)
.percentages_relative_to(Length::zero());
PaddingBorderMargin {
padding_border_sums: LogicalVec2 {
inline: (padding.inline_sum() + border.inline_sum()).into(),
block: (padding.block_sum() + border.block_sum()).into(),
},
padding: padding.into(),
border: border.into(),
margin: margin.map(|t| t.map(|m| m.into())),
}
}
fn padding(
&self,
containing_block_writing_mode: WritingMode,

View file

@ -1,2 +0,0 @@
[gap-normal-used-002.html]
expected: FAIL

View file

@ -44,17 +44,8 @@
[.target > * 41]
expected: FAIL
[.target > * 43]
expected: FAIL
[.target > * 45]
expected: FAIL
[.target > * 47]
expected: FAIL
[.target > * 44]
expected: FAIL
[.target > * 48]
[.target > * 25]
expected: FAIL

View file

@ -1,2 +0,0 @@
[dynamic-orthogonal-flex-item.html]
expected: FAIL

View file

@ -1,16 +1,4 @@
[flex-item-contains-strict.html]
[.inline-flex 4]
expected: FAIL
[.inline-flex 7]
expected: FAIL
[.inline-flex 3]
expected: FAIL
[.inline-flex 8]
expected: FAIL
[.inline-flex 1]
expected: FAIL

View file

@ -0,0 +1,2 @@
[flex-minimum-height-flex-items-014.html]
expected: FAIL

View file

@ -1,6 +1,3 @@
[flex-minimum-size-001.html]
[.flexbox, .inline-flexbox 6]
expected: FAIL
[.flexbox, .inline-flexbox 2]
expected: FAIL

View file

@ -1,2 +0,0 @@
[flexbox-align-items-center-nested-001.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[flexbox-align-self-horiz-001-block.xhtml]
expected: FAIL

View file

@ -1,2 +0,0 @@
[flexbox-align-self-horiz-002.xhtml]
expected: FAIL

View file

@ -1,2 +0,0 @@
[flexbox-align-self-horiz-003.xhtml]
expected: FAIL

View file

@ -1,2 +0,0 @@
[flexbox-baseline-align-self-baseline-horiz-001.html]
expected: FAIL

View file

@ -0,0 +1,2 @@
[flexbox-collapsed-item-horiz-001.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[flexbox-definite-sizes-006.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[flexbox-gap-position-absolute.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[flexbox-sizing-vert-002.xhtml]
expected: FAIL

View file

@ -1,2 +0,0 @@
[flexbox-writing-mode-016.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[flexbox_order-box.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[inline-flex-min-content-height.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[col-wrap-009.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[row-002.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[row-003.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[row-004.html]
expected: FAIL

View file

@ -11,17 +11,8 @@
[.floating-flexbox 4]
expected: FAIL
[.floating-flexbox 5]
expected: FAIL
[.floating-flexbox 6]
expected: FAIL
[.floating-flexbox 7]
expected: FAIL
[.floating-flexbox 8]
expected: FAIL
[.floating-flexbox 9]
expected: FAIL

View file

@ -1,6 +0,0 @@
[row-use-cases-001.html]
[left < right]
expected: FAIL
[no overflow]
expected: FAIL

View file

@ -1,10 +1,4 @@
[row-wrap-001.html]
[.floating-flexbox 2]
expected: FAIL
[.floating-flexbox 3]
expected: FAIL
[.floating-flexbox 4]
expected: FAIL

View file

@ -4,6 +4,3 @@
[.inline-flexbox 2]
expected: FAIL
[.inline-flexbox 1]
expected: FAIL

View file

@ -1,3 +0,0 @@
[multiline-min-preferred-width.html]
[.container 1]
expected: FAIL

View file

@ -1,2 +0,0 @@
[negative-margins-001.html]
expected: FAIL

View file

@ -1,12 +0,0 @@
[overflow-auto-006.html]
[.flexbox, .inline-flexbox 10]
expected: FAIL
[.flexbox, .inline-flexbox 7]
expected: FAIL
[.flexbox, .inline-flexbox 9]
expected: FAIL
[.flexbox, .inline-flexbox 8]
expected: FAIL

View file

@ -1,2 +0,0 @@
[percentage-heights-004.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[percentage-heights-008.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[percentage-heights-009.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[percentage-padding-002.html]
expected: FAIL

View file

@ -1,3 +0,0 @@
[scrollbars-auto-min-content-sizing.html]
[scrollbars-auto-min-content-sizing]
expected: FAIL

View file

@ -1,2 +0,0 @@
[synthesize-vrl-baseline.html]
expected: FAIL