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