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