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