/* 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/. */ use std::cell::LazyCell; use std::convert::From; use std::fmt; use std::ops::{Add, AddAssign, Neg, Sub, SubAssign}; use app_units::{Au, MAX_AU}; use style::Zero; use style::logical_geometry::{BlockFlowDirection, Direction, InlineBaseDirection, WritingMode}; use style::values::computed::{ CSSPixelLength, LengthPercentage, MaxSize as StyleMaxSize, Percentage, Size as StyleSize, }; use style::values::generics::length::GenericLengthPercentageOrAuto as AutoOr; use style_traits::CSSPixel; use crate::ContainingBlock; use crate::sizing::ContentSizes; use crate::style_ext::Clamp; pub type PhysicalPoint = euclid::Point2D; pub type PhysicalSize = euclid::Size2D; pub type PhysicalVec = euclid::Vector2D; pub type PhysicalRect = euclid::Rect; pub type PhysicalSides = euclid::SideOffsets2D; pub type AuOrAuto = AutoOr; pub type LengthPercentageOrAuto<'a> = AutoOr<&'a LengthPercentage>; #[derive(Clone, Copy, PartialEq)] pub struct LogicalVec2 { pub inline: T, pub block: T, } #[derive(Clone, Copy)] pub struct LogicalRect { pub start_corner: LogicalVec2, pub size: LogicalVec2, } #[derive(Clone, Copy, Debug, Default)] pub struct LogicalSides { pub inline_start: T, pub inline_end: T, pub block_start: T, pub block_end: T, } #[derive(Clone, Copy, Debug)] pub(crate) struct LogicalSides1D { pub start: T, pub end: T, } impl fmt::Debug for LogicalVec2 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // Not using f.debug_struct on purpose here, to keep {:?} output somewhat compact f.write_str("Vec2 { i: ")?; self.inline.fmt(f)?; f.write_str(", b: ")?; self.block.fmt(f)?; f.write_str(" }") } } impl Default for LogicalVec2 { fn default() -> Self { Self { inline: T::default(), block: T::default(), } } } impl From for LogicalVec2 { fn from(value: T) -> Self { Self { inline: value, block: value, } } } impl LogicalVec2 { pub(crate) fn as_ref(&self) -> LogicalVec2<&T> { LogicalVec2 { inline: &self.inline, block: &self.block, } } pub fn map_inline_and_block_axes( &self, inline_f: impl FnOnce(&T) -> U, block_f: impl FnOnce(&T) -> U, ) -> LogicalVec2 { LogicalVec2 { inline: inline_f(&self.inline), block: block_f(&self.block), } } } impl LogicalVec2> { pub fn map_inline_and_block_sizes( &self, inline_f: impl FnOnce(T) -> U, block_f: impl FnOnce(T) -> U, ) -> LogicalVec2> { self.map_inline_and_block_axes(|size| size.map(inline_f), |size| size.map(block_f)) } } impl LogicalVec2 { pub fn from_physical_size(physical_size: &PhysicalSize, mode: WritingMode) -> Self { // https://drafts.csswg.org/css-writing-modes/#logical-to-physical let (i, b) = if mode.is_horizontal() { (&physical_size.width, &physical_size.height) } else { (&physical_size.height, &physical_size.width) }; LogicalVec2 { inline: i.clone(), block: b.clone(), } } pub fn map(&self, f: impl Fn(&T) -> U) -> LogicalVec2 { LogicalVec2 { inline: f(&self.inline), block: f(&self.block), } } } impl + Copy> Add> for LogicalVec2 { type Output = LogicalVec2; fn add(self, other: Self) -> Self::Output { LogicalVec2 { inline: self.inline + other.inline, block: self.block + other.block, } } } impl + Copy> Sub> for LogicalVec2 { type Output = LogicalVec2; fn sub(self, other: Self) -> Self::Output { LogicalVec2 { inline: self.inline - other.inline, block: self.block - other.block, } } } impl + Copy> AddAssign> for LogicalVec2 { fn add_assign(&mut self, other: LogicalVec2) { self.inline += other.inline; self.block += other.block; } } impl + Copy> SubAssign> for LogicalVec2 { fn sub_assign(&mut self, other: LogicalVec2) { self.inline -= other.inline; self.block -= other.block; } } impl + Copy> Neg for LogicalVec2 { type Output = LogicalVec2; fn neg(self) -> Self::Output { Self { inline: -self.inline, block: -self.block, } } } impl LogicalVec2 { pub fn zero() -> Self { Self { inline: T::zero(), block: T::zero(), } } } impl LogicalVec2> { pub fn auto_is(&self, f: impl Fn() -> T) -> LogicalVec2 { self.map(|t| t.auto_is(&f)) } } impl LogicalRect { pub fn zero() -> Self { Self { start_corner: LogicalVec2::zero(), size: LogicalVec2::zero(), } } } impl fmt::Debug for LogicalRect { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "Rect(i{}×b{} @ (i{},b{}))", self.size.inline.to_f32_px(), self.size.block.to_f32_px(), self.start_corner.inline.to_f32_px(), self.start_corner.block.to_f32_px(), ) } } impl LogicalVec2 { pub fn to_physical_size(&self, mode: WritingMode) -> PhysicalSize { // https://drafts.csswg.org/css-writing-modes/#logical-to-physical let (x, y) = if mode.is_horizontal() { (&self.inline, &self.block) } else { (&self.block, &self.inline) }; PhysicalSize::new(x.clone(), y.clone()) } } impl> LogicalVec2 { pub fn to_physical_vector(&self, mode: WritingMode) -> PhysicalVec { if mode.is_horizontal() { if mode.is_bidi_ltr() { PhysicalVec::new(self.inline, self.block) } else { PhysicalVec::new(-self.inline, self.block) } } else if mode.is_inline_tb() { PhysicalVec::new(self.block, self.inline) } else { PhysicalVec::new(-self.block, self.inline) } } } impl LogicalSides { pub fn from_physical(sides: &PhysicalSides, mode: WritingMode) -> Self { // https://drafts.csswg.org/css-writing-modes/#logical-to-physical let block_flow = mode.block_flow_direction(); let (bs, be) = match mode.block_flow_direction() { BlockFlowDirection::TopToBottom => (&sides.top, &sides.bottom), BlockFlowDirection::RightToLeft => (&sides.right, &sides.left), BlockFlowDirection::LeftToRight => (&sides.left, &sides.right), }; use BlockFlowDirection::TopToBottom; let (is, ie) = match (block_flow, mode.inline_base_direction()) { (TopToBottom, InlineBaseDirection::LeftToRight) => (&sides.left, &sides.right), (TopToBottom, InlineBaseDirection::RightToLeft) => (&sides.right, &sides.left), (_, InlineBaseDirection::LeftToRight) => (&sides.top, &sides.bottom), (_, InlineBaseDirection::RightToLeft) => (&sides.bottom, &sides.top), }; LogicalSides { inline_start: is.clone(), inline_end: ie.clone(), block_start: bs.clone(), block_end: be.clone(), } } } impl LogicalSides { pub fn map(&self, f: impl Fn(&T) -> U) -> LogicalSides { LogicalSides { inline_start: f(&self.inline_start), inline_end: f(&self.inline_end), block_start: f(&self.block_start), block_end: f(&self.block_end), } } pub fn map_inline_and_block_axes( &self, inline_f: impl Fn(&T) -> U, block_f: impl Fn(&T) -> U, ) -> LogicalSides { LogicalSides { inline_start: inline_f(&self.inline_start), inline_end: inline_f(&self.inline_end), block_start: block_f(&self.block_start), block_end: block_f(&self.block_end), } } pub fn inline_sum(&self) -> T::Output where T: Add + Copy, { self.inline_start + self.inline_end } pub fn block_sum(&self) -> T::Output where T: Add + Copy, { self.block_start + self.block_end } pub fn sum(&self) -> LogicalVec2 where T: Add + Copy, { LogicalVec2 { inline: self.inline_sum(), block: self.block_sum(), } } pub fn to_physical(&self, mode: WritingMode) -> PhysicalSides where T: Clone, { let top; let right; let bottom; let left; if mode.is_vertical() { if mode.is_vertical_lr() { left = self.block_start.clone(); right = self.block_end.clone(); } else { right = self.block_start.clone(); left = self.block_end.clone(); } if mode.is_inline_tb() { top = self.inline_start.clone(); bottom = self.inline_end.clone(); } else { bottom = self.inline_start.clone(); top = self.inline_end.clone(); } } else { top = self.block_start.clone(); bottom = self.block_end.clone(); if mode.is_bidi_ltr() { left = self.inline_start.clone(); right = self.inline_end.clone(); } else { right = self.inline_start.clone(); left = self.inline_end.clone(); } } PhysicalSides::new(top, right, bottom, left) } } impl LogicalSides { pub fn start_offset(&self) -> LogicalVec2 { LogicalVec2 { inline: self.inline_start, block: self.block_start, } } #[inline] pub(crate) fn inline_sides(&self) -> LogicalSides1D { LogicalSides1D::new(self.inline_start, self.inline_end) } #[inline] pub(crate) fn block_sides(&self) -> LogicalSides1D { LogicalSides1D::new(self.block_start, self.block_end) } } impl LogicalSides { pub fn percentages_relative_to(&self, basis: Au) -> LogicalSides { self.map(|value| value.to_used_value(basis)) } } impl LogicalSides> { pub fn percentages_relative_to(&self, basis: Au) -> LogicalSides { self.map(|value| value.map(|value| value.to_used_value(basis))) } } impl LogicalSides> { pub fn auto_is(&self, f: impl Fn() -> T) -> LogicalSides { self.map(|s| s.auto_is(&f)) } } impl + Copy> Add> for LogicalSides { type Output = LogicalSides; fn add(self, other: Self) -> Self::Output { LogicalSides { inline_start: self.inline_start + other.inline_start, inline_end: self.inline_end + other.inline_end, block_start: self.block_start + other.block_start, block_end: self.block_end + other.block_end, } } } impl + Copy> Sub> for LogicalSides { type Output = LogicalSides; fn sub(self, other: Self) -> Self::Output { LogicalSides { inline_start: self.inline_start - other.inline_start, inline_end: self.inline_end - other.inline_end, block_start: self.block_start - other.block_start, block_end: self.block_end - other.block_end, } } } impl + Copy> Neg for LogicalSides { type Output = LogicalSides; fn neg(self) -> Self::Output { Self { inline_start: -self.inline_start, inline_end: -self.inline_end, block_start: -self.block_start, block_end: -self.block_end, } } } impl LogicalSides { pub(crate) fn zero() -> LogicalSides { Self { inline_start: T::zero(), inline_end: T::zero(), block_start: T::zero(), block_end: T::zero(), } } } impl From> for LogicalSides { fn from(value: LogicalSides) -> Self { Self { inline_start: value.inline_start.into(), inline_end: value.inline_end.into(), block_start: value.block_start.into(), block_end: value.block_end.into(), } } } impl From> for LogicalSides { fn from(value: LogicalSides) -> Self { Self { inline_start: value.inline_start.into(), inline_end: value.inline_end.into(), block_start: value.block_start.into(), block_end: value.block_end.into(), } } } impl LogicalSides1D { #[inline] pub(crate) fn new(start: T, end: T) -> Self { Self { start, end } } } impl LogicalSides1D> { #[inline] pub(crate) fn either_specified(&self) -> bool { !self.start.is_auto() || !self.end.is_auto() } #[inline] pub(crate) fn either_auto(&self) -> bool { self.start.is_auto() || self.end.is_auto() } } impl LogicalSides1D { #[inline] pub(crate) fn sum(&self) -> T::Output { self.start + self.end } } impl LogicalRect { pub fn max_inline_position(&self) -> T where T: Add + Copy, { self.start_corner.inline + self.size.inline } pub fn max_block_position(&self) -> T where T: Add + Copy, { self.start_corner.block + self.size.block } pub fn inflate(&self, sides: &LogicalSides) -> Self where T: Add + Copy, T: Sub + Copy, { Self { start_corner: LogicalVec2 { inline: self.start_corner.inline - sides.inline_start, block: self.start_corner.block - sides.block_start, }, size: LogicalVec2 { inline: self.size.inline + sides.inline_sum(), block: self.size.block + sides.block_sum(), }, } } pub fn deflate(&self, sides: &LogicalSides) -> Self where T: Add + Copy, T: Sub + Copy, { LogicalRect { start_corner: LogicalVec2 { inline: self.start_corner.inline + sides.inline_start, block: self.start_corner.block + sides.block_start, }, size: LogicalVec2 { inline: self.size.inline - sides.inline_sum(), block: self.size.block - sides.block_sum(), }, } } } impl LogicalRect { pub(crate) fn as_physical( &self, containing_block: Option<&ContainingBlock<'_>>, ) -> PhysicalRect { let mode = containing_block.map_or_else(WritingMode::horizontal_tb, |containing_block| { containing_block.style.writing_mode }); let (x, y, width, height) = if mode.is_vertical() { // TODO: Bottom-to-top writing modes are not supported. ( self.start_corner.block, self.start_corner.inline, self.size.block, self.size.inline, ) } else { let y = self.start_corner.block; let x = match containing_block { Some(containing_block) if !mode.is_bidi_ltr() => { containing_block.size.inline - self.max_inline_position() }, _ => self.start_corner.inline, }; (x, y, self.size.inline, self.size.block) }; PhysicalRect::new(PhysicalPoint::new(x, y), PhysicalSize::new(width, height)) } } impl From> for LogicalVec2 { fn from(value: LogicalVec2) -> Self { LogicalVec2 { inline: value.inline.into(), block: value.block.into(), } } } impl From> for LogicalVec2 { fn from(value: LogicalVec2) -> Self { LogicalVec2 { inline: value.inline.into(), block: value.block.into(), } } } impl From> for LogicalRect { fn from(value: LogicalRect) -> Self { LogicalRect { start_corner: value.start_corner.into(), size: value.size.into(), } } } impl From> for LogicalRect { fn from(value: LogicalRect) -> Self { LogicalRect { start_corner: value.start_corner.into(), size: value.size.into(), } } } pub(crate) trait ToLogical { fn to_logical(&self, writing_mode: WritingMode) -> LogicalType; } impl ToLogical> for PhysicalSize { fn to_logical(&self, writing_mode: WritingMode) -> LogicalVec2 { LogicalVec2::from_physical_size(self, writing_mode) } } impl ToLogical> for PhysicalSides { fn to_logical(&self, writing_mode: WritingMode) -> LogicalSides { LogicalSides::from_physical(self, writing_mode) } } pub(crate) trait ToLogicalWithContainingBlock { fn to_logical(&self, containing_block: &ContainingBlock) -> LogicalType; } impl ToLogicalWithContainingBlock> for PhysicalPoint { fn to_logical(&self, containing_block: &ContainingBlock) -> LogicalVec2 { let writing_mode = containing_block.style.writing_mode; // TODO: Bottom-to-top and right-to-left vertical writing modes are not supported yet. if writing_mode.is_vertical() { LogicalVec2 { inline: self.y, block: self.x, } } else { LogicalVec2 { inline: if writing_mode.is_bidi_ltr() { self.x } else { containing_block.size.inline - self.x }, block: self.y, } } } } impl ToLogicalWithContainingBlock> for PhysicalRect { fn to_logical(&self, containing_block: &ContainingBlock) -> LogicalRect { let inline_start; let block_start; let inline; let block; let writing_mode = containing_block.style.writing_mode; if writing_mode.is_vertical() { // TODO: Bottom-to-top and right-to-left vertical writing modes are not supported yet. inline = self.size.height; block = self.size.width; block_start = self.origin.x; inline_start = self.origin.y; } else { inline = self.size.width; block = self.size.height; block_start = self.origin.y; if writing_mode.is_bidi_ltr() { inline_start = self.origin.x; } else { inline_start = containing_block.size.inline - (self.origin.x + self.size.width); } } LogicalRect { start_corner: LogicalVec2 { inline: inline_start, block: block_start, }, size: LogicalVec2 { inline, block }, } } } /// The possible values accepted by the sizing properties. /// #[derive(Clone, Debug, PartialEq)] pub(crate) enum Size { /// Represents an `auto` value for the preferred and minimum size properties, /// or `none` for the maximum size properties. /// /// Initial, /// MinContent, /// MaxContent, /// FitContent, /// Stretch, /// Represents a numeric ``, but resolved as a `T`. /// Numeric(T), } impl Copy for Size {} impl Default for Size { #[inline] fn default() -> Self { Self::Initial } } impl Size { #[inline] pub(crate) fn is_initial(&self) -> bool { matches!(self, Self::Initial) } } impl Size { #[inline] pub(crate) fn to_numeric(&self) -> Option { match self { Self::Numeric(numeric) => Some(numeric).cloned(), _ => None, } } #[inline] pub fn map(&self, f: impl FnOnce(T) -> U) -> Size { match self { Size::Initial => Size::Initial, Size::MinContent => Size::MinContent, Size::MaxContent => Size::MaxContent, Size::FitContent => Size::FitContent, Size::Stretch => Size::Stretch, Size::Numeric(numeric) => Size::Numeric(f(numeric.clone())), } } #[inline] pub fn maybe_map(&self, f: impl FnOnce(T) -> Option) -> Option> { Some(match self { Size::Numeric(numeric) => Size::Numeric(f(numeric.clone())?), _ => self.map(|_| unreachable!("This shouldn't be called for keywords")), }) } } impl From for Size { fn from(size: StyleSize) -> Self { match size { StyleSize::LengthPercentage(length) => Size::Numeric(length.0), StyleSize::Auto => Size::Initial, StyleSize::MinContent => Size::MinContent, StyleSize::MaxContent => Size::MaxContent, StyleSize::FitContent => Size::FitContent, StyleSize::Stretch => Size::Stretch, StyleSize::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"), } } } impl From for Size { fn from(max_size: StyleMaxSize) -> Self { match max_size { StyleMaxSize::LengthPercentage(length) => Size::Numeric(length.0), StyleMaxSize::None => Size::Initial, StyleMaxSize::MinContent => Size::MinContent, StyleMaxSize::MaxContent => Size::MaxContent, StyleMaxSize::FitContent => Size::FitContent, StyleMaxSize::Stretch => Size::Stretch, StyleMaxSize::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"), } } } impl Size { #[inline] pub(crate) fn to_percentage(&self) -> Option { self.to_numeric() .and_then(|length_percentage| length_percentage.to_percentage()) } } impl LogicalVec2> { pub(crate) fn maybe_percentages_relative_to_basis( &self, basis: &LogicalVec2>, ) -> LogicalVec2> { LogicalVec2 { inline: self .inline .maybe_map(|v| v.maybe_to_used_value(basis.inline)) .unwrap_or_default(), block: self .block .maybe_map(|v| v.maybe_to_used_value(basis.block)) .unwrap_or_default(), } } pub(crate) fn percentages_relative_to_basis( &self, basis: &LogicalVec2, ) -> LogicalVec2> { LogicalVec2 { inline: self.inline.map(|value| value.to_used_value(basis.inline)), block: self.block.map(|value| value.to_used_value(basis.block)), } } } impl Size { /// Resolves a preferred size into a numerical value. /// #[inline] pub(crate) fn resolve_for_preferred ContentSizes>( &self, automatic_size: Size, stretch_size: Option, content_size: &LazyCell, ) -> Au { match self { Self::Initial => { assert!(!automatic_size.is_initial()); automatic_size.resolve_for_preferred(automatic_size, stretch_size, content_size) }, Self::MinContent => content_size.min_content, Self::MaxContent => content_size.max_content, Self::FitContent => { content_size.shrink_to_fit(stretch_size.unwrap_or_else(|| content_size.max_content)) }, Self::Stretch => stretch_size.unwrap_or_else(|| content_size.max_content), Self::Numeric(numeric) => *numeric, } } /// Resolves a minimum size into a numerical value. /// #[inline] pub(crate) fn resolve_for_min ContentSizes>( &self, get_automatic_minimum_size: impl FnOnce() -> Au, stretch_size: Option, content_size: &LazyCell, ) -> Au { match self { Self::Initial => get_automatic_minimum_size(), Self::MinContent => content_size.min_content, Self::MaxContent => content_size.max_content, Self::FitContent => content_size.shrink_to_fit(stretch_size.unwrap_or_default()), Self::Stretch => stretch_size.unwrap_or_default(), Self::Numeric(numeric) => *numeric, } } /// Resolves a maximum size into a numerical value. /// #[inline] pub(crate) fn resolve_for_max ContentSizes>( &self, stretch_size: Option, content_size: &LazyCell, ) -> Option { Some(match self { Self::Initial => return None, Self::MinContent => content_size.min_content, Self::MaxContent => content_size.max_content, Self::FitContent => content_size.shrink_to_fit(stretch_size.unwrap_or(MAX_AU)), Self::Stretch => return stretch_size, Self::Numeric(numeric) => *numeric, }) } /// Tries to resolve an extrinsic size into a numerical value. /// Extrinsic sizes are those based on the context of an element, without regard for its contents. /// /// /// Returns `None` if either: /// - The size is intrinsic. /// - The size is the initial one. /// TODO: should we allow it to behave as `stretch` instead of assuming it's intrinsic? /// - The provided `stretch_size` is `None` but we need its value. #[inline] pub(crate) fn maybe_resolve_extrinsic(&self, stretch_size: Option) -> Option { match self { Self::Initial | Self::MinContent | Self::MaxContent | Self::FitContent => None, Self::Stretch => stretch_size, Self::Numeric(numeric) => Some(*numeric), } } } /// Represents the sizing constraint that the preferred, min and max sizing properties /// impose on one axis. #[derive(Clone, Copy, Debug, PartialEq)] pub(crate) enum SizeConstraint { /// Represents a definite preferred size, clamped by minimum and maximum sizes (if any). Definite(Au), /// Represents an indefinite preferred size that allows a range of values between /// the first argument (minimum size) and the second one (maximum size). MinMax(Au, Option), } impl Default for SizeConstraint { #[inline] fn default() -> Self { Self::MinMax(Au::default(), None) } } impl SizeConstraint { #[inline] pub(crate) fn new(preferred_size: Option, min_size: Au, max_size: Option) -> Self { preferred_size.map_or_else( || Self::MinMax(min_size, max_size), |size| Self::Definite(size.clamp_between_extremums(min_size, max_size)), ) } #[inline] pub(crate) fn is_definite(self) -> bool { matches!(self, Self::Definite(_)) } #[inline] pub(crate) fn to_definite(self) -> Option { match self { Self::Definite(size) => Some(size), _ => None, } } } impl From> for SizeConstraint { fn from(size: Option) -> Self { size.map(SizeConstraint::Definite).unwrap_or_default() } } #[derive(Clone, Default)] pub(crate) struct Sizes { /// pub preferred: Size, /// pub min: Size, /// pub max: Size, } impl Sizes { #[inline] pub(crate) fn new(preferred: Size, min: Size, max: Size) -> Self { Self { preferred, min, max, } } /// Resolves the three sizes into a single numerical value. #[inline] pub(crate) fn resolve( &self, axis: Direction, automatic_size: Size, get_automatic_minimum_size: impl FnOnce() -> Au, stretch_size: Option, get_content_size: impl FnOnce() -> ContentSizes, is_table: bool, ) -> Au { // The provided `get_content_size` is a FnOnce but we may need its result multiple times. // A LazyCell will only invoke it once if needed, and then reuse the result. let content_size = LazyCell::new(get_content_size); if is_table && axis == Direction::Block { // The intrinsic block size of a table already takes sizing properties into account, // but it can be a smaller amount if there are collapsed rows. // Therefore, disregard sizing properties and just defer to the intrinsic size. // This is being discussed in https://github.com/w3c/csswg-drafts/issues/11408 return content_size.max_content; } let preferred = self.preferred .resolve_for_preferred(automatic_size, stretch_size, &content_size); let mut min = self.min .resolve_for_min(get_automatic_minimum_size, stretch_size, &content_size); if is_table { // In addition to the specified minimum, the inline size of a table is forced to be // at least as big as its min-content size. // Note that if there are collapsed columns, only the inline size of the table grid will // shrink, while the size of the table wrapper (being computed here) won't be affected. // This is being discussed in https://github.com/w3c/csswg-drafts/issues/11408 min.max_assign(content_size.min_content); } let max = self.max.resolve_for_max(stretch_size, &content_size); preferred.clamp_between_extremums(min, max) } /// Tries to extrinsically resolve the three sizes into a single [`SizeConstraint`]. /// Values that are intrinsic or need `stretch_size` when it's `None` are handled as such: /// - On the preferred size, they make the returned value be an indefinite [`SizeConstraint::MinMax`]. /// - On the min size, they are treated as `auto`, enforcing the automatic minimum size. /// - On the max size, they are treated as `none`, enforcing no maximum. #[inline] pub(crate) fn resolve_extrinsic( &self, automatic_size: Size, automatic_minimum_size: Au, stretch_size: Option, ) -> SizeConstraint { let (preferred, min, max) = self.resolve_each_extrinsic(automatic_size, automatic_minimum_size, stretch_size); SizeConstraint::new(preferred, min, max) } /// Tries to extrinsically resolve each of the three sizes into a numerical value, separately. /// This can't resolve values that are intrinsic or need `stretch_size` but it's `None`. /// - The 1st returned value is the resolved preferred size. If it can't be resolved then /// the returned value is `None`. Note that this is different than treating it as `auto`. /// TODO: This needs to be discussed in . /// - The 2nd returned value is the resolved minimum size. If it can't be resolved then we /// treat it as the initial `auto`, returning the automatic minimum size. /// - The 3rd returned value is the resolved maximum size. If it can't be resolved then we /// treat it as the initial `none`, returning `None`. #[inline] pub(crate) fn resolve_each_extrinsic( &self, automatic_size: Size, automatic_minimum_size: Au, stretch_size: Option, ) -> (Option, Au, Option) { ( if self.preferred.is_initial() { automatic_size.maybe_resolve_extrinsic(stretch_size) } else { self.preferred.maybe_resolve_extrinsic(stretch_size) }, self.min .maybe_resolve_extrinsic(stretch_size) .unwrap_or(automatic_minimum_size), self.max.maybe_resolve_extrinsic(stretch_size), ) } }