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 bits of the pointer
8//! being non-zero if it is a non-calc value.
9//!
10//! It is expected to take 64 bits both in x86 and x86-64. This is implemented
11//! as a `union`, with 4 different variants:
12//!
13//!  * The length and percentage variants have a { tag, f32 } (effectively)
14//!    layout. The tag has to overlap with the lower 2 bits of the calc variant.
15//!
16//!  * The `calc()` variant is a { tag, pointer } in x86 (so same as the
17//!    others), or just a { pointer } in x86-64 (so that the two bits of the tag
18//!    can be obtained from the lower bits of the pointer).
19//!
20//!  * There's a `tag` variant just to make clear when only the tag is intended
21//!    to be read. Note that the tag needs to be masked always by `TAG_MASK`, to
22//!    deal with the pointer variant in x86-64.
23//!
24//! The assertions in the constructor methods ensure that the tag getter matches
25//! our expectations.
26
27use super::{position::AnchorSide, Context, Length, Percentage, ToComputedValue};
28use crate::derives::*;
29#[cfg(feature = "gecko")]
30use crate::gecko_bindings::structs::{AnchorPosOffsetResolutionParams, GeckoFontMetrics};
31use crate::logical_geometry::{PhysicalAxis, PhysicalSide};
32use crate::values::animated::{
33    Animate, Context as AnimatedContext, Procedure, ToAnimatedValue, ToAnimatedZero,
34};
35use crate::values::computed::position::TryTacticAdjustment;
36use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
37use crate::values::generics::calc::{CalcUnits, PositivePercentageBasis};
38#[cfg(feature = "gecko")]
39use crate::values::generics::length::AnchorResolutionResult;
40use crate::values::generics::position::GenericAnchorSide;
41use crate::values::generics::{calc, ClampToNonNegative, NonNegative};
42use crate::values::resolved::{Context as ResolvedContext, ToResolvedValue};
43use crate::values::specified::length::{FontBaseSize, EqualsPercentage, LineHeightBase};
44use crate::values::{specified, CSSFloat};
45use crate::{Zero, ZeroNoPercent};
46use app_units::Au;
47use debug_unreachable::debug_unreachable;
48use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
49use serde::{Deserialize, Serialize};
50use std::fmt::{self, Write};
51use style_traits::values::specified::AllowedNumericType;
52use style_traits::{CssWriter, ToCss, ToTyped, TypedValue};
53
54#[doc(hidden)]
55#[derive(Clone, Copy)]
56#[repr(C)]
57pub struct LengthVariant {
58    tag: u8,
59    length: Length,
60}
61
62#[doc(hidden)]
63#[derive(Clone, Copy)]
64#[repr(C)]
65pub struct PercentageVariant {
66    tag: u8,
67    percentage: Percentage,
68}
69
70// NOTE(emilio): cbindgen only understands the #[cfg] on the top level
71// definition.
72#[doc(hidden)]
73#[derive(Clone, Copy)]
74#[repr(C)]
75#[cfg(target_pointer_width = "32")]
76pub struct CalcVariant {
77    tag: u8,
78    // Ideally CalcLengthPercentage, but that would cause circular references
79    // for leaves referencing LengthPercentage.
80    ptr: *mut (),
81}
82
83#[doc(hidden)]
84#[derive(Clone, Copy)]
85#[repr(C)]
86#[cfg(target_pointer_width = "64")]
87pub struct CalcVariant {
88    ptr: usize, // In little-endian byte order
89}
90
91// `CalcLengthPercentage` is `Send + Sync` as asserted below.
92unsafe impl Send for CalcVariant {}
93unsafe impl Sync for CalcVariant {}
94
95#[doc(hidden)]
96#[derive(Clone, Copy)]
97#[repr(C)]
98pub struct TagVariant {
99    tag: u8,
100}
101
102/// A `<length-percentage>` value. This can be either a `<length>`, a
103/// `<percentage>`, or a combination of both via `calc()`.
104///
105/// cbindgen:private-default-tagged-enum-constructor=false
106/// cbindgen:derive-mut-casts=true
107///
108/// https://drafts.csswg.org/css-values-4/#typedef-length-percentage
109///
110/// The tag is stored in the lower two bits.
111///
112/// We need to use a struct instead of the union directly because unions with
113/// Drop implementations are unstable, looks like.
114///
115/// Also we need the union and the variants to be `pub` (even though the member
116/// is private) so that cbindgen generates it. They're not part of the public
117/// API otherwise.
118#[repr(transparent)]
119pub struct LengthPercentage(LengthPercentageUnion);
120
121#[doc(hidden)]
122#[repr(C)]
123pub union LengthPercentageUnion {
124    length: LengthVariant,
125    percentage: PercentageVariant,
126    calc: CalcVariant,
127    tag: TagVariant,
128}
129
130impl LengthPercentageUnion {
131    #[doc(hidden)] // Need to be public so that cbindgen generates it.
132    pub const TAG_CALC: u8 = 0;
133    #[doc(hidden)]
134    pub const TAG_LENGTH: u8 = 1;
135    #[doc(hidden)]
136    pub const TAG_PERCENTAGE: u8 = 2;
137    #[doc(hidden)]
138    pub const TAG_MASK: u8 = 0b11;
139}
140
141#[derive(Clone, Copy, Debug, PartialEq)]
142#[repr(u8)]
143enum Tag {
144    Calc = LengthPercentageUnion::TAG_CALC,
145    Length = LengthPercentageUnion::TAG_LENGTH,
146    Percentage = LengthPercentageUnion::TAG_PERCENTAGE,
147}
148
149// All the members should be 64 bits, even in 32-bit builds.
150#[allow(unused)]
151unsafe fn static_assert() {
152    fn assert_send_and_sync<T: Send + Sync>() {}
153    std::mem::transmute::<u64, LengthVariant>(0u64);
154    std::mem::transmute::<u64, PercentageVariant>(0u64);
155    std::mem::transmute::<u64, CalcVariant>(0u64);
156    std::mem::transmute::<u64, LengthPercentage>(0u64);
157    assert_send_and_sync::<LengthVariant>();
158    assert_send_and_sync::<PercentageVariant>();
159    assert_send_and_sync::<CalcLengthPercentage>();
160}
161
162impl Drop for LengthPercentage {
163    fn drop(&mut self) {
164        if self.tag() == Tag::Calc {
165            let _ = unsafe { Box::from_raw(self.calc_ptr()) };
166        }
167    }
168}
169
170impl MallocSizeOf for LengthPercentage {
171    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
172        match self.unpack() {
173            Unpacked::Length(..) | Unpacked::Percentage(..) => 0,
174            Unpacked::Calc(c) => unsafe { ops.malloc_size_of(c) },
175        }
176    }
177}
178
179impl ToAnimatedValue for LengthPercentage {
180    type AnimatedValue = Self;
181
182    fn to_animated_value(self, context: &AnimatedContext) -> Self::AnimatedValue {
183        if context.style.effective_zoom.is_one() {
184            return self;
185        }
186        self.map_lengths(|l| l.to_animated_value(context))
187    }
188
189    #[inline]
190    fn from_animated_value(value: Self::AnimatedValue) -> Self {
191        value
192    }
193}
194
195impl ToResolvedValue for LengthPercentage {
196    type ResolvedValue = Self;
197
198    fn to_resolved_value(self, context: &ResolvedContext) -> Self::ResolvedValue {
199        if context.style.effective_zoom.is_one() {
200            return self;
201        }
202        self.map_lengths(|l| l.to_resolved_value(context))
203    }
204
205    #[inline]
206    fn from_resolved_value(value: Self::ResolvedValue) -> Self {
207        value
208    }
209}
210
211impl EqualsPercentage for LengthPercentage {
212    fn equals_percentage(&self, v: CSSFloat) -> bool {
213        match self.unpack() {
214            Unpacked::Percentage(p) => p.0 == v,
215            _ => false,
216        }
217    }
218}
219
220/// An unpacked `<length-percentage>` that borrows the `calc()` variant.
221#[derive(Clone, Debug, PartialEq, ToCss, ToTyped)]
222#[typed_value(derive_fields)]
223pub enum Unpacked<'a> {
224    /// A `calc()` value
225    Calc(&'a CalcLengthPercentage),
226    /// A length value
227    Length(Length),
228    /// A percentage value
229    Percentage(Percentage),
230}
231
232/// An unpacked `<length-percentage>` that mutably borrows the `calc()` variant.
233enum UnpackedMut<'a> {
234    Calc(&'a mut CalcLengthPercentage),
235    Length(Length),
236    Percentage(Percentage),
237}
238
239/// An unpacked `<length-percentage>` that owns the `calc()` variant, for
240/// serialization purposes.
241#[derive(Deserialize, PartialEq, Serialize)]
242enum Serializable {
243    Calc(CalcLengthPercentage),
244    Length(Length),
245    Percentage(Percentage),
246}
247
248impl LengthPercentage {
249    /// 1px length value for SVG defaults
250    #[inline]
251    pub fn one() -> Self {
252        Self::new_length(Length::new(1.))
253    }
254
255    /// 0%
256    #[inline]
257    pub fn zero_percent() -> Self {
258        Self::new_percent(Percentage::zero())
259    }
260
261    fn to_calc_node(&self) -> CalcNode {
262        match self.unpack() {
263            Unpacked::Length(l) => CalcNode::Leaf(CalcLengthPercentageLeaf::Length(l)),
264            Unpacked::Percentage(p) => CalcNode::Leaf(CalcLengthPercentageLeaf::Percentage(p)),
265            Unpacked::Calc(p) => p.node.clone(),
266        }
267    }
268
269    fn map_lengths(&self, mut map_fn: impl FnMut(Length) -> Length) -> Self {
270        match self.unpack() {
271            Unpacked::Length(l) => Self::new_length(map_fn(l)),
272            Unpacked::Percentage(p) => Self::new_percent(p),
273            Unpacked::Calc(lp) => Self::new_calc_unchecked(Box::new(CalcLengthPercentage {
274                clamping_mode: lp.clamping_mode,
275                node: lp.node.map_leaves(|leaf| match *leaf {
276                    CalcLengthPercentageLeaf::Length(ref l) => {
277                        CalcLengthPercentageLeaf::Length(map_fn(*l))
278                    },
279                    ref l => l.clone(),
280                }),
281            })),
282        }
283    }
284
285    /// Constructs a length value.
286    #[inline]
287    pub fn new_length(length: Length) -> Self {
288        let length = Self(LengthPercentageUnion {
289            length: LengthVariant {
290                tag: LengthPercentageUnion::TAG_LENGTH,
291                length,
292            },
293        });
294        debug_assert_eq!(length.tag(), Tag::Length);
295        length
296    }
297
298    /// Constructs a percentage value.
299    #[inline]
300    pub fn new_percent(percentage: Percentage) -> Self {
301        let percent = Self(LengthPercentageUnion {
302            percentage: PercentageVariant {
303                tag: LengthPercentageUnion::TAG_PERCENTAGE,
304                percentage,
305            },
306        });
307        debug_assert_eq!(percent.tag(), Tag::Percentage);
308        percent
309    }
310
311    /// Given a `LengthPercentage` value `v`, construct the value representing
312    /// `calc(100% - v)`.
313    pub fn hundred_percent_minus(v: Self, clamping_mode: AllowedNumericType) -> Self {
314        // TODO: This could in theory take ownership of the calc node in `v` if
315        // possible instead of cloning.
316        let mut node = v.to_calc_node();
317        node.negate();
318
319        let new_node = CalcNode::Sum(
320            vec![
321                CalcNode::Leaf(CalcLengthPercentageLeaf::Percentage(Percentage::hundred())),
322                node,
323            ]
324            .into(),
325        );
326
327        Self::new_calc(new_node, clamping_mode)
328    }
329
330    /// Given a list of `LengthPercentage` values, construct the value representing
331    /// `calc(100% - the sum of the list)`.
332    pub fn hundred_percent_minus_list(list: &[&Self], clamping_mode: AllowedNumericType) -> Self {
333        let mut new_list = vec![CalcNode::Leaf(CalcLengthPercentageLeaf::Percentage(
334            Percentage::hundred(),
335        ))];
336
337        for lp in list.iter() {
338            let mut node = lp.to_calc_node();
339            node.negate();
340            new_list.push(node)
341        }
342
343        Self::new_calc(CalcNode::Sum(new_list.into()), clamping_mode)
344    }
345
346    /// Constructs a `calc()` value.
347    #[inline]
348    pub fn new_calc(mut node: CalcNode, clamping_mode: AllowedNumericType) -> Self {
349        node.simplify_and_sort();
350
351        match node {
352            CalcNode::Leaf(l) => {
353                return match l {
354                    CalcLengthPercentageLeaf::Length(l) => {
355                        Self::new_length(Length::new(clamping_mode.clamp(l.px())).normalized())
356                    },
357                    CalcLengthPercentageLeaf::Percentage(p) => Self::new_percent(Percentage(
358                        clamping_mode.clamp(crate::values::normalize(p.0)),
359                    )),
360                    CalcLengthPercentageLeaf::Number(number) => {
361                        debug_assert!(
362                            false,
363                            "The final result of a <length-percentage> should never be a number"
364                        );
365                        Self::new_length(Length::new(number))
366                    },
367                };
368            },
369            _ => Self::new_calc_unchecked(Box::new(CalcLengthPercentage {
370                clamping_mode,
371                node,
372            })),
373        }
374    }
375
376    /// Private version of new_calc() that constructs a calc() variant without
377    /// checking.
378    fn new_calc_unchecked(calc: Box<CalcLengthPercentage>) -> Self {
379        let ptr = Box::into_raw(calc);
380
381        #[cfg(target_pointer_width = "32")]
382        let calc = CalcVariant {
383            tag: LengthPercentageUnion::TAG_CALC,
384            ptr: ptr as *mut (),
385        };
386
387        #[cfg(target_pointer_width = "64")]
388        let calc = CalcVariant {
389            #[cfg(target_endian = "little")]
390            ptr: ptr as usize,
391            #[cfg(target_endian = "big")]
392            ptr: (ptr as usize).swap_bytes(),
393        };
394
395        let calc = Self(LengthPercentageUnion { calc });
396        debug_assert_eq!(calc.tag(), Tag::Calc);
397        calc
398    }
399
400    #[inline]
401    fn tag(&self) -> Tag {
402        match unsafe { self.0.tag.tag & LengthPercentageUnion::TAG_MASK } {
403            LengthPercentageUnion::TAG_CALC => Tag::Calc,
404            LengthPercentageUnion::TAG_LENGTH => Tag::Length,
405            LengthPercentageUnion::TAG_PERCENTAGE => Tag::Percentage,
406            _ => unsafe { debug_unreachable!("Bogus tag?") },
407        }
408    }
409
410    #[inline]
411    fn unpack_mut<'a>(&'a mut self) -> UnpackedMut<'a> {
412        unsafe {
413            match self.tag() {
414                Tag::Calc => UnpackedMut::Calc(&mut *self.calc_ptr()),
415                Tag::Length => UnpackedMut::Length(self.0.length.length),
416                Tag::Percentage => UnpackedMut::Percentage(self.0.percentage.percentage),
417            }
418        }
419    }
420
421    /// Unpack the tagged pointer representation of a length-percentage into an enum
422    /// representation with separate tag and value.
423    #[inline]
424    pub fn unpack<'a>(&'a self) -> Unpacked<'a> {
425        unsafe {
426            match self.tag() {
427                Tag::Calc => Unpacked::Calc(&*self.calc_ptr()),
428                Tag::Length => Unpacked::Length(self.0.length.length),
429                Tag::Percentage => Unpacked::Percentage(self.0.percentage.percentage),
430            }
431        }
432    }
433
434    #[inline]
435    unsafe fn calc_ptr(&self) -> *mut CalcLengthPercentage {
436        #[cfg(not(all(target_endian = "big", target_pointer_width = "64")))]
437        {
438            self.0.calc.ptr as *mut _
439        }
440        #[cfg(all(target_endian = "big", target_pointer_width = "64"))]
441        {
442            self.0.calc.ptr.swap_bytes() as *mut _
443        }
444    }
445
446    #[inline]
447    fn to_serializable(&self) -> Serializable {
448        match self.unpack() {
449            Unpacked::Calc(c) => Serializable::Calc(c.clone()),
450            Unpacked::Length(l) => Serializable::Length(l),
451            Unpacked::Percentage(p) => Serializable::Percentage(p),
452        }
453    }
454
455    #[inline]
456    fn from_serializable(s: Serializable) -> Self {
457        match s {
458            Serializable::Calc(c) => Self::new_calc_unchecked(Box::new(c)),
459            Serializable::Length(l) => Self::new_length(l),
460            Serializable::Percentage(p) => Self::new_percent(p),
461        }
462    }
463
464    /// Resolves the percentage.
465    #[inline]
466    pub fn resolve(&self, basis: Length) -> Length {
467        match self.unpack() {
468            Unpacked::Length(l) => l,
469            Unpacked::Percentage(p) => (basis * p.0).normalized(),
470            Unpacked::Calc(ref c) => c.resolve(basis),
471        }
472    }
473
474    /// Resolves the percentage. Just an alias of resolve().
475    #[inline]
476    pub fn percentage_relative_to(&self, basis: Length) -> Length {
477        self.resolve(basis)
478    }
479
480    /// Return whether there's any percentage in this value.
481    #[inline]
482    pub fn has_percentage(&self) -> bool {
483        match self.unpack() {
484            Unpacked::Length(..) => false,
485            Unpacked::Percentage(..) | Unpacked::Calc(..) => true,
486        }
487    }
488
489    /// Converts to a `<length>` if possible.
490    pub fn to_length(&self) -> Option<Length> {
491        match self.unpack() {
492            Unpacked::Length(l) => Some(l),
493            Unpacked::Percentage(..) | Unpacked::Calc(..) => {
494                debug_assert!(self.has_percentage());
495                return None;
496            },
497        }
498    }
499
500    /// Converts to a `<percentage>` if possible.
501    #[inline]
502    pub fn to_percentage(&self) -> Option<Percentage> {
503        match self.unpack() {
504            Unpacked::Percentage(p) => Some(p),
505            Unpacked::Length(..) | Unpacked::Calc(..) => None,
506        }
507    }
508
509    /// Converts to a `<percentage>` with given basis. Returns None if the basis is 0.
510    #[inline]
511    pub fn to_percentage_of(&self, basis: Length) -> Option<Percentage> {
512        if basis.px() == 0. {
513            return None;
514        }
515        Some(match self.unpack() {
516            Unpacked::Length(l) => Percentage(l.px() / basis.px()),
517            Unpacked::Percentage(p) => p,
518            Unpacked::Calc(ref c) => Percentage(c.resolve(basis).px() / basis.px()),
519        })
520    }
521
522    /// Returns the used value.
523    #[inline]
524    pub fn to_used_value(&self, containing_length: Au) -> Au {
525        let length = self.to_pixel_length(containing_length);
526        if let Unpacked::Percentage(_) = self.unpack() {
527            return Au::from_f32_px_trunc(length.px());
528        }
529        Au::from(length)
530    }
531
532    /// Returns the used value as CSSPixelLength.
533    #[inline]
534    pub fn to_pixel_length(&self, containing_length: Au) -> Length {
535        self.resolve(containing_length.into())
536    }
537
538    /// Convert the computed value into used value.
539    #[inline]
540    pub fn maybe_to_used_value(&self, container_len: Option<Au>) -> Option<Au> {
541        self.maybe_percentage_relative_to(container_len.map(Length::from))
542            .map(if let Unpacked::Percentage(_) = self.unpack() {
543                |length: Length| Au::from_f32_px_trunc(length.px())
544            } else {
545                Au::from
546            })
547    }
548
549    /// If there are special rules for computing percentages in a value (e.g.
550    /// the height property), they apply whenever a calc() expression contains
551    /// percentages.
552    pub fn maybe_percentage_relative_to(&self, container_len: Option<Length>) -> Option<Length> {
553        if let Unpacked::Length(l) = self.unpack() {
554            return Some(l);
555        }
556        Some(self.resolve(container_len?))
557    }
558}
559
560impl ClampToNonNegative for LengthPercentage {
561    /// Returns the clamped non-negative values.
562    #[inline]
563    fn clamp_to_non_negative(mut self) -> Self {
564        match self.unpack_mut() {
565            UnpackedMut::Length(l) => Self::new_length(l.clamp_to_non_negative()),
566            UnpackedMut::Percentage(p) => Self::new_percent(p.clamp_to_non_negative()),
567            UnpackedMut::Calc(ref mut c) => {
568                c.clamping_mode = AllowedNumericType::NonNegative;
569                self
570            },
571        }
572    }
573}
574
575impl PartialEq for LengthPercentage {
576    fn eq(&self, other: &Self) -> bool {
577        self.unpack() == other.unpack()
578    }
579}
580
581impl fmt::Debug for LengthPercentage {
582    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
583        self.unpack().fmt(formatter)
584    }
585}
586
587impl ToAnimatedZero for LengthPercentage {
588    fn to_animated_zero(&self) -> Result<Self, ()> {
589        Ok(match self.unpack() {
590            Unpacked::Length(l) => Self::new_length(l.to_animated_zero()?),
591            Unpacked::Percentage(p) => Self::new_percent(p.to_animated_zero()?),
592            Unpacked::Calc(c) => Self::new_calc_unchecked(Box::new(c.to_animated_zero()?)),
593        })
594    }
595}
596
597impl Clone for LengthPercentage {
598    fn clone(&self) -> Self {
599        match self.unpack() {
600            Unpacked::Length(l) => Self::new_length(l),
601            Unpacked::Percentage(p) => Self::new_percent(p),
602            Unpacked::Calc(c) => Self::new_calc_unchecked(Box::new(c.clone())),
603        }
604    }
605}
606
607impl ToComputedValue for specified::LengthPercentage {
608    type ComputedValue = LengthPercentage;
609
610    fn to_computed_value(&self, context: &Context) -> LengthPercentage {
611        match *self {
612            specified::LengthPercentage::Length(ref value) => {
613                LengthPercentage::new_length(value.to_computed_value(context))
614            },
615            specified::LengthPercentage::Percentage(value) => LengthPercentage::new_percent(value),
616            specified::LengthPercentage::Calc(ref calc) => (**calc).to_computed_value(context),
617        }
618    }
619
620    fn from_computed_value(computed: &LengthPercentage) -> Self {
621        match computed.unpack() {
622            Unpacked::Length(ref l) => {
623                specified::LengthPercentage::Length(ToComputedValue::from_computed_value(l))
624            },
625            Unpacked::Percentage(p) => specified::LengthPercentage::Percentage(p),
626            Unpacked::Calc(c) => {
627                // We simplify before constructing the LengthPercentage if
628                // needed, so this is always fine.
629                specified::LengthPercentage::Calc(Box::new(
630                    specified::CalcLengthPercentage::from_computed_value(c),
631                ))
632            },
633        }
634    }
635}
636
637impl ComputeSquaredDistance for LengthPercentage {
638    #[inline]
639    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
640        // A somewhat arbitrary base, it doesn't really make sense to mix
641        // lengths with percentages, but we can't do much better here, and this
642        // ensures that the distance between length-only and percentage-only
643        // lengths makes sense.
644        let basis = Length::new(100.);
645        self.resolve(basis)
646            .compute_squared_distance(&other.resolve(basis))
647    }
648}
649
650impl ToCss for LengthPercentage {
651    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
652    where
653        W: Write,
654    {
655        self.unpack().to_css(dest)
656    }
657}
658
659impl ToTyped for LengthPercentage {
660    fn to_typed(&self) -> Option<TypedValue> {
661        self.unpack().to_typed()
662    }
663}
664
665impl Zero for LengthPercentage {
666    fn zero() -> Self {
667        LengthPercentage::new_length(Length::zero())
668    }
669
670    /// Returns true if the computed value is absolute 0 or 0%.
671    #[inline]
672    fn is_zero(&self) -> bool {
673        match self.unpack() {
674            Unpacked::Length(l) => l.px() == 0.0,
675            Unpacked::Percentage(p) => p.0 == 0.0,
676            Unpacked::Calc(..) => false,
677        }
678    }
679}
680
681impl ZeroNoPercent for LengthPercentage {
682    #[inline]
683    fn is_zero_no_percent(&self) -> bool {
684        self.to_length().is_some_and(|l| l.px() == 0.0)
685    }
686}
687
688impl Serialize for LengthPercentage {
689    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
690    where
691        S: serde::Serializer,
692    {
693        self.to_serializable().serialize(serializer)
694    }
695}
696
697impl<'de> Deserialize<'de> for LengthPercentage {
698    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
699    where
700        D: serde::Deserializer<'de>,
701    {
702        Ok(Self::from_serializable(Serializable::deserialize(
703            deserializer,
704        )?))
705    }
706}
707
708/// The leaves of a `<length-percentage>` calc expression.
709#[derive(
710    Clone,
711    Debug,
712    Deserialize,
713    MallocSizeOf,
714    PartialEq,
715    Serialize,
716    ToAnimatedZero,
717    ToCss,
718    ToResolvedValue,
719    ToTyped,
720)]
721#[allow(missing_docs)]
722#[repr(u8)]
723#[typed_value(derive_fields)]
724pub enum CalcLengthPercentageLeaf {
725    Length(Length),
726    Percentage(Percentage),
727    Number(f32),
728}
729
730impl CalcLengthPercentageLeaf {
731    fn is_zero_length(&self) -> bool {
732        match *self {
733            Self::Length(ref l) => l.is_zero(),
734            Self::Percentage(..) => false,
735            Self::Number(..) => false,
736        }
737    }
738}
739
740impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf {
741    fn unit(&self) -> CalcUnits {
742        match self {
743            Self::Length(_) => CalcUnits::LENGTH,
744            Self::Percentage(_) => CalcUnits::PERCENTAGE,
745            Self::Number(_) => CalcUnits::empty(),
746        }
747    }
748
749    fn unitless_value(&self) -> Option<f32> {
750        Some(match *self {
751            Self::Length(ref l) => l.px(),
752            Self::Percentage(ref p) => p.0,
753            Self::Number(n) => n,
754        })
755    }
756
757    fn new_number(value: f32) -> Self {
758        Self::Number(value)
759    }
760
761    fn as_number(&self) -> Option<f32> {
762        match *self {
763            Self::Length(_) | Self::Percentage(_) => None,
764            Self::Number(value) => Some(value),
765        }
766    }
767
768    fn compare(&self, other: &Self, basis: PositivePercentageBasis) -> Option<std::cmp::Ordering> {
769        use self::CalcLengthPercentageLeaf::*;
770        if std::mem::discriminant(self) != std::mem::discriminant(other) {
771            return None;
772        }
773
774        if matches!(self, Percentage(..)) && matches!(basis, PositivePercentageBasis::Unknown) {
775            return None;
776        }
777
778        let Ok(self_negative) = self.is_negative() else {
779            return None;
780        };
781        let Ok(other_negative) = other.is_negative() else {
782            return None;
783        };
784        if self_negative != other_negative {
785            return Some(if self_negative {
786                std::cmp::Ordering::Less
787            } else {
788                std::cmp::Ordering::Greater
789            });
790        }
791
792        match (self, other) {
793            (&Length(ref one), &Length(ref other)) => one.partial_cmp(other),
794            (&Percentage(ref one), &Percentage(ref other)) => one.partial_cmp(other),
795            (&Number(ref one), &Number(ref other)) => one.partial_cmp(other),
796            _ => unsafe {
797                match *self {
798                    Length(..) | Percentage(..) | Number(..) => {},
799                }
800                debug_unreachable!("Forgot to handle unit in compare()")
801            },
802        }
803    }
804
805    fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> {
806        use self::CalcLengthPercentageLeaf::*;
807
808        // 0px plus anything else is equal to the right hand side.
809        if self.is_zero_length() {
810            *self = other.clone();
811            return Ok(());
812        }
813
814        if other.is_zero_length() {
815            return Ok(());
816        }
817
818        if std::mem::discriminant(self) != std::mem::discriminant(other) {
819            return Err(());
820        }
821
822        match (self, other) {
823            (&mut Length(ref mut one), &Length(ref other)) => {
824                *one += *other;
825            },
826            (&mut Percentage(ref mut one), &Percentage(ref other)) => {
827                one.0 += other.0;
828            },
829            (&mut Number(ref mut one), &Number(ref other)) => {
830                *one += *other;
831            },
832            _ => unsafe {
833                match *other {
834                    Length(..) | Percentage(..) | Number(..) => {},
835                }
836                debug_unreachable!("Forgot to handle unit in try_sum_in_place()")
837            },
838        }
839
840        Ok(())
841    }
842
843    fn try_product_in_place(&mut self, other: &mut Self) -> bool {
844        if let Self::Number(ref mut left) = *self {
845            if let Self::Number(ref right) = *other {
846                // Both sides are numbers, so we can just modify the left side.
847                *left *= *right;
848                true
849            } else {
850                // The right side is not a number, so the result should be in the units of the right
851                // side.
852                if other.map(|v| v * *left).is_ok() {
853                    std::mem::swap(self, other);
854                    true
855                } else {
856                    false
857                }
858            }
859        } else if let Self::Number(ref right) = *other {
860            // The left side is not a number, but the right side is, so the result is the left
861            // side unit.
862            self.map(|v| v * *right).is_ok()
863        } else {
864            // Neither side is a number, so a product is not possible.
865            false
866        }
867    }
868
869    fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()>
870    where
871        O: Fn(f32, f32) -> f32,
872    {
873        use self::CalcLengthPercentageLeaf::*;
874        if std::mem::discriminant(self) != std::mem::discriminant(other) {
875            return Err(());
876        }
877        Ok(match (self, other) {
878            (&Length(ref one), &Length(ref other)) => {
879                Length(super::Length::new(op(one.px(), other.px())))
880            },
881            (&Percentage(one), &Percentage(other)) => {
882                Self::Percentage(super::Percentage(op(one.0, other.0)))
883            },
884            (&Number(one), &Number(other)) => Self::Number(op(one, other)),
885            _ => unsafe {
886                match *self {
887                    Length(..) | Percentage(..) | Number(..) => {},
888                }
889                debug_unreachable!("Forgot to handle unit in try_op()")
890            },
891        })
892    }
893
894    fn map(&mut self, mut op: impl FnMut(f32) -> f32) -> Result<(), ()> {
895        Ok(match self {
896            Self::Length(value) => {
897                *value = Length::new(op(value.px()));
898            },
899            Self::Percentage(value) => {
900                *value = Percentage(op(value.0));
901            },
902            Self::Number(value) => {
903                *value = op(*value);
904            },
905        })
906    }
907
908    fn simplify(&mut self) {}
909
910    fn sort_key(&self) -> calc::SortKey {
911        match *self {
912            Self::Length(..) => calc::SortKey::Px,
913            Self::Percentage(..) => calc::SortKey::Percentage,
914            Self::Number(..) => calc::SortKey::Number,
915        }
916    }
917}
918
919/// The computed version of a calc() node for `<length-percentage>` values.
920pub type CalcNode = calc::GenericCalcNode<CalcLengthPercentageLeaf>;
921
922/// The representation of a calc() function with mixed lengths and percentages.
923#[derive(
924    Clone,
925    Debug,
926    Deserialize,
927    MallocSizeOf,
928    Serialize,
929    ToAnimatedZero,
930    ToResolvedValue,
931    ToCss,
932    ToTyped,
933)]
934#[repr(C)]
935#[typed_value(derive_fields)]
936pub struct CalcLengthPercentage {
937    #[animation(constant)]
938    #[css(skip)]
939    clamping_mode: AllowedNumericType,
940    node: CalcNode,
941}
942
943/// Type for anchor side in `calc()` and other math fucntions.
944pub type CalcAnchorSide = GenericAnchorSide<Box<CalcNode>>;
945
946/// Result of resolving `CalcLengthPercentage`
947pub struct CalcLengthPercentageResolution {
948    /// The resolved length.
949    pub result: Length,
950    /// Did the resolution of this calc node require resolving percentages?
951    pub percentage_used: bool,
952}
953
954/// What anchor positioning functions are allowed to resolve in calc percentage
955/// values.
956#[repr(C)]
957#[derive(Clone, Copy)]
958pub enum AllowAnchorPosResolutionInCalcPercentage {
959    /// Both `anchor()` and `anchor-size()` are valid and should be resolved.
960    Both(PhysicalSide),
961    /// Only `anchor-size()` is valid and should be resolved.
962    AnchorSizeOnly(PhysicalAxis),
963}
964
965impl AllowAnchorPosResolutionInCalcPercentage {
966    #[cfg(feature = "gecko")]
967    /// Get the `anchor-size()` resolution axis.
968    pub fn to_axis(&self) -> PhysicalAxis {
969        match self {
970            Self::AnchorSizeOnly(axis) => *axis,
971            Self::Both(side) => {
972                if matches!(side, PhysicalSide::Top | PhysicalSide::Bottom) {
973                    PhysicalAxis::Vertical
974                } else {
975                    PhysicalAxis::Horizontal
976                }
977            },
978        }
979    }
980}
981
982impl From<&CalcAnchorSide> for AnchorSide {
983    fn from(value: &CalcAnchorSide) -> Self {
984        match value {
985            CalcAnchorSide::Keyword(k) => Self::Keyword(*k),
986            CalcAnchorSide::Percentage(p) => {
987                if let CalcNode::Leaf(CalcLengthPercentageLeaf::Percentage(p)) = **p {
988                    Self::Percentage(p)
989                } else {
990                    unreachable!("Should have parsed simplified percentage.");
991                }
992            },
993        }
994    }
995}
996
997impl CalcLengthPercentage {
998    /// Resolves the percentage.
999    #[inline]
1000    pub fn resolve(&self, basis: Length) -> Length {
1001        // unwrap() is fine because the conversion below is infallible.
1002        if let CalcLengthPercentageLeaf::Length(px) = self
1003            .node
1004            .resolve_map(|leaf| {
1005                Ok(if let CalcLengthPercentageLeaf::Percentage(p) = leaf {
1006                    CalcLengthPercentageLeaf::Length(Length::new(basis.px() * p.0))
1007                } else {
1008                    leaf.clone()
1009                })
1010            })
1011            .unwrap()
1012        {
1013            Length::new(self.clamping_mode.clamp(px.px())).normalized()
1014        } else {
1015            unreachable!("resolve_map should turn percentages to lengths, and parsing should ensure that we don't end up with a number");
1016        }
1017    }
1018
1019    /// Return a clone of this node with all anchor functions computed and replaced with
1020    /// corresponding values, returning error if the resolution is invalid.
1021    #[inline]
1022    #[cfg(feature = "gecko")]
1023    pub fn resolve_anchor(
1024        &self,
1025        allowed: AllowAnchorPosResolutionInCalcPercentage,
1026        params: &AnchorPosOffsetResolutionParams,
1027    ) -> Result<(CalcNode, AllowedNumericType), ()> {
1028        use crate::values::{
1029            computed::{length::resolve_anchor_size, AnchorFunction},
1030            generics::{length::GenericAnchorSizeFunction, position::GenericAnchorFunction},
1031        };
1032
1033        fn resolve_anchor_function<'a>(
1034            f: &'a GenericAnchorFunction<Box<CalcNode>, Box<CalcNode>>,
1035            side: PhysicalSide,
1036            params: &AnchorPosOffsetResolutionParams,
1037        ) -> AnchorResolutionResult<'a, Box<CalcNode>> {
1038            let anchor_side: &CalcAnchorSide = &f.side;
1039            let resolved = if f.valid_for(side, params.mBaseParams.mPosition) {
1040                AnchorFunction::resolve(&f.target_element, &anchor_side.into(), side, params).ok()
1041            } else {
1042                None
1043            };
1044
1045            resolved.map_or_else(
1046                || {
1047                    let Some(fb) = f.fallback.as_ref() else {
1048                        return AnchorResolutionResult::Invalid;
1049                    };
1050                    let mut node = fb.clone();
1051                    let result = node.map_node(|node| {
1052                        resolve_anchor_functions(
1053                            node,
1054                            AllowAnchorPosResolutionInCalcPercentage::Both(side),
1055                            params,
1056                        )
1057                    });
1058                    if result.is_err() {
1059                        return AnchorResolutionResult::Invalid;
1060                    }
1061                    AnchorResolutionResult::Resolved(node)
1062                },
1063                |v| {
1064                    AnchorResolutionResult::Resolved(Box::new(CalcNode::Leaf(
1065                        CalcLengthPercentageLeaf::Length(v),
1066                    )))
1067                },
1068            )
1069        }
1070
1071        fn resolve_anchor_size_function<'a>(
1072            f: &'a GenericAnchorSizeFunction<Box<CalcNode>>,
1073            allowed: AllowAnchorPosResolutionInCalcPercentage,
1074            params: &AnchorPosOffsetResolutionParams,
1075        ) -> AnchorResolutionResult<'a, Box<CalcNode>> {
1076            let axis = allowed.to_axis();
1077            let resolved = if f.valid_for(params.mBaseParams.mPosition) {
1078                resolve_anchor_size(&f.target_element, axis, f.size, &params.mBaseParams).ok()
1079            } else {
1080                None
1081            };
1082
1083            resolved.map_or_else(
1084                || {
1085                    let Some(fb) = f.fallback.as_ref() else {
1086                        return AnchorResolutionResult::Invalid;
1087                    };
1088                    let mut node = fb.clone();
1089                    let result =
1090                        node.map_node(|node| resolve_anchor_functions(node, allowed, params));
1091                    if result.is_err() {
1092                        return AnchorResolutionResult::Invalid;
1093                    }
1094                    AnchorResolutionResult::Resolved(node)
1095                },
1096                |v| {
1097                    AnchorResolutionResult::Resolved(Box::new(CalcNode::Leaf(
1098                        CalcLengthPercentageLeaf::Length(v),
1099                    )))
1100                },
1101            )
1102        }
1103
1104        fn resolve_anchor_functions(
1105            node: &CalcNode,
1106            allowed: AllowAnchorPosResolutionInCalcPercentage,
1107            params: &AnchorPosOffsetResolutionParams,
1108        ) -> Result<Option<CalcNode>, ()> {
1109            let resolution = match node {
1110                CalcNode::Anchor(f) => {
1111                    let prop_side = match allowed {
1112                        AllowAnchorPosResolutionInCalcPercentage::Both(side) => side,
1113                        AllowAnchorPosResolutionInCalcPercentage::AnchorSizeOnly(_) => {
1114                            unreachable!("anchor() found where disallowed")
1115                        },
1116                    };
1117                    resolve_anchor_function(f, prop_side, params)
1118                },
1119                CalcNode::AnchorSize(f) => resolve_anchor_size_function(f, allowed, params),
1120                _ => return Ok(None),
1121            };
1122
1123            match resolution {
1124                AnchorResolutionResult::Invalid => Err(()),
1125                AnchorResolutionResult::Fallback(fb) => {
1126                    // TODO(dshin, bug 1923759): At least for now, fallbacks should not contain any anchor function.
1127                    Ok(Some(*fb.clone()))
1128                },
1129                AnchorResolutionResult::Resolved(v) => Ok(Some(*v.clone())),
1130            }
1131        }
1132
1133        let mut node = self.node.clone();
1134        node.map_node(|node| resolve_anchor_functions(node, allowed, params))?;
1135        Ok((node, self.clamping_mode))
1136    }
1137}
1138
1139// NOTE(emilio): We don't compare `clamping_mode` since we want to preserve the
1140// invariant that `from_computed_value(length).to_computed_value(..) == length`.
1141//
1142// Right now for e.g. a non-negative length, we set clamping_mode to `All`
1143// unconditionally for non-calc values, and to `NonNegative` for calc.
1144//
1145// If we determine that it's sound, from_computed_value() can generate an
1146// absolute length, which then would get `All` as the clamping mode.
1147//
1148// We may want to just eagerly-detect whether we can clamp in
1149// `LengthPercentage::new` and switch to `AllowedNumericType::NonNegative` then,
1150// maybe.
1151impl PartialEq for CalcLengthPercentage {
1152    fn eq(&self, other: &Self) -> bool {
1153        self.node == other.node
1154    }
1155}
1156
1157impl specified::CalcLengthPercentage {
1158    /// Compute the value, zooming any absolute units by the zoom function.
1159    fn to_computed_value_with_zoom<F>(
1160        &self,
1161        context: &Context,
1162        zoom_fn: F,
1163        base_size: FontBaseSize,
1164        line_height_base: LineHeightBase,
1165    ) -> LengthPercentage
1166    where
1167        F: Fn(Length) -> Length,
1168    {
1169        use crate::values::specified::calc::Leaf;
1170
1171        let node = self.node.map_leaves(|leaf| match *leaf {
1172            Leaf::Percentage(p) => CalcLengthPercentageLeaf::Percentage(Percentage(p)),
1173            Leaf::Length(l) => CalcLengthPercentageLeaf::Length({
1174                let result =
1175                    l.to_computed_value_with_base_size(context, base_size, line_height_base);
1176                if l.should_zoom_text() {
1177                    zoom_fn(result)
1178                } else {
1179                    result
1180                }
1181            }),
1182            Leaf::Number(n) => CalcLengthPercentageLeaf::Number(n),
1183            Leaf::Angle(..) | Leaf::Time(..) | Leaf::Resolution(..) | Leaf::ColorComponent(..) => {
1184                unreachable!("Shouldn't have parsed")
1185            },
1186        });
1187
1188        LengthPercentage::new_calc(node, self.clamping_mode)
1189    }
1190
1191    /// Compute font-size or line-height taking into account text-zoom if necessary.
1192    pub fn to_computed_value_zoomed(
1193        &self,
1194        context: &Context,
1195        base_size: FontBaseSize,
1196        line_height_base: LineHeightBase,
1197    ) -> LengthPercentage {
1198        self.to_computed_value_with_zoom(
1199            context,
1200            |abs| context.maybe_zoom_text(abs),
1201            base_size,
1202            line_height_base,
1203        )
1204    }
1205
1206    /// Compute the value into pixel length as CSSFloat without context,
1207    /// so it returns Err(()) if there is any non-absolute unit.
1208    pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> {
1209        use crate::values::specified::calc::Leaf;
1210        use crate::values::specified::length::NoCalcLength;
1211
1212        // Simplification should've turned this into an absolute length,
1213        // otherwise it wouldn't have been able to.
1214        match self.node {
1215            calc::CalcNode::Leaf(Leaf::Length(NoCalcLength::Absolute(ref l))) => Ok(l.to_px()),
1216            _ => Err(()),
1217        }
1218    }
1219
1220    /// Compute the value into pixel length as CSSFloat, using the get_font_metrics function
1221    /// if provided to resolve font-relative dimensions.
1222    #[cfg(feature = "gecko")]
1223    pub fn to_computed_pixel_length_with_font_metrics(
1224        &self,
1225        get_font_metrics: Option<impl Fn() -> GeckoFontMetrics>,
1226    ) -> Result<CSSFloat, ()> {
1227        use crate::values::specified::calc::Leaf;
1228        use crate::values::specified::length::NoCalcLength;
1229
1230        match self.node {
1231            calc::CalcNode::Leaf(Leaf::Length(NoCalcLength::Absolute(ref l))) => Ok(l.to_px()),
1232            calc::CalcNode::Leaf(Leaf::Length(NoCalcLength::FontRelative(ref l))) => {
1233                if let Some(getter) = get_font_metrics {
1234                    l.to_computed_pixel_length_with_font_metrics(getter)
1235                } else {
1236                    Err(())
1237                }
1238            },
1239            _ => Err(()),
1240        }
1241    }
1242
1243    /// Compute the calc using the current font-size and line-height. (and without text-zoom).
1244    pub fn to_computed_value(&self, context: &Context) -> LengthPercentage {
1245        self.to_computed_value_with_zoom(
1246            context,
1247            |abs| abs,
1248            FontBaseSize::CurrentStyle,
1249            LineHeightBase::CurrentStyle,
1250        )
1251    }
1252
1253    #[inline]
1254    fn from_computed_value(computed: &CalcLengthPercentage) -> Self {
1255        use crate::values::specified::calc::Leaf;
1256        use crate::values::specified::length::NoCalcLength;
1257
1258        specified::CalcLengthPercentage {
1259            clamping_mode: computed.clamping_mode,
1260            node: computed.node.map_leaves(|l| match l {
1261                CalcLengthPercentageLeaf::Length(ref l) => {
1262                    Leaf::Length(NoCalcLength::from_px(l.px()))
1263                },
1264                CalcLengthPercentageLeaf::Percentage(ref p) => Leaf::Percentage(p.0),
1265                CalcLengthPercentageLeaf::Number(n) => Leaf::Number(*n),
1266            }),
1267        }
1268    }
1269}
1270
1271/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc
1272/// https://drafts.csswg.org/css-values-4/#combine-math
1273/// https://drafts.csswg.org/css-values-4/#combine-mixed
1274impl Animate for LengthPercentage {
1275    #[inline]
1276    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
1277        Ok(match (self.unpack(), other.unpack()) {
1278            (Unpacked::Length(one), Unpacked::Length(other)) => {
1279                Self::new_length(one.animate(&other, procedure)?)
1280            },
1281            (Unpacked::Percentage(one), Unpacked::Percentage(other)) => {
1282                Self::new_percent(one.animate(&other, procedure)?)
1283            },
1284            _ => {
1285                use calc::CalcNodeLeaf;
1286
1287                fn product_with(mut node: CalcNode, product: f32) -> CalcNode {
1288                    let mut number = CalcNode::Leaf(CalcLengthPercentageLeaf::new_number(product));
1289                    if !node.try_product_in_place(&mut number) {
1290                        CalcNode::Product(vec![node, number].into())
1291                    } else {
1292                        node
1293                    }
1294                }
1295
1296                let (l, r) = procedure.weights();
1297                let one = product_with(self.to_calc_node(), l as f32);
1298                let other = product_with(other.to_calc_node(), r as f32);
1299
1300                Self::new_calc(
1301                    CalcNode::Sum(vec![one, other].into()),
1302                    AllowedNumericType::All,
1303                )
1304            },
1305        })
1306    }
1307}
1308
1309/// A wrapper of LengthPercentage, whose value must be >= 0.
1310pub type NonNegativeLengthPercentage = NonNegative<LengthPercentage>;
1311
1312impl NonNegativeLengthPercentage {
1313    /// Returns the used value.
1314    #[inline]
1315    pub fn to_used_value(&self, containing_length: Au) -> Au {
1316        let resolved = self.0.to_used_value(containing_length);
1317        std::cmp::max(resolved, Au(0))
1318    }
1319
1320    /// Convert the computed value into used value.
1321    #[inline]
1322    pub fn maybe_to_used_value(&self, containing_length: Option<Au>) -> Option<Au> {
1323        let resolved = self.0.maybe_to_used_value(containing_length)?;
1324        Some(std::cmp::max(resolved, Au(0)))
1325    }
1326}
1327
1328impl TryTacticAdjustment for LengthPercentage {
1329    fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) {
1330        match self.unpack_mut() {
1331            UnpackedMut::Calc(calc) => calc.node.try_tactic_adjustment(old_side, new_side),
1332            UnpackedMut::Percentage(mut p) => {
1333                p.try_tactic_adjustment(old_side, new_side);
1334                *self = Self::new_percent(p);
1335            },
1336            UnpackedMut::Length(..) => {},
1337        }
1338    }
1339}
1340
1341impl TryTacticAdjustment for CalcNode {
1342    fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) {
1343        self.visit_depth_first(|node| match node {
1344            Self::Leaf(CalcLengthPercentageLeaf::Percentage(p)) => {
1345                p.try_tactic_adjustment(old_side, new_side)
1346            },
1347            Self::Anchor(a) => a.try_tactic_adjustment(old_side, new_side),
1348            Self::AnchorSize(a) => a.try_tactic_adjustment(old_side, new_side),
1349            _ => {},
1350        });
1351    }
1352}