Skip to main content

style/values/computed/
length_percentage.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! `<length-percentage>` computed values, and related ones.
6//!
7//! The over-all design is a tagged pointer, with the lower bit of the pointer
8//! being non-zero if it is a non-calc value. See `tagged_numeric` for the
9//! shared implementation details.
10
11use super::{position::AnchorSide, Context, Length, Percentage, ToComputedValue};
12use crate::derives::*;
13#[cfg(feature = "gecko")]
14use crate::gecko_bindings::structs::{AnchorPosOffsetResolutionParams, GeckoFontMetrics};
15use crate::logical_geometry::{PhysicalAxis, PhysicalSide};
16use crate::typed_om::{ToTyped, TypedValue};
17use crate::values::animated::{
18    Animate, Context as AnimatedContext, Procedure, ToAnimatedValue, ToAnimatedZero,
19};
20use crate::values::computed::position::TryTacticAdjustment;
21use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
22use crate::values::generics::calc::GenericAnchorFunctionFallback;
23#[cfg(feature = "gecko")]
24use crate::values::generics::length::AnchorResolutionResult;
25use crate::values::generics::position::GenericAnchorSide;
26use crate::values::generics::{calc, ClampToNonNegative, NonNegative};
27use crate::values::resolved::{Context as ResolvedContext, ToResolvedValue};
28use crate::values::specified::length::{EqualsPercentage, FontBaseSize, LineHeightBase};
29use crate::values::specified::number::NoCalcNumber;
30use crate::values::specified::percentage::NoCalcPercentage;
31use crate::values::tagged_numeric::{self as tagged, NumericUnion};
32use crate::values::{specified, CSSFloat};
33use crate::{Zero, ZeroNoPercent};
34use app_units::Au;
35use serde::{Deserialize, Serialize};
36use std::fmt::{self, Write};
37use style_traits::values::specified::AllowedNumericType;
38use style_traits::{CssWriter, ToCss};
39use thin_vec::ThinVec;
40
41pub use super::calc::ComputedLeaf;
42
43/// The discriminator used for inline LengthPercentage variants.
44#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)]
45#[repr(u8)]
46pub enum LengthPercentageTag {
47    /// A `<length>` value.
48    Length = 0,
49    /// A `<percentage>` value.
50    Percentage = 1,
51}
52
53/// A `<length-percentage>` value. This can be either a `<length>`, a
54/// `<percentage>`, or a combination of both via `calc()`.
55///
56/// https://drafts.csswg.org/css-values-4/#typedef-length-percentage
57///
58/// cbindgen:derive-eq=false
59/// cbindgen:derive-neq=false
60#[derive(MallocSizeOf)]
61#[repr(C)]
62pub struct LengthPercentage(NumericUnion<LengthPercentageTag, f32, CalcLengthPercentage>);
63
64impl ToAnimatedValue for LengthPercentage {
65    type AnimatedValue = Self;
66
67    fn to_animated_value(self, context: &AnimatedContext) -> Self::AnimatedValue {
68        if context.style.effective_zoom.is_one() {
69            return self;
70        }
71        self.map_lengths(|l| l.to_animated_value(context))
72    }
73
74    #[inline]
75    fn from_animated_value(value: Self::AnimatedValue) -> Self {
76        value
77    }
78}
79
80impl ToResolvedValue for LengthPercentage {
81    type ResolvedValue = Self;
82
83    fn to_resolved_value(self, context: &ResolvedContext) -> Self::ResolvedValue {
84        if context.style.effective_zoom.is_one() {
85            return self;
86        }
87        self.map_lengths(|l| l.to_resolved_value(context))
88    }
89
90    #[inline]
91    fn from_resolved_value(value: Self::ResolvedValue) -> Self {
92        value
93    }
94}
95
96impl EqualsPercentage for LengthPercentage {
97    fn equals_percentage(&self, v: CSSFloat) -> bool {
98        match self.unpack() {
99            Unpacked::Percentage(p) => p.0 == v,
100            _ => false,
101        }
102    }
103}
104
105/// An unpacked `<length-percentage>` that borrows the `calc()` variant.
106#[derive(Clone, Debug, PartialEq, ToCss, ToTyped)]
107pub enum Unpacked<'a> {
108    /// A `calc()` value
109    Calc(&'a CalcLengthPercentage),
110    /// A length value
111    Length(Length),
112    /// A percentage value
113    Percentage(Percentage),
114}
115
116/// An unpacked `<length-percentage>` that mutably borrows the `calc()` variant.
117enum UnpackedMut<'a> {
118    Calc(&'a mut CalcLengthPercentage),
119    Length(Length),
120    Percentage(Percentage),
121}
122
123/// An unpacked `<length-percentage>` that owns the `calc()` variant, for
124/// serialization purposes.
125#[derive(Deserialize, PartialEq, Serialize)]
126enum Serializable {
127    Calc(CalcLengthPercentage),
128    Length(Length),
129    Percentage(Percentage),
130}
131
132impl LengthPercentage {
133    /// 1px length value for SVG defaults
134    #[inline]
135    pub fn one() -> Self {
136        Self::new_length(Length::new(1.))
137    }
138
139    /// 0%
140    #[inline]
141    pub fn zero_percent() -> Self {
142        Self::new_percent(Percentage::zero())
143    }
144
145    /// 100%
146    #[inline]
147    pub fn hundred_percent() -> Self {
148        Self::new_percent(Percentage::hundred())
149    }
150
151    fn to_calc_node(&self) -> CalcNode {
152        match self.unpack() {
153            Unpacked::Length(l) => CalcNode::Leaf(ComputedLeaf::Length(l)),
154            Unpacked::Percentage(p) => CalcNode::Leaf(ComputedLeaf::Percentage(p)),
155            Unpacked::Calc(p) => p.node.clone(),
156        }
157    }
158
159    fn map_lengths(&self, mut map_fn: impl FnMut(Length) -> Length) -> Self {
160        match self.unpack() {
161            Unpacked::Length(l) => Self::new_length(map_fn(l)),
162            Unpacked::Percentage(p) => Self::new_percent(p),
163            Unpacked::Calc(lp) => Self::new_calc_unchecked(Box::new(CalcLengthPercentage {
164                clamping_mode: lp.clamping_mode,
165                node: lp.node.map_leaves(|leaf| match *leaf {
166                    ComputedLeaf::Length(ref l) => ComputedLeaf::Length(map_fn(*l)),
167                    ref l => l.clone(),
168                }),
169            })),
170        }
171    }
172
173    /// Constructs a length value.
174    #[inline]
175    pub fn new_length(length: Length) -> Self {
176        Self(NumericUnion::inline(
177            LengthPercentageTag::Length,
178            length.px(),
179        ))
180    }
181
182    /// Constructs a percentage value.
183    #[inline]
184    pub fn new_percent(percentage: Percentage) -> Self {
185        Self(NumericUnion::inline(
186            LengthPercentageTag::Percentage,
187            percentage.0,
188        ))
189    }
190
191    /// Given a `LengthPercentage` value `v`, construct the value representing
192    /// `calc(100% - v)`.
193    pub fn hundred_percent_minus(v: Self, clamping_mode: AllowedNumericType) -> Self {
194        // TODO: This could in theory take ownership of the calc node in `v` if
195        // possible instead of cloning.
196        let mut node = v.to_calc_node();
197        node.negate();
198
199        let new_node = CalcNode::Sum(
200            vec![
201                CalcNode::Leaf(ComputedLeaf::Percentage(Percentage::hundred())),
202                node,
203            ]
204            .into(),
205        );
206
207        Self::new_calc(new_node, clamping_mode)
208    }
209
210    /// Given a list of `LengthPercentage` values, construct the value representing
211    /// `calc(100% - the sum of the list)`.
212    pub fn hundred_percent_minus_list(list: &[&Self], clamping_mode: AllowedNumericType) -> Self {
213        let mut new_list = vec![CalcNode::Leaf(ComputedLeaf::Percentage(
214            Percentage::hundred(),
215        ))];
216
217        for lp in list.iter() {
218            let mut node = lp.to_calc_node();
219            node.negate();
220            new_list.push(node)
221        }
222
223        Self::new_calc(CalcNode::Sum(new_list.into()), clamping_mode)
224    }
225
226    /// Constructs a `calc()` value.
227    #[inline]
228    pub fn new_calc(mut node: CalcNode, clamping_mode: AllowedNumericType) -> Self {
229        node.simplify_and_sort();
230
231        match node {
232            CalcNode::Leaf(l) => {
233                return match l {
234                    ComputedLeaf::Length(l) => {
235                        Self::new_length(Length::new(clamping_mode.clamp(l.px())).normalized())
236                    },
237                    ComputedLeaf::Percentage(p) => Self::new_percent(Percentage(
238                        clamping_mode.clamp(crate::values::normalize(p.0)),
239                    )),
240                    ComputedLeaf::Number(number) => {
241                        debug_assert!(
242                            false,
243                            "The final result of a <length-percentage> should never be a number"
244                        );
245                        Self::new_length(Length::new(number))
246                    },
247                    ComputedLeaf::Angle(..)
248                    | ComputedLeaf::Time(..)
249                    | ComputedLeaf::Resolution(..) => {
250                        debug_assert!(
251                            false,
252                            "The final result of a <length-percentage> should never be an angle, time, or resolution"
253                        );
254                        Self::zero()
255                    },
256                };
257            },
258            _ => Self::new_calc_unchecked(Box::new(CalcLengthPercentage {
259                clamping_mode,
260                node,
261            })),
262        }
263    }
264
265    /// Private version of new_calc() that constructs a calc() variant without
266    /// checking.
267    fn new_calc_unchecked(calc: Box<CalcLengthPercentage>) -> Self {
268        Self(NumericUnion::boxed(calc))
269    }
270
271    #[inline]
272    fn unpack_mut<'a>(&'a mut self) -> UnpackedMut<'a> {
273        match self.0.unpack_mut() {
274            tagged::UnpackedMut::Boxed(calc) => UnpackedMut::Calc(calc),
275            tagged::UnpackedMut::Inline(t, n) => match *t {
276                LengthPercentageTag::Length => UnpackedMut::Length(Length::new(*n)),
277                LengthPercentageTag::Percentage => UnpackedMut::Percentage(Percentage(*n)),
278            },
279        }
280    }
281
282    /// Unpack the tagged pointer representation of a length-percentage into an enum
283    /// representation with separate tag and value.
284    #[inline]
285    pub fn unpack<'a>(&'a self) -> Unpacked<'a> {
286        match self.0.unpack() {
287            tagged::Unpacked::Boxed(calc) => Unpacked::Calc(calc),
288            tagged::Unpacked::Inline(LengthPercentageTag::Length, v) => {
289                Unpacked::Length(Length::new(v))
290            },
291            tagged::Unpacked::Inline(LengthPercentageTag::Percentage, v) => {
292                Unpacked::Percentage(Percentage(v))
293            },
294        }
295    }
296
297    #[inline]
298    fn to_serializable(&self) -> Serializable {
299        match self.unpack() {
300            Unpacked::Calc(c) => Serializable::Calc(c.clone()),
301            Unpacked::Length(l) => Serializable::Length(l),
302            Unpacked::Percentage(p) => Serializable::Percentage(p),
303        }
304    }
305
306    #[inline]
307    fn from_serializable(s: Serializable) -> Self {
308        match s {
309            Serializable::Calc(c) => Self::new_calc_unchecked(Box::new(c)),
310            Serializable::Length(l) => Self::new_length(l),
311            Serializable::Percentage(p) => Self::new_percent(p),
312        }
313    }
314
315    /// Resolves the percentage.
316    #[inline]
317    pub fn resolve(&self, basis: Length) -> Length {
318        match self.unpack() {
319            Unpacked::Length(l) => l,
320            Unpacked::Percentage(p) => (basis * p.0).normalized(),
321            Unpacked::Calc(ref c) => c.resolve(basis),
322        }
323    }
324
325    /// Resolves the percentage. Just an alias of resolve().
326    #[inline]
327    pub fn percentage_relative_to(&self, basis: Length) -> Length {
328        self.resolve(basis)
329    }
330
331    /// Return whether there's any percentage in this value.
332    #[inline]
333    pub fn has_percentage(&self) -> bool {
334        match self.unpack() {
335            Unpacked::Length(..) => false,
336            Unpacked::Percentage(..) | Unpacked::Calc(..) => true,
337        }
338    }
339
340    /// Converts to a `<length>` if possible.
341    pub fn to_length(&self) -> Option<Length> {
342        match self.unpack() {
343            Unpacked::Length(l) => Some(l),
344            Unpacked::Percentage(..) | Unpacked::Calc(..) => {
345                debug_assert!(self.has_percentage());
346                return None;
347            },
348        }
349    }
350
351    /// Converts to a `<percentage>` if possible.
352    #[inline]
353    pub fn to_percentage(&self) -> Option<Percentage> {
354        match self.unpack() {
355            Unpacked::Percentage(p) => Some(p),
356            Unpacked::Length(..) | Unpacked::Calc(..) => None,
357        }
358    }
359
360    /// Converts to a `<percentage>` with given basis. Returns None if the basis is 0.
361    #[inline]
362    pub fn to_percentage_of(&self, basis: Length) -> Option<Percentage> {
363        if basis.px() == 0. {
364            return None;
365        }
366        Some(match self.unpack() {
367            Unpacked::Length(l) => Percentage(l.px() / basis.px()),
368            Unpacked::Percentage(p) => p,
369            Unpacked::Calc(ref c) => Percentage(c.resolve(basis).px() / basis.px()),
370        })
371    }
372
373    /// Returns the used value.
374    #[inline]
375    pub fn to_used_value(&self, containing_length: Au) -> Au {
376        let length = self.to_pixel_length(containing_length);
377        if let Unpacked::Percentage(_) = self.unpack() {
378            return Au::from_f32_px_trunc(length.px());
379        }
380        Au::from(length)
381    }
382
383    /// Returns the used value as CSSPixelLength.
384    #[inline]
385    pub fn to_pixel_length(&self, containing_length: Au) -> Length {
386        self.resolve(containing_length.into())
387    }
388
389    /// Convert the computed value into used value.
390    #[inline]
391    pub fn maybe_to_used_value(&self, container_len: Option<Au>) -> Option<Au> {
392        self.maybe_percentage_relative_to(container_len.map(Length::from))
393            .map(if let Unpacked::Percentage(_) = self.unpack() {
394                |length: Length| Au::from_f32_px_trunc(length.px())
395            } else {
396                Au::from
397            })
398    }
399
400    /// If there are special rules for computing percentages in a value (e.g.
401    /// the height property), they apply whenever a calc() expression contains
402    /// percentages.
403    pub fn maybe_percentage_relative_to(&self, container_len: Option<Length>) -> Option<Length> {
404        if let Unpacked::Length(l) = self.unpack() {
405            return Some(l);
406        }
407        Some(self.resolve(container_len?))
408    }
409}
410
411impl ClampToNonNegative for LengthPercentage {
412    /// Returns the clamped non-negative values.
413    #[inline]
414    fn clamp_to_non_negative(mut self) -> Self {
415        match self.unpack_mut() {
416            UnpackedMut::Length(l) => Self::new_length(l.clamp_to_non_negative()),
417            UnpackedMut::Percentage(p) => Self::new_percent(p.clamp_to_non_negative()),
418            UnpackedMut::Calc(ref mut c) => {
419                c.clamping_mode = AllowedNumericType::NonNegative;
420                self
421            },
422        }
423    }
424}
425
426impl PartialEq for LengthPercentage {
427    fn eq(&self, other: &Self) -> bool {
428        self.unpack() == other.unpack()
429    }
430}
431
432impl fmt::Debug for LengthPercentage {
433    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
434        self.unpack().fmt(formatter)
435    }
436}
437
438impl ToAnimatedZero for LengthPercentage {
439    fn to_animated_zero(&self) -> Result<Self, ()> {
440        Ok(match self.unpack() {
441            Unpacked::Length(l) => Self::new_length(l.to_animated_zero()?),
442            Unpacked::Percentage(p) => Self::new_percent(p.to_animated_zero()?),
443            Unpacked::Calc(c) => Self::new_calc_unchecked(Box::new(c.to_animated_zero()?)),
444        })
445    }
446}
447
448impl Clone for LengthPercentage {
449    fn clone(&self) -> Self {
450        match self.unpack() {
451            Unpacked::Length(l) => Self::new_length(l),
452            Unpacked::Percentage(p) => Self::new_percent(p),
453            Unpacked::Calc(c) => Self::new_calc_unchecked(Box::new(c.clone())),
454        }
455    }
456}
457
458impl ToComputedValue for specified::LengthPercentage {
459    type ComputedValue = LengthPercentage;
460
461    fn to_computed_value(&self, context: &Context) -> LengthPercentage {
462        match *self {
463            specified::LengthPercentage::Length(ref value) => {
464                LengthPercentage::new_length(value.to_computed_value(context))
465            },
466            specified::LengthPercentage::Percentage(value) => {
467                LengthPercentage::new_percent(value.to_computed_value(context))
468            },
469            specified::LengthPercentage::Calc(ref calc) => (**calc).to_computed_value(context),
470        }
471    }
472
473    fn from_computed_value(computed: &LengthPercentage) -> Self {
474        match computed.unpack() {
475            Unpacked::Length(ref l) => {
476                specified::LengthPercentage::Length(ToComputedValue::from_computed_value(l))
477            },
478            Unpacked::Percentage(p) => {
479                specified::LengthPercentage::Percentage(NoCalcPercentage::new(p.0))
480            },
481            Unpacked::Calc(c) => {
482                // We simplify before constructing the LengthPercentage if
483                // needed, so this is always fine.
484                specified::LengthPercentage::Calc(Box::new(
485                    specified::CalcLengthPercentage::from_computed_value(c),
486                ))
487            },
488        }
489    }
490}
491
492impl ComputeSquaredDistance for LengthPercentage {
493    #[inline]
494    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
495        // A somewhat arbitrary base, it doesn't really make sense to mix
496        // lengths with percentages, but we can't do much better here, and this
497        // ensures that the distance between length-only and percentage-only
498        // lengths makes sense.
499        let basis = Length::new(100.);
500        self.resolve(basis)
501            .compute_squared_distance(&other.resolve(basis))
502    }
503}
504
505impl ToCss for LengthPercentage {
506    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
507    where
508        W: Write,
509    {
510        self.unpack().to_css(dest)
511    }
512}
513
514impl ToTyped for LengthPercentage {
515    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
516        self.unpack().to_typed(dest)
517    }
518}
519
520impl Zero for LengthPercentage {
521    fn zero() -> Self {
522        LengthPercentage::new_length(Length::zero())
523    }
524
525    /// Returns true if the computed value is absolute 0 or 0%.
526    #[inline]
527    fn is_zero(&self) -> bool {
528        match self.unpack() {
529            Unpacked::Length(l) => l.px() == 0.0,
530            Unpacked::Percentage(p) => p.0 == 0.0,
531            Unpacked::Calc(..) => false,
532        }
533    }
534}
535
536impl ZeroNoPercent for LengthPercentage {
537    #[inline]
538    fn is_zero_no_percent(&self) -> bool {
539        self.to_length().is_some_and(|l| l.px() == 0.0)
540    }
541}
542
543impl Serialize for LengthPercentage {
544    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
545    where
546        S: serde::Serializer,
547    {
548        self.to_serializable().serialize(serializer)
549    }
550}
551
552impl<'de> Deserialize<'de> for LengthPercentage {
553    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
554    where
555        D: serde::Deserializer<'de>,
556    {
557        Ok(Self::from_serializable(Serializable::deserialize(
558            deserializer,
559        )?))
560    }
561}
562
563/// The computed version of a calc() node for `<length-percentage>` values.
564pub type CalcNode = calc::GenericCalcNode<ComputedLeaf>;
565
566/// The representation of a calc() function with mixed lengths and percentages.
567#[derive(
568    Clone,
569    Debug,
570    Deserialize,
571    MallocSizeOf,
572    Serialize,
573    ToAnimatedZero,
574    ToResolvedValue,
575    ToCss,
576    ToTyped,
577)]
578#[repr(C)]
579pub struct CalcLengthPercentage {
580    #[animation(constant)]
581    #[css(skip)]
582    clamping_mode: AllowedNumericType,
583    node: CalcNode,
584}
585
586/// Type for anchor side in `calc()` and other math fucntions.
587pub type CalcAnchorSide = GenericAnchorSide<Box<CalcNode>>;
588
589/// Result of resolving `CalcLengthPercentage`
590pub struct CalcLengthPercentageResolution {
591    /// The resolved length.
592    pub result: Length,
593    /// Did the resolution of this calc node require resolving percentages?
594    pub percentage_used: bool,
595}
596
597/// What anchor positioning functions are allowed to resolve in calc percentage
598/// values.
599#[repr(C)]
600#[derive(Clone, Copy)]
601pub enum AllowAnchorPosResolutionInCalcPercentage {
602    /// Both `anchor()` and `anchor-size()` are valid and should be resolved.
603    Both(PhysicalSide),
604    /// Only `anchor-size()` is valid and should be resolved.
605    AnchorSizeOnly(PhysicalAxis),
606}
607
608impl AllowAnchorPosResolutionInCalcPercentage {
609    #[cfg(feature = "gecko")]
610    /// Get the `anchor-size()` resolution axis.
611    pub fn to_axis(&self) -> PhysicalAxis {
612        match self {
613            Self::AnchorSizeOnly(axis) => *axis,
614            Self::Both(side) => {
615                if matches!(side, PhysicalSide::Top | PhysicalSide::Bottom) {
616                    PhysicalAxis::Vertical
617                } else {
618                    PhysicalAxis::Horizontal
619                }
620            },
621        }
622    }
623}
624
625impl From<&CalcAnchorSide> for AnchorSide {
626    fn from(value: &CalcAnchorSide) -> Self {
627        match value {
628            CalcAnchorSide::Keyword(k) => Self::Keyword(*k),
629            CalcAnchorSide::Percentage(p) => {
630                if let CalcNode::Leaf(ComputedLeaf::Percentage(p)) = **p {
631                    Self::Percentage(p)
632                } else {
633                    unreachable!("Should have parsed simplified percentage.");
634                }
635            },
636        }
637    }
638}
639
640impl CalcLengthPercentage {
641    /// Resolves the percentage.
642    #[inline]
643    pub fn resolve(&self, basis: Length) -> Length {
644        // unwrap() is fine because the conversion below is infallible.
645        if let ComputedLeaf::Length(px) = self
646            .node
647            .resolve_map(|leaf| {
648                Ok(if let ComputedLeaf::Percentage(p) = leaf {
649                    ComputedLeaf::Length(Length::new(basis.px() * p.0))
650                } else {
651                    leaf.clone()
652                })
653            })
654            .unwrap()
655        {
656            Length::new(self.clamping_mode.clamp(px.px())).normalized()
657        } else {
658            unreachable!("resolve_map should turn percentages to lengths, and parsing should ensure that we don't end up with a number");
659        }
660    }
661
662    /// Return a clone of this node with all anchor functions computed and replaced with
663    /// corresponding values, returning error if the resolution is invalid.
664    #[inline]
665    #[cfg(feature = "gecko")]
666    pub fn resolve_anchor(
667        &self,
668        allowed: AllowAnchorPosResolutionInCalcPercentage,
669        params: &AnchorPosOffsetResolutionParams,
670    ) -> Result<(CalcNode, AllowedNumericType), ()> {
671        use crate::values::{
672            computed::{length::resolve_anchor_size, AnchorFunction},
673            generics::{length::GenericAnchorSizeFunction, position::GenericAnchorFunction},
674        };
675
676        fn resolve_anchor_function<'a>(
677            f: &'a GenericAnchorFunction<
678                Box<CalcNode>,
679                Box<GenericAnchorFunctionFallback<ComputedLeaf>>,
680            >,
681            side: PhysicalSide,
682            params: &AnchorPosOffsetResolutionParams,
683        ) -> AnchorResolutionResult<'a, Box<CalcNode>> {
684            let anchor_side: &CalcAnchorSide = &f.side;
685            let resolved = if f.valid_for(side, params.mBaseParams.mPosition) {
686                AnchorFunction::resolve(&f.target_element, &anchor_side.into(), side, params).ok()
687            } else {
688                None
689            };
690
691            resolved.map_or_else(
692                || {
693                    let Some(fb) = f.fallback.as_ref() else {
694                        return AnchorResolutionResult::Invalid;
695                    };
696                    let mut node = Box::new(fb.node.clone());
697                    let result = node.map_node(|node| {
698                        resolve_anchor_functions(
699                            node,
700                            AllowAnchorPosResolutionInCalcPercentage::Both(side),
701                            params,
702                        )
703                    });
704                    if result.is_err() {
705                        return AnchorResolutionResult::Invalid;
706                    }
707                    AnchorResolutionResult::Resolved(node)
708                },
709                |v| {
710                    AnchorResolutionResult::Resolved(Box::new(CalcNode::Leaf(
711                        ComputedLeaf::Length(v),
712                    )))
713                },
714            )
715        }
716
717        fn resolve_anchor_size_function<'a>(
718            f: &'a GenericAnchorSizeFunction<Box<GenericAnchorFunctionFallback<ComputedLeaf>>>,
719            allowed: AllowAnchorPosResolutionInCalcPercentage,
720            params: &AnchorPosOffsetResolutionParams,
721        ) -> AnchorResolutionResult<'a, Box<CalcNode>> {
722            let axis = allowed.to_axis();
723            let resolved = if f.valid_for(params.mBaseParams.mPosition) {
724                resolve_anchor_size(&f.target_element, axis, f.size, &params.mBaseParams).ok()
725            } else {
726                None
727            };
728
729            resolved.map_or_else(
730                || {
731                    let Some(fb) = f.fallback.as_ref() else {
732                        return AnchorResolutionResult::Invalid;
733                    };
734                    let mut node = Box::new(fb.node.clone());
735                    let result =
736                        node.map_node(|node| resolve_anchor_functions(node, allowed, params));
737                    if result.is_err() {
738                        return AnchorResolutionResult::Invalid;
739                    }
740                    AnchorResolutionResult::Resolved(node)
741                },
742                |v| {
743                    AnchorResolutionResult::Resolved(Box::new(CalcNode::Leaf(
744                        ComputedLeaf::Length(v),
745                    )))
746                },
747            )
748        }
749
750        fn resolve_anchor_functions(
751            node: &CalcNode,
752            allowed: AllowAnchorPosResolutionInCalcPercentage,
753            params: &AnchorPosOffsetResolutionParams,
754        ) -> Result<Option<CalcNode>, ()> {
755            let resolution = match node {
756                CalcNode::Anchor(f) => {
757                    let prop_side = match allowed {
758                        AllowAnchorPosResolutionInCalcPercentage::Both(side) => side,
759                        AllowAnchorPosResolutionInCalcPercentage::AnchorSizeOnly(_) => {
760                            unreachable!("anchor() found where disallowed")
761                        },
762                    };
763                    resolve_anchor_function(f, prop_side, params)
764                },
765                CalcNode::AnchorSize(f) => resolve_anchor_size_function(f, allowed, params),
766                _ => return Ok(None),
767            };
768
769            match resolution {
770                AnchorResolutionResult::Invalid => Err(()),
771                AnchorResolutionResult::Fallback(fb) => {
772                    // TODO(dshin, bug 1923759): At least for now, fallbacks should not contain any anchor function.
773                    Ok(Some(*fb.clone()))
774                },
775                AnchorResolutionResult::Resolved(v) => Ok(Some(*v.clone())),
776            }
777        }
778
779        let mut node = self.node.clone();
780        node.map_node(|node| resolve_anchor_functions(node, allowed, params))?;
781        Ok((node, self.clamping_mode))
782    }
783}
784
785// NOTE(emilio): We don't compare `clamping_mode` since we want to preserve the
786// invariant that `from_computed_value(length).to_computed_value(..) == length`.
787//
788// Right now for e.g. a non-negative length, we set clamping_mode to `All`
789// unconditionally for non-calc values, and to `NonNegative` for calc.
790//
791// If we determine that it's sound, from_computed_value() can generate an
792// absolute length, which then would get `All` as the clamping mode.
793//
794// We may want to just eagerly-detect whether we can clamp in
795// `LengthPercentage::new` and switch to `AllowedNumericType::NonNegative` then,
796// maybe.
797impl PartialEq for CalcLengthPercentage {
798    fn eq(&self, other: &Self) -> bool {
799        self.node == other.node
800    }
801}
802
803impl specified::CalcLengthPercentage {
804    /// Compute the value, zooming any absolute units by the zoom function.
805    fn to_computed_value_with_zoom<F>(
806        &self,
807        context: &Context,
808        zoom_fn: F,
809        base_size: FontBaseSize,
810        line_height_base: LineHeightBase,
811    ) -> LengthPercentage
812    where
813        F: Fn(Length) -> Length,
814    {
815        use crate::values::specified::calc::Leaf;
816
817        let node = self.0.node.map_leaves(|leaf| match *leaf {
818            Leaf::Percentage(p) => ComputedLeaf::Percentage(Percentage(p.get())),
819            Leaf::Length(l) => ComputedLeaf::Length({
820                let result =
821                    l.to_computed_value_with_base_size(context, base_size, line_height_base);
822                if l.should_zoom_text() {
823                    zoom_fn(result)
824                } else {
825                    result
826                }
827            }),
828            Leaf::Number(n) => ComputedLeaf::Number(n.get()),
829            Leaf::Angle(a) => {
830                ComputedLeaf::Angle(specified::Angle::new(a).to_computed_value(context))
831            },
832            Leaf::Time(t) => ComputedLeaf::Time(specified::Time::new(t).to_computed_value(context)),
833            Leaf::Resolution(r) => {
834                ComputedLeaf::Resolution(specified::Resolution::new(r).to_computed_value(context))
835            },
836            Leaf::ColorComponent(..) => unreachable!("Shouldn't have parsed"),
837        });
838
839        LengthPercentage::new_calc(node, self.0.clamping_mode)
840    }
841
842    /// Compute font-size or line-height taking into account text-zoom if necessary.
843    pub fn to_computed_value_zoomed(
844        &self,
845        context: &Context,
846        base_size: FontBaseSize,
847        line_height_base: LineHeightBase,
848    ) -> LengthPercentage {
849        self.to_computed_value_with_zoom(
850            context,
851            |abs| context.maybe_zoom_text(abs),
852            base_size,
853            line_height_base,
854        )
855    }
856
857    /// Compute the value into pixel length as CSSFloat without context,
858    /// so it returns Err(()) if there is any non-absolute unit.
859    pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> {
860        use crate::values::specified::calc::Leaf;
861
862        // Simplification should've turned this into an absolute length,
863        // otherwise it wouldn't have been able to.
864        match self.0.node {
865            calc::CalcNode::Leaf(Leaf::Length(ref l)) => {
866                l.to_computed_pixel_length_without_context()
867            },
868            _ => Err(()),
869        }
870    }
871
872    /// Compute the value into pixel length as CSSFloat, using the get_font_metrics function
873    /// if provided to resolve font-relative dimensions.
874    #[cfg(feature = "gecko")]
875    pub fn to_computed_pixel_length_with_font_metrics(
876        &self,
877        get_font_metrics: Option<impl Fn() -> GeckoFontMetrics>,
878    ) -> Result<CSSFloat, ()> {
879        use crate::values::specified::calc::Leaf;
880
881        match self.0.node {
882            calc::CalcNode::Leaf(Leaf::Length(ref l)) => {
883                l.to_computed_pixel_length_with_font_metrics(get_font_metrics)
884            },
885            _ => Err(()),
886        }
887    }
888
889    /// Compute the calc using the current font-size and line-height. (and without text-zoom).
890    pub fn to_computed_value(&self, context: &Context) -> LengthPercentage {
891        self.to_computed_value_with_zoom(
892            context,
893            |abs| abs,
894            FontBaseSize::CurrentStyle,
895            LineHeightBase::CurrentStyle,
896        )
897    }
898
899    #[inline]
900    fn from_computed_value(computed: &CalcLengthPercentage) -> Self {
901        use crate::values::specified::angle::NoCalcAngle;
902        use crate::values::specified::calc::Leaf;
903        use crate::values::specified::length::NoCalcLength;
904        use crate::values::specified::resolution::NoCalcResolution;
905        use crate::values::specified::time::NoCalcTime;
906
907        specified::CalcLengthPercentage(specified::CalcNumeric {
908            clamping_mode: computed.clamping_mode,
909            node: computed.node.map_leaves(|l| match l {
910                ComputedLeaf::Length(ref l) => Leaf::Length(NoCalcLength::from_px(l.px())),
911                ComputedLeaf::Percentage(ref p) => Leaf::Percentage(NoCalcPercentage::new(p.0)),
912                ComputedLeaf::Number(n) => Leaf::Number(NoCalcNumber::new(*n)),
913                ComputedLeaf::Angle(a) => Leaf::Angle(NoCalcAngle::from_degrees(a.degrees())),
914                ComputedLeaf::Time(t) => Leaf::Time(NoCalcTime::from_seconds(t.seconds())),
915                ComputedLeaf::Resolution(r) => {
916                    Leaf::Resolution(NoCalcResolution::from_dppx(r.dppx()))
917                },
918            }),
919        })
920    }
921}
922
923/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc
924/// https://drafts.csswg.org/css-values-4/#combine-math
925/// https://drafts.csswg.org/css-values-4/#combine-mixed
926impl Animate for LengthPercentage {
927    #[inline]
928    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
929        Ok(match (self.unpack(), other.unpack()) {
930            (Unpacked::Length(one), Unpacked::Length(other)) => {
931                Self::new_length(one.animate(&other, procedure)?)
932            },
933            (Unpacked::Percentage(one), Unpacked::Percentage(other)) => {
934                Self::new_percent(one.animate(&other, procedure)?)
935            },
936            _ => {
937                use calc::CalcNodeLeaf;
938
939                fn product_with(mut node: CalcNode, product: f32) -> CalcNode {
940                    let mut number = CalcNode::Leaf(ComputedLeaf::new_number(product));
941                    if !node.try_product_in_place(&mut number) {
942                        CalcNode::Product(vec![node, number].into())
943                    } else {
944                        node
945                    }
946                }
947
948                let (l, r) = procedure.weights();
949                let one = product_with(self.to_calc_node(), l as f32);
950                let other = product_with(other.to_calc_node(), r as f32);
951
952                Self::new_calc(
953                    CalcNode::Sum(vec![one, other].into()),
954                    AllowedNumericType::All,
955                )
956            },
957        })
958    }
959}
960
961/// A wrapper of LengthPercentage, whose value must be >= 0.
962pub type NonNegativeLengthPercentage = NonNegative<LengthPercentage>;
963
964impl NonNegativeLengthPercentage {
965    /// Returns the used value.
966    #[inline]
967    pub fn to_used_value(&self, containing_length: Au) -> Au {
968        let resolved = self.0.to_used_value(containing_length);
969        std::cmp::max(resolved, Au(0))
970    }
971
972    /// Convert the computed value into used value.
973    #[inline]
974    pub fn maybe_to_used_value(&self, containing_length: Option<Au>) -> Option<Au> {
975        let resolved = self.0.maybe_to_used_value(containing_length)?;
976        Some(std::cmp::max(resolved, Au(0)))
977    }
978}
979
980impl TryTacticAdjustment for LengthPercentage {
981    fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) {
982        match self.unpack_mut() {
983            UnpackedMut::Calc(calc) => calc.node.try_tactic_adjustment(old_side, new_side),
984            UnpackedMut::Percentage(mut p) => {
985                p.try_tactic_adjustment(old_side, new_side);
986                *self = Self::new_percent(p);
987            },
988            UnpackedMut::Length(..) => {},
989        }
990    }
991}
992
993impl TryTacticAdjustment for GenericAnchorFunctionFallback<ComputedLeaf> {
994    fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) {
995        self.node.try_tactic_adjustment(old_side, new_side)
996    }
997}
998
999impl TryTacticAdjustment for CalcNode {
1000    fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) {
1001        self.visit_depth_first(|node| match node {
1002            Self::Leaf(ComputedLeaf::Percentage(p)) => p.try_tactic_adjustment(old_side, new_side),
1003            Self::Anchor(a) => a.try_tactic_adjustment(old_side, new_side),
1004            Self::AnchorSize(a) => a.try_tactic_adjustment(old_side, new_side),
1005            _ => {},
1006        });
1007    }
1008}