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