diff --git a/components/layout_2020/flow/mod.rs b/components/layout_2020/flow/mod.rs index 8bda0c69d24..d7b9854668a 100644 --- a/components/layout_2020/flow/mod.rs +++ b/components/layout_2020/flow/mod.rs @@ -20,7 +20,8 @@ use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIter use rayon_croissant::ParallelIteratorExt; use servo_arc::Arc; use style::properties::ComputedValues; -use style::values::computed::{Length, LengthOrAuto}; +use style::values::computed::{Length, LengthOrAuto, LengthPercentage, LengthPercentageOrAuto}; +use style::values::generics::length::MaxSize; use style::Zero; mod construct; @@ -355,27 +356,59 @@ fn layout_in_flow_non_replaced_block_level<'a>( let cbis = containing_block.inline_size; let padding = style.padding().percentages_relative_to(cbis); let border = style.border_width(); - let mut computed_margin = style.margin().percentages_relative_to(cbis); + let margin = style.margin().percentages_relative_to(cbis); let pb = &padding + &border; - let box_size = style.box_size(); - let inline_size = box_size.inline.percentage_relative_to(cbis); - if let LengthOrAuto::LengthPercentage(is) = inline_size { - let (margin_inline_start, margin_inline_end) = solve_inline_margins_for_in_flow_block_level( + let pb_inline_sum = pb.inline_sum(); + + let box_size = percent_resolved_box_size(style.box_size(), containing_block); + let max_box_size = percent_resolved_max_box_size(style.max_box_size(), containing_block); + let min_box_size = + percent_resolved_box_size(style.min_box_size(), containing_block).auto_is(Length::zero); + + // https://drafts.csswg.org/css2/visudet.html#min-max-widths + let solve_inline_margins = |inline_size| { + solve_inline_margins_for_in_flow_block_level( containing_block, - pb.inline_sum(), - computed_margin.inline_start, - computed_margin.inline_end, - is, - ); - computed_margin.inline_start = LengthOrAuto::LengthPercentage(margin_inline_start); - computed_margin.inline_end = LengthOrAuto::LengthPercentage(margin_inline_end); + pb_inline_sum, + margin.inline_start, + margin.inline_end, + inline_size, + ) + }; + let (mut inline_size, mut inline_margins) = + if let Some(inline_size) = box_size.inline.non_auto() { + (inline_size, solve_inline_margins(inline_size)) + } else { + let margin_inline_start = margin.inline_start.auto_is(Length::zero); + let margin_inline_end = margin.inline_end.auto_is(Length::zero); + let margin_inline_sum = margin_inline_start + margin_inline_end; + let inline_size = cbis - pb_inline_sum - margin_inline_sum; + (inline_size, (margin_inline_start, margin_inline_end)) + }; + if let Some(max_inline_size) = max_box_size.inline { + if inline_size > max_inline_size { + inline_size = max_inline_size; + inline_margins = solve_inline_margins(inline_size); + } } - let margin = computed_margin.auto_is(Length::zero); - let mut block_margins_collapsed_with_children = CollapsedBlockMargins::from_margin(&margin); - let inline_size = inline_size.auto_is(|| cbis - pb.inline_sum() - margin.inline_sum()); - let block_size = box_size - .block - .maybe_percentage_relative_to(containing_block.block_size.non_auto()); + if inline_size < min_box_size.inline { + inline_size = min_box_size.inline; + inline_margins = solve_inline_margins(inline_size); + } + + let margin = Sides { + inline_start: inline_margins.0, + inline_end: inline_margins.1, + block_start: margin.block_start.auto_is(Length::zero), + block_end: margin.block_end.auto_is(Length::zero), + }; + + // https://drafts.csswg.org/css2/visudet.html#min-max-heights + let mut block_size = box_size.block; + if let LengthOrAuto::LengthPercentage(ref mut block_size) = block_size { + *block_size = clamp_between_extremums(*block_size, min_box_size.block, max_box_size.block); + } + let containing_block_for_children = ContainingBlock { inline_size, block_size, @@ -386,16 +419,15 @@ fn layout_in_flow_non_replaced_block_level<'a>( containing_block.mode, containing_block_for_children.mode, "Mixed writing modes are not supported yet" ); + let this_start_margin_can_collapse_with_children = CollapsibleWithParentStartMargin( block_level_kind == BlockLevelKind::SameFormattingContextBlock && pb.block_start == Length::zero(), ); - let this_end_margin_can_collapse_with_children = (block_level_kind, pb.block_end, block_size) == - ( - BlockLevelKind::SameFormattingContextBlock, - Length::zero(), - LengthOrAuto::Auto, - ); + let this_end_margin_can_collapse_with_children = block_size == LengthOrAuto::Auto && + min_box_size.block == Length::zero() && + pb.block_end == Length::zero() && + block_level_kind == BlockLevelKind::SameFormattingContextBlock; let mut nested_abspos = vec![]; let mut flow_layout = layout_contents( &containing_block_for_children, @@ -406,6 +438,7 @@ fn layout_in_flow_non_replaced_block_level<'a>( }, this_start_margin_can_collapse_with_children, ); + let mut block_margins_collapsed_with_children = CollapsedBlockMargins::from_margin(&margin); if this_start_margin_can_collapse_with_children.0 { block_margins_collapsed_with_children .start @@ -436,7 +469,13 @@ fn layout_in_flow_non_replaced_block_level<'a>( .collapsible_margins_in_children .collapsed_through; let relative_adjustement = relative_adjustement(style, inline_size, block_size); - let block_size = block_size.auto_is(|| flow_layout.content_block_size); + let block_size = block_size.auto_is(|| { + clamp_between_extremums( + flow_layout.content_block_size, + min_box_size.block, + max_box_size.block, + ) + }); let content_rect = Rect { start_corner: Vec2 { block: pb.block_start + relative_adjustement.block, @@ -486,23 +525,124 @@ fn layout_in_flow_replaced_block_level<'a>( let intrinsic_size = replaced.intrinsic_size.size_to_flow_relative(mode); // FIXME(nox): This can divide by zero. let intrinsic_ratio = intrinsic_size.inline.px() / intrinsic_size.block.px(); - let box_size = style.box_size(); - let inline_size = box_size.inline.percentage_relative_to(cbis); - let block_size = box_size - .block - .maybe_percentage_relative_to(containing_block.block_size.non_auto()); - let (inline_size, block_size) = match (inline_size, block_size) { + + let box_size = percent_resolved_box_size(style.box_size(), containing_block); + let min_box_size = + percent_resolved_box_size(style.min_box_size(), containing_block).auto_is(Length::zero); + let max_box_size = percent_resolved_max_box_size(style.max_box_size(), containing_block); + + let clamp = |inline_size, block_size| { + ( + clamp_between_extremums(inline_size, min_box_size.inline, max_box_size.inline), + clamp_between_extremums(block_size, min_box_size.block, max_box_size.block), + ) + }; + // https://drafts.csswg.org/css2/visudet.html#min-max-widths + // https://drafts.csswg.org/css2/visudet.html#min-max-heights + let (inline_size, block_size) = match (box_size.inline, box_size.block) { (LengthOrAuto::LengthPercentage(inline), LengthOrAuto::LengthPercentage(block)) => { - (inline, block) + clamp(inline, block) }, (LengthOrAuto::LengthPercentage(inline), LengthOrAuto::Auto) => { - (inline, inline / intrinsic_ratio) + clamp(inline, inline / intrinsic_ratio) }, (LengthOrAuto::Auto, LengthOrAuto::LengthPercentage(block)) => { - (block * intrinsic_ratio, block) + clamp(block * intrinsic_ratio, block) + }, + (LengthOrAuto::Auto, LengthOrAuto::Auto) => { + enum Violation { + None, + Below(Length), + Above(Length), + } + let violation = |size, min_size, mut max_size: Option| { + if let Some(max) = max_size.as_mut() { + max.max_assign(min_size); + } + if size < min_size { + return Violation::Below(min_size); + } + match max_size { + Some(max_size) if size > max_size => Violation::Above(max_size), + _ => Violation::None, + } + }; + match ( + violation( + intrinsic_size.inline, + min_box_size.inline, + max_box_size.inline, + ), + violation(intrinsic_size.block, min_box_size.block, max_box_size.block), + ) { + // Row 1. + (Violation::None, Violation::None) => (intrinsic_size.inline, intrinsic_size.block), + // Row 2. + (Violation::Above(max_inline_size), Violation::None) => { + let block_size = (max_inline_size / intrinsic_ratio).max(min_box_size.block); + (max_inline_size, block_size) + }, + // Row 3. + (Violation::Below(min_inline_size), Violation::None) => { + let block_size = + clamp_below_max(min_inline_size / intrinsic_ratio, max_box_size.block); + (min_inline_size, block_size) + }, + // Row 4. + (Violation::None, Violation::Above(max_block_size)) => { + let inline_size = (max_block_size * intrinsic_ratio).max(min_box_size.inline); + (inline_size, max_block_size) + }, + // Row 5. + (Violation::None, Violation::Below(min_block_size)) => { + let inline_size = + clamp_below_max(min_block_size * intrinsic_ratio, max_box_size.inline); + (inline_size, min_block_size) + }, + // Rows 6-7. + (Violation::Above(max_inline_size), Violation::Above(max_block_size)) => { + if max_inline_size.px() / intrinsic_size.inline.px() <= + max_block_size.px() / intrinsic_size.block.px() + { + // Row 6. + let block_size = + (max_inline_size / intrinsic_ratio).max(min_box_size.block); + (max_inline_size, block_size) + } else { + // Row 7. + let inline_size = + (max_block_size * intrinsic_ratio).max(min_box_size.inline); + (inline_size, max_block_size) + } + }, + // Rows 8-9. + (Violation::Below(min_inline_size), Violation::Below(min_block_size)) => { + if min_inline_size.px() / intrinsic_size.inline.px() <= + min_block_size.px() / intrinsic_size.block.px() + { + // Row 8. + let inline_size = + clamp_below_max(min_block_size * intrinsic_ratio, max_box_size.inline); + (inline_size, min_block_size) + } else { + // Row 9. + let block_size = + clamp_below_max(min_inline_size / intrinsic_ratio, max_box_size.block); + (min_inline_size, block_size) + } + }, + // Row 10. + (Violation::Below(min_inline_size), Violation::Above(max_block_size)) => { + (min_inline_size, max_block_size) + }, + // Row 11. + (Violation::Above(max_inline_size), Violation::Below(min_block_size)) => { + (max_inline_size, min_block_size) + }, + } }, - (LengthOrAuto::Auto, LengthOrAuto::Auto) => (intrinsic_size.inline, intrinsic_size.block), }; + let (margin_inline_start, margin_inline_end) = solve_inline_margins_for_in_flow_block_level( containing_block, pb.inline_sum(), @@ -567,3 +707,45 @@ fn solve_inline_margins_for_in_flow_block_level( (LengthOrAuto::LengthPercentage(start), _) => (start, inline_margins - start), } } + +fn clamp_between_extremums(size: Length, min_size: Length, max_size: Option) -> Length { + clamp_below_max(size, max_size).max(min_size) +} + +fn clamp_below_max(size: Length, max_size: Option) -> Length { + max_size.map_or(size, |max_size| size.min(max_size)) +} + +fn percent_resolved_box_size( + box_size: Vec2, + containing_block: &ContainingBlock, +) -> Vec2 { + Vec2 { + inline: box_size + .inline + .percentage_relative_to(containing_block.inline_size), + block: box_size + .block + .maybe_percentage_relative_to(containing_block.block_size.non_auto()), + } +} + +fn percent_resolved_max_box_size( + max_box_size: Vec2>, + containing_block: &ContainingBlock, +) -> Vec2> { + Vec2 { + inline: match max_box_size.inline { + MaxSize::LengthPercentage(max_inline_size) => { + Some(max_inline_size.percentage_relative_to(containing_block.inline_size)) + }, + MaxSize::None => None, + }, + block: match max_box_size.block { + MaxSize::LengthPercentage(max_block_size) => { + max_block_size.maybe_percentage_relative_to(containing_block.block_size.non_auto()) + }, + MaxSize::None => None, + }, + } +} diff --git a/components/layout_2020/geom.rs b/components/layout_2020/geom.rs index d75a1634561..39c7cb331e4 100644 --- a/components/layout_2020/geom.rs +++ b/components/layout_2020/geom.rs @@ -141,6 +141,15 @@ impl flow_relative::Vec2 { } } +impl flow_relative::Vec2 { + pub fn auto_is(&self, f: impl Fn() -> Length) -> flow_relative::Vec2 { + flow_relative::Vec2 { + inline: self.inline.auto_is(&f), + block: self.block.auto_is(&f), + } + } +} + impl flow_relative::Rect { pub fn zero() -> Self { Self { diff --git a/components/layout_2020/style_ext.rs b/components/layout_2020/style_ext.rs index e6d4537f47c..8c145f23c6f 100644 --- a/components/layout_2020/style_ext.rs +++ b/components/layout_2020/style_ext.rs @@ -4,7 +4,9 @@ use crate::geom::{flow_relative, physical}; use style::properties::ComputedValues; -use style::values::computed::{Length, LengthPercentage, LengthPercentageOrAuto, Size}; +use style::values::computed::{Length, LengthPercentage, LengthPercentageOrAuto}; +use style::values::computed::{NonNegativeLengthPercentage, Size}; +use style::values::generics::length::MaxSize; use style::values::specified::box_ as stylo; pub use style::computed_values::direction::T as Direction; @@ -45,6 +47,8 @@ pub(crate) trait ComputedValuesExt { fn writing_mode(&self) -> (WritingMode, Direction); fn box_offsets(&self) -> flow_relative::Sides; fn box_size(&self) -> flow_relative::Vec2; + fn min_box_size(&self) -> flow_relative::Vec2; + fn max_box_size(&self) -> flow_relative::Vec2>; fn padding(&self) -> flow_relative::Sides; fn border_width(&self) -> flow_relative::Sides; fn margin(&self) -> flow_relative::Sides; @@ -80,6 +84,30 @@ impl ComputedValuesExt for ComputedValues { .size_to_flow_relative(self.writing_mode()) } + #[inline] + fn min_box_size(&self) -> flow_relative::Vec2 { + let position = self.get_position(); + physical::Vec2 { + x: size_to_length(position.min_width), + y: size_to_length(position.min_height), + } + .size_to_flow_relative(self.writing_mode()) + } + + #[inline] + fn max_box_size(&self) -> flow_relative::Vec2> { + let unwrap = |max_size: MaxSize| match max_size { + MaxSize::LengthPercentage(length) => MaxSize::LengthPercentage(length.0), + MaxSize::None => MaxSize::None, + }; + let position = self.get_position(); + physical::Vec2 { + x: unwrap(position.max_width), + y: unwrap(position.max_height), + } + .size_to_flow_relative(self.writing_mode()) + } + #[inline] fn padding(&self) -> flow_relative::Sides { let padding = self.get_padding(); diff --git a/components/style/properties/longhands/position.mako.rs b/components/style/properties/longhands/position.mako.rs index 95595f36316..d974a0f5915 100644 --- a/components/style/properties/longhands/position.mako.rs +++ b/components/style/properties/longhands/position.mako.rs @@ -289,7 +289,6 @@ ${helpers.predefined_type( "Size", "computed::Size::auto()", engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.2020.unimplemented", logical=logical, logical_group="min-size", allow_quirks="No" if logical else "Yes", @@ -302,7 +301,6 @@ ${helpers.predefined_type( "MaxSize", "computed::MaxSize::none()", engines="gecko servo-2013 servo-2020", - servo_2020_pref="layout.2020.unimplemented", logical=logical, logical_group="max-size", allow_quirks="No" if logical else "Yes",