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