style/values/computed/
length_percentage.rs

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