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::{EqualsPercentage, 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, ToTyped, TypedValue};
53use thin_vec::ThinVec;
54
55#[doc(hidden)]
56#[derive(Clone, Copy)]
57#[repr(C)]
58pub struct LengthVariant {
59 tag: u8,
60 length: Length,
61}
62
63#[doc(hidden)]
64#[derive(Clone, Copy)]
65#[repr(C)]
66pub struct PercentageVariant {
67 tag: u8,
68 percentage: Percentage,
69}
70
71#[doc(hidden)]
74#[derive(Clone, Copy)]
75#[repr(C)]
76#[cfg(target_pointer_width = "32")]
77pub struct CalcVariant {
78 tag: u8,
79 ptr: *mut (),
82}
83
84#[doc(hidden)]
85#[derive(Clone, Copy)]
86#[repr(C)]
87#[cfg(target_pointer_width = "64")]
88pub struct CalcVariant {
89 ptr: usize, }
91
92unsafe impl Send for CalcVariant {}
94unsafe impl Sync for CalcVariant {}
95
96#[doc(hidden)]
97#[derive(Clone, Copy)]
98#[repr(C)]
99pub struct TagVariant {
100 tag: u8,
101}
102
103#[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
212impl EqualsPercentage for LengthPercentage {
213 fn equals_percentage(&self, v: CSSFloat) -> bool {
214 match self.unpack() {
215 Unpacked::Percentage(p) => p.0 == v,
216 _ => false,
217 }
218 }
219}
220
221#[derive(Clone, Debug, PartialEq, ToCss, ToTyped)]
223#[typed_value(derive_fields)]
224pub enum Unpacked<'a> {
225 Calc(&'a CalcLengthPercentage),
227 Length(Length),
229 Percentage(Percentage),
231}
232
233enum UnpackedMut<'a> {
235 Calc(&'a mut CalcLengthPercentage),
236 Length(Length),
237 Percentage(Percentage),
238}
239
240#[derive(Deserialize, PartialEq, Serialize)]
243enum Serializable {
244 Calc(CalcLengthPercentage),
245 Length(Length),
246 Percentage(Percentage),
247}
248
249impl LengthPercentage {
250 #[inline]
252 pub fn one() -> Self {
253 Self::new_length(Length::new(1.))
254 }
255
256 #[inline]
258 pub fn zero_percent() -> Self {
259 Self::new_percent(Percentage::zero())
260 }
261
262 #[inline]
264 pub fn hundred_percent() -> Self {
265 Self::new_percent(Percentage::hundred())
266 }
267
268 fn to_calc_node(&self) -> CalcNode {
269 match self.unpack() {
270 Unpacked::Length(l) => CalcNode::Leaf(CalcLengthPercentageLeaf::Length(l)),
271 Unpacked::Percentage(p) => CalcNode::Leaf(CalcLengthPercentageLeaf::Percentage(p)),
272 Unpacked::Calc(p) => p.node.clone(),
273 }
274 }
275
276 fn map_lengths(&self, mut map_fn: impl FnMut(Length) -> Length) -> Self {
277 match self.unpack() {
278 Unpacked::Length(l) => Self::new_length(map_fn(l)),
279 Unpacked::Percentage(p) => Self::new_percent(p),
280 Unpacked::Calc(lp) => Self::new_calc_unchecked(Box::new(CalcLengthPercentage {
281 clamping_mode: lp.clamping_mode,
282 node: lp.node.map_leaves(|leaf| match *leaf {
283 CalcLengthPercentageLeaf::Length(ref l) => {
284 CalcLengthPercentageLeaf::Length(map_fn(*l))
285 },
286 ref l => l.clone(),
287 }),
288 })),
289 }
290 }
291
292 #[inline]
294 pub fn new_length(length: Length) -> Self {
295 let length = Self(LengthPercentageUnion {
296 length: LengthVariant {
297 tag: LengthPercentageUnion::TAG_LENGTH,
298 length,
299 },
300 });
301 debug_assert_eq!(length.tag(), Tag::Length);
302 length
303 }
304
305 #[inline]
307 pub fn new_percent(percentage: Percentage) -> Self {
308 let percent = Self(LengthPercentageUnion {
309 percentage: PercentageVariant {
310 tag: LengthPercentageUnion::TAG_PERCENTAGE,
311 percentage,
312 },
313 });
314 debug_assert_eq!(percent.tag(), Tag::Percentage);
315 percent
316 }
317
318 pub fn hundred_percent_minus(v: Self, clamping_mode: AllowedNumericType) -> Self {
321 let mut node = v.to_calc_node();
324 node.negate();
325
326 let new_node = CalcNode::Sum(
327 vec![
328 CalcNode::Leaf(CalcLengthPercentageLeaf::Percentage(Percentage::hundred())),
329 node,
330 ]
331 .into(),
332 );
333
334 Self::new_calc(new_node, clamping_mode)
335 }
336
337 pub fn hundred_percent_minus_list(list: &[&Self], clamping_mode: AllowedNumericType) -> Self {
340 let mut new_list = vec![CalcNode::Leaf(CalcLengthPercentageLeaf::Percentage(
341 Percentage::hundred(),
342 ))];
343
344 for lp in list.iter() {
345 let mut node = lp.to_calc_node();
346 node.negate();
347 new_list.push(node)
348 }
349
350 Self::new_calc(CalcNode::Sum(new_list.into()), clamping_mode)
351 }
352
353 #[inline]
355 pub fn new_calc(mut node: CalcNode, clamping_mode: AllowedNumericType) -> Self {
356 node.simplify_and_sort();
357
358 match node {
359 CalcNode::Leaf(l) => {
360 return match l {
361 CalcLengthPercentageLeaf::Length(l) => {
362 Self::new_length(Length::new(clamping_mode.clamp(l.px())).normalized())
363 },
364 CalcLengthPercentageLeaf::Percentage(p) => Self::new_percent(Percentage(
365 clamping_mode.clamp(crate::values::normalize(p.0)),
366 )),
367 CalcLengthPercentageLeaf::Number(number) => {
368 debug_assert!(
369 false,
370 "The final result of a <length-percentage> should never be a number"
371 );
372 Self::new_length(Length::new(number))
373 },
374 };
375 },
376 _ => Self::new_calc_unchecked(Box::new(CalcLengthPercentage {
377 clamping_mode,
378 node,
379 })),
380 }
381 }
382
383 fn new_calc_unchecked(calc: Box<CalcLengthPercentage>) -> Self {
386 let ptr = Box::into_raw(calc);
387
388 #[cfg(target_pointer_width = "32")]
389 let calc = CalcVariant {
390 tag: LengthPercentageUnion::TAG_CALC,
391 ptr: ptr as *mut (),
392 };
393
394 #[cfg(target_pointer_width = "64")]
395 let calc = CalcVariant {
396 #[cfg(target_endian = "little")]
397 ptr: ptr as usize,
398 #[cfg(target_endian = "big")]
399 ptr: (ptr as usize).swap_bytes(),
400 };
401
402 let calc = Self(LengthPercentageUnion { calc });
403 debug_assert_eq!(calc.tag(), Tag::Calc);
404 calc
405 }
406
407 #[inline]
408 fn tag(&self) -> Tag {
409 match unsafe { self.0.tag.tag & LengthPercentageUnion::TAG_MASK } {
410 LengthPercentageUnion::TAG_CALC => Tag::Calc,
411 LengthPercentageUnion::TAG_LENGTH => Tag::Length,
412 LengthPercentageUnion::TAG_PERCENTAGE => Tag::Percentage,
413 _ => unsafe { debug_unreachable!("Bogus tag?") },
414 }
415 }
416
417 #[inline]
418 fn unpack_mut<'a>(&'a mut self) -> UnpackedMut<'a> {
419 unsafe {
420 match self.tag() {
421 Tag::Calc => UnpackedMut::Calc(&mut *self.calc_ptr()),
422 Tag::Length => UnpackedMut::Length(self.0.length.length),
423 Tag::Percentage => UnpackedMut::Percentage(self.0.percentage.percentage),
424 }
425 }
426 }
427
428 #[inline]
431 pub fn unpack<'a>(&'a self) -> Unpacked<'a> {
432 unsafe {
433 match self.tag() {
434 Tag::Calc => Unpacked::Calc(&*self.calc_ptr()),
435 Tag::Length => Unpacked::Length(self.0.length.length),
436 Tag::Percentage => Unpacked::Percentage(self.0.percentage.percentage),
437 }
438 }
439 }
440
441 #[inline]
442 unsafe fn calc_ptr(&self) -> *mut CalcLengthPercentage {
443 #[cfg(not(all(target_endian = "big", target_pointer_width = "64")))]
444 {
445 self.0.calc.ptr as *mut _
446 }
447 #[cfg(all(target_endian = "big", target_pointer_width = "64"))]
448 {
449 self.0.calc.ptr.swap_bytes() as *mut _
450 }
451 }
452
453 #[inline]
454 fn to_serializable(&self) -> Serializable {
455 match self.unpack() {
456 Unpacked::Calc(c) => Serializable::Calc(c.clone()),
457 Unpacked::Length(l) => Serializable::Length(l),
458 Unpacked::Percentage(p) => Serializable::Percentage(p),
459 }
460 }
461
462 #[inline]
463 fn from_serializable(s: Serializable) -> Self {
464 match s {
465 Serializable::Calc(c) => Self::new_calc_unchecked(Box::new(c)),
466 Serializable::Length(l) => Self::new_length(l),
467 Serializable::Percentage(p) => Self::new_percent(p),
468 }
469 }
470
471 #[inline]
473 pub fn resolve(&self, basis: Length) -> Length {
474 match self.unpack() {
475 Unpacked::Length(l) => l,
476 Unpacked::Percentage(p) => (basis * p.0).normalized(),
477 Unpacked::Calc(ref c) => c.resolve(basis),
478 }
479 }
480
481 #[inline]
483 pub fn percentage_relative_to(&self, basis: Length) -> Length {
484 self.resolve(basis)
485 }
486
487 #[inline]
489 pub fn has_percentage(&self) -> bool {
490 match self.unpack() {
491 Unpacked::Length(..) => false,
492 Unpacked::Percentage(..) | Unpacked::Calc(..) => true,
493 }
494 }
495
496 pub fn to_length(&self) -> Option<Length> {
498 match self.unpack() {
499 Unpacked::Length(l) => Some(l),
500 Unpacked::Percentage(..) | Unpacked::Calc(..) => {
501 debug_assert!(self.has_percentage());
502 return None;
503 },
504 }
505 }
506
507 #[inline]
509 pub fn to_percentage(&self) -> Option<Percentage> {
510 match self.unpack() {
511 Unpacked::Percentage(p) => Some(p),
512 Unpacked::Length(..) | Unpacked::Calc(..) => None,
513 }
514 }
515
516 #[inline]
518 pub fn to_percentage_of(&self, basis: Length) -> Option<Percentage> {
519 if basis.px() == 0. {
520 return None;
521 }
522 Some(match self.unpack() {
523 Unpacked::Length(l) => Percentage(l.px() / basis.px()),
524 Unpacked::Percentage(p) => p,
525 Unpacked::Calc(ref c) => Percentage(c.resolve(basis).px() / basis.px()),
526 })
527 }
528
529 #[inline]
531 pub fn to_used_value(&self, containing_length: Au) -> Au {
532 let length = self.to_pixel_length(containing_length);
533 if let Unpacked::Percentage(_) = self.unpack() {
534 return Au::from_f32_px_trunc(length.px());
535 }
536 Au::from(length)
537 }
538
539 #[inline]
541 pub fn to_pixel_length(&self, containing_length: Au) -> Length {
542 self.resolve(containing_length.into())
543 }
544
545 #[inline]
547 pub fn maybe_to_used_value(&self, container_len: Option<Au>) -> Option<Au> {
548 self.maybe_percentage_relative_to(container_len.map(Length::from))
549 .map(if let Unpacked::Percentage(_) = self.unpack() {
550 |length: Length| Au::from_f32_px_trunc(length.px())
551 } else {
552 Au::from
553 })
554 }
555
556 pub fn maybe_percentage_relative_to(&self, container_len: Option<Length>) -> Option<Length> {
560 if let Unpacked::Length(l) = self.unpack() {
561 return Some(l);
562 }
563 Some(self.resolve(container_len?))
564 }
565}
566
567impl ClampToNonNegative for LengthPercentage {
568 #[inline]
570 fn clamp_to_non_negative(mut self) -> Self {
571 match self.unpack_mut() {
572 UnpackedMut::Length(l) => Self::new_length(l.clamp_to_non_negative()),
573 UnpackedMut::Percentage(p) => Self::new_percent(p.clamp_to_non_negative()),
574 UnpackedMut::Calc(ref mut c) => {
575 c.clamping_mode = AllowedNumericType::NonNegative;
576 self
577 },
578 }
579 }
580}
581
582impl PartialEq for LengthPercentage {
583 fn eq(&self, other: &Self) -> bool {
584 self.unpack() == other.unpack()
585 }
586}
587
588impl fmt::Debug for LengthPercentage {
589 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
590 self.unpack().fmt(formatter)
591 }
592}
593
594impl ToAnimatedZero for LengthPercentage {
595 fn to_animated_zero(&self) -> Result<Self, ()> {
596 Ok(match self.unpack() {
597 Unpacked::Length(l) => Self::new_length(l.to_animated_zero()?),
598 Unpacked::Percentage(p) => Self::new_percent(p.to_animated_zero()?),
599 Unpacked::Calc(c) => Self::new_calc_unchecked(Box::new(c.to_animated_zero()?)),
600 })
601 }
602}
603
604impl Clone for LengthPercentage {
605 fn clone(&self) -> Self {
606 match self.unpack() {
607 Unpacked::Length(l) => Self::new_length(l),
608 Unpacked::Percentage(p) => Self::new_percent(p),
609 Unpacked::Calc(c) => Self::new_calc_unchecked(Box::new(c.clone())),
610 }
611 }
612}
613
614impl ToComputedValue for specified::LengthPercentage {
615 type ComputedValue = LengthPercentage;
616
617 fn to_computed_value(&self, context: &Context) -> LengthPercentage {
618 match *self {
619 specified::LengthPercentage::Length(ref value) => {
620 LengthPercentage::new_length(value.to_computed_value(context))
621 },
622 specified::LengthPercentage::Percentage(value) => LengthPercentage::new_percent(value),
623 specified::LengthPercentage::Calc(ref calc) => (**calc).to_computed_value(context),
624 }
625 }
626
627 fn from_computed_value(computed: &LengthPercentage) -> Self {
628 match computed.unpack() {
629 Unpacked::Length(ref l) => {
630 specified::LengthPercentage::Length(ToComputedValue::from_computed_value(l))
631 },
632 Unpacked::Percentage(p) => specified::LengthPercentage::Percentage(p),
633 Unpacked::Calc(c) => {
634 specified::LengthPercentage::Calc(Box::new(
637 specified::CalcLengthPercentage::from_computed_value(c),
638 ))
639 },
640 }
641 }
642}
643
644impl ComputeSquaredDistance for LengthPercentage {
645 #[inline]
646 fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
647 let basis = Length::new(100.);
652 self.resolve(basis)
653 .compute_squared_distance(&other.resolve(basis))
654 }
655}
656
657impl ToCss for LengthPercentage {
658 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
659 where
660 W: Write,
661 {
662 self.unpack().to_css(dest)
663 }
664}
665
666impl ToTyped for LengthPercentage {
667 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
668 self.unpack().to_typed(dest)
669 }
670}
671
672impl Zero for LengthPercentage {
673 fn zero() -> Self {
674 LengthPercentage::new_length(Length::zero())
675 }
676
677 #[inline]
679 fn is_zero(&self) -> bool {
680 match self.unpack() {
681 Unpacked::Length(l) => l.px() == 0.0,
682 Unpacked::Percentage(p) => p.0 == 0.0,
683 Unpacked::Calc(..) => false,
684 }
685 }
686}
687
688impl ZeroNoPercent for LengthPercentage {
689 #[inline]
690 fn is_zero_no_percent(&self) -> bool {
691 self.to_length().is_some_and(|l| l.px() == 0.0)
692 }
693}
694
695impl Serialize for LengthPercentage {
696 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
697 where
698 S: serde::Serializer,
699 {
700 self.to_serializable().serialize(serializer)
701 }
702}
703
704impl<'de> Deserialize<'de> for LengthPercentage {
705 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
706 where
707 D: serde::Deserializer<'de>,
708 {
709 Ok(Self::from_serializable(Serializable::deserialize(
710 deserializer,
711 )?))
712 }
713}
714
715#[derive(
717 Clone,
718 Debug,
719 Deserialize,
720 MallocSizeOf,
721 PartialEq,
722 Serialize,
723 ToAnimatedZero,
724 ToCss,
725 ToResolvedValue,
726 ToTyped,
727)]
728#[allow(missing_docs)]
729#[repr(u8)]
730#[typed_value(derive_fields)]
731pub enum CalcLengthPercentageLeaf {
732 Length(Length),
733 Percentage(Percentage),
734 Number(f32),
735}
736
737impl CalcLengthPercentageLeaf {
738 fn is_zero_length(&self) -> bool {
739 match *self {
740 Self::Length(ref l) => l.is_zero(),
741 Self::Percentage(..) => false,
742 Self::Number(..) => false,
743 }
744 }
745}
746
747impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf {
748 fn unit(&self) -> CalcUnits {
749 match self {
750 Self::Length(_) => CalcUnits::LENGTH,
751 Self::Percentage(_) => CalcUnits::PERCENTAGE,
752 Self::Number(_) => CalcUnits::empty(),
753 }
754 }
755
756 fn unitless_value(&self) -> Option<f32> {
757 Some(match *self {
758 Self::Length(ref l) => l.px(),
759 Self::Percentage(ref p) => p.0,
760 Self::Number(n) => n,
761 })
762 }
763
764 fn new_number(value: f32) -> Self {
765 Self::Number(value)
766 }
767
768 fn as_number(&self) -> Option<f32> {
769 match *self {
770 Self::Length(_) | Self::Percentage(_) => None,
771 Self::Number(value) => Some(value),
772 }
773 }
774
775 fn compare(&self, other: &Self, basis: PositivePercentageBasis) -> Option<std::cmp::Ordering> {
776 use self::CalcLengthPercentageLeaf::*;
777 if std::mem::discriminant(self) != std::mem::discriminant(other) {
778 return None;
779 }
780
781 if matches!(self, Percentage(..)) && matches!(basis, PositivePercentageBasis::Unknown) {
782 return None;
783 }
784
785 let Ok(self_negative) = self.is_negative() else {
786 return None;
787 };
788 let Ok(other_negative) = other.is_negative() else {
789 return None;
790 };
791 if self_negative != other_negative {
792 return Some(if self_negative {
793 std::cmp::Ordering::Less
794 } else {
795 std::cmp::Ordering::Greater
796 });
797 }
798
799 match (self, other) {
800 (&Length(ref one), &Length(ref other)) => one.partial_cmp(other),
801 (&Percentage(ref one), &Percentage(ref other)) => one.partial_cmp(other),
802 (&Number(ref one), &Number(ref other)) => one.partial_cmp(other),
803 _ => unsafe {
804 match *self {
805 Length(..) | Percentage(..) | Number(..) => {},
806 }
807 debug_unreachable!("Forgot to handle unit in compare()")
808 },
809 }
810 }
811
812 fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> {
813 use self::CalcLengthPercentageLeaf::*;
814
815 if self.is_zero_length() {
817 *self = other.clone();
818 return Ok(());
819 }
820
821 if other.is_zero_length() {
822 return Ok(());
823 }
824
825 if std::mem::discriminant(self) != std::mem::discriminant(other) {
826 return Err(());
827 }
828
829 match (self, other) {
830 (&mut Length(ref mut one), &Length(ref other)) => {
831 *one += *other;
832 },
833 (&mut Percentage(ref mut one), &Percentage(ref other)) => {
834 one.0 += other.0;
835 },
836 (&mut Number(ref mut one), &Number(ref other)) => {
837 *one += *other;
838 },
839 _ => unsafe {
840 match *other {
841 Length(..) | Percentage(..) | Number(..) => {},
842 }
843 debug_unreachable!("Forgot to handle unit in try_sum_in_place()")
844 },
845 }
846
847 Ok(())
848 }
849
850 fn try_product_in_place(&mut self, other: &mut Self) -> bool {
851 if let Self::Number(ref mut left) = *self {
852 if let Self::Number(ref right) = *other {
853 *left *= *right;
855 true
856 } else {
857 if other.map(|v| v * *left).is_ok() {
860 std::mem::swap(self, other);
861 true
862 } else {
863 false
864 }
865 }
866 } else if let Self::Number(ref right) = *other {
867 self.map(|v| v * *right).is_ok()
870 } else {
871 false
873 }
874 }
875
876 fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()>
877 where
878 O: Fn(f32, f32) -> f32,
879 {
880 use self::CalcLengthPercentageLeaf::*;
881 if std::mem::discriminant(self) != std::mem::discriminant(other) {
882 return Err(());
883 }
884 Ok(match (self, other) {
885 (&Length(ref one), &Length(ref other)) => {
886 Length(super::Length::new(op(one.px(), other.px())))
887 },
888 (&Percentage(one), &Percentage(other)) => {
889 Self::Percentage(super::Percentage(op(one.0, other.0)))
890 },
891 (&Number(one), &Number(other)) => Self::Number(op(one, other)),
892 _ => unsafe {
893 match *self {
894 Length(..) | Percentage(..) | Number(..) => {},
895 }
896 debug_unreachable!("Forgot to handle unit in try_op()")
897 },
898 })
899 }
900
901 fn map(&mut self, mut op: impl FnMut(f32) -> f32) -> Result<(), ()> {
902 Ok(match self {
903 Self::Length(value) => {
904 *value = Length::new(op(value.px()));
905 },
906 Self::Percentage(value) => {
907 *value = Percentage(op(value.0));
908 },
909 Self::Number(value) => {
910 *value = op(*value);
911 },
912 })
913 }
914
915 fn simplify(&mut self) {}
916
917 fn sort_key(&self) -> calc::SortKey {
918 match *self {
919 Self::Length(..) => calc::SortKey::Px,
920 Self::Percentage(..) => calc::SortKey::Percentage,
921 Self::Number(..) => calc::SortKey::Number,
922 }
923 }
924}
925
926pub type CalcNode = calc::GenericCalcNode<CalcLengthPercentageLeaf>;
928
929#[derive(
931 Clone,
932 Debug,
933 Deserialize,
934 MallocSizeOf,
935 Serialize,
936 ToAnimatedZero,
937 ToResolvedValue,
938 ToCss,
939 ToTyped,
940)]
941#[repr(C)]
942#[typed_value(derive_fields)]
943pub struct CalcLengthPercentage {
944 #[animation(constant)]
945 #[css(skip)]
946 clamping_mode: AllowedNumericType,
947 node: CalcNode,
948}
949
950pub type CalcAnchorSide = GenericAnchorSide<Box<CalcNode>>;
952
953pub struct CalcLengthPercentageResolution {
955 pub result: Length,
957 pub percentage_used: bool,
959}
960
961#[repr(C)]
964#[derive(Clone, Copy)]
965pub enum AllowAnchorPosResolutionInCalcPercentage {
966 Both(PhysicalSide),
968 AnchorSizeOnly(PhysicalAxis),
970}
971
972impl AllowAnchorPosResolutionInCalcPercentage {
973 #[cfg(feature = "gecko")]
974 pub fn to_axis(&self) -> PhysicalAxis {
976 match self {
977 Self::AnchorSizeOnly(axis) => *axis,
978 Self::Both(side) => {
979 if matches!(side, PhysicalSide::Top | PhysicalSide::Bottom) {
980 PhysicalAxis::Vertical
981 } else {
982 PhysicalAxis::Horizontal
983 }
984 },
985 }
986 }
987}
988
989impl From<&CalcAnchorSide> for AnchorSide {
990 fn from(value: &CalcAnchorSide) -> Self {
991 match value {
992 CalcAnchorSide::Keyword(k) => Self::Keyword(*k),
993 CalcAnchorSide::Percentage(p) => {
994 if let CalcNode::Leaf(CalcLengthPercentageLeaf::Percentage(p)) = **p {
995 Self::Percentage(p)
996 } else {
997 unreachable!("Should have parsed simplified percentage.");
998 }
999 },
1000 }
1001 }
1002}
1003
1004impl CalcLengthPercentage {
1005 #[inline]
1007 pub fn resolve(&self, basis: Length) -> Length {
1008 if let CalcLengthPercentageLeaf::Length(px) = self
1010 .node
1011 .resolve_map(|leaf| {
1012 Ok(if let CalcLengthPercentageLeaf::Percentage(p) = leaf {
1013 CalcLengthPercentageLeaf::Length(Length::new(basis.px() * p.0))
1014 } else {
1015 leaf.clone()
1016 })
1017 })
1018 .unwrap()
1019 {
1020 Length::new(self.clamping_mode.clamp(px.px())).normalized()
1021 } else {
1022 unreachable!("resolve_map should turn percentages to lengths, and parsing should ensure that we don't end up with a number");
1023 }
1024 }
1025
1026 #[inline]
1029 #[cfg(feature = "gecko")]
1030 pub fn resolve_anchor(
1031 &self,
1032 allowed: AllowAnchorPosResolutionInCalcPercentage,
1033 params: &AnchorPosOffsetResolutionParams,
1034 ) -> Result<(CalcNode, AllowedNumericType), ()> {
1035 use crate::values::{
1036 computed::{length::resolve_anchor_size, AnchorFunction},
1037 generics::{length::GenericAnchorSizeFunction, position::GenericAnchorFunction},
1038 };
1039
1040 fn resolve_anchor_function<'a>(
1041 f: &'a GenericAnchorFunction<Box<CalcNode>, Box<CalcNode>>,
1042 side: PhysicalSide,
1043 params: &AnchorPosOffsetResolutionParams,
1044 ) -> AnchorResolutionResult<'a, Box<CalcNode>> {
1045 let anchor_side: &CalcAnchorSide = &f.side;
1046 let resolved = if f.valid_for(side, params.mBaseParams.mPosition) {
1047 AnchorFunction::resolve(&f.target_element, &anchor_side.into(), side, params).ok()
1048 } else {
1049 None
1050 };
1051
1052 resolved.map_or_else(
1053 || {
1054 let Some(fb) = f.fallback.as_ref() else {
1055 return AnchorResolutionResult::Invalid;
1056 };
1057 let mut node = fb.clone();
1058 let result = node.map_node(|node| {
1059 resolve_anchor_functions(
1060 node,
1061 AllowAnchorPosResolutionInCalcPercentage::Both(side),
1062 params,
1063 )
1064 });
1065 if result.is_err() {
1066 return AnchorResolutionResult::Invalid;
1067 }
1068 AnchorResolutionResult::Resolved(node)
1069 },
1070 |v| {
1071 AnchorResolutionResult::Resolved(Box::new(CalcNode::Leaf(
1072 CalcLengthPercentageLeaf::Length(v),
1073 )))
1074 },
1075 )
1076 }
1077
1078 fn resolve_anchor_size_function<'a>(
1079 f: &'a GenericAnchorSizeFunction<Box<CalcNode>>,
1080 allowed: AllowAnchorPosResolutionInCalcPercentage,
1081 params: &AnchorPosOffsetResolutionParams,
1082 ) -> AnchorResolutionResult<'a, Box<CalcNode>> {
1083 let axis = allowed.to_axis();
1084 let resolved = if f.valid_for(params.mBaseParams.mPosition) {
1085 resolve_anchor_size(&f.target_element, axis, f.size, ¶ms.mBaseParams).ok()
1086 } else {
1087 None
1088 };
1089
1090 resolved.map_or_else(
1091 || {
1092 let Some(fb) = f.fallback.as_ref() else {
1093 return AnchorResolutionResult::Invalid;
1094 };
1095 let mut node = fb.clone();
1096 let result =
1097 node.map_node(|node| resolve_anchor_functions(node, allowed, params));
1098 if result.is_err() {
1099 return AnchorResolutionResult::Invalid;
1100 }
1101 AnchorResolutionResult::Resolved(node)
1102 },
1103 |v| {
1104 AnchorResolutionResult::Resolved(Box::new(CalcNode::Leaf(
1105 CalcLengthPercentageLeaf::Length(v),
1106 )))
1107 },
1108 )
1109 }
1110
1111 fn resolve_anchor_functions(
1112 node: &CalcNode,
1113 allowed: AllowAnchorPosResolutionInCalcPercentage,
1114 params: &AnchorPosOffsetResolutionParams,
1115 ) -> Result<Option<CalcNode>, ()> {
1116 let resolution = match node {
1117 CalcNode::Anchor(f) => {
1118 let prop_side = match allowed {
1119 AllowAnchorPosResolutionInCalcPercentage::Both(side) => side,
1120 AllowAnchorPosResolutionInCalcPercentage::AnchorSizeOnly(_) => {
1121 unreachable!("anchor() found where disallowed")
1122 },
1123 };
1124 resolve_anchor_function(f, prop_side, params)
1125 },
1126 CalcNode::AnchorSize(f) => resolve_anchor_size_function(f, allowed, params),
1127 _ => return Ok(None),
1128 };
1129
1130 match resolution {
1131 AnchorResolutionResult::Invalid => Err(()),
1132 AnchorResolutionResult::Fallback(fb) => {
1133 Ok(Some(*fb.clone()))
1135 },
1136 AnchorResolutionResult::Resolved(v) => Ok(Some(*v.clone())),
1137 }
1138 }
1139
1140 let mut node = self.node.clone();
1141 node.map_node(|node| resolve_anchor_functions(node, allowed, params))?;
1142 Ok((node, self.clamping_mode))
1143 }
1144}
1145
1146impl PartialEq for CalcLengthPercentage {
1159 fn eq(&self, other: &Self) -> bool {
1160 self.node == other.node
1161 }
1162}
1163
1164impl specified::CalcLengthPercentage {
1165 fn to_computed_value_with_zoom<F>(
1167 &self,
1168 context: &Context,
1169 zoom_fn: F,
1170 base_size: FontBaseSize,
1171 line_height_base: LineHeightBase,
1172 ) -> LengthPercentage
1173 where
1174 F: Fn(Length) -> Length,
1175 {
1176 use crate::values::specified::calc::Leaf;
1177
1178 let node = self.node.map_leaves(|leaf| match *leaf {
1179 Leaf::Percentage(p) => CalcLengthPercentageLeaf::Percentage(Percentage(p)),
1180 Leaf::Length(l) => CalcLengthPercentageLeaf::Length({
1181 let result =
1182 l.to_computed_value_with_base_size(context, base_size, line_height_base);
1183 if l.should_zoom_text() {
1184 zoom_fn(result)
1185 } else {
1186 result
1187 }
1188 }),
1189 Leaf::Number(n) => CalcLengthPercentageLeaf::Number(n),
1190 Leaf::Angle(..) | Leaf::Time(..) | Leaf::Resolution(..) | Leaf::ColorComponent(..) => {
1191 unreachable!("Shouldn't have parsed")
1192 },
1193 });
1194
1195 LengthPercentage::new_calc(node, self.clamping_mode)
1196 }
1197
1198 pub fn to_computed_value_zoomed(
1200 &self,
1201 context: &Context,
1202 base_size: FontBaseSize,
1203 line_height_base: LineHeightBase,
1204 ) -> LengthPercentage {
1205 self.to_computed_value_with_zoom(
1206 context,
1207 |abs| context.maybe_zoom_text(abs),
1208 base_size,
1209 line_height_base,
1210 )
1211 }
1212
1213 pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> {
1216 use crate::values::specified::calc::Leaf;
1217 use crate::values::specified::length::NoCalcLength;
1218
1219 match self.node {
1222 calc::CalcNode::Leaf(Leaf::Length(NoCalcLength::Absolute(ref l))) => Ok(l.to_px()),
1223 _ => Err(()),
1224 }
1225 }
1226
1227 #[cfg(feature = "gecko")]
1230 pub fn to_computed_pixel_length_with_font_metrics(
1231 &self,
1232 get_font_metrics: Option<impl Fn() -> GeckoFontMetrics>,
1233 ) -> Result<CSSFloat, ()> {
1234 use crate::values::specified::calc::Leaf;
1235 use crate::values::specified::length::NoCalcLength;
1236
1237 match self.node {
1238 calc::CalcNode::Leaf(Leaf::Length(NoCalcLength::Absolute(ref l))) => Ok(l.to_px()),
1239 calc::CalcNode::Leaf(Leaf::Length(NoCalcLength::FontRelative(ref l))) => {
1240 if let Some(getter) = get_font_metrics {
1241 l.to_computed_pixel_length_with_font_metrics(getter)
1242 } else {
1243 Err(())
1244 }
1245 },
1246 _ => Err(()),
1247 }
1248 }
1249
1250 pub fn to_computed_value(&self, context: &Context) -> LengthPercentage {
1252 self.to_computed_value_with_zoom(
1253 context,
1254 |abs| abs,
1255 FontBaseSize::CurrentStyle,
1256 LineHeightBase::CurrentStyle,
1257 )
1258 }
1259
1260 #[inline]
1261 fn from_computed_value(computed: &CalcLengthPercentage) -> Self {
1262 use crate::values::specified::calc::Leaf;
1263 use crate::values::specified::length::NoCalcLength;
1264
1265 specified::CalcLengthPercentage {
1266 clamping_mode: computed.clamping_mode,
1267 node: computed.node.map_leaves(|l| match l {
1268 CalcLengthPercentageLeaf::Length(ref l) => {
1269 Leaf::Length(NoCalcLength::from_px(l.px()))
1270 },
1271 CalcLengthPercentageLeaf::Percentage(ref p) => Leaf::Percentage(p.0),
1272 CalcLengthPercentageLeaf::Number(n) => Leaf::Number(*n),
1273 }),
1274 }
1275 }
1276}
1277
1278impl Animate for LengthPercentage {
1282 #[inline]
1283 fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
1284 Ok(match (self.unpack(), other.unpack()) {
1285 (Unpacked::Length(one), Unpacked::Length(other)) => {
1286 Self::new_length(one.animate(&other, procedure)?)
1287 },
1288 (Unpacked::Percentage(one), Unpacked::Percentage(other)) => {
1289 Self::new_percent(one.animate(&other, procedure)?)
1290 },
1291 _ => {
1292 use calc::CalcNodeLeaf;
1293
1294 fn product_with(mut node: CalcNode, product: f32) -> CalcNode {
1295 let mut number = CalcNode::Leaf(CalcLengthPercentageLeaf::new_number(product));
1296 if !node.try_product_in_place(&mut number) {
1297 CalcNode::Product(vec![node, number].into())
1298 } else {
1299 node
1300 }
1301 }
1302
1303 let (l, r) = procedure.weights();
1304 let one = product_with(self.to_calc_node(), l as f32);
1305 let other = product_with(other.to_calc_node(), r as f32);
1306
1307 Self::new_calc(
1308 CalcNode::Sum(vec![one, other].into()),
1309 AllowedNumericType::All,
1310 )
1311 },
1312 })
1313 }
1314}
1315
1316pub type NonNegativeLengthPercentage = NonNegative<LengthPercentage>;
1318
1319impl NonNegativeLengthPercentage {
1320 #[inline]
1322 pub fn to_used_value(&self, containing_length: Au) -> Au {
1323 let resolved = self.0.to_used_value(containing_length);
1324 std::cmp::max(resolved, Au(0))
1325 }
1326
1327 #[inline]
1329 pub fn maybe_to_used_value(&self, containing_length: Option<Au>) -> Option<Au> {
1330 let resolved = self.0.maybe_to_used_value(containing_length)?;
1331 Some(std::cmp::max(resolved, Au(0)))
1332 }
1333}
1334
1335impl TryTacticAdjustment for LengthPercentage {
1336 fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) {
1337 match self.unpack_mut() {
1338 UnpackedMut::Calc(calc) => calc.node.try_tactic_adjustment(old_side, new_side),
1339 UnpackedMut::Percentage(mut p) => {
1340 p.try_tactic_adjustment(old_side, new_side);
1341 *self = Self::new_percent(p);
1342 },
1343 UnpackedMut::Length(..) => {},
1344 }
1345 }
1346}
1347
1348impl TryTacticAdjustment for CalcNode {
1349 fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) {
1350 self.visit_depth_first(|node| match node {
1351 Self::Leaf(CalcLengthPercentageLeaf::Percentage(p)) => {
1352 p.try_tactic_adjustment(old_side, new_side)
1353 },
1354 Self::Anchor(a) => a.try_tactic_adjustment(old_side, new_side),
1355 Self::AnchorSize(a) => a.try_tactic_adjustment(old_side, new_side),
1356 _ => {},
1357 });
1358 }
1359}