Skip to main content

style/values/specified/
percentage.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Specified percentages.
6
7use crate::derives::*;
8use crate::parser::{Parse, ParserContext};
9use crate::typed_om::{ToTyped, TypedValue};
10use crate::values::computed::percentage::Percentage as ComputedPercentage;
11use crate::values::computed::{Context, ToComputedValue};
12use crate::values::generics::NonNegative;
13use crate::values::specified::calc::{CalcNode, CalcNumeric, Leaf};
14use crate::values::specified::{CalcLengthPercentage, LengthPercentage, NoCalcNumber, Number};
15use crate::values::tagged_numeric::{Extracted, NumericUnion, Unpacked, UnpackedMut};
16use crate::values::{normalize, reify_percentage, serialize_percentage, CSSFloat};
17use cssparser::{Parser, Token};
18use std::fmt::{self, Write};
19use style_traits::values::specified::AllowedNumericType;
20use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, ToCss};
21use thin_vec::ThinVec;
22
23/// A percentage value, where [0 .. 100%] maps to [0.0 .. 1.0]
24#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
25#[repr(C)]
26pub struct NoCalcPercentage(CSSFloat);
27
28impl SpecifiedValueInfo for NoCalcPercentage {}
29
30impl ToCss for NoCalcPercentage {
31    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
32    where
33        W: Write,
34    {
35        serialize_percentage(self.0, dest)
36    }
37}
38
39impl ToTyped for NoCalcPercentage {
40    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
41        reify_percentage(self.0, /* was_calc = */ false, dest)
42    }
43}
44
45impl NoCalcPercentage {
46    /// Creates a percentage from a numeric value.
47    pub fn new(value: CSSFloat) -> Self {
48        Self(value)
49    }
50
51    /// `0%`
52    #[inline]
53    pub fn zero() -> Self {
54        Self::new(0.)
55    }
56
57    /// `100%`
58    #[inline]
59    pub fn hundred() -> Self {
60        Self::new(1.)
61    }
62
63    /// Gets the underlying value for this float.
64    #[inline]
65    pub fn get(&self) -> CSSFloat {
66        self.0
67    }
68
69    /// Return the unit, as a string.
70    pub fn unit(&self) -> &'static str {
71        "percent"
72    }
73
74    /// Return no canonical unit (percent values do not have one).
75    pub fn canonical_unit(&self) -> Option<&'static str> {
76        None
77    }
78
79    /// Convert only if the unit is the same (conversion to other units does
80    /// not make sense).
81    pub fn to(&self, unit: &str) -> Result<Self, ()> {
82        if !unit.eq_ignore_ascii_case("percent") {
83            return Err(());
84        }
85        Ok(self.clone())
86    }
87}
88
89impl ToComputedValue for NoCalcPercentage {
90    type ComputedValue = ComputedPercentage;
91
92    fn to_computed_value(&self, _: &Context) -> Self::ComputedValue {
93        ComputedPercentage(normalize(self.get()))
94    }
95
96    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
97        Self::new(computed.0)
98    }
99}
100
101/// A specified percentage value, either a plain value or a `calc()` expression.
102#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
103pub struct Percentage(NumericUnion<(), f32, CalcNumeric>);
104
105impl ToCss for Percentage {
106    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
107    where
108        W: Write,
109    {
110        match self.0.unpack() {
111            Unpacked::Inline((), p) => NoCalcPercentage(p).to_css(dest),
112            Unpacked::Boxed(calc) => calc.to_css(dest),
113        }
114    }
115}
116
117impl ToTyped for Percentage {
118    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
119        match self.0.unpack() {
120            Unpacked::Inline((), p) => NoCalcPercentage(p).to_typed(dest),
121            Unpacked::Boxed(calc) => calc.to_typed(dest),
122        }
123    }
124}
125
126impl Percentage {
127    /// Creates a percentage from a numeric value.
128    pub fn new(value: CSSFloat) -> Self {
129        Self(NumericUnion::inline((), value))
130    }
131
132    /// Returns a new percentage calc value with the value `val`.
133    #[inline]
134    pub fn new_calc(val: Box<CalcNumeric>) -> Self {
135        Self(NumericUnion::boxed(val))
136    }
137
138    /// `0%`
139    #[inline]
140    pub fn zero() -> Self {
141        Self::new(0.)
142    }
143
144    /// `100%`
145    #[inline]
146    pub fn hundred() -> Self {
147        Self::new(1.)
148    }
149
150    /// Returns the value if this is a plain (non-calc) percentage, or None otherwise.
151    /// Use `resolve()` to also handle resolvable calc expressions, or `to_computed_value()`
152    /// when computed context is available.
153    #[inline]
154    pub fn get(&self) -> Option<f32> {
155        match self.0.unpack() {
156            Unpacked::Inline((), f) => Some(f),
157            Unpacked::Boxed(..) => None,
158        }
159    }
160
161    /// Returns the value if it can be resolved at parse time, including resolvable calc
162    /// expressions. Returns None for calc expressions that require computed context
163    /// (e.g. those using relative lengths or sibling-index()).
164    #[inline]
165    pub fn resolve(&self) -> Option<CSSFloat> {
166        match self.0.unpack() {
167            Unpacked::Inline((), f) => Some(f),
168            Unpacked::Boxed(calc) => calc.as_percentage().map(|p| p.get()),
169        }
170    }
171
172    /// Returns this percentage as a number.
173    pub fn to_number(&self) -> Option<Number> {
174        Some(match self.0.unpack() {
175            Unpacked::Inline((), p) => Number::new(p),
176            Unpacked::Boxed(ref calc) => {
177                let p = calc.as_percentage()?.get();
178                Number::new_calc(Box::new(
179                    calc.with_leaf_node(Leaf::Number(NoCalcNumber::new(p))),
180                ))
181            },
182        })
183    }
184
185    /// Returns this percentage as a LengthPercentage.
186    pub fn to_length_percentage(self) -> LengthPercentage {
187        match self.0.extract() {
188            Extracted::Inline((), p) => LengthPercentage::Percentage(NoCalcPercentage(p)),
189            Extracted::Boxed(calc) => LengthPercentage::Calc(Box::new(CalcLengthPercentage(*calc))),
190        }
191    }
192
193    /// Reverses this percentage, preserving calc-ness.
194    ///
195    /// For example: If it was 20%, convert it into 80%.
196    pub fn reverse(&mut self) {
197        match self.0.unpack_mut() {
198            UnpackedMut::Inline(_, p) => {
199                *p = 1. - *p;
200            },
201            UnpackedMut::Boxed(calc) => {
202                let mut sum = smallvec::SmallVec::<[CalcNode; 2]>::new();
203                sum.push(CalcNode::Leaf(
204                    Leaf::Percentage(NoCalcPercentage::hundred()),
205                ));
206                let mut node = calc.node.clone();
207                node.negate();
208                sum.push(node);
209                let mut diff = CalcNode::Sum(sum.into_boxed_slice().into());
210                diff.simplify_and_sort();
211                calc.node = diff;
212            },
213        }
214    }
215
216    /// Parses a specific kind of percentage.
217    pub fn parse_with_clamping_mode<'i, 't>(
218        context: &ParserContext,
219        input: &mut Parser<'i, 't>,
220        num_context: AllowedNumericType,
221    ) -> Result<Self, ParseError<'i>> {
222        let location = input.current_source_location();
223        Ok(Self(match *input.next()? {
224            Token::Percentage { unit_value, .. }
225                if num_context.is_ok(context.parsing_mode, unit_value) =>
226            {
227                NumericUnion::inline((), unit_value)
228            },
229            Token::Function(ref name) => {
230                let function = CalcNode::math_function(context, name, location)?;
231                let calc = CalcNode::parse_percentage(context, input, num_context, function)?;
232                NumericUnion::boxed(Box::new(calc))
233            },
234            ref t => return Err(location.new_unexpected_token_error(t.clone())),
235        }))
236    }
237
238    /// Parses a percentage token, but rejects it if it's negative.
239    pub fn parse_non_negative<'i, 't>(
240        context: &ParserContext,
241        input: &mut Parser<'i, 't>,
242    ) -> Result<Self, ParseError<'i>> {
243        Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
244    }
245
246    /// Parses a percentage token, but rejects it if it's negative or more than
247    /// 100%.
248    pub fn parse_zero_to_a_hundred<'i, 't>(
249        context: &ParserContext,
250        input: &mut Parser<'i, 't>,
251    ) -> Result<Self, ParseError<'i>> {
252        Self::parse_with_clamping_mode(context, input, AllowedNumericType::ZeroToOne)
253    }
254
255    /// Clamp to 100% if the value is over 100%.
256    #[inline]
257    pub fn clamp_to_hundred(&mut self) {
258        match self.0.unpack_mut() {
259            UnpackedMut::Inline((), p) => *p = p.min(1.),
260            UnpackedMut::Boxed(calc) => {
261                calc.clamping_mode = AllowedNumericType::ZeroToOne;
262            },
263        }
264    }
265}
266
267impl Parse for Percentage {
268    #[inline]
269    fn parse<'i, 't>(
270        context: &ParserContext,
271        input: &mut Parser<'i, 't>,
272    ) -> Result<Self, ParseError<'i>> {
273        Self::parse_with_clamping_mode(context, input, AllowedNumericType::All)
274    }
275}
276
277impl ToComputedValue for Percentage {
278    type ComputedValue = ComputedPercentage;
279
280    #[inline]
281    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
282        match self.0.unpack() {
283            Unpacked::Inline((), p) => NoCalcPercentage(p).to_computed_value(context),
284            Unpacked::Boxed(ref calc) => {
285                let value = calc.resolve(context, |result| match result {
286                    Ok(Leaf::Percentage(p)) => p.get(),
287                    _ => {
288                        debug_assert!(
289                            false,
290                            "Unexpected Percentage::Calc without resolved percentage"
291                        );
292                        f32::NAN
293                    },
294                });
295                ComputedPercentage(crate::values::normalize(value).min(f32::MAX).max(f32::MIN))
296            },
297        }
298    }
299
300    #[inline]
301    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
302        Percentage::new(computed.0)
303    }
304}
305
306impl SpecifiedValueInfo for Percentage {}
307
308/// Turns the percentage into a plain float.
309pub trait ToPercentage {
310    /// Returns whether this percentage used to be a calc().
311    fn is_calc(&self) -> bool {
312        false
313    }
314    /// Returns the percentage as a plain float, or None for calc expressions that require
315    /// computed context. Will always return Some if `is_calc` is false.
316    fn to_percentage(&self) -> Option<CSSFloat>;
317}
318
319impl ToPercentage for Percentage {
320    fn is_calc(&self) -> bool {
321        self.0.is_boxed()
322    }
323
324    fn to_percentage(&self) -> Option<CSSFloat> {
325        self.resolve()
326    }
327}
328
329/// A wrapper of Percentage, whose value must be >= 0.
330pub type NonNegativePercentage = NonNegative<Percentage>;
331
332impl Parse for NonNegativePercentage {
333    #[inline]
334    fn parse<'i, 't>(
335        context: &ParserContext,
336        input: &mut Parser<'i, 't>,
337    ) -> Result<Self, ParseError<'i>> {
338        Ok(NonNegative(Percentage::parse_non_negative(context, input)?))
339    }
340}
341
342impl NonNegativePercentage {
343    /// Convert to ComputedPercentage, for FontFaceRule size-adjust getter.
344    /// Returns None if the value is a calc expression that cannot be resolved at parse time.
345    #[inline]
346    pub fn compute(&self) -> Option<ComputedPercentage> {
347        self.0
348            .resolve()
349            .map(|f| AllowedNumericType::NonNegative.clamp(f))
350            .map(ComputedPercentage)
351    }
352}