1use 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#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
23pub struct Percentage {
24 value: CSSFloat,
28 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 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 pub fn new(value: CSSFloat) -> Self {
71 Self::new_with_clamping_mode(value, None)
72 }
73
74 #[inline]
76 pub fn zero() -> Self {
77 Percentage {
78 value: 0.,
79 calc_clamping_mode: None,
80 }
81 }
82
83 #[inline]
85 pub fn hundred() -> Self {
86 Percentage {
87 value: 1.,
88 calc_clamping_mode: None,
89 }
90 }
91
92 pub fn get(&self) -> CSSFloat {
94 self.calc_clamping_mode
95 .map_or(self.value, |mode| mode.clamp(self.value))
96 }
97
98 pub fn unit(&self) -> &'static str {
100 "percent"
101 }
102
103 pub fn canonical_unit(&self) -> Option<&'static str> {
105 None
106 }
107
108 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 pub fn to_number(&self) -> Number {
122 Number::new_with_clamping_mode(self.value, self.calc_clamping_mode)
123 }
124
125 pub fn calc_clamping_mode(&self) -> Option<AllowedNumericType> {
127 self.calc_clamping_mode
128 }
129
130 pub fn reverse(&mut self) {
134 let new_value = 1. - self.value;
135 self.value = new_value;
136 }
137
138 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 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 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 #[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
216pub trait ToPercentage {
218 fn is_calc(&self) -> bool {
220 false
221 }
222 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
236pub 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 #[inline]
252 pub fn compute(&self) -> ComputedPercentage {
253 ComputedPercentage(self.0.get())
254 }
255}