/* 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; use serde::Serialize; use style::logical_geometry::{BlockFlowDirection, InlineBaseDirection, WritingMode}; use style::values::computed::{ CSSPixelLength, LengthPercentage, MaxSize as StyleMaxSize, Size as StyleSize, }; use style::values::generics::length::GenericLengthPercentageOrAuto as AutoOr; use style::Zero; use style_traits::CSSPixel; use crate::sizing::ContentSizes; use crate::style_ext::Clamp; use crate::ContainingBlock; 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, Serialize)] pub struct LogicalVec2 { pub inline: T, pub block: T, } #[derive(Clone, Copy, Serialize)] pub struct LogicalRect { pub start_corner: LogicalVec2, pub size: LogicalVec2, } #[derive(Clone, Copy, Debug, Serialize)] pub struct LogicalSides { pub inline_start: T, pub inline_end: T, pub block_start: T, pub block_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 LogicalVec2 { 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 LogicalVec2 { #[inline] pub fn to_physical_point( &self, containing_block: Option<&ContainingBlock>, ) -> PhysicalPoint { let mode = containing_block.map_or_else(WritingMode::horizontal_tb, |containing_block| { containing_block.style.writing_mode }); if mode.is_vertical() { // TODO: Bottom-to-top writing modes are not supported yet. PhysicalPoint::new(self.block, self.inline) } else { let y = self.block; let x = match containing_block { Some(containing_block) if !mode.is_bidi_ltr() => { containing_block.inline_size - self.inline }, _ => self.inline, }; PhysicalPoint::new(x, y) } } } 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, } } } impl LogicalSides<&'_ LengthPercentage> { 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 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 fn to_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.inline_size - 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.inline_size - 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.inline_size - (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, 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_numeric(&self) -> bool { matches!(self, Self::Numeric(_)) } #[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(crate) fn to_auto_or(&self) -> AutoOr { self.to_numeric() .map_or(AutoOr::Auto, AutoOr::LengthPercentage) } #[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 LogicalVec2> { pub(crate) fn percentages_relative_to( &self, containing_block: &ContainingBlock, ) -> LogicalVec2> { self.map_inline_and_block_axes( |inline_size| inline_size.map(|lp| lp.to_used_value(containing_block.inline_size)), |block_size| { block_size .maybe_map(|lp| lp.maybe_to_used_value(containing_block.block_size.non_auto())) .unwrap_or_default() }, ) } 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 any size into a numerical value. #[inline] pub(crate) fn resolve ContentSizes>( &self, initial_behavior: Self, stretch_size: Au, content_size: &LazyCell, ) -> Au { if self.is_initial() { assert!(!initial_behavior.is_initial()); initial_behavior.resolve_non_initial(stretch_size, content_size) } else { self.resolve_non_initial(stretch_size, content_size) } .unwrap() } /// Resolves a non-initial size into a numerical value. /// Returns `None` if the size is the initial one. #[inline] pub(crate) fn resolve_non_initial ContentSizes>( &self, stretch_size: Au, content_size: &LazyCell, ) -> Option { match self { Self::Initial => None, Self::MinContent => Some(content_size.min_content), Self::MaxContent => Some(content_size.max_content), Self::FitContent => Some(content_size.shrink_to_fit(stretch_size)), Self::Stretch => Some(stretch_size), Self::Numeric(numeric) => Some(*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, Serialize)] 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 to_definite(self) -> Option { match self { Self::Definite(size) => Some(size), _ => None, } } #[inline] pub(crate) fn to_auto_or(self) -> AutoOr { self.to_definite() .map_or(AutoOr::Auto, AutoOr::LengthPercentage) } }