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