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::values::computed::percentage::Percentage as ComputedPercentage;
10use crate::values::computed::{Context, ToComputedValue};
11use crate::values::generics::NonNegative;
12use crate::values::specified::calc::CalcNode;
13use crate::values::specified::Number;
14use crate::values::{normalize, reify_percentage, serialize_percentage, CSSFloat};
15use cssparser::{Parser, Token};
16use std::fmt::{self, Write};
17use style_traits::values::specified::AllowedNumericType;
18use style_traits::{
19    CssWriter, MathSum, NumericValue, ParseError, SpecifiedValueInfo, ToCss, ToTyped, TypedValue,
20};
21use thin_vec::ThinVec;
22
23/// A percentage value.
24#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
25pub struct Percentage {
26    /// The percentage value as a float.
27    ///
28    /// [0 .. 100%] maps to [0.0 .. 1.0]
29    value: CSSFloat,
30    /// If this percentage came from a calc() expression, this tells how
31    /// clamping should be done on the value.
32    calc_clamping_mode: Option<AllowedNumericType>,
33}
34
35impl ToCss for Percentage {
36    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
37    where
38        W: Write,
39    {
40        if self.calc_clamping_mode.is_some() {
41            dest.write_str("calc(")?;
42        }
43
44        serialize_percentage(self.value, dest)?;
45
46        if self.calc_clamping_mode.is_some() {
47            dest.write_char(')')?;
48        }
49        Ok(())
50    }
51}
52
53impl ToTyped for Percentage {
54    fn to_typed(&self) -> Option<TypedValue> {
55        let numeric_value = reify_percentage(self.value);
56
57        if self.calc_clamping_mode.is_some() {
58            Some(TypedValue::Numeric(NumericValue::Sum(MathSum {
59                values: ThinVec::from([numeric_value]),
60            })))
61        } else {
62            Some(TypedValue::Numeric(numeric_value))
63        }
64    }
65}
66
67impl Percentage {
68    /// Creates a percentage from a numeric value.
69    pub(super) fn new_with_clamping_mode(
70        value: CSSFloat,
71        calc_clamping_mode: Option<AllowedNumericType>,
72    ) -> Self {
73        Self {
74            value,
75            calc_clamping_mode,
76        }
77    }
78
79    /// Creates a percentage from a numeric value.
80    pub fn new(value: CSSFloat) -> Self {
81        Self::new_with_clamping_mode(value, None)
82    }
83
84    /// `0%`
85    #[inline]
86    pub fn zero() -> Self {
87        Percentage {
88            value: 0.,
89            calc_clamping_mode: None,
90        }
91    }
92
93    /// `100%`
94    #[inline]
95    pub fn hundred() -> Self {
96        Percentage {
97            value: 1.,
98            calc_clamping_mode: None,
99        }
100    }
101
102    /// Gets the underlying value for this float.
103    pub fn get(&self) -> CSSFloat {
104        self.calc_clamping_mode
105            .map_or(self.value, |mode| mode.clamp(self.value))
106    }
107
108    /// Return the unit, as a string.
109    pub fn unit(&self) -> &'static str {
110        "percent"
111    }
112
113    /// Return no canonical unit (percent values do not have one).
114    pub fn canonical_unit(&self) -> Option<&'static str> {
115        None
116    }
117
118    /// Convert only if the unit is the same (conversion to other units does
119    /// not make sense).
120    pub fn to(&self, unit: &str) -> Result<Self, ()> {
121        if !unit.eq_ignore_ascii_case("percent") {
122            return Err(());
123        }
124        Ok(Self {
125            value: self.value,
126            calc_clamping_mode: self.calc_clamping_mode,
127        })
128    }
129
130    /// Returns this percentage as a number.
131    pub fn to_number(&self) -> Number {
132        Number::new_with_clamping_mode(self.value, self.calc_clamping_mode)
133    }
134
135    /// Returns the calc() clamping mode for this percentage.
136    pub fn calc_clamping_mode(&self) -> Option<AllowedNumericType> {
137        self.calc_clamping_mode
138    }
139
140    /// Reverses this percentage, preserving calc-ness.
141    ///
142    /// For example: If it was 20%, convert it into 80%.
143    pub fn reverse(&mut self) {
144        let new_value = 1. - self.value;
145        self.value = new_value;
146    }
147
148    /// Parses a specific kind of percentage.
149    pub fn parse_with_clamping_mode<'i, 't>(
150        context: &ParserContext,
151        input: &mut Parser<'i, 't>,
152        num_context: AllowedNumericType,
153    ) -> Result<Self, ParseError<'i>> {
154        let location = input.current_source_location();
155        match *input.next()? {
156            Token::Percentage { unit_value, .. }
157                if num_context.is_ok(context.parsing_mode, unit_value) =>
158            {
159                Ok(Percentage::new(unit_value))
160            },
161            Token::Function(ref name) => {
162                let function = CalcNode::math_function(context, name, location)?;
163                let value = CalcNode::parse_percentage(context, input, function)?;
164                Ok(Percentage {
165                    value,
166                    calc_clamping_mode: Some(num_context),
167                })
168            },
169            ref t => Err(location.new_unexpected_token_error(t.clone())),
170        }
171    }
172
173    /// Parses a percentage token, but rejects it if it's negative.
174    pub fn parse_non_negative<'i, 't>(
175        context: &ParserContext,
176        input: &mut Parser<'i, 't>,
177    ) -> Result<Self, ParseError<'i>> {
178        Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
179    }
180
181    /// Parses a percentage token, but rejects it if it's negative or more than
182    /// 100%.
183    pub fn parse_zero_to_a_hundred<'i, 't>(
184        context: &ParserContext,
185        input: &mut Parser<'i, 't>,
186    ) -> Result<Self, ParseError<'i>> {
187        Self::parse_with_clamping_mode(context, input, AllowedNumericType::ZeroToOne)
188    }
189
190    /// Clamp to 100% if the value is over 100%.
191    #[inline]
192    pub fn clamp_to_hundred(self) -> Self {
193        Percentage {
194            value: self.value.min(1.),
195            calc_clamping_mode: self.calc_clamping_mode,
196        }
197    }
198}
199
200impl Parse for Percentage {
201    #[inline]
202    fn parse<'i, 't>(
203        context: &ParserContext,
204        input: &mut Parser<'i, 't>,
205    ) -> Result<Self, ParseError<'i>> {
206        Self::parse_with_clamping_mode(context, input, AllowedNumericType::All)
207    }
208}
209
210impl ToComputedValue for Percentage {
211    type ComputedValue = ComputedPercentage;
212
213    #[inline]
214    fn to_computed_value(&self, _: &Context) -> Self::ComputedValue {
215        ComputedPercentage(normalize(self.get()))
216    }
217
218    #[inline]
219    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
220        Percentage::new(computed.0)
221    }
222}
223
224impl SpecifiedValueInfo for Percentage {}
225
226/// Turns the percentage into a plain float.
227pub trait ToPercentage {
228    /// Returns whether this percentage used to be a calc().
229    fn is_calc(&self) -> bool {
230        false
231    }
232    /// Turns the percentage into a plain float.
233    fn to_percentage(&self) -> CSSFloat;
234}
235
236impl ToPercentage for Percentage {
237    fn is_calc(&self) -> bool {
238        self.calc_clamping_mode.is_some()
239    }
240
241    fn to_percentage(&self) -> CSSFloat {
242        self.get()
243    }
244}
245
246/// A wrapper of Percentage, whose value must be >= 0.
247pub type NonNegativePercentage = NonNegative<Percentage>;
248
249impl Parse for NonNegativePercentage {
250    #[inline]
251    fn parse<'i, 't>(
252        context: &ParserContext,
253        input: &mut Parser<'i, 't>,
254    ) -> Result<Self, ParseError<'i>> {
255        Ok(NonNegative(Percentage::parse_non_negative(context, input)?))
256    }
257}
258
259impl NonNegativePercentage {
260    /// Convert to ComputedPercentage, for FontFaceRule size-adjust getter.
261    #[inline]
262    pub fn compute(&self) -> ComputedPercentage {
263        ComputedPercentage(self.0.get())
264    }
265}