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::{
19 CssWriter, MathSum, NumericValue, ParseError, SpecifiedValueInfo, ToCss, ToTyped, TypedValue,
20};
21use thin_vec::ThinVec;
22
23#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
25pub struct Percentage {
26 value: CSSFloat,
30 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 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 pub fn new(value: CSSFloat) -> Self {
81 Self::new_with_clamping_mode(value, None)
82 }
83
84 #[inline]
86 pub fn zero() -> Self {
87 Percentage {
88 value: 0.,
89 calc_clamping_mode: None,
90 }
91 }
92
93 #[inline]
95 pub fn hundred() -> Self {
96 Percentage {
97 value: 1.,
98 calc_clamping_mode: None,
99 }
100 }
101
102 pub fn get(&self) -> CSSFloat {
104 self.calc_clamping_mode
105 .map_or(self.value, |mode| mode.clamp(self.value))
106 }
107
108 pub fn unit(&self) -> &'static str {
110 "percent"
111 }
112
113 pub fn canonical_unit(&self) -> Option<&'static str> {
115 None
116 }
117
118 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 pub fn to_number(&self) -> Number {
132 Number::new_with_clamping_mode(self.value, self.calc_clamping_mode)
133 }
134
135 pub fn calc_clamping_mode(&self) -> Option<AllowedNumericType> {
137 self.calc_clamping_mode
138 }
139
140 pub fn reverse(&mut self) {
144 let new_value = 1. - self.value;
145 self.value = new_value;
146 }
147
148 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 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 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 #[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
226pub trait ToPercentage {
228 fn is_calc(&self) -> bool {
230 false
231 }
232 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
246pub 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 #[inline]
262 pub fn compute(&self) -> ComputedPercentage {
263 ComputedPercentage(self.0.get())
264 }
265}