style/values/computed/
length_percentage.rs

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