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