style/values/computed/
length_percentage.rs

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