Skip to main content

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