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