From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../style/values/computed/length_percentage.rs | 1055 ++++++++++++++++++++ 1 file changed, 1055 insertions(+) create mode 100644 servo/components/style/values/computed/length_percentage.rs (limited to 'servo/components/style/values/computed/length_percentage.rs') diff --git a/servo/components/style/values/computed/length_percentage.rs b/servo/components/style/values/computed/length_percentage.rs new file mode 100644 index 0000000000..898281a7ef --- /dev/null +++ b/servo/components/style/values/computed/length_percentage.rs @@ -0,0 +1,1055 @@ +/* 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/. */ + +//! `` computed values, and related ones. +//! +//! The over-all design is a tagged pointer, with the lower bits of the pointer +//! being non-zero if it is a non-calc value. +//! +//! It is expected to take 64 bits both in x86 and x86-64. This is implemented +//! as a `union`, with 4 different variants: +//! +//! * The length and percentage variants have a { tag, f32 } (effectively) +//! layout. The tag has to overlap with the lower 2 bits of the calc variant. +//! +//! * The `calc()` variant is a { tag, pointer } in x86 (so same as the +//! others), or just a { pointer } in x86-64 (so that the two bits of the tag +//! can be obtained from the lower bits of the pointer). +//! +//! * There's a `tag` variant just to make clear when only the tag is intended +//! to be read. Note that the tag needs to be masked always by `TAG_MASK`, to +//! deal with the pointer variant in x86-64. +//! +//! The assertions in the constructor methods ensure that the tag getter matches +//! our expectations. + +use super::{Context, Length, Percentage, ToComputedValue}; +use crate::gecko_bindings::structs::GeckoFontMetrics; +use crate::values::animated::{Animate, Procedure, ToAnimatedValue, ToAnimatedZero}; +use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; +use crate::values::generics::calc::{CalcUnits, PositivePercentageBasis}; +use crate::values::generics::{calc, NonNegative}; +use crate::values::specified::length::{FontBaseSize, LineHeightBase}; +use crate::values::{specified, CSSFloat}; +use crate::{Zero, ZeroNoPercent}; +use app_units::Au; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; +use serde::{Deserialize, Serialize}; +use std::borrow::Cow; +use std::fmt::{self, Write}; +use style_traits::values::specified::AllowedNumericType; +use style_traits::{CssWriter, ToCss}; + +#[doc(hidden)] +#[derive(Clone, Copy)] +#[repr(C)] +pub struct LengthVariant { + tag: u8, + length: Length, +} + +#[doc(hidden)] +#[derive(Clone, Copy)] +#[repr(C)] +pub struct PercentageVariant { + tag: u8, + percentage: Percentage, +} + +// NOTE(emilio): cbindgen only understands the #[cfg] on the top level +// definition. +#[doc(hidden)] +#[derive(Clone, Copy)] +#[repr(C)] +#[cfg(target_pointer_width = "32")] +pub struct CalcVariant { + tag: u8, + ptr: *mut CalcLengthPercentage, +} + +#[doc(hidden)] +#[derive(Clone, Copy)] +#[repr(C)] +#[cfg(target_pointer_width = "64")] +pub struct CalcVariant { + ptr: usize, // In little-endian byte order +} + +// `CalcLengthPercentage` is `Send + Sync` as asserted below. +unsafe impl Send for CalcVariant {} +unsafe impl Sync for CalcVariant {} + +#[doc(hidden)] +#[derive(Clone, Copy)] +#[repr(C)] +pub struct TagVariant { + tag: u8, +} + +/// A `` value. This can be either a ``, a +/// ``, or a combination of both via `calc()`. +/// +/// cbindgen:private-default-tagged-enum-constructor=false +/// cbindgen:derive-mut-casts=true +/// +/// https://drafts.csswg.org/css-values-4/#typedef-length-percentage +/// +/// The tag is stored in the lower two bits. +/// +/// We need to use a struct instead of the union directly because unions with +/// Drop implementations are unstable, looks like. +/// +/// Also we need the union and the variants to be `pub` (even though the member +/// is private) so that cbindgen generates it. They're not part of the public +/// API otherwise. +#[repr(transparent)] +pub struct LengthPercentage(LengthPercentageUnion); + +#[doc(hidden)] +#[repr(C)] +pub union LengthPercentageUnion { + length: LengthVariant, + percentage: PercentageVariant, + calc: CalcVariant, + tag: TagVariant, +} + +impl LengthPercentageUnion { + #[doc(hidden)] // Need to be public so that cbindgen generates it. + pub const TAG_CALC: u8 = 0; + #[doc(hidden)] + pub const TAG_LENGTH: u8 = 1; + #[doc(hidden)] + pub const TAG_PERCENTAGE: u8 = 2; + #[doc(hidden)] + pub const TAG_MASK: u8 = 0b11; +} + +#[derive(Clone, Copy, Debug, PartialEq)] +#[repr(u8)] +enum Tag { + Calc = LengthPercentageUnion::TAG_CALC, + Length = LengthPercentageUnion::TAG_LENGTH, + Percentage = LengthPercentageUnion::TAG_PERCENTAGE, +} + +// All the members should be 64 bits, even in 32-bit builds. +#[allow(unused)] +unsafe fn static_assert() { + fn assert_send_and_sync() {} + std::mem::transmute::(0u64); + std::mem::transmute::(0u64); + std::mem::transmute::(0u64); + std::mem::transmute::(0u64); + assert_send_and_sync::(); + assert_send_and_sync::(); + assert_send_and_sync::(); +} + +impl Drop for LengthPercentage { + fn drop(&mut self) { + if self.tag() == Tag::Calc { + let _ = unsafe { Box::from_raw(self.calc_ptr()) }; + } + } +} + +impl MallocSizeOf for LengthPercentage { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + match self.unpack() { + Unpacked::Length(..) | Unpacked::Percentage(..) => 0, + Unpacked::Calc(c) => unsafe { ops.malloc_size_of(c) }, + } + } +} + +/// An unpacked `` that borrows the `calc()` variant. +#[derive(Clone, Debug, PartialEq, ToCss)] +enum Unpacked<'a> { + Calc(&'a CalcLengthPercentage), + Length(Length), + Percentage(Percentage), +} + +/// An unpacked `` that mutably borrows the `calc()` variant. +enum UnpackedMut<'a> { + Calc(&'a mut CalcLengthPercentage), + Length(Length), + Percentage(Percentage), +} + +/// An unpacked `` that owns the `calc()` variant, for +/// serialization purposes. +#[derive(Deserialize, PartialEq, Serialize)] +enum Serializable { + Calc(CalcLengthPercentage), + Length(Length), + Percentage(Percentage), +} + +impl LengthPercentage { + /// 1px length value for SVG defaults + #[inline] + pub fn one() -> Self { + Self::new_length(Length::new(1.)) + } + + /// 0% + #[inline] + pub fn zero_percent() -> Self { + Self::new_percent(Percentage::zero()) + } + + fn to_calc_node(&self) -> Cow { + match self.unpack() { + Unpacked::Length(l) => Cow::Owned(CalcNode::Leaf(CalcLengthPercentageLeaf::Length(l))), + Unpacked::Percentage(p) => { + Cow::Owned(CalcNode::Leaf(CalcLengthPercentageLeaf::Percentage(p))) + }, + Unpacked::Calc(p) => Cow::Borrowed(&p.node), + } + } + + /// Constructs a length value. + #[inline] + pub fn new_length(length: Length) -> Self { + let length = Self(LengthPercentageUnion { + length: LengthVariant { + tag: LengthPercentageUnion::TAG_LENGTH, + length, + }, + }); + debug_assert_eq!(length.tag(), Tag::Length); + length + } + + /// Constructs a percentage value. + #[inline] + pub fn new_percent(percentage: Percentage) -> Self { + let percent = Self(LengthPercentageUnion { + percentage: PercentageVariant { + tag: LengthPercentageUnion::TAG_PERCENTAGE, + percentage, + }, + }); + debug_assert_eq!(percent.tag(), Tag::Percentage); + percent + } + + /// Given a `LengthPercentage` value `v`, construct the value representing + /// `calc(100% - v)`. + pub fn hundred_percent_minus(v: Self, clamping_mode: AllowedNumericType) -> Self { + // TODO: This could in theory take ownership of the calc node in `v` if + // possible instead of cloning. + let mut node = v.to_calc_node().into_owned(); + node.negate(); + + let new_node = CalcNode::Sum( + vec![ + CalcNode::Leaf(CalcLengthPercentageLeaf::Percentage(Percentage::hundred())), + node, + ] + .into(), + ); + + Self::new_calc(new_node, clamping_mode) + } + + /// Given a list of `LengthPercentage` values, construct the value representing + /// `calc(100% - the sum of the list)`. + pub fn hundred_percent_minus_list(list: &[&Self], clamping_mode: AllowedNumericType) -> Self { + let mut new_list = vec![CalcNode::Leaf(CalcLengthPercentageLeaf::Percentage( + Percentage::hundred(), + ))]; + + for lp in list.iter() { + let mut node = lp.to_calc_node().into_owned(); + node.negate(); + new_list.push(node) + } + + Self::new_calc(CalcNode::Sum(new_list.into()), clamping_mode) + } + + /// Constructs a `calc()` value. + #[inline] + pub fn new_calc(mut node: CalcNode, clamping_mode: AllowedNumericType) -> Self { + node.simplify_and_sort(); + + match node { + CalcNode::Leaf(l) => { + return match l { + CalcLengthPercentageLeaf::Length(l) => { + Self::new_length(Length::new(clamping_mode.clamp(l.px())).normalized()) + }, + CalcLengthPercentageLeaf::Percentage(p) => Self::new_percent(Percentage( + clamping_mode.clamp(crate::values::normalize(p.0)), + )), + CalcLengthPercentageLeaf::Number(number) => { + debug_assert!( + false, + "The final result of a should never be a number" + ); + Self::new_length(Length::new(number)) + }, + }; + }, + _ => Self::new_calc_unchecked(Box::new(CalcLengthPercentage { + clamping_mode, + node, + })), + } + } + + /// Private version of new_calc() that constructs a calc() variant without + /// checking. + fn new_calc_unchecked(calc: Box) -> Self { + let ptr = Box::into_raw(calc); + + #[cfg(target_pointer_width = "32")] + let calc = CalcVariant { + tag: LengthPercentageUnion::TAG_CALC, + ptr, + }; + + #[cfg(target_pointer_width = "64")] + let calc = CalcVariant { + #[cfg(target_endian = "little")] + ptr: ptr as usize, + #[cfg(target_endian = "big")] + ptr: (ptr as usize).swap_bytes(), + }; + + let calc = Self(LengthPercentageUnion { calc }); + debug_assert_eq!(calc.tag(), Tag::Calc); + calc + } + + #[inline] + fn tag(&self) -> Tag { + match unsafe { self.0.tag.tag & LengthPercentageUnion::TAG_MASK } { + LengthPercentageUnion::TAG_CALC => Tag::Calc, + LengthPercentageUnion::TAG_LENGTH => Tag::Length, + LengthPercentageUnion::TAG_PERCENTAGE => Tag::Percentage, + _ => unsafe { debug_unreachable!("Bogus tag?") }, + } + } + + #[inline] + fn unpack_mut<'a>(&'a mut self) -> UnpackedMut<'a> { + unsafe { + match self.tag() { + Tag::Calc => UnpackedMut::Calc(&mut *self.calc_ptr()), + Tag::Length => UnpackedMut::Length(self.0.length.length), + Tag::Percentage => UnpackedMut::Percentage(self.0.percentage.percentage), + } + } + } + + #[inline] + fn unpack<'a>(&'a self) -> Unpacked<'a> { + unsafe { + match self.tag() { + Tag::Calc => Unpacked::Calc(&*self.calc_ptr()), + Tag::Length => Unpacked::Length(self.0.length.length), + Tag::Percentage => Unpacked::Percentage(self.0.percentage.percentage), + } + } + } + + #[inline] + unsafe fn calc_ptr(&self) -> *mut CalcLengthPercentage { + #[cfg(not(all(target_endian = "big", target_pointer_width = "64")))] + { + self.0.calc.ptr as *mut _ + } + #[cfg(all(target_endian = "big", target_pointer_width = "64"))] + { + self.0.calc.ptr.swap_bytes() as *mut _ + } + } + + #[inline] + fn to_serializable(&self) -> Serializable { + match self.unpack() { + Unpacked::Calc(c) => Serializable::Calc(c.clone()), + Unpacked::Length(l) => Serializable::Length(l), + Unpacked::Percentage(p) => Serializable::Percentage(p), + } + } + + #[inline] + fn from_serializable(s: Serializable) -> Self { + match s { + Serializable::Calc(c) => Self::new_calc_unchecked(Box::new(c)), + Serializable::Length(l) => Self::new_length(l), + Serializable::Percentage(p) => Self::new_percent(p), + } + } + + /// Returns true if the computed value is absolute 0 or 0%. + #[inline] + pub fn is_definitely_zero(&self) -> bool { + match self.unpack() { + Unpacked::Length(l) => l.px() == 0.0, + Unpacked::Percentage(p) => p.0 == 0.0, + Unpacked::Calc(..) => false, + } + } + + /// Resolves the percentage. + #[inline] + pub fn resolve(&self, basis: Length) -> Length { + match self.unpack() { + Unpacked::Length(l) => l, + Unpacked::Percentage(p) => (basis * p.0).normalized(), + Unpacked::Calc(ref c) => c.resolve(basis), + } + } + + /// Resolves the percentage. Just an alias of resolve(). + #[inline] + pub fn percentage_relative_to(&self, basis: Length) -> Length { + self.resolve(basis) + } + + /// Return whether there's any percentage in this value. + #[inline] + pub fn has_percentage(&self) -> bool { + match self.unpack() { + Unpacked::Length(..) => false, + Unpacked::Percentage(..) | Unpacked::Calc(..) => true, + } + } + + /// Converts to a `` if possible. + pub fn to_length(&self) -> Option { + match self.unpack() { + Unpacked::Length(l) => Some(l), + Unpacked::Percentage(..) | Unpacked::Calc(..) => { + debug_assert!(self.has_percentage()); + return None; + }, + } + } + + /// Converts to a `` if possible. + #[inline] + pub fn to_percentage(&self) -> Option { + match self.unpack() { + Unpacked::Percentage(p) => Some(p), + Unpacked::Length(..) | Unpacked::Calc(..) => None, + } + } + + /// Returns the used value. + #[inline] + pub fn to_used_value(&self, containing_length: Au) -> Au { + Au::from(self.to_pixel_length(containing_length)) + } + + /// Returns the used value as CSSPixelLength. + #[inline] + pub fn to_pixel_length(&self, containing_length: Au) -> Length { + self.resolve(containing_length.into()) + } + + /// Convert the computed value into used value. + #[inline] + pub fn maybe_to_used_value(&self, container_len: Option) -> Option { + self.maybe_percentage_relative_to(container_len) + .map(Au::from) + } + + /// If there are special rules for computing percentages in a value (e.g. + /// the height property), they apply whenever a calc() expression contains + /// percentages. + pub fn maybe_percentage_relative_to(&self, container_len: Option) -> Option { + if let Unpacked::Length(l) = self.unpack() { + return Some(l); + } + Some(self.resolve(container_len?)) + } + + /// Returns the clamped non-negative values. + #[inline] + pub fn clamp_to_non_negative(mut self) -> Self { + match self.unpack_mut() { + UnpackedMut::Length(l) => Self::new_length(l.clamp_to_non_negative()), + UnpackedMut::Percentage(p) => Self::new_percent(p.clamp_to_non_negative()), + UnpackedMut::Calc(ref mut c) => { + c.clamping_mode = AllowedNumericType::NonNegative; + self + }, + } + } +} + +impl PartialEq for LengthPercentage { + fn eq(&self, other: &Self) -> bool { + self.unpack() == other.unpack() + } +} + +impl fmt::Debug for LengthPercentage { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + self.unpack().fmt(formatter) + } +} + +impl ToAnimatedZero for LengthPercentage { + fn to_animated_zero(&self) -> Result { + Ok(match self.unpack() { + Unpacked::Length(l) => Self::new_length(l.to_animated_zero()?), + Unpacked::Percentage(p) => Self::new_percent(p.to_animated_zero()?), + Unpacked::Calc(c) => Self::new_calc_unchecked(Box::new(c.to_animated_zero()?)), + }) + } +} + +impl Clone for LengthPercentage { + fn clone(&self) -> Self { + match self.unpack() { + Unpacked::Length(l) => Self::new_length(l), + Unpacked::Percentage(p) => Self::new_percent(p), + Unpacked::Calc(c) => Self::new_calc_unchecked(Box::new(c.clone())), + } + } +} + +impl ToComputedValue for specified::LengthPercentage { + type ComputedValue = LengthPercentage; + + fn to_computed_value(&self, context: &Context) -> LengthPercentage { + match *self { + specified::LengthPercentage::Length(ref value) => { + LengthPercentage::new_length(value.to_computed_value(context)) + }, + specified::LengthPercentage::Percentage(value) => LengthPercentage::new_percent(value), + specified::LengthPercentage::Calc(ref calc) => (**calc).to_computed_value(context), + } + } + + fn from_computed_value(computed: &LengthPercentage) -> Self { + match computed.unpack() { + Unpacked::Length(ref l) => { + specified::LengthPercentage::Length(ToComputedValue::from_computed_value(l)) + }, + Unpacked::Percentage(p) => specified::LengthPercentage::Percentage(p), + Unpacked::Calc(c) => { + // We simplify before constructing the LengthPercentage if + // needed, so this is always fine. + specified::LengthPercentage::Calc(Box::new( + specified::CalcLengthPercentage::from_computed_value(c), + )) + }, + } + } +} + +impl ComputeSquaredDistance for LengthPercentage { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result { + // A somewhat arbitrary base, it doesn't really make sense to mix + // lengths with percentages, but we can't do much better here, and this + // ensures that the distance between length-only and percentage-only + // lengths makes sense. + let basis = Length::new(100.); + self.resolve(basis) + .compute_squared_distance(&other.resolve(basis)) + } +} + +impl ToCss for LengthPercentage { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + self.unpack().to_css(dest) + } +} + +impl Zero for LengthPercentage { + fn zero() -> Self { + LengthPercentage::new_length(Length::zero()) + } + + #[inline] + fn is_zero(&self) -> bool { + self.is_definitely_zero() + } +} + +impl ZeroNoPercent for LengthPercentage { + #[inline] + fn is_zero_no_percent(&self) -> bool { + self.is_definitely_zero() && !self.has_percentage() + } +} + +impl Serialize for LengthPercentage { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.to_serializable().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for LengthPercentage { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(Self::from_serializable(Serializable::deserialize( + deserializer, + )?)) + } +} + +/// The leaves of a `` calc expression. +#[derive( + Clone, + Debug, + Deserialize, + MallocSizeOf, + PartialEq, + Serialize, + ToAnimatedZero, + ToCss, + ToResolvedValue, +)] +#[allow(missing_docs)] +#[repr(u8)] +pub enum CalcLengthPercentageLeaf { + Length(Length), + Percentage(Percentage), + Number(f32), +} + +impl CalcLengthPercentageLeaf { + fn is_zero_length(&self) -> bool { + match *self { + Self::Length(ref l) => l.is_zero(), + Self::Percentage(..) => false, + Self::Number(..) => false, + } + } +} + +impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf { + fn unit(&self) -> CalcUnits { + match self { + Self::Length(_) => CalcUnits::LENGTH, + Self::Percentage(_) => CalcUnits::PERCENTAGE, + Self::Number(_) => CalcUnits::empty(), + } + } + + fn unitless_value(&self) -> f32 { + match *self { + Self::Length(ref l) => l.px(), + Self::Percentage(ref p) => p.0, + Self::Number(n) => n, + } + } + + fn new_number(value: f32) -> Self { + Self::Number(value) + } + + fn as_number(&self) -> Option { + match *self { + Self::Length(_) | Self::Percentage(_) => None, + Self::Number(value) => Some(value), + } + } + + fn compare(&self, other: &Self, basis: PositivePercentageBasis) -> Option { + use self::CalcLengthPercentageLeaf::*; + if std::mem::discriminant(self) != std::mem::discriminant(other) { + return None; + } + + if matches!(self, Percentage(..)) && matches!(basis, PositivePercentageBasis::Unknown) { + return None; + } + + let self_negative = self.is_negative(); + if self_negative != other.is_negative() { + return Some(if self_negative { std::cmp::Ordering::Less } else { std::cmp::Ordering::Greater }); + } + + match (self, other) { + (&Length(ref one), &Length(ref other)) => one.partial_cmp(other), + (&Percentage(ref one), &Percentage(ref other)) => one.partial_cmp(other), + (&Number(ref one), &Number(ref other)) => one.partial_cmp(other), + _ => unsafe { + match *self { + Length(..) | Percentage(..) | Number(..) => {}, + } + debug_unreachable!("Forgot to handle unit in compare()") + }, + } + } + + fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> { + use self::CalcLengthPercentageLeaf::*; + + // 0px plus anything else is equal to the right hand side. + if self.is_zero_length() { + *self = other.clone(); + return Ok(()); + } + + if other.is_zero_length() { + return Ok(()); + } + + if std::mem::discriminant(self) != std::mem::discriminant(other) { + return Err(()); + } + + match (self, other) { + (&mut Length(ref mut one), &Length(ref other)) => { + *one += *other; + }, + (&mut Percentage(ref mut one), &Percentage(ref other)) => { + one.0 += other.0; + }, + (&mut Number(ref mut one), &Number(ref other)) => { + *one += *other; + }, + _ => unsafe { + match *other { + Length(..) | Percentage(..) | Number(..) => {}, + } + debug_unreachable!("Forgot to handle unit in try_sum_in_place()") + }, + } + + Ok(()) + } + + fn try_product_in_place(&mut self, other: &mut Self) -> bool { + if let Self::Number(ref mut left) = *self { + if let Self::Number(ref right) = *other { + // Both sides are numbers, so we can just modify the left side. + *left *= *right; + true + } else { + // The right side is not a number, so the result should be in the units of the right + // side. + other.map(|v| v * *left); + std::mem::swap(self, other); + true + } + } else if let Self::Number(ref right) = *other { + // The left side is not a number, but the right side is, so the result is the left + // side unit. + self.map(|v| v * *right); + true + } else { + // Neither side is a number, so a product is not possible. + false + } + } + + fn try_op(&self, other: &Self, op: O) -> Result + where + O: Fn(f32, f32) -> f32, + { + use self::CalcLengthPercentageLeaf::*; + if std::mem::discriminant(self) != std::mem::discriminant(other) { + return Err(()); + } + Ok(match (self, other) { + (&Length(ref one), &Length(ref other)) => { + Length(super::Length::new(op(one.px(), other.px()))) + }, + (&Percentage(one), &Percentage(other)) => { + Self::Percentage(super::Percentage(op(one.0, other.0))) + }, + (&Number(one), &Number(other)) => Self::Number(op(one, other)), + _ => unsafe { + match *self { + Length(..) | Percentage(..) | Number(..) => {}, + } + debug_unreachable!("Forgot to handle unit in try_op()") + }, + }) + } + + fn map(&mut self, mut op: impl FnMut(f32) -> f32) { + match self { + Self::Length(value) => { + *value = Length::new(op(value.px())); + }, + Self::Percentage(value) => { + *value = Percentage(op(value.0)); + }, + Self::Number(value) => { + *value = op(*value); + }, + } + } + + fn simplify(&mut self) {} + + fn sort_key(&self) -> calc::SortKey { + match *self { + Self::Length(..) => calc::SortKey::Px, + Self::Percentage(..) => calc::SortKey::Percentage, + Self::Number(..) => calc::SortKey::Number, + } + } +} + +/// The computed version of a calc() node for `` values. +pub type CalcNode = calc::GenericCalcNode; + +/// The representation of a calc() function with mixed lengths and percentages. +#[derive( + Clone, Debug, Deserialize, MallocSizeOf, Serialize, ToAnimatedZero, ToResolvedValue, ToCss, +)] +#[repr(C)] +pub struct CalcLengthPercentage { + #[animation(constant)] + #[css(skip)] + clamping_mode: AllowedNumericType, + node: CalcNode, +} + +impl CalcLengthPercentage { + /// Resolves the percentage. + #[inline] + pub fn resolve(&self, basis: Length) -> Length { + // unwrap() is fine because the conversion below is infallible. + if let CalcLengthPercentageLeaf::Length(px) = self + .node + .resolve_map(|leaf| { + Ok(if let CalcLengthPercentageLeaf::Percentage(p) = leaf { + CalcLengthPercentageLeaf::Length(Length::new(basis.px() * p.0)) + } else { + leaf.clone() + }) + }) + .unwrap() + { + Length::new(self.clamping_mode.clamp(px.px())).normalized() + } else { + unreachable!("resolve_map should turn percentages to lengths, and parsing should ensure that we don't end up with a number"); + } + } +} + +// NOTE(emilio): We don't compare `clamping_mode` since we want to preserve the +// invariant that `from_computed_value(length).to_computed_value(..) == length`. +// +// Right now for e.g. a non-negative length, we set clamping_mode to `All` +// unconditionally for non-calc values, and to `NonNegative` for calc. +// +// If we determine that it's sound, from_computed_value() can generate an +// absolute length, which then would get `All` as the clamping mode. +// +// We may want to just eagerly-detect whether we can clamp in +// `LengthPercentage::new` and switch to `AllowedNumericType::NonNegative` then, +// maybe. +impl PartialEq for CalcLengthPercentage { + fn eq(&self, other: &Self) -> bool { + self.node == other.node + } +} + +impl specified::CalcLengthPercentage { + /// Compute the value, zooming any absolute units by the zoom function. + fn to_computed_value_with_zoom( + &self, + context: &Context, + zoom_fn: F, + base_size: FontBaseSize, + line_height_base: LineHeightBase, + ) -> LengthPercentage + where + F: Fn(Length) -> Length, + { + use crate::values::specified::calc::Leaf; + + let node = self.node.map_leaves(|leaf| match *leaf { + Leaf::Percentage(p) => CalcLengthPercentageLeaf::Percentage(Percentage(p)), + Leaf::Length(l) => CalcLengthPercentageLeaf::Length({ + let result = + l.to_computed_value_with_base_size(context, base_size, line_height_base); + if l.should_zoom_text() { + zoom_fn(result) + } else { + result + } + }), + Leaf::Number(n) => CalcLengthPercentageLeaf::Number(n), + Leaf::Angle(..) | Leaf::Time(..) | Leaf::Resolution(..) => { + unreachable!("Shouldn't have parsed") + }, + }); + + LengthPercentage::new_calc(node, self.clamping_mode) + } + + /// Compute font-size or line-height taking into account text-zoom if necessary. + pub fn to_computed_value_zoomed( + &self, + context: &Context, + base_size: FontBaseSize, + line_height_base: LineHeightBase, + ) -> LengthPercentage { + self.to_computed_value_with_zoom( + context, + |abs| context.maybe_zoom_text(abs), + base_size, + line_height_base, + ) + } + + /// Compute the value into pixel length as CSSFloat without context, + /// so it returns Err(()) if there is any non-absolute unit. + pub fn to_computed_pixel_length_without_context(&self) -> Result { + use crate::values::specified::calc::Leaf; + use crate::values::specified::length::NoCalcLength; + + // Simplification should've turned this into an absolute length, + // otherwise it wouldn't have been able to. + match self.node { + calc::CalcNode::Leaf(Leaf::Length(NoCalcLength::Absolute(ref l))) => Ok(l.to_px()), + _ => Err(()), + } + } + + /// Compute the value into pixel length as CSSFloat, using the get_font_metrics function + /// if provided to resolve font-relative dimensions. + pub fn to_computed_pixel_length_with_font_metrics( + &self, + get_font_metrics: Option GeckoFontMetrics>, + ) -> Result { + use crate::values::specified::calc::Leaf; + use crate::values::specified::length::NoCalcLength; + + match self.node { + calc::CalcNode::Leaf(Leaf::Length(NoCalcLength::Absolute(ref l))) => Ok(l.to_px()), + calc::CalcNode::Leaf(Leaf::Length(NoCalcLength::FontRelative(ref l))) => { + if let Some(getter) = get_font_metrics { + l.to_computed_pixel_length_with_font_metrics(getter) + } else { + Err(()) + } + }, + _ => Err(()), + } + } + + /// Compute the calc using the current font-size and line-height. (and without text-zoom). + pub fn to_computed_value(&self, context: &Context) -> LengthPercentage { + self.to_computed_value_with_zoom( + context, + |abs| abs, + FontBaseSize::CurrentStyle, + LineHeightBase::CurrentStyle, + ) + } + + #[inline] + fn from_computed_value(computed: &CalcLengthPercentage) -> Self { + use crate::values::specified::calc::Leaf; + use crate::values::specified::length::NoCalcLength; + + specified::CalcLengthPercentage { + clamping_mode: computed.clamping_mode, + node: computed.node.map_leaves(|l| match l { + CalcLengthPercentageLeaf::Length(ref l) => { + Leaf::Length(NoCalcLength::from_px(l.px())) + }, + CalcLengthPercentageLeaf::Percentage(ref p) => Leaf::Percentage(p.0), + CalcLengthPercentageLeaf::Number(n) => Leaf::Number(*n), + }), + } + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc +/// https://drafts.csswg.org/css-values-4/#combine-math +/// https://drafts.csswg.org/css-values-4/#combine-mixed +impl Animate for LengthPercentage { + #[inline] + fn animate(&self, other: &Self, procedure: Procedure) -> Result { + Ok(match (self.unpack(), other.unpack()) { + (Unpacked::Length(one), Unpacked::Length(other)) => { + Self::new_length(one.animate(&other, procedure)?) + }, + (Unpacked::Percentage(one), Unpacked::Percentage(other)) => { + Self::new_percent(one.animate(&other, procedure)?) + }, + _ => { + use calc::CalcNodeLeaf; + + fn product_with(mut node: CalcNode, product: f32) -> CalcNode { + let mut number = CalcNode::Leaf(CalcLengthPercentageLeaf::new_number(product)); + if !node.try_product_in_place(&mut number) { + CalcNode::Product(vec![node, number].into()) + } else { + node + } + } + + let (l, r) = procedure.weights(); + let one = product_with(self.to_calc_node().into_owned(), l as f32); + let other = product_with(other.to_calc_node().into_owned(), r as f32); + + Self::new_calc( + CalcNode::Sum(vec![one, other].into()), + AllowedNumericType::All, + ) + }, + }) + } +} + +/// A wrapper of LengthPercentage, whose value must be >= 0. +pub type NonNegativeLengthPercentage = NonNegative; + +impl ToAnimatedValue for NonNegativeLengthPercentage { + type AnimatedValue = LengthPercentage; + + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + self.0 + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + NonNegative(animated.clamp_to_non_negative()) + } +} + +impl NonNegativeLengthPercentage { + /// Returns true if the computed value is absolute 0 or 0%. + #[inline] + pub fn is_definitely_zero(&self) -> bool { + self.0.is_definitely_zero() + } + + /// Returns the used value. + #[inline] + pub fn to_used_value(&self, containing_length: Au) -> Au { + let resolved = self.0.to_used_value(containing_length); + std::cmp::max(resolved, Au(0)) + } + + /// Convert the computed value into used value. + #[inline] + pub fn maybe_to_used_value(&self, containing_length: Option) -> Option { + let resolved = self + .0 + .maybe_to_used_value(containing_length.map(|v| v.into()))?; + Some(std::cmp::max(resolved, Au(0))) + } +} -- cgit v1.2.3