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