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