1use super::{Context, Length, Percentage, ToComputedValue, position::AnchorSide};
12use crate::derives::*;
13#[cfg(feature = "gecko")]
14use crate::gecko_bindings::structs::{AnchorPosOffsetResolutionParams, GeckoFontMetrics};
15use crate::logical_geometry::{PhysicalAxis, PhysicalSide};
16use crate::typed_om::{NumericBaseType, ToTyped, TypedValue};
17use crate::values::animated::{
18 Animate, Context as AnimatedContext, Procedure, ToAnimatedValue, ToAnimatedZero,
19};
20use crate::values::computed::position::TryTacticAdjustment;
21use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
22use crate::values::generics::Optional;
23use crate::values::generics::calc::GenericAnchorFunctionFallback;
24#[cfg(feature = "gecko")]
25use crate::values::generics::length::AnchorResolutionResult;
26use crate::values::generics::position::GenericAnchorSide;
27use crate::values::generics::{ClampToNonNegative, NonNegative, calc};
28use crate::values::resolved::{Context as ResolvedContext, ToResolvedValue};
29use crate::values::specified::length::{EqualsPercentage, FontBaseSize, LineHeightBase};
30use crate::values::specified::number::NoCalcNumber;
31use crate::values::specified::percentage::NoCalcPercentage;
32use crate::values::tagged_numeric::{self as tagged, NumericUnion};
33use crate::values::{CSSFloat, specified};
34use crate::{Zero, ZeroNoPercent};
35use app_units::Au;
36use serde::{Deserialize, Serialize};
37use std::fmt::{self, Write};
38use style_traits::values::specified::AllowedNumericType;
39use style_traits::{CssWriter, ToCss};
40use thin_vec::ThinVec;
41
42pub use super::calc::{CalcPercentageLeaf, ComputedLeaf};
43
44#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)]
46#[repr(u8)]
47pub enum LengthPercentageTag {
48 Length = 0,
50 Percentage = 1,
52}
53
54#[derive(MallocSizeOf)]
62#[repr(C)]
63pub struct LengthPercentage(NumericUnion<LengthPercentageTag, f32, CalcLengthPercentage>);
64
65impl ToAnimatedValue for LengthPercentage {
66 type AnimatedValue = Self;
67
68 fn to_animated_value(self, context: &AnimatedContext) -> Self::AnimatedValue {
69 if context.style.effective_zoom.is_one() {
70 return self;
71 }
72 self.map_lengths(|l| l.to_animated_value(context))
73 }
74
75 #[inline]
76 fn from_animated_value(value: Self::AnimatedValue) -> Self {
77 value
78 }
79}
80
81impl ToResolvedValue for LengthPercentage {
82 type ResolvedValue = Self;
83
84 fn to_resolved_value(self, context: &ResolvedContext) -> Self::ResolvedValue {
85 if context.style.effective_zoom.is_one() {
86 return self;
87 }
88 self.map_lengths(|l| l.to_resolved_value(context))
89 }
90
91 #[inline]
92 fn from_resolved_value(value: Self::ResolvedValue) -> Self {
93 value
94 }
95}
96
97impl EqualsPercentage for LengthPercentage {
98 fn equals_percentage(&self, v: CSSFloat) -> bool {
99 match self.unpack() {
100 Unpacked::Percentage(p) => p.0 == v,
101 _ => false,
102 }
103 }
104}
105
106#[derive(Clone, Debug, PartialEq, ToCss, ToTyped)]
108pub enum Unpacked<'a> {
109 Calc(&'a CalcLengthPercentage),
111 Length(Length),
113 Percentage(Percentage),
115}
116
117enum UnpackedMut<'a> {
119 Calc(&'a mut CalcLengthPercentage),
120 Length(Length),
121 Percentage(Percentage),
122}
123
124#[derive(Deserialize, PartialEq, Serialize)]
127enum Serializable {
128 Calc(CalcLengthPercentage),
129 Length(Length),
130 Percentage(Percentage),
131}
132
133impl LengthPercentage {
134 #[inline]
136 pub fn one() -> Self {
137 Self::new_length(Length::new(1.))
138 }
139
140 #[inline]
142 pub fn zero_percent() -> Self {
143 Self::new_percent(Percentage::zero())
144 }
145
146 #[inline]
148 pub fn hundred_percent() -> Self {
149 Self::new_percent(Percentage::hundred())
150 }
151
152 fn to_calc_node(&self) -> CalcNode {
153 match self.unpack() {
154 Unpacked::Length(l) => CalcNode::Leaf(ComputedLeaf::Length(l)),
155 Unpacked::Percentage(p) => CalcNode::Leaf(ComputedLeaf::Percentage(
156 CalcPercentageLeaf::new(p.0, Optional::Some(NumericBaseType::Length)),
157 )),
158 Unpacked::Calc(p) => p.node.clone(),
159 }
160 }
161
162 fn map_lengths(&self, mut map_fn: impl FnMut(Length) -> Length) -> Self {
163 match self.unpack() {
164 Unpacked::Length(l) => Self::new_length(map_fn(l)),
165 Unpacked::Percentage(p) => Self::new_percent(p),
166 Unpacked::Calc(lp) => Self::new_calc_unchecked(Box::new(CalcLengthPercentage {
167 clamping_mode: lp.clamping_mode,
168 node: lp.node.map_leaves(|leaf| match *leaf {
169 ComputedLeaf::Length(ref l) => ComputedLeaf::Length(map_fn(*l)),
170 ref l => l.clone(),
171 }),
172 })),
173 }
174 }
175
176 #[inline]
178 pub fn new_length(length: Length) -> Self {
179 Self(NumericUnion::inline(
180 LengthPercentageTag::Length,
181 length.px(),
182 ))
183 }
184
185 #[inline]
187 pub fn new_percent(percentage: Percentage) -> Self {
188 Self(NumericUnion::inline(
189 LengthPercentageTag::Percentage,
190 percentage.0,
191 ))
192 }
193
194 pub fn hundred_percent_minus(v: Self, clamping_mode: AllowedNumericType) -> Self {
197 let mut node = v.to_calc_node();
200 node.negate();
201
202 let new_node = CalcNode::Sum(
203 vec![
204 CalcNode::Leaf(ComputedLeaf::Percentage(CalcPercentageLeaf::new(
205 1.,
206 Optional::Some(NumericBaseType::Length),
207 ))),
208 node,
209 ]
210 .into(),
211 );
212
213 Self::new_calc(new_node, clamping_mode)
214 }
215
216 pub fn hundred_percent_minus_list(list: &[&Self], clamping_mode: AllowedNumericType) -> Self {
219 let mut new_list = vec![CalcNode::Leaf(ComputedLeaf::Percentage(
220 CalcPercentageLeaf::new(1., Optional::Some(NumericBaseType::Length)),
221 ))];
222
223 for lp in list.iter() {
224 let mut node = lp.to_calc_node();
225 node.negate();
226 new_list.push(node)
227 }
228
229 Self::new_calc(CalcNode::Sum(new_list.into()), clamping_mode)
230 }
231
232 #[inline]
234 pub fn new_calc(mut node: CalcNode, clamping_mode: AllowedNumericType) -> Self {
235 node.simplify_and_sort();
236
237 match node {
238 CalcNode::Leaf(l) => match l {
239 ComputedLeaf::Length(l) => {
240 Self::new_length(Length::new(clamping_mode.clamp(l.px())).finite())
241 },
242 ComputedLeaf::Percentage(p) => Self::new_percent(Percentage(
243 clamping_mode.clamp(crate::values::normalize(p.get())),
244 )),
245 ComputedLeaf::Number(number) => {
246 debug_assert!(
247 false,
248 "The final result of a <length-percentage> should never be a number"
249 );
250 Self::new_length(Length::new(number))
251 },
252 ComputedLeaf::Angle(..) | ComputedLeaf::Time(..) | ComputedLeaf::Resolution(..) => {
253 debug_assert!(
254 false,
255 "The final result of a <length-percentage> should never be an angle, time, or resolution"
256 );
257 Self::zero()
258 },
259 },
260 _ => Self::new_calc_unchecked(Box::new(CalcLengthPercentage {
261 clamping_mode,
262 node,
263 })),
264 }
265 }
266
267 fn new_calc_unchecked(calc: Box<CalcLengthPercentage>) -> Self {
270 Self(NumericUnion::boxed(calc))
271 }
272
273 #[inline]
274 fn unpack_mut<'a>(&'a mut self) -> UnpackedMut<'a> {
275 match self.0.unpack_mut() {
276 tagged::UnpackedMut::Boxed(calc) => UnpackedMut::Calc(calc),
277 tagged::UnpackedMut::Inline(t, n) => match *t {
278 LengthPercentageTag::Length => UnpackedMut::Length(Length::new(*n)),
279 LengthPercentageTag::Percentage => UnpackedMut::Percentage(Percentage(*n)),
280 },
281 }
282 }
283
284 #[inline]
287 pub fn unpack<'a>(&'a self) -> Unpacked<'a> {
288 match self.0.unpack() {
289 tagged::Unpacked::Boxed(calc) => Unpacked::Calc(calc),
290 tagged::Unpacked::Inline(LengthPercentageTag::Length, v) => {
291 Unpacked::Length(Length::new(v))
292 },
293 tagged::Unpacked::Inline(LengthPercentageTag::Percentage, v) => {
294 Unpacked::Percentage(Percentage(v))
295 },
296 }
297 }
298
299 #[inline]
300 fn to_serializable(&self) -> Serializable {
301 match self.unpack() {
302 Unpacked::Calc(c) => Serializable::Calc(c.clone()),
303 Unpacked::Length(l) => Serializable::Length(l),
304 Unpacked::Percentage(p) => Serializable::Percentage(p),
305 }
306 }
307
308 #[inline]
309 fn from_serializable(s: Serializable) -> Self {
310 match s {
311 Serializable::Calc(c) => Self::new_calc_unchecked(Box::new(c)),
312 Serializable::Length(l) => Self::new_length(l),
313 Serializable::Percentage(p) => Self::new_percent(p),
314 }
315 }
316
317 #[inline]
319 pub fn resolve(&self, basis: Length) -> Length {
320 match self.unpack() {
321 Unpacked::Length(l) => l,
322 Unpacked::Percentage(p) => (basis * p.0).normalized(),
323 Unpacked::Calc(c) => c.resolve(basis),
324 }
325 }
326
327 #[inline]
329 pub fn percentage_relative_to(&self, basis: Length) -> Length {
330 self.resolve(basis)
331 }
332
333 #[inline]
335 pub fn has_percentage(&self) -> bool {
336 match self.unpack() {
337 Unpacked::Length(..) => false,
338 Unpacked::Percentage(..) | Unpacked::Calc(..) => true,
339 }
340 }
341
342 pub fn to_length(&self) -> Option<Length> {
344 match self.unpack() {
345 Unpacked::Length(l) => Some(l),
346 Unpacked::Percentage(..) | Unpacked::Calc(..) => {
347 debug_assert!(self.has_percentage());
348 None
349 },
350 }
351 }
352
353 #[inline]
355 pub fn to_percentage(&self) -> Option<Percentage> {
356 match self.unpack() {
357 Unpacked::Percentage(p) => Some(p),
358 Unpacked::Length(..) | Unpacked::Calc(..) => None,
359 }
360 }
361
362 #[inline]
364 pub fn to_percentage_of(&self, basis: Length) -> Option<Percentage> {
365 if basis.px() == 0. {
366 return None;
367 }
368 Some(match self.unpack() {
369 Unpacked::Length(l) => Percentage(l.px() / basis.px()),
370 Unpacked::Percentage(p) => p,
371 Unpacked::Calc(c) => Percentage(c.resolve(basis).px() / basis.px()),
372 })
373 }
374
375 #[inline]
377 pub fn to_used_value(&self, containing_length: Au) -> Au {
378 let length = self.to_pixel_length(containing_length);
379 if let Unpacked::Percentage(_) = self.unpack() {
380 return Au::from_f32_px_trunc(length.px());
381 }
382 Au::from(length)
383 }
384
385 #[inline]
387 pub fn to_pixel_length(&self, containing_length: Au) -> Length {
388 self.resolve(containing_length.into())
389 }
390
391 #[inline]
393 pub fn maybe_to_used_value(&self, container_len: Option<Au>) -> Option<Au> {
394 self.maybe_percentage_relative_to(container_len.map(Length::from))
395 .map(if let Unpacked::Percentage(_) = self.unpack() {
396 |length: Length| Au::from_f32_px_trunc(length.px())
397 } else {
398 Au::from
399 })
400 }
401
402 pub fn maybe_percentage_relative_to(&self, container_len: Option<Length>) -> Option<Length> {
406 if let Unpacked::Length(l) = self.unpack() {
407 return Some(l);
408 }
409 Some(self.resolve(container_len?))
410 }
411}
412
413impl ClampToNonNegative for LengthPercentage {
414 #[inline]
416 fn clamp_to_non_negative(mut self) -> Self {
417 match self.unpack_mut() {
418 UnpackedMut::Length(l) => Self::new_length(l.clamp_to_non_negative()),
419 UnpackedMut::Percentage(p) => Self::new_percent(p.clamp_to_non_negative()),
420 UnpackedMut::Calc(ref mut c) => {
421 c.clamping_mode = AllowedNumericType::NonNegative;
422 self
423 },
424 }
425 }
426}
427
428impl PartialEq for LengthPercentage {
429 fn eq(&self, other: &Self) -> bool {
430 self.unpack() == other.unpack()
431 }
432}
433
434impl fmt::Debug for LengthPercentage {
435 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
436 self.unpack().fmt(formatter)
437 }
438}
439
440impl ToAnimatedZero for LengthPercentage {
441 fn to_animated_zero(&self) -> Result<Self, ()> {
442 Ok(match self.unpack() {
443 Unpacked::Length(l) => Self::new_length(l.to_animated_zero()?),
444 Unpacked::Percentage(p) => Self::new_percent(p.to_animated_zero()?),
445 Unpacked::Calc(c) => Self::new_calc_unchecked(Box::new(c.to_animated_zero()?)),
446 })
447 }
448}
449
450impl Clone for LengthPercentage {
451 fn clone(&self) -> Self {
452 match self.unpack() {
453 Unpacked::Length(l) => Self::new_length(l),
454 Unpacked::Percentage(p) => Self::new_percent(p),
455 Unpacked::Calc(c) => Self::new_calc_unchecked(Box::new(c.clone())),
456 }
457 }
458}
459
460impl ToComputedValue for specified::LengthPercentage {
461 type ComputedValue = LengthPercentage;
462
463 fn to_computed_value(&self, context: &Context) -> LengthPercentage {
464 match *self {
465 specified::LengthPercentage::Length(ref value) => {
466 LengthPercentage::new_length(value.to_computed_value(context))
467 },
468 specified::LengthPercentage::Percentage(value) => {
469 LengthPercentage::new_percent(value.to_computed_value(context))
470 },
471 specified::LengthPercentage::Calc(ref calc) => (**calc).to_computed_value(context),
472 }
473 }
474
475 fn from_computed_value(computed: &LengthPercentage) -> Self {
476 match computed.unpack() {
477 Unpacked::Length(ref l) => {
478 specified::LengthPercentage::Length(ToComputedValue::from_computed_value(l))
479 },
480 Unpacked::Percentage(p) => {
481 specified::LengthPercentage::Percentage(NoCalcPercentage::new(p.0))
482 },
483 Unpacked::Calc(c) => {
484 specified::LengthPercentage::Calc(Box::new(
487 specified::CalcLengthPercentage::from_computed_value(c),
488 ))
489 },
490 }
491 }
492}
493
494impl ComputeSquaredDistance for LengthPercentage {
495 #[inline]
496 fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
497 let basis = Length::new(100.);
502 self.resolve(basis)
503 .compute_squared_distance(&other.resolve(basis))
504 }
505}
506
507impl ToCss for LengthPercentage {
508 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
509 where
510 W: Write,
511 {
512 self.unpack().to_css(dest)
513 }
514}
515
516impl ToTyped for LengthPercentage {
517 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
518 self.unpack().to_typed(dest)
519 }
520}
521
522impl Zero for LengthPercentage {
523 fn zero() -> Self {
524 LengthPercentage::new_length(Length::zero())
525 }
526
527 #[inline]
529 fn is_zero(&self) -> bool {
530 match self.unpack() {
531 Unpacked::Length(l) => l.px() == 0.0,
532 Unpacked::Percentage(p) => p.0 == 0.0,
533 Unpacked::Calc(..) => false,
534 }
535 }
536}
537
538impl ZeroNoPercent for LengthPercentage {
539 #[inline]
540 fn is_zero_no_percent(&self) -> bool {
541 self.to_length().is_some_and(|l| l.px() == 0.0)
542 }
543}
544
545impl Serialize for LengthPercentage {
546 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
547 where
548 S: serde::Serializer,
549 {
550 self.to_serializable().serialize(serializer)
551 }
552}
553
554impl<'de> Deserialize<'de> for LengthPercentage {
555 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
556 where
557 D: serde::Deserializer<'de>,
558 {
559 Ok(Self::from_serializable(Serializable::deserialize(
560 deserializer,
561 )?))
562 }
563}
564
565pub type CalcNode = calc::GenericCalcNode<ComputedLeaf>;
567
568#[derive(
570 Clone,
571 Debug,
572 Deserialize,
573 MallocSizeOf,
574 Serialize,
575 ToAnimatedZero,
576 ToResolvedValue,
577 ToCss,
578 ToTyped,
579)]
580#[repr(C)]
581pub struct CalcLengthPercentage {
582 #[animation(constant)]
583 #[css(skip)]
584 clamping_mode: AllowedNumericType,
585 node: CalcNode,
586}
587
588pub type CalcAnchorSide = GenericAnchorSide<Box<CalcNode>>;
590
591pub struct CalcLengthPercentageResolution {
593 pub result: Length,
595 pub percentage_used: bool,
597}
598
599#[repr(C)]
602#[derive(Clone, Copy)]
603pub enum AllowAnchorPosResolutionInCalcPercentage {
604 Both(PhysicalSide),
606 AnchorSizeOnly(PhysicalAxis),
608}
609
610impl AllowAnchorPosResolutionInCalcPercentage {
611 #[cfg(feature = "gecko")]
612 pub fn to_axis(&self) -> PhysicalAxis {
614 match self {
615 Self::AnchorSizeOnly(axis) => *axis,
616 Self::Both(side) => {
617 if matches!(side, PhysicalSide::Top | PhysicalSide::Bottom) {
618 PhysicalAxis::Vertical
619 } else {
620 PhysicalAxis::Horizontal
621 }
622 },
623 }
624 }
625}
626
627impl From<&CalcAnchorSide> for AnchorSide {
628 fn from(value: &CalcAnchorSide) -> Self {
629 match value {
630 CalcAnchorSide::Keyword(k) => Self::Keyword(*k),
631 CalcAnchorSide::Percentage(p) => {
632 if let CalcNode::Leaf(ComputedLeaf::Percentage(p)) = **p {
633 Self::Percentage(Percentage(p.get()))
634 } else {
635 unreachable!("Should have parsed simplified percentage.");
636 }
637 },
638 }
639 }
640}
641
642impl CalcLengthPercentage {
643 #[inline]
645 pub fn resolve(&self, basis: Length) -> Length {
646 if let ComputedLeaf::Length(px) = self
648 .node
649 .resolve_map(|leaf| {
650 Ok(if let ComputedLeaf::Percentage(p) = leaf {
651 ComputedLeaf::Length(Length::new(basis.px() * p.get()))
652 } else {
653 leaf.clone()
654 })
655 })
656 .unwrap()
657 {
658 Length::new(self.clamping_mode.clamp(px.px())).normalized()
659 } else {
660 unreachable!(
661 "resolve_map should turn percentages to lengths, and parsing should ensure that we don't end up with a number"
662 );
663 }
664 }
665
666 #[inline]
669 #[cfg(feature = "gecko")]
670 pub fn resolve_anchor(
671 &self,
672 allowed: AllowAnchorPosResolutionInCalcPercentage,
673 params: &AnchorPosOffsetResolutionParams,
674 ) -> Result<(CalcNode, AllowedNumericType), ()> {
675 use crate::values::{
676 computed::{AnchorFunction, length::resolve_anchor_size},
677 generics::{length::GenericAnchorSizeFunction, position::GenericAnchorFunction},
678 };
679
680 fn resolve_anchor_function<'a>(
681 f: &'a GenericAnchorFunction<
682 Box<CalcNode>,
683 Box<GenericAnchorFunctionFallback<ComputedLeaf>>,
684 >,
685 side: PhysicalSide,
686 params: &AnchorPosOffsetResolutionParams,
687 ) -> AnchorResolutionResult<'a, Box<CalcNode>> {
688 let anchor_side: &CalcAnchorSide = &f.side;
689 let resolved = if f.valid_for(side, params.mBaseParams.mPosition) {
690 AnchorFunction::resolve(&f.target_element, &anchor_side.into(), side, params).ok()
691 } else {
692 None
693 };
694
695 resolved.map_or_else(
696 || {
697 let Some(fb) = f.fallback.as_ref() else {
698 return AnchorResolutionResult::Invalid;
699 };
700 let mut node = Box::new(fb.node.clone());
701 let result = node.map_node(|node| {
702 resolve_anchor_functions(
703 node,
704 AllowAnchorPosResolutionInCalcPercentage::Both(side),
705 params,
706 )
707 });
708 if result.is_err() {
709 return AnchorResolutionResult::Invalid;
710 }
711 AnchorResolutionResult::Resolved(node)
712 },
713 |v| {
714 AnchorResolutionResult::Resolved(Box::new(CalcNode::Leaf(
715 ComputedLeaf::Length(v),
716 )))
717 },
718 )
719 }
720
721 fn resolve_anchor_size_function<'a>(
722 f: &'a GenericAnchorSizeFunction<Box<GenericAnchorFunctionFallback<ComputedLeaf>>>,
723 allowed: AllowAnchorPosResolutionInCalcPercentage,
724 params: &AnchorPosOffsetResolutionParams,
725 ) -> AnchorResolutionResult<'a, Box<CalcNode>> {
726 let axis = allowed.to_axis();
727 let resolved = if f.valid_for(params.mBaseParams.mPosition) {
728 resolve_anchor_size(&f.target_element, axis, f.size, ¶ms.mBaseParams).ok()
729 } else {
730 None
731 };
732
733 resolved.map_or_else(
734 || {
735 let Some(fb) = f.fallback.as_ref() else {
736 return AnchorResolutionResult::Invalid;
737 };
738 let mut node = Box::new(fb.node.clone());
739 let result =
740 node.map_node(|node| resolve_anchor_functions(node, allowed, params));
741 if result.is_err() {
742 return AnchorResolutionResult::Invalid;
743 }
744 AnchorResolutionResult::Resolved(node)
745 },
746 |v| {
747 AnchorResolutionResult::Resolved(Box::new(CalcNode::Leaf(
748 ComputedLeaf::Length(v),
749 )))
750 },
751 )
752 }
753
754 fn resolve_anchor_functions(
755 node: &CalcNode,
756 allowed: AllowAnchorPosResolutionInCalcPercentage,
757 params: &AnchorPosOffsetResolutionParams,
758 ) -> Result<Option<CalcNode>, ()> {
759 let resolution = match node {
760 CalcNode::Anchor(f) => {
761 let prop_side = match allowed {
762 AllowAnchorPosResolutionInCalcPercentage::Both(side) => side,
763 AllowAnchorPosResolutionInCalcPercentage::AnchorSizeOnly(_) => {
764 unreachable!("anchor() found where disallowed")
765 },
766 };
767 resolve_anchor_function(f, prop_side, params)
768 },
769 CalcNode::AnchorSize(f) => resolve_anchor_size_function(f, allowed, params),
770 _ => return Ok(None),
771 };
772
773 match resolution {
774 AnchorResolutionResult::Invalid => Err(()),
775 AnchorResolutionResult::Fallback(fb) => {
776 Ok(Some(*fb.clone()))
778 },
779 AnchorResolutionResult::Resolved(v) => Ok(Some(*v.clone())),
780 }
781 }
782
783 let mut node = self.node.clone();
784 node.map_node(|node| resolve_anchor_functions(node, allowed, params))?;
785 Ok((node, self.clamping_mode))
786 }
787}
788
789impl PartialEq for CalcLengthPercentage {
802 fn eq(&self, other: &Self) -> bool {
803 self.node == other.node
804 }
805}
806
807impl specified::CalcLengthPercentage {
808 fn to_computed_value_with_zoom<F>(
810 &self,
811 context: &Context,
812 zoom_fn: F,
813 base_size: FontBaseSize,
814 line_height_base: LineHeightBase,
815 ) -> LengthPercentage
816 where
817 F: Fn(Length) -> Length,
818 {
819 use crate::values::specified::calc::Leaf;
820
821 let node = self.0.node.map_leaves(|leaf| match *leaf {
822 Leaf::Percentage(p) => {
823 ComputedLeaf::Percentage(CalcPercentageLeaf::new(p.get(), p.hint))
824 },
825 Leaf::Length(l) => ComputedLeaf::Length({
826 let result =
827 l.to_computed_value_with_base_size(context, base_size, line_height_base);
828 if l.should_zoom_text() {
829 zoom_fn(result)
830 } else {
831 result
832 }
833 }),
834 Leaf::Number(n) => ComputedLeaf::Number(n.get()),
835 Leaf::Angle(a) => {
836 ComputedLeaf::Angle(specified::Angle::new(a).to_computed_value(context))
837 },
838 Leaf::Time(t) => ComputedLeaf::Time(specified::Time::new(t).to_computed_value(context)),
839 Leaf::Resolution(r) => {
840 ComputedLeaf::Resolution(specified::Resolution::new(r).to_computed_value(context))
841 },
842 Leaf::ColorComponent(..) => unreachable!("Shouldn't have parsed"),
843 Leaf::TreeCountingFunction(t) => {
844 ComputedLeaf::Number(t.to_computed_value(context) as f32)
845 },
846 Leaf::RandomKey(ref key) => ComputedLeaf::Number(*key.to_computed_value(context)),
847 });
848
849 LengthPercentage::new_calc(node, self.0.clamping_mode)
850 }
851
852 pub fn to_computed_value_zoomed(
854 &self,
855 context: &Context,
856 base_size: FontBaseSize,
857 line_height_base: LineHeightBase,
858 ) -> LengthPercentage {
859 self.to_computed_value_with_zoom(
860 context,
861 |abs| context.maybe_zoom_text(abs),
862 base_size,
863 line_height_base,
864 )
865 }
866
867 pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> {
870 use crate::values::specified::calc::Leaf;
871
872 match self.0.node {
875 calc::CalcNode::Leaf(Leaf::Length(ref l)) => {
876 l.to_computed_pixel_length_without_context()
877 },
878 _ => Err(()),
879 }
880 }
881
882 pub fn compute_without_context(&self) -> Option<LengthPercentage> {
887 use crate::values::specified::calc::Leaf;
888
889 let mut resolvable = true;
890 let node = self.0.node.map_leaves(|leaf| match *leaf {
891 Leaf::Percentage(p) => ComputedLeaf::Percentage(p),
892 Leaf::Length(l) => {
893 ComputedLeaf::Length(match l.to_computed_pixel_length_without_context() {
894 Ok(px) => Length::new(px),
895 Err(()) => {
896 resolvable = false;
897 Length::new(0.)
898 },
899 })
900 },
901 Leaf::Number(n) => ComputedLeaf::Number(n.get()),
902 _ => {
903 resolvable = false;
904 ComputedLeaf::Number(0.)
905 },
906 });
907
908 if !resolvable {
909 return None;
910 }
911 Some(LengthPercentage::new_calc(node, self.0.clamping_mode))
912 }
913
914 #[cfg(feature = "gecko")]
917 pub fn to_computed_pixel_length_with_font_metrics(
918 &self,
919 get_font_metrics: Option<impl Fn() -> GeckoFontMetrics>,
920 ) -> Result<CSSFloat, ()> {
921 use crate::values::specified::calc::Leaf;
922
923 match self.0.node {
924 calc::CalcNode::Leaf(Leaf::Length(ref l)) => {
925 l.to_computed_pixel_length_with_font_metrics(get_font_metrics)
926 },
927 _ => Err(()),
928 }
929 }
930
931 pub fn to_computed_value(&self, context: &Context) -> LengthPercentage {
933 self.to_computed_value_with_zoom(
934 context,
935 |abs| abs,
936 FontBaseSize::CurrentStyle,
937 LineHeightBase::CurrentStyle,
938 )
939 }
940
941 #[inline]
942 fn from_computed_value(computed: &CalcLengthPercentage) -> Self {
943 use crate::values::specified::angle::NoCalcAngle;
944 use crate::values::specified::calc::Leaf;
945 use crate::values::specified::length::NoCalcLength;
946 use crate::values::specified::resolution::NoCalcResolution;
947 use crate::values::specified::time::NoCalcTime;
948
949 specified::CalcLengthPercentage(specified::CalcNumeric {
950 clamping_mode: computed.clamping_mode,
951 node: computed.node.map_leaves(|l| match l {
952 ComputedLeaf::Length(l) => Leaf::Length(NoCalcLength::from_px(l.px())),
953 ComputedLeaf::Percentage(p) => Leaf::Percentage(*p),
954 ComputedLeaf::Number(n) => Leaf::Number(NoCalcNumber::new(*n)),
955 ComputedLeaf::Angle(a) => Leaf::Angle(NoCalcAngle::from_degrees(a.degrees())),
956 ComputedLeaf::Time(t) => Leaf::Time(NoCalcTime::from_seconds(t.seconds())),
957 ComputedLeaf::Resolution(r) => {
958 Leaf::Resolution(NoCalcResolution::from_dppx(r.dppx()))
959 },
960 }),
961 })
962 }
963}
964
965impl Animate for LengthPercentage {
969 #[inline]
970 fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
971 Ok(match (self.unpack(), other.unpack()) {
972 (Unpacked::Length(one), Unpacked::Length(other)) => {
973 Self::new_length(one.animate(&other, procedure)?)
974 },
975 (Unpacked::Percentage(one), Unpacked::Percentage(other)) => {
976 Self::new_percent(one.animate(&other, procedure)?)
977 },
978 _ => {
979 use calc::CalcNodeLeaf;
980
981 fn product_with(mut node: CalcNode, product: f32) -> CalcNode {
982 let mut number = CalcNode::Leaf(ComputedLeaf::new_number(product));
983 if !node.try_product_in_place(&mut number) {
984 CalcNode::Product(vec![node, number].into())
985 } else {
986 node
987 }
988 }
989
990 let (l, r) = procedure.weights();
991 let one = product_with(self.to_calc_node(), l as f32);
992 let other = product_with(other.to_calc_node(), r as f32);
993
994 Self::new_calc(
995 CalcNode::Sum(vec![one, other].into()),
996 AllowedNumericType::All,
997 )
998 },
999 })
1000 }
1001}
1002
1003pub type NonNegativeLengthPercentage = NonNegative<LengthPercentage>;
1005
1006impl NonNegativeLengthPercentage {
1007 #[inline]
1009 pub fn to_used_value(&self, containing_length: Au) -> Au {
1010 let resolved = self.0.to_used_value(containing_length);
1011 std::cmp::max(resolved, Au(0))
1012 }
1013
1014 #[inline]
1016 pub fn maybe_to_used_value(&self, containing_length: Option<Au>) -> Option<Au> {
1017 let resolved = self.0.maybe_to_used_value(containing_length)?;
1018 Some(std::cmp::max(resolved, Au(0)))
1019 }
1020}
1021
1022impl TryTacticAdjustment for LengthPercentage {
1023 fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) {
1024 match self.unpack_mut() {
1025 UnpackedMut::Calc(calc) => calc.node.try_tactic_adjustment(old_side, new_side),
1026 UnpackedMut::Percentage(mut p) => {
1027 p.try_tactic_adjustment(old_side, new_side);
1028 *self = Self::new_percent(p);
1029 },
1030 UnpackedMut::Length(..) => {},
1031 }
1032 }
1033}
1034
1035impl TryTacticAdjustment for GenericAnchorFunctionFallback<ComputedLeaf> {
1036 fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) {
1037 self.node.try_tactic_adjustment(old_side, new_side)
1038 }
1039}
1040
1041impl TryTacticAdjustment for CalcNode {
1042 fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) {
1043 self.visit_depth_first(|node| match node {
1044 Self::Leaf(ComputedLeaf::Percentage(p)) => {
1045 p.value.try_tactic_adjustment(old_side, new_side)
1046 },
1047 Self::Anchor(a) => a.try_tactic_adjustment(old_side, new_side),
1048 Self::AnchorSize(a) => a.try_tactic_adjustment(old_side, new_side),
1049 _ => {},
1050 });
1051 }
1052}