1use 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, ClampToNonNegative, 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#[doc(hidden)]
71#[derive(Clone, Copy)]
72#[repr(C)]
73#[cfg(target_pointer_width = "32")]
74pub struct CalcVariant {
75 tag: u8,
76 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, }
88
89unsafe 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#[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)] 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#[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#[derive(Clone, Debug, PartialEq, ToCss)]
212pub enum Unpacked<'a> {
213 Calc(&'a CalcLengthPercentage),
215 Length(Length),
217 Percentage(Percentage),
219}
220
221enum UnpackedMut<'a> {
223 Calc(&'a mut CalcLengthPercentage),
224 Length(Length),
225 Percentage(Percentage),
226}
227
228#[derive(Deserialize, PartialEq, Serialize)]
231enum Serializable {
232 Calc(CalcLengthPercentage),
233 Length(Length),
234 Percentage(Percentage),
235}
236
237impl LengthPercentage {
238 #[inline]
240 pub fn one() -> Self {
241 Self::new_length(Length::new(1.))
242 }
243
244 #[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 #[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 #[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 pub fn hundred_percent_minus(v: Self, clamping_mode: AllowedNumericType) -> Self {
303 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 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 #[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 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 #[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 #[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 #[inline]
465 pub fn percentage_relative_to(&self, basis: Length) -> Length {
466 self.resolve(basis)
467 }
468
469 #[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 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 #[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 #[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 #[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 #[inline]
523 pub fn to_pixel_length(&self, containing_length: Au) -> Length {
524 self.resolve(containing_length.into())
525 }
526
527 #[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 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
549impl ClampToNonNegative for LengthPercentage {
550 #[inline]
552 fn clamp_to_non_negative(mut self) -> Self {
553 match self.unpack_mut() {
554 UnpackedMut::Length(l) => Self::new_length(l.clamp_to_non_negative()),
555 UnpackedMut::Percentage(p) => Self::new_percent(p.clamp_to_non_negative()),
556 UnpackedMut::Calc(ref mut c) => {
557 c.clamping_mode = AllowedNumericType::NonNegative;
558 self
559 },
560 }
561 }
562}
563
564impl PartialEq for LengthPercentage {
565 fn eq(&self, other: &Self) -> bool {
566 self.unpack() == other.unpack()
567 }
568}
569
570impl fmt::Debug for LengthPercentage {
571 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
572 self.unpack().fmt(formatter)
573 }
574}
575
576impl ToAnimatedZero for LengthPercentage {
577 fn to_animated_zero(&self) -> Result<Self, ()> {
578 Ok(match self.unpack() {
579 Unpacked::Length(l) => Self::new_length(l.to_animated_zero()?),
580 Unpacked::Percentage(p) => Self::new_percent(p.to_animated_zero()?),
581 Unpacked::Calc(c) => Self::new_calc_unchecked(Box::new(c.to_animated_zero()?)),
582 })
583 }
584}
585
586impl Clone for LengthPercentage {
587 fn clone(&self) -> Self {
588 match self.unpack() {
589 Unpacked::Length(l) => Self::new_length(l),
590 Unpacked::Percentage(p) => Self::new_percent(p),
591 Unpacked::Calc(c) => Self::new_calc_unchecked(Box::new(c.clone())),
592 }
593 }
594}
595
596impl ToComputedValue for specified::LengthPercentage {
597 type ComputedValue = LengthPercentage;
598
599 fn to_computed_value(&self, context: &Context) -> LengthPercentage {
600 match *self {
601 specified::LengthPercentage::Length(ref value) => {
602 LengthPercentage::new_length(value.to_computed_value(context))
603 },
604 specified::LengthPercentage::Percentage(value) => LengthPercentage::new_percent(value),
605 specified::LengthPercentage::Calc(ref calc) => (**calc).to_computed_value(context),
606 }
607 }
608
609 fn from_computed_value(computed: &LengthPercentage) -> Self {
610 match computed.unpack() {
611 Unpacked::Length(ref l) => {
612 specified::LengthPercentage::Length(ToComputedValue::from_computed_value(l))
613 },
614 Unpacked::Percentage(p) => specified::LengthPercentage::Percentage(p),
615 Unpacked::Calc(c) => {
616 specified::LengthPercentage::Calc(Box::new(
619 specified::CalcLengthPercentage::from_computed_value(c),
620 ))
621 },
622 }
623 }
624}
625
626impl ComputeSquaredDistance for LengthPercentage {
627 #[inline]
628 fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
629 let basis = Length::new(100.);
634 self.resolve(basis)
635 .compute_squared_distance(&other.resolve(basis))
636 }
637}
638
639impl ToCss for LengthPercentage {
640 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
641 where
642 W: Write,
643 {
644 self.unpack().to_css(dest)
645 }
646}
647
648impl Zero for LengthPercentage {
649 fn zero() -> Self {
650 LengthPercentage::new_length(Length::zero())
651 }
652
653 #[inline]
655 fn is_zero(&self) -> bool {
656 match self.unpack() {
657 Unpacked::Length(l) => l.px() == 0.0,
658 Unpacked::Percentage(p) => p.0 == 0.0,
659 Unpacked::Calc(..) => false,
660 }
661 }
662}
663
664impl ZeroNoPercent for LengthPercentage {
665 #[inline]
666 fn is_zero_no_percent(&self) -> bool {
667 self.to_length().is_some_and(|l| l.px() == 0.0)
668 }
669}
670
671impl Serialize for LengthPercentage {
672 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
673 where
674 S: serde::Serializer,
675 {
676 self.to_serializable().serialize(serializer)
677 }
678}
679
680impl<'de> Deserialize<'de> for LengthPercentage {
681 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
682 where
683 D: serde::Deserializer<'de>,
684 {
685 Ok(Self::from_serializable(Serializable::deserialize(
686 deserializer,
687 )?))
688 }
689}
690
691#[derive(
693 Clone,
694 Debug,
695 Deserialize,
696 MallocSizeOf,
697 PartialEq,
698 Serialize,
699 ToAnimatedZero,
700 ToCss,
701 ToResolvedValue,
702)]
703#[allow(missing_docs)]
704#[repr(u8)]
705pub enum CalcLengthPercentageLeaf {
706 Length(Length),
707 Percentage(Percentage),
708 Number(f32),
709}
710
711impl CalcLengthPercentageLeaf {
712 fn is_zero_length(&self) -> bool {
713 match *self {
714 Self::Length(ref l) => l.is_zero(),
715 Self::Percentage(..) => false,
716 Self::Number(..) => false,
717 }
718 }
719}
720
721impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf {
722 fn unit(&self) -> CalcUnits {
723 match self {
724 Self::Length(_) => CalcUnits::LENGTH,
725 Self::Percentage(_) => CalcUnits::PERCENTAGE,
726 Self::Number(_) => CalcUnits::empty(),
727 }
728 }
729
730 fn unitless_value(&self) -> Option<f32> {
731 Some(match *self {
732 Self::Length(ref l) => l.px(),
733 Self::Percentage(ref p) => p.0,
734 Self::Number(n) => n,
735 })
736 }
737
738 fn new_number(value: f32) -> Self {
739 Self::Number(value)
740 }
741
742 fn as_number(&self) -> Option<f32> {
743 match *self {
744 Self::Length(_) | Self::Percentage(_) => None,
745 Self::Number(value) => Some(value),
746 }
747 }
748
749 fn compare(&self, other: &Self, basis: PositivePercentageBasis) -> Option<std::cmp::Ordering> {
750 use self::CalcLengthPercentageLeaf::*;
751 if std::mem::discriminant(self) != std::mem::discriminant(other) {
752 return None;
753 }
754
755 if matches!(self, Percentage(..)) && matches!(basis, PositivePercentageBasis::Unknown) {
756 return None;
757 }
758
759 let Ok(self_negative) = self.is_negative() else {
760 return None;
761 };
762 let Ok(other_negative) = other.is_negative() else {
763 return None;
764 };
765 if self_negative != other_negative {
766 return Some(if self_negative {
767 std::cmp::Ordering::Less
768 } else {
769 std::cmp::Ordering::Greater
770 });
771 }
772
773 match (self, other) {
774 (&Length(ref one), &Length(ref other)) => one.partial_cmp(other),
775 (&Percentage(ref one), &Percentage(ref other)) => one.partial_cmp(other),
776 (&Number(ref one), &Number(ref other)) => one.partial_cmp(other),
777 _ => unsafe {
778 match *self {
779 Length(..) | Percentage(..) | Number(..) => {},
780 }
781 debug_unreachable!("Forgot to handle unit in compare()")
782 },
783 }
784 }
785
786 fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> {
787 use self::CalcLengthPercentageLeaf::*;
788
789 if self.is_zero_length() {
791 *self = other.clone();
792 return Ok(());
793 }
794
795 if other.is_zero_length() {
796 return Ok(());
797 }
798
799 if std::mem::discriminant(self) != std::mem::discriminant(other) {
800 return Err(());
801 }
802
803 match (self, other) {
804 (&mut Length(ref mut one), &Length(ref other)) => {
805 *one += *other;
806 },
807 (&mut Percentage(ref mut one), &Percentage(ref other)) => {
808 one.0 += other.0;
809 },
810 (&mut Number(ref mut one), &Number(ref other)) => {
811 *one += *other;
812 },
813 _ => unsafe {
814 match *other {
815 Length(..) | Percentage(..) | Number(..) => {},
816 }
817 debug_unreachable!("Forgot to handle unit in try_sum_in_place()")
818 },
819 }
820
821 Ok(())
822 }
823
824 fn try_product_in_place(&mut self, other: &mut Self) -> bool {
825 if let Self::Number(ref mut left) = *self {
826 if let Self::Number(ref right) = *other {
827 *left *= *right;
829 true
830 } else {
831 if other.map(|v| v * *left).is_ok() {
834 std::mem::swap(self, other);
835 true
836 } else {
837 false
838 }
839 }
840 } else if let Self::Number(ref right) = *other {
841 self.map(|v| v * *right).is_ok()
844 } else {
845 false
847 }
848 }
849
850 fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()>
851 where
852 O: Fn(f32, f32) -> f32,
853 {
854 use self::CalcLengthPercentageLeaf::*;
855 if std::mem::discriminant(self) != std::mem::discriminant(other) {
856 return Err(());
857 }
858 Ok(match (self, other) {
859 (&Length(ref one), &Length(ref other)) => {
860 Length(super::Length::new(op(one.px(), other.px())))
861 },
862 (&Percentage(one), &Percentage(other)) => {
863 Self::Percentage(super::Percentage(op(one.0, other.0)))
864 },
865 (&Number(one), &Number(other)) => Self::Number(op(one, other)),
866 _ => unsafe {
867 match *self {
868 Length(..) | Percentage(..) | Number(..) => {},
869 }
870 debug_unreachable!("Forgot to handle unit in try_op()")
871 },
872 })
873 }
874
875 fn map(&mut self, mut op: impl FnMut(f32) -> f32) -> Result<(), ()> {
876 Ok(match self {
877 Self::Length(value) => {
878 *value = Length::new(op(value.px()));
879 },
880 Self::Percentage(value) => {
881 *value = Percentage(op(value.0));
882 },
883 Self::Number(value) => {
884 *value = op(*value);
885 },
886 })
887 }
888
889 fn simplify(&mut self) {}
890
891 fn sort_key(&self) -> calc::SortKey {
892 match *self {
893 Self::Length(..) => calc::SortKey::Px,
894 Self::Percentage(..) => calc::SortKey::Percentage,
895 Self::Number(..) => calc::SortKey::Number,
896 }
897 }
898}
899
900pub type CalcNode = calc::GenericCalcNode<CalcLengthPercentageLeaf>;
902
903#[derive(
905 Clone, Debug, Deserialize, MallocSizeOf, Serialize, ToAnimatedZero, ToResolvedValue, ToCss,
906)]
907#[repr(C)]
908pub struct CalcLengthPercentage {
909 #[animation(constant)]
910 #[css(skip)]
911 clamping_mode: AllowedNumericType,
912 node: CalcNode,
913}
914
915pub type CalcAnchorSide = GenericAnchorSide<Box<CalcNode>>;
917
918impl CalcAnchorSide {
919 pub fn keyword_and_percentage(&self) -> (AnchorSideKeyword, f32) {
921 let p = match self {
922 Self::Percentage(p) => p,
923 Self::Keyword(k) => {
924 return if matches!(k, AnchorSideKeyword::Center) {
925 (AnchorSideKeyword::Start, 0.5)
926 } else {
927 (*k, 1.0)
928 }
929 },
930 };
931
932 if let CalcNode::Leaf(l) = &**p {
933 if let CalcLengthPercentageLeaf::Percentage(v) = l {
934 return (AnchorSideKeyword::Start, v.0);
935 }
936 }
937 debug_assert!(false, "Parsed non-percentage?");
938 (AnchorSideKeyword::Start, 1.0)
939 }
940}
941
942pub struct CalcLengthPercentageResolution {
944 pub result: Length,
946 pub percentage_used: bool,
948}
949
950#[repr(C)]
953#[derive(Clone, Copy)]
954pub enum AllowAnchorPosResolutionInCalcPercentage {
955 Both(PhysicalSide),
957 AnchorSizeOnly(PhysicalAxis),
959}
960
961impl AllowAnchorPosResolutionInCalcPercentage {
962 #[cfg(feature = "gecko")]
963 pub fn to_axis(&self) -> PhysicalAxis {
965 match self {
966 Self::AnchorSizeOnly(axis) => *axis,
967 Self::Both(side) => {
968 if matches!(side, PhysicalSide::Top | PhysicalSide::Bottom) {
969 PhysicalAxis::Vertical
970 } else {
971 PhysicalAxis::Horizontal
972 }
973 },
974 }
975 }
976}
977
978impl From<&CalcAnchorSide> for AnchorSide {
979 fn from(value: &CalcAnchorSide) -> Self {
980 match value {
981 CalcAnchorSide::Keyword(k) => Self::Keyword(*k),
982 CalcAnchorSide::Percentage(p) => {
983 if let CalcNode::Leaf(CalcLengthPercentageLeaf::Percentage(p)) = **p {
984 Self::Percentage(p)
985 } else {
986 unreachable!("Should have parsed simplified percentage.");
987 }
988 },
989 }
990 }
991}
992
993impl CalcLengthPercentage {
994 #[inline]
996 pub fn resolve(&self, basis: Length) -> Length {
997 if let CalcLengthPercentageLeaf::Length(px) = self
999 .node
1000 .resolve_map(|leaf| {
1001 Ok(if let CalcLengthPercentageLeaf::Percentage(p) = leaf {
1002 CalcLengthPercentageLeaf::Length(Length::new(basis.px() * p.0))
1003 } else {
1004 leaf.clone()
1005 })
1006 })
1007 .unwrap()
1008 {
1009 Length::new(self.clamping_mode.clamp(px.px())).normalized()
1010 } else {
1011 unreachable!("resolve_map should turn percentages to lengths, and parsing should ensure that we don't end up with a number");
1012 }
1013 }
1014
1015 #[inline]
1018 #[cfg(feature = "gecko")]
1019 pub fn resolve_anchor(
1020 &self,
1021 allowed: AllowAnchorPosResolutionInCalcPercentage,
1022 params: &AnchorPosOffsetResolutionParams,
1023 ) -> Result<(CalcNode, AllowedNumericType), ()> {
1024 use crate::values::{
1025 computed::{length::resolve_anchor_size, AnchorFunction},
1026 generics::{length::GenericAnchorSizeFunction, position::GenericAnchorFunction},
1027 };
1028
1029 fn resolve_anchor_function<'a>(
1030 f: &'a GenericAnchorFunction<Box<CalcNode>, Box<CalcNode>>,
1031 side: PhysicalSide,
1032 params: &AnchorPosOffsetResolutionParams,
1033 ) -> AnchorResolutionResult<'a, Box<CalcNode>> {
1034 let anchor_side: &CalcAnchorSide = &f.side;
1035 let resolved = if f.valid_for(side, params.mBaseParams.mPosition) {
1036 AnchorFunction::resolve(&f.target_element, &anchor_side.into(), side, params).ok()
1037 } else {
1038 None
1039 };
1040
1041 resolved.map_or_else(
1042 || {
1043 let Some(fb) = f.fallback.as_ref() else {
1044 return AnchorResolutionResult::Invalid;
1045 };
1046 let mut node = fb.clone();
1047 let result = node.map_node(|node| {
1048 resolve_anchor_functions(
1049 node,
1050 AllowAnchorPosResolutionInCalcPercentage::Both(side),
1051 params,
1052 )
1053 });
1054 if result.is_err() {
1055 return AnchorResolutionResult::Invalid;
1056 }
1057 AnchorResolutionResult::Resolved(node)
1058 },
1059 |v| {
1060 AnchorResolutionResult::Resolved(Box::new(CalcNode::Leaf(
1061 CalcLengthPercentageLeaf::Length(v),
1062 )))
1063 },
1064 )
1065 }
1066
1067 fn resolve_anchor_size_function<'a>(
1068 f: &'a GenericAnchorSizeFunction<Box<CalcNode>>,
1069 allowed: AllowAnchorPosResolutionInCalcPercentage,
1070 params: &AnchorPosOffsetResolutionParams,
1071 ) -> AnchorResolutionResult<'a, Box<CalcNode>> {
1072 let axis = allowed.to_axis();
1073 let resolved = if f.valid_for(params.mBaseParams.mPosition) {
1074 resolve_anchor_size(&f.target_element, axis, f.size, ¶ms.mBaseParams).ok()
1075 } else {
1076 None
1077 };
1078
1079 resolved.map_or_else(
1080 || {
1081 let Some(fb) = f.fallback.as_ref() else {
1082 return AnchorResolutionResult::Invalid;
1083 };
1084 let mut node = fb.clone();
1085 let result =
1086 node.map_node(|node| resolve_anchor_functions(node, allowed, params));
1087 if result.is_err() {
1088 return AnchorResolutionResult::Invalid;
1089 }
1090 AnchorResolutionResult::Resolved(node)
1091 },
1092 |v| {
1093 AnchorResolutionResult::Resolved(Box::new(CalcNode::Leaf(
1094 CalcLengthPercentageLeaf::Length(v),
1095 )))
1096 },
1097 )
1098 }
1099
1100 fn resolve_anchor_functions(
1101 node: &CalcNode,
1102 allowed: AllowAnchorPosResolutionInCalcPercentage,
1103 params: &AnchorPosOffsetResolutionParams,
1104 ) -> Result<Option<CalcNode>, ()> {
1105 let resolution = match node {
1106 CalcNode::Anchor(f) => {
1107 let prop_side = match allowed {
1108 AllowAnchorPosResolutionInCalcPercentage::Both(side) => side,
1109 AllowAnchorPosResolutionInCalcPercentage::AnchorSizeOnly(_) => {
1110 unreachable!("anchor() found where disallowed")
1111 },
1112 };
1113 resolve_anchor_function(f, prop_side, params)
1114 },
1115 CalcNode::AnchorSize(f) => resolve_anchor_size_function(f, allowed, params),
1116 _ => return Ok(None),
1117 };
1118
1119 match resolution {
1120 AnchorResolutionResult::Invalid => Err(()),
1121 AnchorResolutionResult::Fallback(fb) => {
1122 Ok(Some(*fb.clone()))
1124 },
1125 AnchorResolutionResult::Resolved(v) => Ok(Some(*v.clone())),
1126 }
1127 }
1128
1129 let mut node = self.node.clone();
1130 node.map_node(|node| resolve_anchor_functions(node, allowed, params))?;
1131 Ok((node, self.clamping_mode))
1132 }
1133}
1134
1135impl PartialEq for CalcLengthPercentage {
1148 fn eq(&self, other: &Self) -> bool {
1149 self.node == other.node
1150 }
1151}
1152
1153impl specified::CalcLengthPercentage {
1154 fn to_computed_value_with_zoom<F>(
1156 &self,
1157 context: &Context,
1158 zoom_fn: F,
1159 base_size: FontBaseSize,
1160 line_height_base: LineHeightBase,
1161 ) -> LengthPercentage
1162 where
1163 F: Fn(Length) -> Length,
1164 {
1165 use crate::values::specified::calc::Leaf;
1166
1167 let node = self.node.map_leaves(|leaf| match *leaf {
1168 Leaf::Percentage(p) => CalcLengthPercentageLeaf::Percentage(Percentage(p)),
1169 Leaf::Length(l) => CalcLengthPercentageLeaf::Length({
1170 let result =
1171 l.to_computed_value_with_base_size(context, base_size, line_height_base);
1172 if l.should_zoom_text() {
1173 zoom_fn(result)
1174 } else {
1175 result
1176 }
1177 }),
1178 Leaf::Number(n) => CalcLengthPercentageLeaf::Number(n),
1179 Leaf::Angle(..) | Leaf::Time(..) | Leaf::Resolution(..) | Leaf::ColorComponent(..) => {
1180 unreachable!("Shouldn't have parsed")
1181 },
1182 });
1183
1184 LengthPercentage::new_calc(node, self.clamping_mode)
1185 }
1186
1187 pub fn to_computed_value_zoomed(
1189 &self,
1190 context: &Context,
1191 base_size: FontBaseSize,
1192 line_height_base: LineHeightBase,
1193 ) -> LengthPercentage {
1194 self.to_computed_value_with_zoom(
1195 context,
1196 |abs| context.maybe_zoom_text(abs),
1197 base_size,
1198 line_height_base,
1199 )
1200 }
1201
1202 pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> {
1205 use crate::values::specified::calc::Leaf;
1206 use crate::values::specified::length::NoCalcLength;
1207
1208 match self.node {
1211 calc::CalcNode::Leaf(Leaf::Length(NoCalcLength::Absolute(ref l))) => Ok(l.to_px()),
1212 _ => Err(()),
1213 }
1214 }
1215
1216 #[cfg(feature = "gecko")]
1219 pub fn to_computed_pixel_length_with_font_metrics(
1220 &self,
1221 get_font_metrics: Option<impl Fn() -> GeckoFontMetrics>,
1222 ) -> Result<CSSFloat, ()> {
1223 use crate::values::specified::calc::Leaf;
1224 use crate::values::specified::length::NoCalcLength;
1225
1226 match self.node {
1227 calc::CalcNode::Leaf(Leaf::Length(NoCalcLength::Absolute(ref l))) => Ok(l.to_px()),
1228 calc::CalcNode::Leaf(Leaf::Length(NoCalcLength::FontRelative(ref l))) => {
1229 if let Some(getter) = get_font_metrics {
1230 l.to_computed_pixel_length_with_font_metrics(getter)
1231 } else {
1232 Err(())
1233 }
1234 },
1235 _ => Err(()),
1236 }
1237 }
1238
1239 pub fn to_computed_value(&self, context: &Context) -> LengthPercentage {
1241 self.to_computed_value_with_zoom(
1242 context,
1243 |abs| abs,
1244 FontBaseSize::CurrentStyle,
1245 LineHeightBase::CurrentStyle,
1246 )
1247 }
1248
1249 #[inline]
1250 fn from_computed_value(computed: &CalcLengthPercentage) -> Self {
1251 use crate::values::specified::calc::Leaf;
1252 use crate::values::specified::length::NoCalcLength;
1253
1254 specified::CalcLengthPercentage {
1255 clamping_mode: computed.clamping_mode,
1256 node: computed.node.map_leaves(|l| match l {
1257 CalcLengthPercentageLeaf::Length(ref l) => {
1258 Leaf::Length(NoCalcLength::from_px(l.px()))
1259 },
1260 CalcLengthPercentageLeaf::Percentage(ref p) => Leaf::Percentage(p.0),
1261 CalcLengthPercentageLeaf::Number(n) => Leaf::Number(*n),
1262 }),
1263 }
1264 }
1265}
1266
1267impl Animate for LengthPercentage {
1271 #[inline]
1272 fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
1273 Ok(match (self.unpack(), other.unpack()) {
1274 (Unpacked::Length(one), Unpacked::Length(other)) => {
1275 Self::new_length(one.animate(&other, procedure)?)
1276 },
1277 (Unpacked::Percentage(one), Unpacked::Percentage(other)) => {
1278 Self::new_percent(one.animate(&other, procedure)?)
1279 },
1280 _ => {
1281 use calc::CalcNodeLeaf;
1282
1283 fn product_with(mut node: CalcNode, product: f32) -> CalcNode {
1284 let mut number = CalcNode::Leaf(CalcLengthPercentageLeaf::new_number(product));
1285 if !node.try_product_in_place(&mut number) {
1286 CalcNode::Product(vec![node, number].into())
1287 } else {
1288 node
1289 }
1290 }
1291
1292 let (l, r) = procedure.weights();
1293 let one = product_with(self.to_calc_node(), l as f32);
1294 let other = product_with(other.to_calc_node(), r as f32);
1295
1296 Self::new_calc(
1297 CalcNode::Sum(vec![one, other].into()),
1298 AllowedNumericType::All,
1299 )
1300 },
1301 })
1302 }
1303}
1304
1305pub type NonNegativeLengthPercentage = NonNegative<LengthPercentage>;
1307
1308impl NonNegativeLengthPercentage {
1309 #[inline]
1311 pub fn to_used_value(&self, containing_length: Au) -> Au {
1312 let resolved = self.0.to_used_value(containing_length);
1313 std::cmp::max(resolved, Au(0))
1314 }
1315
1316 #[inline]
1318 pub fn maybe_to_used_value(&self, containing_length: Option<Au>) -> Option<Au> {
1319 let resolved = self.0.maybe_to_used_value(containing_length)?;
1320 Some(std::cmp::max(resolved, Au(0)))
1321 }
1322}
1323
1324impl TryTacticAdjustment for LengthPercentage {
1325 fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) {
1326 match self.unpack_mut() {
1327 UnpackedMut::Calc(calc) => calc.node.try_tactic_adjustment(old_side, new_side),
1328 UnpackedMut::Percentage(mut p) => {
1329 p.try_tactic_adjustment(old_side, new_side);
1330 *self = Self::new_percent(p);
1331 },
1332 UnpackedMut::Length(..) => {},
1333 }
1334 }
1335}
1336
1337impl TryTacticAdjustment for CalcNode {
1338 fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) {
1339 self.visit_depth_first(|node| match node {
1340 Self::Leaf(CalcLengthPercentageLeaf::Percentage(p)) => {
1341 p.try_tactic_adjustment(old_side, new_side)
1342 },
1343 Self::Anchor(a) => a.try_tactic_adjustment(old_side, new_side),
1344 Self::AnchorSize(a) => a.try_tactic_adjustment(old_side, new_side),
1345 _ => {},
1346 });
1347 }
1348}