1use super::{position::AnchorSide, Context, Length, Percentage, ToComputedValue};
28use crate::derives::*;
29#[cfg(feature = "gecko")]
30use crate::gecko_bindings::structs::{AnchorPosOffsetResolutionParams, GeckoFontMetrics};
31use crate::logical_geometry::{PhysicalAxis, PhysicalSide};
32use crate::values::animated::{
33 Animate, Context as AnimatedContext, Procedure, ToAnimatedValue, ToAnimatedZero,
34};
35use crate::values::computed::position::TryTacticAdjustment;
36use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
37use crate::values::generics::calc::{CalcUnits, PositivePercentageBasis};
38#[cfg(feature = "gecko")]
39use crate::values::generics::length::AnchorResolutionResult;
40use crate::values::generics::position::GenericAnchorSide;
41use crate::values::generics::{calc, ClampToNonNegative, NonNegative};
42use crate::values::resolved::{Context as ResolvedContext, ToResolvedValue};
43use crate::values::specified::length::{FontBaseSize, EqualsPercentage, 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};
53
54#[doc(hidden)]
55#[derive(Clone, Copy)]
56#[repr(C)]
57pub struct LengthVariant {
58 tag: u8,
59 length: Length,
60}
61
62#[doc(hidden)]
63#[derive(Clone, Copy)]
64#[repr(C)]
65pub struct PercentageVariant {
66 tag: u8,
67 percentage: Percentage,
68}
69
70#[doc(hidden)]
73#[derive(Clone, Copy)]
74#[repr(C)]
75#[cfg(target_pointer_width = "32")]
76pub struct CalcVariant {
77 tag: u8,
78 ptr: *mut (),
81}
82
83#[doc(hidden)]
84#[derive(Clone, Copy)]
85#[repr(C)]
86#[cfg(target_pointer_width = "64")]
87pub struct CalcVariant {
88 ptr: usize, }
90
91unsafe impl Send for CalcVariant {}
93unsafe impl Sync for CalcVariant {}
94
95#[doc(hidden)]
96#[derive(Clone, Copy)]
97#[repr(C)]
98pub struct TagVariant {
99 tag: u8,
100}
101
102#[repr(transparent)]
119pub struct LengthPercentage(LengthPercentageUnion);
120
121#[doc(hidden)]
122#[repr(C)]
123pub union LengthPercentageUnion {
124 length: LengthVariant,
125 percentage: PercentageVariant,
126 calc: CalcVariant,
127 tag: TagVariant,
128}
129
130impl LengthPercentageUnion {
131 #[doc(hidden)] pub const TAG_CALC: u8 = 0;
133 #[doc(hidden)]
134 pub const TAG_LENGTH: u8 = 1;
135 #[doc(hidden)]
136 pub const TAG_PERCENTAGE: u8 = 2;
137 #[doc(hidden)]
138 pub const TAG_MASK: u8 = 0b11;
139}
140
141#[derive(Clone, Copy, Debug, PartialEq)]
142#[repr(u8)]
143enum Tag {
144 Calc = LengthPercentageUnion::TAG_CALC,
145 Length = LengthPercentageUnion::TAG_LENGTH,
146 Percentage = LengthPercentageUnion::TAG_PERCENTAGE,
147}
148
149#[allow(unused)]
151unsafe fn static_assert() {
152 fn assert_send_and_sync<T: Send + Sync>() {}
153 std::mem::transmute::<u64, LengthVariant>(0u64);
154 std::mem::transmute::<u64, PercentageVariant>(0u64);
155 std::mem::transmute::<u64, CalcVariant>(0u64);
156 std::mem::transmute::<u64, LengthPercentage>(0u64);
157 assert_send_and_sync::<LengthVariant>();
158 assert_send_and_sync::<PercentageVariant>();
159 assert_send_and_sync::<CalcLengthPercentage>();
160}
161
162impl Drop for LengthPercentage {
163 fn drop(&mut self) {
164 if self.tag() == Tag::Calc {
165 let _ = unsafe { Box::from_raw(self.calc_ptr()) };
166 }
167 }
168}
169
170impl MallocSizeOf for LengthPercentage {
171 fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
172 match self.unpack() {
173 Unpacked::Length(..) | Unpacked::Percentage(..) => 0,
174 Unpacked::Calc(c) => unsafe { ops.malloc_size_of(c) },
175 }
176 }
177}
178
179impl ToAnimatedValue for LengthPercentage {
180 type AnimatedValue = Self;
181
182 fn to_animated_value(self, context: &AnimatedContext) -> Self::AnimatedValue {
183 if context.style.effective_zoom.is_one() {
184 return self;
185 }
186 self.map_lengths(|l| l.to_animated_value(context))
187 }
188
189 #[inline]
190 fn from_animated_value(value: Self::AnimatedValue) -> Self {
191 value
192 }
193}
194
195impl ToResolvedValue for LengthPercentage {
196 type ResolvedValue = Self;
197
198 fn to_resolved_value(self, context: &ResolvedContext) -> Self::ResolvedValue {
199 if context.style.effective_zoom.is_one() {
200 return self;
201 }
202 self.map_lengths(|l| l.to_resolved_value(context))
203 }
204
205 #[inline]
206 fn from_resolved_value(value: Self::ResolvedValue) -> Self {
207 value
208 }
209}
210
211impl EqualsPercentage for LengthPercentage {
212 fn equals_percentage(&self, v: CSSFloat) -> bool {
213 match self.unpack() {
214 Unpacked::Percentage(p) => p.0 == v,
215 _ => false,
216 }
217 }
218}
219
220#[derive(Clone, Debug, PartialEq, ToCss, ToTyped)]
222#[typed_value(derive_fields)]
223pub enum Unpacked<'a> {
224 Calc(&'a CalcLengthPercentage),
226 Length(Length),
228 Percentage(Percentage),
230}
231
232enum UnpackedMut<'a> {
234 Calc(&'a mut CalcLengthPercentage),
235 Length(Length),
236 Percentage(Percentage),
237}
238
239#[derive(Deserialize, PartialEq, Serialize)]
242enum Serializable {
243 Calc(CalcLengthPercentage),
244 Length(Length),
245 Percentage(Percentage),
246}
247
248impl LengthPercentage {
249 #[inline]
251 pub fn one() -> Self {
252 Self::new_length(Length::new(1.))
253 }
254
255 #[inline]
257 pub fn zero_percent() -> Self {
258 Self::new_percent(Percentage::zero())
259 }
260
261 fn to_calc_node(&self) -> CalcNode {
262 match self.unpack() {
263 Unpacked::Length(l) => CalcNode::Leaf(CalcLengthPercentageLeaf::Length(l)),
264 Unpacked::Percentage(p) => CalcNode::Leaf(CalcLengthPercentageLeaf::Percentage(p)),
265 Unpacked::Calc(p) => p.node.clone(),
266 }
267 }
268
269 fn map_lengths(&self, mut map_fn: impl FnMut(Length) -> Length) -> Self {
270 match self.unpack() {
271 Unpacked::Length(l) => Self::new_length(map_fn(l)),
272 Unpacked::Percentage(p) => Self::new_percent(p),
273 Unpacked::Calc(lp) => Self::new_calc_unchecked(Box::new(CalcLengthPercentage {
274 clamping_mode: lp.clamping_mode,
275 node: lp.node.map_leaves(|leaf| match *leaf {
276 CalcLengthPercentageLeaf::Length(ref l) => {
277 CalcLengthPercentageLeaf::Length(map_fn(*l))
278 },
279 ref l => l.clone(),
280 }),
281 })),
282 }
283 }
284
285 #[inline]
287 pub fn new_length(length: Length) -> Self {
288 let length = Self(LengthPercentageUnion {
289 length: LengthVariant {
290 tag: LengthPercentageUnion::TAG_LENGTH,
291 length,
292 },
293 });
294 debug_assert_eq!(length.tag(), Tag::Length);
295 length
296 }
297
298 #[inline]
300 pub fn new_percent(percentage: Percentage) -> Self {
301 let percent = Self(LengthPercentageUnion {
302 percentage: PercentageVariant {
303 tag: LengthPercentageUnion::TAG_PERCENTAGE,
304 percentage,
305 },
306 });
307 debug_assert_eq!(percent.tag(), Tag::Percentage);
308 percent
309 }
310
311 pub fn hundred_percent_minus(v: Self, clamping_mode: AllowedNumericType) -> Self {
314 let mut node = v.to_calc_node();
317 node.negate();
318
319 let new_node = CalcNode::Sum(
320 vec![
321 CalcNode::Leaf(CalcLengthPercentageLeaf::Percentage(Percentage::hundred())),
322 node,
323 ]
324 .into(),
325 );
326
327 Self::new_calc(new_node, clamping_mode)
328 }
329
330 pub fn hundred_percent_minus_list(list: &[&Self], clamping_mode: AllowedNumericType) -> Self {
333 let mut new_list = vec![CalcNode::Leaf(CalcLengthPercentageLeaf::Percentage(
334 Percentage::hundred(),
335 ))];
336
337 for lp in list.iter() {
338 let mut node = lp.to_calc_node();
339 node.negate();
340 new_list.push(node)
341 }
342
343 Self::new_calc(CalcNode::Sum(new_list.into()), clamping_mode)
344 }
345
346 #[inline]
348 pub fn new_calc(mut node: CalcNode, clamping_mode: AllowedNumericType) -> Self {
349 node.simplify_and_sort();
350
351 match node {
352 CalcNode::Leaf(l) => {
353 return match l {
354 CalcLengthPercentageLeaf::Length(l) => {
355 Self::new_length(Length::new(clamping_mode.clamp(l.px())).normalized())
356 },
357 CalcLengthPercentageLeaf::Percentage(p) => Self::new_percent(Percentage(
358 clamping_mode.clamp(crate::values::normalize(p.0)),
359 )),
360 CalcLengthPercentageLeaf::Number(number) => {
361 debug_assert!(
362 false,
363 "The final result of a <length-percentage> should never be a number"
364 );
365 Self::new_length(Length::new(number))
366 },
367 };
368 },
369 _ => Self::new_calc_unchecked(Box::new(CalcLengthPercentage {
370 clamping_mode,
371 node,
372 })),
373 }
374 }
375
376 fn new_calc_unchecked(calc: Box<CalcLengthPercentage>) -> Self {
379 let ptr = Box::into_raw(calc);
380
381 #[cfg(target_pointer_width = "32")]
382 let calc = CalcVariant {
383 tag: LengthPercentageUnion::TAG_CALC,
384 ptr: ptr as *mut (),
385 };
386
387 #[cfg(target_pointer_width = "64")]
388 let calc = CalcVariant {
389 #[cfg(target_endian = "little")]
390 ptr: ptr as usize,
391 #[cfg(target_endian = "big")]
392 ptr: (ptr as usize).swap_bytes(),
393 };
394
395 let calc = Self(LengthPercentageUnion { calc });
396 debug_assert_eq!(calc.tag(), Tag::Calc);
397 calc
398 }
399
400 #[inline]
401 fn tag(&self) -> Tag {
402 match unsafe { self.0.tag.tag & LengthPercentageUnion::TAG_MASK } {
403 LengthPercentageUnion::TAG_CALC => Tag::Calc,
404 LengthPercentageUnion::TAG_LENGTH => Tag::Length,
405 LengthPercentageUnion::TAG_PERCENTAGE => Tag::Percentage,
406 _ => unsafe { debug_unreachable!("Bogus tag?") },
407 }
408 }
409
410 #[inline]
411 fn unpack_mut<'a>(&'a mut self) -> UnpackedMut<'a> {
412 unsafe {
413 match self.tag() {
414 Tag::Calc => UnpackedMut::Calc(&mut *self.calc_ptr()),
415 Tag::Length => UnpackedMut::Length(self.0.length.length),
416 Tag::Percentage => UnpackedMut::Percentage(self.0.percentage.percentage),
417 }
418 }
419 }
420
421 #[inline]
424 pub fn unpack<'a>(&'a self) -> Unpacked<'a> {
425 unsafe {
426 match self.tag() {
427 Tag::Calc => Unpacked::Calc(&*self.calc_ptr()),
428 Tag::Length => Unpacked::Length(self.0.length.length),
429 Tag::Percentage => Unpacked::Percentage(self.0.percentage.percentage),
430 }
431 }
432 }
433
434 #[inline]
435 unsafe fn calc_ptr(&self) -> *mut CalcLengthPercentage {
436 #[cfg(not(all(target_endian = "big", target_pointer_width = "64")))]
437 {
438 self.0.calc.ptr as *mut _
439 }
440 #[cfg(all(target_endian = "big", target_pointer_width = "64"))]
441 {
442 self.0.calc.ptr.swap_bytes() as *mut _
443 }
444 }
445
446 #[inline]
447 fn to_serializable(&self) -> Serializable {
448 match self.unpack() {
449 Unpacked::Calc(c) => Serializable::Calc(c.clone()),
450 Unpacked::Length(l) => Serializable::Length(l),
451 Unpacked::Percentage(p) => Serializable::Percentage(p),
452 }
453 }
454
455 #[inline]
456 fn from_serializable(s: Serializable) -> Self {
457 match s {
458 Serializable::Calc(c) => Self::new_calc_unchecked(Box::new(c)),
459 Serializable::Length(l) => Self::new_length(l),
460 Serializable::Percentage(p) => Self::new_percent(p),
461 }
462 }
463
464 #[inline]
466 pub fn resolve(&self, basis: Length) -> Length {
467 match self.unpack() {
468 Unpacked::Length(l) => l,
469 Unpacked::Percentage(p) => (basis * p.0).normalized(),
470 Unpacked::Calc(ref c) => c.resolve(basis),
471 }
472 }
473
474 #[inline]
476 pub fn percentage_relative_to(&self, basis: Length) -> Length {
477 self.resolve(basis)
478 }
479
480 #[inline]
482 pub fn has_percentage(&self) -> bool {
483 match self.unpack() {
484 Unpacked::Length(..) => false,
485 Unpacked::Percentage(..) | Unpacked::Calc(..) => true,
486 }
487 }
488
489 pub fn to_length(&self) -> Option<Length> {
491 match self.unpack() {
492 Unpacked::Length(l) => Some(l),
493 Unpacked::Percentage(..) | Unpacked::Calc(..) => {
494 debug_assert!(self.has_percentage());
495 return None;
496 },
497 }
498 }
499
500 #[inline]
502 pub fn to_percentage(&self) -> Option<Percentage> {
503 match self.unpack() {
504 Unpacked::Percentage(p) => Some(p),
505 Unpacked::Length(..) | Unpacked::Calc(..) => None,
506 }
507 }
508
509 #[inline]
511 pub fn to_percentage_of(&self, basis: Length) -> Option<Percentage> {
512 if basis.px() == 0. {
513 return None;
514 }
515 Some(match self.unpack() {
516 Unpacked::Length(l) => Percentage(l.px() / basis.px()),
517 Unpacked::Percentage(p) => p,
518 Unpacked::Calc(ref c) => Percentage(c.resolve(basis).px() / basis.px()),
519 })
520 }
521
522 #[inline]
524 pub fn to_used_value(&self, containing_length: Au) -> Au {
525 let length = self.to_pixel_length(containing_length);
526 if let Unpacked::Percentage(_) = self.unpack() {
527 return Au::from_f32_px_trunc(length.px());
528 }
529 Au::from(length)
530 }
531
532 #[inline]
534 pub fn to_pixel_length(&self, containing_length: Au) -> Length {
535 self.resolve(containing_length.into())
536 }
537
538 #[inline]
540 pub fn maybe_to_used_value(&self, container_len: Option<Au>) -> Option<Au> {
541 self.maybe_percentage_relative_to(container_len.map(Length::from))
542 .map(if let Unpacked::Percentage(_) = self.unpack() {
543 |length: Length| Au::from_f32_px_trunc(length.px())
544 } else {
545 Au::from
546 })
547 }
548
549 pub fn maybe_percentage_relative_to(&self, container_len: Option<Length>) -> Option<Length> {
553 if let Unpacked::Length(l) = self.unpack() {
554 return Some(l);
555 }
556 Some(self.resolve(container_len?))
557 }
558}
559
560impl ClampToNonNegative for LengthPercentage {
561 #[inline]
563 fn clamp_to_non_negative(mut self) -> Self {
564 match self.unpack_mut() {
565 UnpackedMut::Length(l) => Self::new_length(l.clamp_to_non_negative()),
566 UnpackedMut::Percentage(p) => Self::new_percent(p.clamp_to_non_negative()),
567 UnpackedMut::Calc(ref mut c) => {
568 c.clamping_mode = AllowedNumericType::NonNegative;
569 self
570 },
571 }
572 }
573}
574
575impl PartialEq for LengthPercentage {
576 fn eq(&self, other: &Self) -> bool {
577 self.unpack() == other.unpack()
578 }
579}
580
581impl fmt::Debug for LengthPercentage {
582 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
583 self.unpack().fmt(formatter)
584 }
585}
586
587impl ToAnimatedZero for LengthPercentage {
588 fn to_animated_zero(&self) -> Result<Self, ()> {
589 Ok(match self.unpack() {
590 Unpacked::Length(l) => Self::new_length(l.to_animated_zero()?),
591 Unpacked::Percentage(p) => Self::new_percent(p.to_animated_zero()?),
592 Unpacked::Calc(c) => Self::new_calc_unchecked(Box::new(c.to_animated_zero()?)),
593 })
594 }
595}
596
597impl Clone for LengthPercentage {
598 fn clone(&self) -> Self {
599 match self.unpack() {
600 Unpacked::Length(l) => Self::new_length(l),
601 Unpacked::Percentage(p) => Self::new_percent(p),
602 Unpacked::Calc(c) => Self::new_calc_unchecked(Box::new(c.clone())),
603 }
604 }
605}
606
607impl ToComputedValue for specified::LengthPercentage {
608 type ComputedValue = LengthPercentage;
609
610 fn to_computed_value(&self, context: &Context) -> LengthPercentage {
611 match *self {
612 specified::LengthPercentage::Length(ref value) => {
613 LengthPercentage::new_length(value.to_computed_value(context))
614 },
615 specified::LengthPercentage::Percentage(value) => LengthPercentage::new_percent(value),
616 specified::LengthPercentage::Calc(ref calc) => (**calc).to_computed_value(context),
617 }
618 }
619
620 fn from_computed_value(computed: &LengthPercentage) -> Self {
621 match computed.unpack() {
622 Unpacked::Length(ref l) => {
623 specified::LengthPercentage::Length(ToComputedValue::from_computed_value(l))
624 },
625 Unpacked::Percentage(p) => specified::LengthPercentage::Percentage(p),
626 Unpacked::Calc(c) => {
627 specified::LengthPercentage::Calc(Box::new(
630 specified::CalcLengthPercentage::from_computed_value(c),
631 ))
632 },
633 }
634 }
635}
636
637impl ComputeSquaredDistance for LengthPercentage {
638 #[inline]
639 fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
640 let basis = Length::new(100.);
645 self.resolve(basis)
646 .compute_squared_distance(&other.resolve(basis))
647 }
648}
649
650impl ToCss for LengthPercentage {
651 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
652 where
653 W: Write,
654 {
655 self.unpack().to_css(dest)
656 }
657}
658
659impl ToTyped for LengthPercentage {
660 fn to_typed(&self) -> Option<TypedValue> {
661 self.unpack().to_typed()
662 }
663}
664
665impl Zero for LengthPercentage {
666 fn zero() -> Self {
667 LengthPercentage::new_length(Length::zero())
668 }
669
670 #[inline]
672 fn is_zero(&self) -> bool {
673 match self.unpack() {
674 Unpacked::Length(l) => l.px() == 0.0,
675 Unpacked::Percentage(p) => p.0 == 0.0,
676 Unpacked::Calc(..) => false,
677 }
678 }
679}
680
681impl ZeroNoPercent for LengthPercentage {
682 #[inline]
683 fn is_zero_no_percent(&self) -> bool {
684 self.to_length().is_some_and(|l| l.px() == 0.0)
685 }
686}
687
688impl Serialize for LengthPercentage {
689 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
690 where
691 S: serde::Serializer,
692 {
693 self.to_serializable().serialize(serializer)
694 }
695}
696
697impl<'de> Deserialize<'de> for LengthPercentage {
698 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
699 where
700 D: serde::Deserializer<'de>,
701 {
702 Ok(Self::from_serializable(Serializable::deserialize(
703 deserializer,
704 )?))
705 }
706}
707
708#[derive(
710 Clone,
711 Debug,
712 Deserialize,
713 MallocSizeOf,
714 PartialEq,
715 Serialize,
716 ToAnimatedZero,
717 ToCss,
718 ToResolvedValue,
719 ToTyped,
720)]
721#[allow(missing_docs)]
722#[repr(u8)]
723#[typed_value(derive_fields)]
724pub enum CalcLengthPercentageLeaf {
725 Length(Length),
726 Percentage(Percentage),
727 Number(f32),
728}
729
730impl CalcLengthPercentageLeaf {
731 fn is_zero_length(&self) -> bool {
732 match *self {
733 Self::Length(ref l) => l.is_zero(),
734 Self::Percentage(..) => false,
735 Self::Number(..) => false,
736 }
737 }
738}
739
740impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf {
741 fn unit(&self) -> CalcUnits {
742 match self {
743 Self::Length(_) => CalcUnits::LENGTH,
744 Self::Percentage(_) => CalcUnits::PERCENTAGE,
745 Self::Number(_) => CalcUnits::empty(),
746 }
747 }
748
749 fn unitless_value(&self) -> Option<f32> {
750 Some(match *self {
751 Self::Length(ref l) => l.px(),
752 Self::Percentage(ref p) => p.0,
753 Self::Number(n) => n,
754 })
755 }
756
757 fn new_number(value: f32) -> Self {
758 Self::Number(value)
759 }
760
761 fn as_number(&self) -> Option<f32> {
762 match *self {
763 Self::Length(_) | Self::Percentage(_) => None,
764 Self::Number(value) => Some(value),
765 }
766 }
767
768 fn compare(&self, other: &Self, basis: PositivePercentageBasis) -> Option<std::cmp::Ordering> {
769 use self::CalcLengthPercentageLeaf::*;
770 if std::mem::discriminant(self) != std::mem::discriminant(other) {
771 return None;
772 }
773
774 if matches!(self, Percentage(..)) && matches!(basis, PositivePercentageBasis::Unknown) {
775 return None;
776 }
777
778 let Ok(self_negative) = self.is_negative() else {
779 return None;
780 };
781 let Ok(other_negative) = other.is_negative() else {
782 return None;
783 };
784 if self_negative != other_negative {
785 return Some(if self_negative {
786 std::cmp::Ordering::Less
787 } else {
788 std::cmp::Ordering::Greater
789 });
790 }
791
792 match (self, other) {
793 (&Length(ref one), &Length(ref other)) => one.partial_cmp(other),
794 (&Percentage(ref one), &Percentage(ref other)) => one.partial_cmp(other),
795 (&Number(ref one), &Number(ref other)) => one.partial_cmp(other),
796 _ => unsafe {
797 match *self {
798 Length(..) | Percentage(..) | Number(..) => {},
799 }
800 debug_unreachable!("Forgot to handle unit in compare()")
801 },
802 }
803 }
804
805 fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> {
806 use self::CalcLengthPercentageLeaf::*;
807
808 if self.is_zero_length() {
810 *self = other.clone();
811 return Ok(());
812 }
813
814 if other.is_zero_length() {
815 return Ok(());
816 }
817
818 if std::mem::discriminant(self) != std::mem::discriminant(other) {
819 return Err(());
820 }
821
822 match (self, other) {
823 (&mut Length(ref mut one), &Length(ref other)) => {
824 *one += *other;
825 },
826 (&mut Percentage(ref mut one), &Percentage(ref other)) => {
827 one.0 += other.0;
828 },
829 (&mut Number(ref mut one), &Number(ref other)) => {
830 *one += *other;
831 },
832 _ => unsafe {
833 match *other {
834 Length(..) | Percentage(..) | Number(..) => {},
835 }
836 debug_unreachable!("Forgot to handle unit in try_sum_in_place()")
837 },
838 }
839
840 Ok(())
841 }
842
843 fn try_product_in_place(&mut self, other: &mut Self) -> bool {
844 if let Self::Number(ref mut left) = *self {
845 if let Self::Number(ref right) = *other {
846 *left *= *right;
848 true
849 } else {
850 if other.map(|v| v * *left).is_ok() {
853 std::mem::swap(self, other);
854 true
855 } else {
856 false
857 }
858 }
859 } else if let Self::Number(ref right) = *other {
860 self.map(|v| v * *right).is_ok()
863 } else {
864 false
866 }
867 }
868
869 fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()>
870 where
871 O: Fn(f32, f32) -> f32,
872 {
873 use self::CalcLengthPercentageLeaf::*;
874 if std::mem::discriminant(self) != std::mem::discriminant(other) {
875 return Err(());
876 }
877 Ok(match (self, other) {
878 (&Length(ref one), &Length(ref other)) => {
879 Length(super::Length::new(op(one.px(), other.px())))
880 },
881 (&Percentage(one), &Percentage(other)) => {
882 Self::Percentage(super::Percentage(op(one.0, other.0)))
883 },
884 (&Number(one), &Number(other)) => Self::Number(op(one, other)),
885 _ => unsafe {
886 match *self {
887 Length(..) | Percentage(..) | Number(..) => {},
888 }
889 debug_unreachable!("Forgot to handle unit in try_op()")
890 },
891 })
892 }
893
894 fn map(&mut self, mut op: impl FnMut(f32) -> f32) -> Result<(), ()> {
895 Ok(match self {
896 Self::Length(value) => {
897 *value = Length::new(op(value.px()));
898 },
899 Self::Percentage(value) => {
900 *value = Percentage(op(value.0));
901 },
902 Self::Number(value) => {
903 *value = op(*value);
904 },
905 })
906 }
907
908 fn simplify(&mut self) {}
909
910 fn sort_key(&self) -> calc::SortKey {
911 match *self {
912 Self::Length(..) => calc::SortKey::Px,
913 Self::Percentage(..) => calc::SortKey::Percentage,
914 Self::Number(..) => calc::SortKey::Number,
915 }
916 }
917}
918
919pub type CalcNode = calc::GenericCalcNode<CalcLengthPercentageLeaf>;
921
922#[derive(
924 Clone,
925 Debug,
926 Deserialize,
927 MallocSizeOf,
928 Serialize,
929 ToAnimatedZero,
930 ToResolvedValue,
931 ToCss,
932 ToTyped,
933)]
934#[repr(C)]
935#[typed_value(derive_fields)]
936pub struct CalcLengthPercentage {
937 #[animation(constant)]
938 #[css(skip)]
939 clamping_mode: AllowedNumericType,
940 node: CalcNode,
941}
942
943pub type CalcAnchorSide = GenericAnchorSide<Box<CalcNode>>;
945
946pub struct CalcLengthPercentageResolution {
948 pub result: Length,
950 pub percentage_used: bool,
952}
953
954#[repr(C)]
957#[derive(Clone, Copy)]
958pub enum AllowAnchorPosResolutionInCalcPercentage {
959 Both(PhysicalSide),
961 AnchorSizeOnly(PhysicalAxis),
963}
964
965impl AllowAnchorPosResolutionInCalcPercentage {
966 #[cfg(feature = "gecko")]
967 pub fn to_axis(&self) -> PhysicalAxis {
969 match self {
970 Self::AnchorSizeOnly(axis) => *axis,
971 Self::Both(side) => {
972 if matches!(side, PhysicalSide::Top | PhysicalSide::Bottom) {
973 PhysicalAxis::Vertical
974 } else {
975 PhysicalAxis::Horizontal
976 }
977 },
978 }
979 }
980}
981
982impl From<&CalcAnchorSide> for AnchorSide {
983 fn from(value: &CalcAnchorSide) -> Self {
984 match value {
985 CalcAnchorSide::Keyword(k) => Self::Keyword(*k),
986 CalcAnchorSide::Percentage(p) => {
987 if let CalcNode::Leaf(CalcLengthPercentageLeaf::Percentage(p)) = **p {
988 Self::Percentage(p)
989 } else {
990 unreachable!("Should have parsed simplified percentage.");
991 }
992 },
993 }
994 }
995}
996
997impl CalcLengthPercentage {
998 #[inline]
1000 pub fn resolve(&self, basis: Length) -> Length {
1001 if let CalcLengthPercentageLeaf::Length(px) = self
1003 .node
1004 .resolve_map(|leaf| {
1005 Ok(if let CalcLengthPercentageLeaf::Percentage(p) = leaf {
1006 CalcLengthPercentageLeaf::Length(Length::new(basis.px() * p.0))
1007 } else {
1008 leaf.clone()
1009 })
1010 })
1011 .unwrap()
1012 {
1013 Length::new(self.clamping_mode.clamp(px.px())).normalized()
1014 } else {
1015 unreachable!("resolve_map should turn percentages to lengths, and parsing should ensure that we don't end up with a number");
1016 }
1017 }
1018
1019 #[inline]
1022 #[cfg(feature = "gecko")]
1023 pub fn resolve_anchor(
1024 &self,
1025 allowed: AllowAnchorPosResolutionInCalcPercentage,
1026 params: &AnchorPosOffsetResolutionParams,
1027 ) -> Result<(CalcNode, AllowedNumericType), ()> {
1028 use crate::values::{
1029 computed::{length::resolve_anchor_size, AnchorFunction},
1030 generics::{length::GenericAnchorSizeFunction, position::GenericAnchorFunction},
1031 };
1032
1033 fn resolve_anchor_function<'a>(
1034 f: &'a GenericAnchorFunction<Box<CalcNode>, Box<CalcNode>>,
1035 side: PhysicalSide,
1036 params: &AnchorPosOffsetResolutionParams,
1037 ) -> AnchorResolutionResult<'a, Box<CalcNode>> {
1038 let anchor_side: &CalcAnchorSide = &f.side;
1039 let resolved = if f.valid_for(side, params.mBaseParams.mPosition) {
1040 AnchorFunction::resolve(&f.target_element, &anchor_side.into(), side, params).ok()
1041 } else {
1042 None
1043 };
1044
1045 resolved.map_or_else(
1046 || {
1047 let Some(fb) = f.fallback.as_ref() else {
1048 return AnchorResolutionResult::Invalid;
1049 };
1050 let mut node = fb.clone();
1051 let result = node.map_node(|node| {
1052 resolve_anchor_functions(
1053 node,
1054 AllowAnchorPosResolutionInCalcPercentage::Both(side),
1055 params,
1056 )
1057 });
1058 if result.is_err() {
1059 return AnchorResolutionResult::Invalid;
1060 }
1061 AnchorResolutionResult::Resolved(node)
1062 },
1063 |v| {
1064 AnchorResolutionResult::Resolved(Box::new(CalcNode::Leaf(
1065 CalcLengthPercentageLeaf::Length(v),
1066 )))
1067 },
1068 )
1069 }
1070
1071 fn resolve_anchor_size_function<'a>(
1072 f: &'a GenericAnchorSizeFunction<Box<CalcNode>>,
1073 allowed: AllowAnchorPosResolutionInCalcPercentage,
1074 params: &AnchorPosOffsetResolutionParams,
1075 ) -> AnchorResolutionResult<'a, Box<CalcNode>> {
1076 let axis = allowed.to_axis();
1077 let resolved = if f.valid_for(params.mBaseParams.mPosition) {
1078 resolve_anchor_size(&f.target_element, axis, f.size, ¶ms.mBaseParams).ok()
1079 } else {
1080 None
1081 };
1082
1083 resolved.map_or_else(
1084 || {
1085 let Some(fb) = f.fallback.as_ref() else {
1086 return AnchorResolutionResult::Invalid;
1087 };
1088 let mut node = fb.clone();
1089 let result =
1090 node.map_node(|node| resolve_anchor_functions(node, allowed, params));
1091 if result.is_err() {
1092 return AnchorResolutionResult::Invalid;
1093 }
1094 AnchorResolutionResult::Resolved(node)
1095 },
1096 |v| {
1097 AnchorResolutionResult::Resolved(Box::new(CalcNode::Leaf(
1098 CalcLengthPercentageLeaf::Length(v),
1099 )))
1100 },
1101 )
1102 }
1103
1104 fn resolve_anchor_functions(
1105 node: &CalcNode,
1106 allowed: AllowAnchorPosResolutionInCalcPercentage,
1107 params: &AnchorPosOffsetResolutionParams,
1108 ) -> Result<Option<CalcNode>, ()> {
1109 let resolution = match node {
1110 CalcNode::Anchor(f) => {
1111 let prop_side = match allowed {
1112 AllowAnchorPosResolutionInCalcPercentage::Both(side) => side,
1113 AllowAnchorPosResolutionInCalcPercentage::AnchorSizeOnly(_) => {
1114 unreachable!("anchor() found where disallowed")
1115 },
1116 };
1117 resolve_anchor_function(f, prop_side, params)
1118 },
1119 CalcNode::AnchorSize(f) => resolve_anchor_size_function(f, allowed, params),
1120 _ => return Ok(None),
1121 };
1122
1123 match resolution {
1124 AnchorResolutionResult::Invalid => Err(()),
1125 AnchorResolutionResult::Fallback(fb) => {
1126 Ok(Some(*fb.clone()))
1128 },
1129 AnchorResolutionResult::Resolved(v) => Ok(Some(*v.clone())),
1130 }
1131 }
1132
1133 let mut node = self.node.clone();
1134 node.map_node(|node| resolve_anchor_functions(node, allowed, params))?;
1135 Ok((node, self.clamping_mode))
1136 }
1137}
1138
1139impl PartialEq for CalcLengthPercentage {
1152 fn eq(&self, other: &Self) -> bool {
1153 self.node == other.node
1154 }
1155}
1156
1157impl specified::CalcLengthPercentage {
1158 fn to_computed_value_with_zoom<F>(
1160 &self,
1161 context: &Context,
1162 zoom_fn: F,
1163 base_size: FontBaseSize,
1164 line_height_base: LineHeightBase,
1165 ) -> LengthPercentage
1166 where
1167 F: Fn(Length) -> Length,
1168 {
1169 use crate::values::specified::calc::Leaf;
1170
1171 let node = self.node.map_leaves(|leaf| match *leaf {
1172 Leaf::Percentage(p) => CalcLengthPercentageLeaf::Percentage(Percentage(p)),
1173 Leaf::Length(l) => CalcLengthPercentageLeaf::Length({
1174 let result =
1175 l.to_computed_value_with_base_size(context, base_size, line_height_base);
1176 if l.should_zoom_text() {
1177 zoom_fn(result)
1178 } else {
1179 result
1180 }
1181 }),
1182 Leaf::Number(n) => CalcLengthPercentageLeaf::Number(n),
1183 Leaf::Angle(..) | Leaf::Time(..) | Leaf::Resolution(..) | Leaf::ColorComponent(..) => {
1184 unreachable!("Shouldn't have parsed")
1185 },
1186 });
1187
1188 LengthPercentage::new_calc(node, self.clamping_mode)
1189 }
1190
1191 pub fn to_computed_value_zoomed(
1193 &self,
1194 context: &Context,
1195 base_size: FontBaseSize,
1196 line_height_base: LineHeightBase,
1197 ) -> LengthPercentage {
1198 self.to_computed_value_with_zoom(
1199 context,
1200 |abs| context.maybe_zoom_text(abs),
1201 base_size,
1202 line_height_base,
1203 )
1204 }
1205
1206 pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> {
1209 use crate::values::specified::calc::Leaf;
1210 use crate::values::specified::length::NoCalcLength;
1211
1212 match self.node {
1215 calc::CalcNode::Leaf(Leaf::Length(NoCalcLength::Absolute(ref l))) => Ok(l.to_px()),
1216 _ => Err(()),
1217 }
1218 }
1219
1220 #[cfg(feature = "gecko")]
1223 pub fn to_computed_pixel_length_with_font_metrics(
1224 &self,
1225 get_font_metrics: Option<impl Fn() -> GeckoFontMetrics>,
1226 ) -> Result<CSSFloat, ()> {
1227 use crate::values::specified::calc::Leaf;
1228 use crate::values::specified::length::NoCalcLength;
1229
1230 match self.node {
1231 calc::CalcNode::Leaf(Leaf::Length(NoCalcLength::Absolute(ref l))) => Ok(l.to_px()),
1232 calc::CalcNode::Leaf(Leaf::Length(NoCalcLength::FontRelative(ref l))) => {
1233 if let Some(getter) = get_font_metrics {
1234 l.to_computed_pixel_length_with_font_metrics(getter)
1235 } else {
1236 Err(())
1237 }
1238 },
1239 _ => Err(()),
1240 }
1241 }
1242
1243 pub fn to_computed_value(&self, context: &Context) -> LengthPercentage {
1245 self.to_computed_value_with_zoom(
1246 context,
1247 |abs| abs,
1248 FontBaseSize::CurrentStyle,
1249 LineHeightBase::CurrentStyle,
1250 )
1251 }
1252
1253 #[inline]
1254 fn from_computed_value(computed: &CalcLengthPercentage) -> Self {
1255 use crate::values::specified::calc::Leaf;
1256 use crate::values::specified::length::NoCalcLength;
1257
1258 specified::CalcLengthPercentage {
1259 clamping_mode: computed.clamping_mode,
1260 node: computed.node.map_leaves(|l| match l {
1261 CalcLengthPercentageLeaf::Length(ref l) => {
1262 Leaf::Length(NoCalcLength::from_px(l.px()))
1263 },
1264 CalcLengthPercentageLeaf::Percentage(ref p) => Leaf::Percentage(p.0),
1265 CalcLengthPercentageLeaf::Number(n) => Leaf::Number(*n),
1266 }),
1267 }
1268 }
1269}
1270
1271impl Animate for LengthPercentage {
1275 #[inline]
1276 fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
1277 Ok(match (self.unpack(), other.unpack()) {
1278 (Unpacked::Length(one), Unpacked::Length(other)) => {
1279 Self::new_length(one.animate(&other, procedure)?)
1280 },
1281 (Unpacked::Percentage(one), Unpacked::Percentage(other)) => {
1282 Self::new_percent(one.animate(&other, procedure)?)
1283 },
1284 _ => {
1285 use calc::CalcNodeLeaf;
1286
1287 fn product_with(mut node: CalcNode, product: f32) -> CalcNode {
1288 let mut number = CalcNode::Leaf(CalcLengthPercentageLeaf::new_number(product));
1289 if !node.try_product_in_place(&mut number) {
1290 CalcNode::Product(vec![node, number].into())
1291 } else {
1292 node
1293 }
1294 }
1295
1296 let (l, r) = procedure.weights();
1297 let one = product_with(self.to_calc_node(), l as f32);
1298 let other = product_with(other.to_calc_node(), r as f32);
1299
1300 Self::new_calc(
1301 CalcNode::Sum(vec![one, other].into()),
1302 AllowedNumericType::All,
1303 )
1304 },
1305 })
1306 }
1307}
1308
1309pub type NonNegativeLengthPercentage = NonNegative<LengthPercentage>;
1311
1312impl NonNegativeLengthPercentage {
1313 #[inline]
1315 pub fn to_used_value(&self, containing_length: Au) -> Au {
1316 let resolved = self.0.to_used_value(containing_length);
1317 std::cmp::max(resolved, Au(0))
1318 }
1319
1320 #[inline]
1322 pub fn maybe_to_used_value(&self, containing_length: Option<Au>) -> Option<Au> {
1323 let resolved = self.0.maybe_to_used_value(containing_length)?;
1324 Some(std::cmp::max(resolved, Au(0)))
1325 }
1326}
1327
1328impl TryTacticAdjustment for LengthPercentage {
1329 fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) {
1330 match self.unpack_mut() {
1331 UnpackedMut::Calc(calc) => calc.node.try_tactic_adjustment(old_side, new_side),
1332 UnpackedMut::Percentage(mut p) => {
1333 p.try_tactic_adjustment(old_side, new_side);
1334 *self = Self::new_percent(p);
1335 },
1336 UnpackedMut::Length(..) => {},
1337 }
1338 }
1339}
1340
1341impl TryTacticAdjustment for CalcNode {
1342 fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) {
1343 self.visit_depth_first(|node| match node {
1344 Self::Leaf(CalcLengthPercentageLeaf::Percentage(p)) => {
1345 p.try_tactic_adjustment(old_side, new_side)
1346 },
1347 Self::Anchor(a) => a.try_tactic_adjustment(old_side, new_side),
1348 Self::AnchorSize(a) => a.try_tactic_adjustment(old_side, new_side),
1349 _ => {},
1350 });
1351 }
1352}