diff options
Diffstat (limited to 'components/layout/sizing.rs')
-rw-r--r-- | components/layout/sizing.rs | 297 |
1 files changed, 297 insertions, 0 deletions
diff --git a/components/layout/sizing.rs b/components/layout/sizing.rs new file mode 100644 index 00000000000..c6e4b7f9498 --- /dev/null +++ b/components/layout/sizing.rs @@ -0,0 +1,297 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! <https://drafts.csswg.org/css-sizing/> + +use std::cell::LazyCell; +use std::ops::{Add, AddAssign}; + +use app_units::Au; +use malloc_size_of_derive::MallocSizeOf; +use style::Zero; +use style::values::computed::LengthPercentage; + +use crate::context::LayoutContext; +use crate::geom::Size; +use crate::style_ext::{AspectRatio, Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, LayoutStyle}; +use crate::{ConstraintSpace, IndefiniteContainingBlock, LogicalVec2}; + +#[derive(PartialEq)] +pub(crate) enum IntrinsicSizingMode { + /// Used to refer to a min-content contribution or max-content contribution. + /// This is the size that a box contributes to its containing block’s min-content + /// or max-content size. Note this is based on the outer size of the box, + /// and takes into account the relevant sizing properties of the element. + /// <https://drafts.csswg.org/css-sizing-3/#contributions> + Contribution, + /// Used to refer to a min-content size or max-content size. + /// This is the size based on the contents of an element, without regard for its context. + /// Note this is usually based on the inner (content-box) size of the box, + /// and ignores the relevant sizing properties of the element. + /// <https://drafts.csswg.org/css-sizing-3/#intrinsic> + Size, +} + +#[derive(Clone, Copy, Debug, Default, MallocSizeOf)] +pub(crate) struct ContentSizes { + pub min_content: Au, + pub max_content: Au, +} + +/// <https://drafts.csswg.org/css-sizing/#intrinsic-sizes> +impl ContentSizes { + pub fn max(&self, other: Self) -> Self { + Self { + min_content: self.min_content.max(other.min_content), + max_content: self.max_content.max(other.max_content), + } + } + + pub fn max_assign(&mut self, other: Self) { + *self = self.max(other); + } + + pub fn union(&self, other: &Self) -> Self { + Self { + min_content: self.min_content.max(other.min_content), + max_content: self.max_content + other.max_content, + } + } + + pub fn map(&self, f: impl Fn(Au) -> Au) -> Self { + Self { + min_content: f(self.min_content), + max_content: f(self.max_content), + } + } +} + +impl Zero for ContentSizes { + fn zero() -> Self { + Au::zero().into() + } + + fn is_zero(&self) -> bool { + self.min_content.is_zero() && self.max_content.is_zero() + } +} + +impl Add for ContentSizes { + type Output = Self; + + fn add(self, rhs: Self) -> Self { + Self { + min_content: self.min_content + rhs.min_content, + max_content: self.max_content + rhs.max_content, + } + } +} + +impl AddAssign for ContentSizes { + fn add_assign(&mut self, rhs: Self) { + *self = self.add(rhs) + } +} + +impl ContentSizes { + /// Clamps the provided amount to be between the min-content and the max-content. + /// This is called "shrink-to-fit" in CSS2, and "fit-content" in CSS Sizing. + /// <https://drafts.csswg.org/css2/visudet.html#shrink-to-fit-float> + /// <https://drafts.csswg.org/css-sizing/#funcdef-width-fit-content> + pub fn shrink_to_fit(&self, available_size: Au) -> Au { + // This formula is slightly different than what the spec says, + // to ensure that the minimum wins for a malformed ContentSize + // whose min_content is larger than its max_content. + available_size.min(self.max_content).max(self.min_content) + } +} + +impl From<Au> for ContentSizes { + fn from(size: Au) -> Self { + Self { + min_content: size, + max_content: size, + } + } +} + +#[allow(clippy::too_many_arguments)] +pub(crate) fn outer_inline( + layout_style: &LayoutStyle, + containing_block: &IndefiniteContainingBlock, + auto_minimum: &LogicalVec2<Au>, + auto_block_size_stretches_to_containing_block: bool, + is_replaced: bool, + establishes_containing_block: bool, + get_preferred_aspect_ratio: impl FnOnce(&LogicalVec2<Au>) -> Option<AspectRatio>, + get_content_size: impl FnOnce(&ConstraintSpace) -> InlineContentSizesResult, +) -> InlineContentSizesResult { + let ContentBoxSizesAndPBM { + content_box_sizes, + pbm, + mut depends_on_block_constraints, + preferred_size_computes_to_auto, + } = layout_style.content_box_sizes_and_padding_border_margin(containing_block); + let margin = pbm.margin.map(|v| v.auto_is(Au::zero)); + let pbm_sums = LogicalVec2 { + block: pbm.padding_border_sums.block + margin.block_sum(), + inline: pbm.padding_border_sums.inline + margin.inline_sum(), + }; + let style = layout_style.style(); + let content_size = LazyCell::new(|| { + let constraint_space = if establishes_containing_block { + let available_block_size = containing_block + .size + .block + .map(|v| Au::zero().max(v - pbm_sums.block)); + let automatic_size = if preferred_size_computes_to_auto.block && + auto_block_size_stretches_to_containing_block + { + depends_on_block_constraints = true; + Size::Stretch + } else { + Size::FitContent + }; + ConstraintSpace::new( + content_box_sizes.block.resolve_extrinsic( + automatic_size, + auto_minimum.block, + available_block_size, + ), + style.writing_mode, + get_preferred_aspect_ratio(&pbm.padding_border_sums), + ) + } else { + // This assumes that there is no preferred aspect ratio, or that there is no + // block size constraint to be transferred so the ratio is irrelevant. + // We only get into here for anonymous blocks, for which the assumption holds. + ConstraintSpace::new( + containing_block.size.block.into(), + containing_block.writing_mode, + None, + ) + }; + get_content_size(&constraint_space) + }); + let resolve_non_initial = |inline_size, stretch_values| { + Some(match inline_size { + Size::Initial => return None, + Size::Numeric(numeric) => (numeric, numeric, false), + Size::MinContent => ( + content_size.sizes.min_content, + content_size.sizes.min_content, + content_size.depends_on_block_constraints, + ), + Size::MaxContent => ( + content_size.sizes.max_content, + content_size.sizes.max_content, + content_size.depends_on_block_constraints, + ), + Size::FitContent => ( + content_size.sizes.min_content, + content_size.sizes.max_content, + content_size.depends_on_block_constraints, + ), + Size::FitContentFunction(size) => { + let size = content_size.sizes.shrink_to_fit(size); + (size, size, content_size.depends_on_block_constraints) + }, + Size::Stretch => return stretch_values, + }) + }; + let (mut preferred_min_content, preferred_max_content, preferred_depends_on_block_constraints) = + resolve_non_initial(content_box_sizes.inline.preferred, None) + .unwrap_or_else(|| resolve_non_initial(Size::FitContent, None).unwrap()); + let (mut min_min_content, mut min_max_content, mut min_depends_on_block_constraints) = + resolve_non_initial( + content_box_sizes.inline.min, + Some((Au::zero(), Au::zero(), false)), + ) + .unwrap_or((auto_minimum.inline, auto_minimum.inline, false)); + let (mut max_min_content, max_max_content, max_depends_on_block_constraints) = + resolve_non_initial(content_box_sizes.inline.max, None) + .map(|(min_content, max_content, depends_on_block_constraints)| { + ( + Some(min_content), + Some(max_content), + depends_on_block_constraints, + ) + }) + .unwrap_or_default(); + + // https://drafts.csswg.org/css-sizing-3/#replaced-percentage-min-contribution + // > If the box is replaced, a cyclic percentage in the value of any max size property + // > or preferred size property (width/max-width/height/max-height), is resolved against + // > zero when calculating the min-content contribution in the corresponding axis. + // + // This means that e.g. the min-content contribution of `width: calc(100% + 100px)` + // should be 100px, but it's just zero on other browsers, so we do the same. + if is_replaced { + let has_percentage = |size: Size<LengthPercentage>| { + // We need a comment here to avoid breaking `./mach test-tidy`. + matches!(size, Size::Numeric(numeric) if numeric.has_percentage()) + }; + if content_box_sizes.inline.preferred.is_initial() && + has_percentage(style.box_size(containing_block.writing_mode).inline) + { + preferred_min_content = Au::zero(); + } + if content_box_sizes.inline.max.is_initial() && + has_percentage(style.max_box_size(containing_block.writing_mode).inline) + { + max_min_content = Some(Au::zero()); + } + } + + // Regardless of their sizing properties, tables are always forced to be at least + // as big as their min-content size, so floor the minimums. + if layout_style.is_table() { + min_min_content.max_assign(content_size.sizes.min_content); + min_max_content.max_assign(content_size.sizes.min_content); + min_depends_on_block_constraints |= content_size.depends_on_block_constraints; + } + + InlineContentSizesResult { + sizes: ContentSizes { + min_content: preferred_min_content + .clamp_between_extremums(min_min_content, max_min_content) + + pbm_sums.inline, + max_content: preferred_max_content + .clamp_between_extremums(min_max_content, max_max_content) + + pbm_sums.inline, + }, + depends_on_block_constraints: depends_on_block_constraints && + (preferred_depends_on_block_constraints || + min_depends_on_block_constraints || + max_depends_on_block_constraints), + } +} + +#[derive(Clone, Copy, Debug, MallocSizeOf)] +pub(crate) struct InlineContentSizesResult { + pub sizes: ContentSizes, + pub depends_on_block_constraints: bool, +} + +pub(crate) trait ComputeInlineContentSizes { + fn compute_inline_content_sizes( + &self, + layout_context: &LayoutContext, + constraint_space: &ConstraintSpace, + ) -> InlineContentSizesResult; + + /// Returns the same result as [`Self::compute_inline_content_sizes()`], but adjusted + /// to floor the max-content size by the min-content size. + /// This is being discussed in <https://github.com/w3c/csswg-drafts/issues/12076>. + fn compute_inline_content_sizes_with_fixup( + &self, + layout_context: &LayoutContext, + constraint_space: &ConstraintSpace, + ) -> InlineContentSizesResult { + let mut result = self.compute_inline_content_sizes(layout_context, constraint_space); + let sizes = &mut result.sizes; + sizes.max_content.max_assign(sizes.min_content); + result + } +} |