1use super::{position::AnchorSide, Context, Length, Percentage, ToComputedValue};
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::calc::GenericAnchorFunctionFallback;
23#[cfg(feature = "gecko")]
24use crate::values::generics::length::AnchorResolutionResult;
25use crate::values::generics::position::GenericAnchorSide;
26use crate::values::generics::Optional;
27use crate::values::generics::{calc, ClampToNonNegative, NonNegative};
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::{specified, CSSFloat};
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(p.value)
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!("resolve_map should turn percentages to lengths, and parsing should ensure that we don't end up with a number");
661 }
662 }
663
664 #[inline]
667 #[cfg(feature = "gecko")]
668 pub fn resolve_anchor(
669 &self,
670 allowed: AllowAnchorPosResolutionInCalcPercentage,
671 params: &AnchorPosOffsetResolutionParams,
672 ) -> Result<(CalcNode, AllowedNumericType), ()> {
673 use crate::values::{
674 computed::{length::resolve_anchor_size, AnchorFunction},
675 generics::{length::GenericAnchorSizeFunction, position::GenericAnchorFunction},
676 };
677
678 fn resolve_anchor_function<'a>(
679 f: &'a GenericAnchorFunction<
680 Box<CalcNode>,
681 Box<GenericAnchorFunctionFallback<ComputedLeaf>>,
682 >,
683 side: PhysicalSide,
684 params: &AnchorPosOffsetResolutionParams,
685 ) -> AnchorResolutionResult<'a, Box<CalcNode>> {
686 let anchor_side: &CalcAnchorSide = &f.side;
687 let resolved = if f.valid_for(side, params.mBaseParams.mPosition) {
688 AnchorFunction::resolve(&f.target_element, &anchor_side.into(), side, params).ok()
689 } else {
690 None
691 };
692
693 resolved.map_or_else(
694 || {
695 let Some(fb) = f.fallback.as_ref() else {
696 return AnchorResolutionResult::Invalid;
697 };
698 let mut node = Box::new(fb.node.clone());
699 let result = node.map_node(|node| {
700 resolve_anchor_functions(
701 node,
702 AllowAnchorPosResolutionInCalcPercentage::Both(side),
703 params,
704 )
705 });
706 if result.is_err() {
707 return AnchorResolutionResult::Invalid;
708 }
709 AnchorResolutionResult::Resolved(node)
710 },
711 |v| {
712 AnchorResolutionResult::Resolved(Box::new(CalcNode::Leaf(
713 ComputedLeaf::Length(v),
714 )))
715 },
716 )
717 }
718
719 fn resolve_anchor_size_function<'a>(
720 f: &'a GenericAnchorSizeFunction<Box<GenericAnchorFunctionFallback<ComputedLeaf>>>,
721 allowed: AllowAnchorPosResolutionInCalcPercentage,
722 params: &AnchorPosOffsetResolutionParams,
723 ) -> AnchorResolutionResult<'a, Box<CalcNode>> {
724 let axis = allowed.to_axis();
725 let resolved = if f.valid_for(params.mBaseParams.mPosition) {
726 resolve_anchor_size(&f.target_element, axis, f.size, ¶ms.mBaseParams).ok()
727 } else {
728 None
729 };
730
731 resolved.map_or_else(
732 || {
733 let Some(fb) = f.fallback.as_ref() else {
734 return AnchorResolutionResult::Invalid;
735 };
736 let mut node = Box::new(fb.node.clone());
737 let result =
738 node.map_node(|node| resolve_anchor_functions(node, allowed, params));
739 if result.is_err() {
740 return AnchorResolutionResult::Invalid;
741 }
742 AnchorResolutionResult::Resolved(node)
743 },
744 |v| {
745 AnchorResolutionResult::Resolved(Box::new(CalcNode::Leaf(
746 ComputedLeaf::Length(v),
747 )))
748 },
749 )
750 }
751
752 fn resolve_anchor_functions(
753 node: &CalcNode,
754 allowed: AllowAnchorPosResolutionInCalcPercentage,
755 params: &AnchorPosOffsetResolutionParams,
756 ) -> Result<Option<CalcNode>, ()> {
757 let resolution = match node {
758 CalcNode::Anchor(f) => {
759 let prop_side = match allowed {
760 AllowAnchorPosResolutionInCalcPercentage::Both(side) => side,
761 AllowAnchorPosResolutionInCalcPercentage::AnchorSizeOnly(_) => {
762 unreachable!("anchor() found where disallowed")
763 },
764 };
765 resolve_anchor_function(f, prop_side, params)
766 },
767 CalcNode::AnchorSize(f) => resolve_anchor_size_function(f, allowed, params),
768 _ => return Ok(None),
769 };
770
771 match resolution {
772 AnchorResolutionResult::Invalid => Err(()),
773 AnchorResolutionResult::Fallback(fb) => {
774 Ok(Some(*fb.clone()))
776 },
777 AnchorResolutionResult::Resolved(v) => Ok(Some(*v.clone())),
778 }
779 }
780
781 let mut node = self.node.clone();
782 node.map_node(|node| resolve_anchor_functions(node, allowed, params))?;
783 Ok((node, self.clamping_mode))
784 }
785}
786
787impl PartialEq for CalcLengthPercentage {
800 fn eq(&self, other: &Self) -> bool {
801 self.node == other.node
802 }
803}
804
805impl specified::CalcLengthPercentage {
806 fn to_computed_value_with_zoom<F>(
808 &self,
809 context: &Context,
810 zoom_fn: F,
811 base_size: FontBaseSize,
812 line_height_base: LineHeightBase,
813 ) -> LengthPercentage
814 where
815 F: Fn(Length) -> Length,
816 {
817 use crate::values::specified::calc::Leaf;
818
819 let node = self.0.node.map_leaves(|leaf| match *leaf {
820 Leaf::Percentage(p) => {
821 ComputedLeaf::Percentage(CalcPercentageLeaf::new(p.get(), p.hint))
822 },
823 Leaf::Length(l) => ComputedLeaf::Length({
824 let result =
825 l.to_computed_value_with_base_size(context, base_size, line_height_base);
826 if l.should_zoom_text() {
827 zoom_fn(result)
828 } else {
829 result
830 }
831 }),
832 Leaf::Number(n) => ComputedLeaf::Number(n.get()),
833 Leaf::Angle(a) => {
834 ComputedLeaf::Angle(specified::Angle::new(a).to_computed_value(context))
835 },
836 Leaf::Time(t) => ComputedLeaf::Time(specified::Time::new(t).to_computed_value(context)),
837 Leaf::Resolution(r) => {
838 ComputedLeaf::Resolution(specified::Resolution::new(r).to_computed_value(context))
839 },
840 Leaf::ColorComponent(..) => unreachable!("Shouldn't have parsed"),
841 Leaf::TreeCountingFunction(t) => {
842 ComputedLeaf::Number(t.to_computed_value(context) as f32)
843 },
844 });
845
846 LengthPercentage::new_calc(node, self.0.clamping_mode)
847 }
848
849 pub fn to_computed_value_zoomed(
851 &self,
852 context: &Context,
853 base_size: FontBaseSize,
854 line_height_base: LineHeightBase,
855 ) -> LengthPercentage {
856 self.to_computed_value_with_zoom(
857 context,
858 |abs| context.maybe_zoom_text(abs),
859 base_size,
860 line_height_base,
861 )
862 }
863
864 pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> {
867 use crate::values::specified::calc::Leaf;
868
869 match self.0.node {
872 calc::CalcNode::Leaf(Leaf::Length(ref l)) => {
873 l.to_computed_pixel_length_without_context()
874 },
875 _ => Err(()),
876 }
877 }
878
879 pub fn compute_without_context(&self) -> Option<LengthPercentage> {
884 use crate::values::specified::calc::Leaf;
885
886 let mut resolvable = true;
887 let node = self.0.node.map_leaves(|leaf| match *leaf {
888 Leaf::Percentage(p) => {
889 ComputedLeaf::Percentage(CalcPercentageLeaf::new(p.get(), p.hint))
890 },
891 Leaf::Length(l) => {
892 ComputedLeaf::Length(match l.to_computed_pixel_length_without_context() {
893 Ok(px) => Length::new(px),
894 Err(()) => {
895 resolvable = false;
896 Length::new(0.)
897 },
898 })
899 },
900 Leaf::Number(n) => ComputedLeaf::Number(n.get()),
901 _ => {
902 resolvable = false;
903 ComputedLeaf::Number(0.)
904 },
905 });
906
907 if !resolvable {
908 return None;
909 }
910 Some(LengthPercentage::new_calc(node, self.0.clamping_mode))
911 }
912
913 #[cfg(feature = "gecko")]
916 pub fn to_computed_pixel_length_with_font_metrics(
917 &self,
918 get_font_metrics: Option<impl Fn() -> GeckoFontMetrics>,
919 ) -> Result<CSSFloat, ()> {
920 use crate::values::specified::calc::Leaf;
921
922 match self.0.node {
923 calc::CalcNode::Leaf(Leaf::Length(ref l)) => {
924 l.to_computed_pixel_length_with_font_metrics(get_font_metrics)
925 },
926 _ => Err(()),
927 }
928 }
929
930 pub fn to_computed_value(&self, context: &Context) -> LengthPercentage {
932 self.to_computed_value_with_zoom(
933 context,
934 |abs| abs,
935 FontBaseSize::CurrentStyle,
936 LineHeightBase::CurrentStyle,
937 )
938 }
939
940 #[inline]
941 fn from_computed_value(computed: &CalcLengthPercentage) -> Self {
942 use crate::values::specified::angle::NoCalcAngle;
943 use crate::values::specified::calc::{
944 CalcPercentageLeaf as SpecifiedCalcPercentageLeaf, Leaf,
945 };
946 use crate::values::specified::length::NoCalcLength;
947 use crate::values::specified::resolution::NoCalcResolution;
948 use crate::values::specified::time::NoCalcTime;
949
950 specified::CalcLengthPercentage(specified::CalcNumeric {
951 clamping_mode: computed.clamping_mode,
952 node: computed.node.map_leaves(|l| match l {
953 ComputedLeaf::Length(l) => Leaf::Length(NoCalcLength::from_px(l.px())),
954 ComputedLeaf::Percentage(p) => {
955 Leaf::Percentage(SpecifiedCalcPercentageLeaf::new(p.get(), p.hint))
956 },
957 ComputedLeaf::Number(n) => Leaf::Number(NoCalcNumber::new(*n)),
958 ComputedLeaf::Angle(a) => Leaf::Angle(NoCalcAngle::from_degrees(a.degrees())),
959 ComputedLeaf::Time(t) => Leaf::Time(NoCalcTime::from_seconds(t.seconds())),
960 ComputedLeaf::Resolution(r) => {
961 Leaf::Resolution(NoCalcResolution::from_dppx(r.dppx()))
962 },
963 }),
964 })
965 }
966}
967
968impl Animate for LengthPercentage {
972 #[inline]
973 fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
974 Ok(match (self.unpack(), other.unpack()) {
975 (Unpacked::Length(one), Unpacked::Length(other)) => {
976 Self::new_length(one.animate(&other, procedure)?)
977 },
978 (Unpacked::Percentage(one), Unpacked::Percentage(other)) => {
979 Self::new_percent(one.animate(&other, procedure)?)
980 },
981 _ => {
982 use calc::CalcNodeLeaf;
983
984 fn product_with(mut node: CalcNode, product: f32) -> CalcNode {
985 let mut number = CalcNode::Leaf(ComputedLeaf::new_number(product));
986 if !node.try_product_in_place(&mut number) {
987 CalcNode::Product(vec![node, number].into())
988 } else {
989 node
990 }
991 }
992
993 let (l, r) = procedure.weights();
994 let one = product_with(self.to_calc_node(), l as f32);
995 let other = product_with(other.to_calc_node(), r as f32);
996
997 Self::new_calc(
998 CalcNode::Sum(vec![one, other].into()),
999 AllowedNumericType::All,
1000 )
1001 },
1002 })
1003 }
1004}
1005
1006pub type NonNegativeLengthPercentage = NonNegative<LengthPercentage>;
1008
1009impl NonNegativeLengthPercentage {
1010 #[inline]
1012 pub fn to_used_value(&self, containing_length: Au) -> Au {
1013 let resolved = self.0.to_used_value(containing_length);
1014 std::cmp::max(resolved, Au(0))
1015 }
1016
1017 #[inline]
1019 pub fn maybe_to_used_value(&self, containing_length: Option<Au>) -> Option<Au> {
1020 let resolved = self.0.maybe_to_used_value(containing_length)?;
1021 Some(std::cmp::max(resolved, Au(0)))
1022 }
1023}
1024
1025impl TryTacticAdjustment for LengthPercentage {
1026 fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) {
1027 match self.unpack_mut() {
1028 UnpackedMut::Calc(calc) => calc.node.try_tactic_adjustment(old_side, new_side),
1029 UnpackedMut::Percentage(mut p) => {
1030 p.try_tactic_adjustment(old_side, new_side);
1031 *self = Self::new_percent(p);
1032 },
1033 UnpackedMut::Length(..) => {},
1034 }
1035 }
1036}
1037
1038impl TryTacticAdjustment for GenericAnchorFunctionFallback<ComputedLeaf> {
1039 fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) {
1040 self.node.try_tactic_adjustment(old_side, new_side)
1041 }
1042}
1043
1044impl TryTacticAdjustment for CalcNode {
1045 fn try_tactic_adjustment(&mut self, old_side: PhysicalSide, new_side: PhysicalSide) {
1046 self.visit_depth_first(|node| match node {
1047 Self::Leaf(ComputedLeaf::Percentage(p)) => {
1048 p.value.try_tactic_adjustment(old_side, new_side)
1049 },
1050 Self::Anchor(a) => a.try_tactic_adjustment(old_side, new_side),
1051 Self::AnchorSize(a) => a.try_tactic_adjustment(old_side, new_side),
1052 _ => {},
1053 });
1054 }
1055}