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