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