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