style/values/computed/
length_percentage.rs

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