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::{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::calc::GenericAnchorFunctionFallback;
23#[cfg(feature = "gecko")]
24use crate::values::generics::length::AnchorResolutionResult;
25use crate::values::generics::position::GenericAnchorSide;
26use crate::values::generics::Optional;
27use crate::values::generics::{calc, ClampToNonNegative, NonNegative};
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::{specified, CSSFloat};
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(p.value)
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!("resolve_map should turn percentages to lengths, and parsing should ensure that we don't end up with a number");
661        }
662    }
663
664    /// Return a clone of this node with all anchor functions computed and replaced with
665    /// corresponding values, returning error if the resolution is invalid.
666    #[inline]
667    #[cfg(feature = "gecko")]
668    pub fn resolve_anchor(
669        &self,
670        allowed: AllowAnchorPosResolutionInCalcPercentage,
671        params: &AnchorPosOffsetResolutionParams,
672    ) -> Result<(CalcNode, AllowedNumericType), ()> {
673        use crate::values::{
674            computed::{length::resolve_anchor_size, AnchorFunction},
675            generics::{length::GenericAnchorSizeFunction, position::GenericAnchorFunction},
676        };
677
678        fn resolve_anchor_function<'a>(
679            f: &'a GenericAnchorFunction<
680                Box<CalcNode>,
681                Box<GenericAnchorFunctionFallback<ComputedLeaf>>,
682            >,
683            side: PhysicalSide,
684            params: &AnchorPosOffsetResolutionParams,
685        ) -> AnchorResolutionResult<'a, Box<CalcNode>> {
686            let anchor_side: &CalcAnchorSide = &f.side;
687            let resolved = if f.valid_for(side, params.mBaseParams.mPosition) {
688                AnchorFunction::resolve(&f.target_element, &anchor_side.into(), side, params).ok()
689            } else {
690                None
691            };
692
693            resolved.map_or_else(
694                || {
695                    let Some(fb) = f.fallback.as_ref() else {
696                        return AnchorResolutionResult::Invalid;
697                    };
698                    let mut node = Box::new(fb.node.clone());
699                    let result = node.map_node(|node| {
700                        resolve_anchor_functions(
701                            node,
702                            AllowAnchorPosResolutionInCalcPercentage::Both(side),
703                            params,
704                        )
705                    });
706                    if result.is_err() {
707                        return AnchorResolutionResult::Invalid;
708                    }
709                    AnchorResolutionResult::Resolved(node)
710                },
711                |v| {
712                    AnchorResolutionResult::Resolved(Box::new(CalcNode::Leaf(
713                        ComputedLeaf::Length(v),
714                    )))
715                },
716            )
717        }
718
719        fn resolve_anchor_size_function<'a>(
720            f: &'a GenericAnchorSizeFunction<Box<GenericAnchorFunctionFallback<ComputedLeaf>>>,
721            allowed: AllowAnchorPosResolutionInCalcPercentage,
722            params: &AnchorPosOffsetResolutionParams,
723        ) -> AnchorResolutionResult<'a, Box<CalcNode>> {
724            let axis = allowed.to_axis();
725            let resolved = if f.valid_for(params.mBaseParams.mPosition) {
726                resolve_anchor_size(&f.target_element, axis, f.size, &params.mBaseParams).ok()
727            } else {
728                None
729            };
730
731            resolved.map_or_else(
732                || {
733                    let Some(fb) = f.fallback.as_ref() else {
734                        return AnchorResolutionResult::Invalid;
735                    };
736                    let mut node = Box::new(fb.node.clone());
737                    let result =
738                        node.map_node(|node| resolve_anchor_functions(node, allowed, params));
739                    if result.is_err() {
740                        return AnchorResolutionResult::Invalid;
741                    }
742                    AnchorResolutionResult::Resolved(node)
743                },
744                |v| {
745                    AnchorResolutionResult::Resolved(Box::new(CalcNode::Leaf(
746                        ComputedLeaf::Length(v),
747                    )))
748                },
749            )
750        }
751
752        fn resolve_anchor_functions(
753            node: &CalcNode,
754            allowed: AllowAnchorPosResolutionInCalcPercentage,
755            params: &AnchorPosOffsetResolutionParams,
756        ) -> Result<Option<CalcNode>, ()> {
757            let resolution = match node {
758                CalcNode::Anchor(f) => {
759                    let prop_side = match allowed {
760                        AllowAnchorPosResolutionInCalcPercentage::Both(side) => side,
761                        AllowAnchorPosResolutionInCalcPercentage::AnchorSizeOnly(_) => {
762                            unreachable!("anchor() found where disallowed")
763                        },
764                    };
765                    resolve_anchor_function(f, prop_side, params)
766                },
767                CalcNode::AnchorSize(f) => resolve_anchor_size_function(f, allowed, params),
768                _ => return Ok(None),
769            };
770
771            match resolution {
772                AnchorResolutionResult::Invalid => Err(()),
773                AnchorResolutionResult::Fallback(fb) => {
774                    // TODO(dshin, bug 1923759): At least for now, fallbacks should not contain any anchor function.
775                    Ok(Some(*fb.clone()))
776                },
777                AnchorResolutionResult::Resolved(v) => Ok(Some(*v.clone())),
778            }
779        }
780
781        let mut node = self.node.clone();
782        node.map_node(|node| resolve_anchor_functions(node, allowed, params))?;
783        Ok((node, self.clamping_mode))
784    }
785}
786
787// NOTE(emilio): We don't compare `clamping_mode` since we want to preserve the
788// invariant that `from_computed_value(length).to_computed_value(..) == length`.
789//
790// Right now for e.g. a non-negative length, we set clamping_mode to `All`
791// unconditionally for non-calc values, and to `NonNegative` for calc.
792//
793// If we determine that it's sound, from_computed_value() can generate an
794// absolute length, which then would get `All` as the clamping mode.
795//
796// We may want to just eagerly-detect whether we can clamp in
797// `LengthPercentage::new` and switch to `AllowedNumericType::NonNegative` then,
798// maybe.
799impl PartialEq for CalcLengthPercentage {
800    fn eq(&self, other: &Self) -> bool {
801        self.node == other.node
802    }
803}
804
805impl specified::CalcLengthPercentage {
806    /// Compute the value, zooming any absolute units by the zoom function.
807    fn to_computed_value_with_zoom<F>(
808        &self,
809        context: &Context,
810        zoom_fn: F,
811        base_size: FontBaseSize,
812        line_height_base: LineHeightBase,
813    ) -> LengthPercentage
814    where
815        F: Fn(Length) -> Length,
816    {
817        use crate::values::specified::calc::Leaf;
818
819        let node = self.0.node.map_leaves(|leaf| match *leaf {
820            Leaf::Percentage(p) => {
821                ComputedLeaf::Percentage(CalcPercentageLeaf::new(p.get(), p.hint))
822            },
823            Leaf::Length(l) => ComputedLeaf::Length({
824                let result =
825                    l.to_computed_value_with_base_size(context, base_size, line_height_base);
826                if l.should_zoom_text() {
827                    zoom_fn(result)
828                } else {
829                    result
830                }
831            }),
832            Leaf::Number(n) => ComputedLeaf::Number(n.get()),
833            Leaf::Angle(a) => {
834                ComputedLeaf::Angle(specified::Angle::new(a).to_computed_value(context))
835            },
836            Leaf::Time(t) => ComputedLeaf::Time(specified::Time::new(t).to_computed_value(context)),
837            Leaf::Resolution(r) => {
838                ComputedLeaf::Resolution(specified::Resolution::new(r).to_computed_value(context))
839            },
840            Leaf::ColorComponent(..) => unreachable!("Shouldn't have parsed"),
841            Leaf::TreeCountingFunction(t) => {
842                ComputedLeaf::Number(t.to_computed_value(context) as f32)
843            },
844        });
845
846        LengthPercentage::new_calc(node, self.0.clamping_mode)
847    }
848
849    /// Compute font-size or line-height taking into account text-zoom if necessary.
850    pub fn to_computed_value_zoomed(
851        &self,
852        context: &Context,
853        base_size: FontBaseSize,
854        line_height_base: LineHeightBase,
855    ) -> LengthPercentage {
856        self.to_computed_value_with_zoom(
857            context,
858            |abs| context.maybe_zoom_text(abs),
859            base_size,
860            line_height_base,
861        )
862    }
863
864    /// Compute the value into pixel length as CSSFloat without context,
865    /// so it returns Err(()) if there is any non-absolute unit.
866    pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> {
867        use crate::values::specified::calc::Leaf;
868
869        // Simplification should've turned this into an absolute length,
870        // otherwise it wouldn't have been able to.
871        match self.0.node {
872            calc::CalcNode::Leaf(Leaf::Length(ref l)) => {
873                l.to_computed_pixel_length_without_context()
874            },
875            _ => Err(()),
876        }
877    }
878
879    /// Computes the value without a style context, returning None if any leaf
880    /// needs a one to resolve (e.g. a font- or viewport-relative length).
881    /// Absolute lengths and percentages (and calc() combining them) resolve
882    /// correctly.
883    pub fn compute_without_context(&self) -> Option<LengthPercentage> {
884        use crate::values::specified::calc::Leaf;
885
886        let mut resolvable = true;
887        let node = self.0.node.map_leaves(|leaf| match *leaf {
888            Leaf::Percentage(p) => {
889                ComputedLeaf::Percentage(CalcPercentageLeaf::new(p.get(), p.hint))
890            },
891            Leaf::Length(l) => {
892                ComputedLeaf::Length(match l.to_computed_pixel_length_without_context() {
893                    Ok(px) => Length::new(px),
894                    Err(()) => {
895                        resolvable = false;
896                        Length::new(0.)
897                    },
898                })
899            },
900            Leaf::Number(n) => ComputedLeaf::Number(n.get()),
901            _ => {
902                resolvable = false;
903                ComputedLeaf::Number(0.)
904            },
905        });
906
907        if !resolvable {
908            return None;
909        }
910        Some(LengthPercentage::new_calc(node, self.0.clamping_mode))
911    }
912
913    /// Compute the value into pixel length as CSSFloat, using the get_font_metrics function
914    /// if provided to resolve font-relative dimensions.
915    #[cfg(feature = "gecko")]
916    pub fn to_computed_pixel_length_with_font_metrics(
917        &self,
918        get_font_metrics: Option<impl Fn() -> GeckoFontMetrics>,
919    ) -> Result<CSSFloat, ()> {
920        use crate::values::specified::calc::Leaf;
921
922        match self.0.node {
923            calc::CalcNode::Leaf(Leaf::Length(ref l)) => {
924                l.to_computed_pixel_length_with_font_metrics(get_font_metrics)
925            },
926            _ => Err(()),
927        }
928    }
929
930    /// Compute the calc using the current font-size and line-height. (and without text-zoom).
931    pub fn to_computed_value(&self, context: &Context) -> LengthPercentage {
932        self.to_computed_value_with_zoom(
933            context,
934            |abs| abs,
935            FontBaseSize::CurrentStyle,
936            LineHeightBase::CurrentStyle,
937        )
938    }
939
940    #[inline]
941    fn from_computed_value(computed: &CalcLengthPercentage) -> Self {
942        use crate::values::specified::angle::NoCalcAngle;
943        use crate::values::specified::calc::{
944            CalcPercentageLeaf as SpecifiedCalcPercentageLeaf, Leaf,
945        };
946        use crate::values::specified::length::NoCalcLength;
947        use crate::values::specified::resolution::NoCalcResolution;
948        use crate::values::specified::time::NoCalcTime;
949
950        specified::CalcLengthPercentage(specified::CalcNumeric {
951            clamping_mode: computed.clamping_mode,
952            node: computed.node.map_leaves(|l| match l {
953                ComputedLeaf::Length(l) => Leaf::Length(NoCalcLength::from_px(l.px())),
954                ComputedLeaf::Percentage(p) => {
955                    Leaf::Percentage(SpecifiedCalcPercentageLeaf::new(p.get(), p.hint))
956                },
957                ComputedLeaf::Number(n) => Leaf::Number(NoCalcNumber::new(*n)),
958                ComputedLeaf::Angle(a) => Leaf::Angle(NoCalcAngle::from_degrees(a.degrees())),
959                ComputedLeaf::Time(t) => Leaf::Time(NoCalcTime::from_seconds(t.seconds())),
960                ComputedLeaf::Resolution(r) => {
961                    Leaf::Resolution(NoCalcResolution::from_dppx(r.dppx()))
962                },
963            }),
964        })
965    }
966}
967
968/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc
969/// https://drafts.csswg.org/css-values-4/#combine-math
970/// https://drafts.csswg.org/css-values-4/#combine-mixed
971impl Animate for LengthPercentage {
972    #[inline]
973    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
974        Ok(match (self.unpack(), other.unpack()) {
975            (Unpacked::Length(one), Unpacked::Length(other)) => {
976                Self::new_length(one.animate(&other, procedure)?)
977            },
978            (Unpacked::Percentage(one), Unpacked::Percentage(other)) => {
979                Self::new_percent(one.animate(&other, procedure)?)
980            },
981            _ => {
982                use calc::CalcNodeLeaf;
983
984                fn product_with(mut node: CalcNode, product: f32) -> CalcNode {
985                    let mut number = CalcNode::Leaf(ComputedLeaf::new_number(product));
986                    if !node.try_product_in_place(&mut number) {
987                        CalcNode::Product(vec![node, number].into())
988                    } else {
989                        node
990                    }
991                }
992
993                let (l, r) = procedure.weights();
994                let one = product_with(self.to_calc_node(), l as f32);
995                let other = product_with(other.to_calc_node(), r as f32);
996
997                Self::new_calc(
998                    CalcNode::Sum(vec![one, other].into()),
999                    AllowedNumericType::All,
1000                )
1001            },
1002        })
1003    }
1004}
1005
1006/// A wrapper of LengthPercentage, whose value must be >= 0.
1007pub type NonNegativeLengthPercentage = NonNegative<LengthPercentage>;
1008
1009impl NonNegativeLengthPercentage {
1010    /// Returns the used value.
1011    #[inline]
1012    pub fn to_used_value(&self, containing_length: Au) -> Au {
1013        let resolved = self.0.to_used_value(containing_length);
1014        std::cmp::max(resolved, Au(0))
1015    }
1016
1017    /// Convert the computed value into used value.
1018    #[inline]
1019    pub fn maybe_to_used_value(&self, containing_length: Option<Au>) -> Option<Au> {
1020        let resolved = self.0.maybe_to_used_value(containing_length)?;
1021        Some(std::cmp::max(resolved, Au(0)))
1022    }
1023}
1024
1025impl TryTacticAdjustment for LengthPercentage {
1026    fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) {
1027        match self.unpack_mut() {
1028            UnpackedMut::Calc(calc) => calc.node.try_tactic_adjustment(old_side, new_side),
1029            UnpackedMut::Percentage(mut p) => {
1030                p.try_tactic_adjustment(old_side, new_side);
1031                *self = Self::new_percent(p);
1032            },
1033            UnpackedMut::Length(..) => {},
1034        }
1035    }
1036}
1037
1038impl TryTacticAdjustment for GenericAnchorFunctionFallback<ComputedLeaf> {
1039    fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) {
1040        self.node.try_tactic_adjustment(old_side, new_side)
1041    }
1042}
1043
1044impl TryTacticAdjustment for CalcNode {
1045    fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) {
1046        self.visit_depth_first(|node| match node {
1047            Self::Leaf(ComputedLeaf::Percentage(p)) => {
1048                p.value.try_tactic_adjustment(old_side, new_side)
1049            },
1050            Self::Anchor(a) => a.try_tactic_adjustment(old_side, new_side),
1051            Self::AnchorSize(a) => a.try_tactic_adjustment(old_side, new_side),
1052            _ => {},
1053        });
1054    }
1055}