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