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