1use crate::derives::*;
8use crate::parser::{Parse, ParserContext};
9use crate::typed_om::{ToTyped, TypedValue};
10use crate::values::computed::percentage::Percentage as ComputedPercentage;
11use crate::values::computed::{Context, ToComputedValue};
12use crate::values::generics::{NonNegative, Optional};
13use crate::values::specified::calc::{CalcNode, CalcNumeric, CalcPercentageLeaf, Leaf};
14use crate::values::specified::{CalcLengthPercentage, LengthPercentage, NoCalcNumber, Number};
15use crate::values::tagged_numeric::{Extracted, NumericUnion, Unpacked, UnpackedMut};
16use crate::values::{CSSFloat, normalize, reify_percentage, serialize_percentage};
17use cssparser::{Parser, Token};
18use std::fmt::{self, Write};
19use style_traits::values::specified::AllowedNumericType;
20use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, ToCss};
21use thin_vec::ThinVec;
22
23#[derive(
25 Clone,
26 Copy,
27 Debug,
28 Default,
29 Deserialize,
30 MallocSizeOf,
31 PartialEq,
32 PartialOrd,
33 Serialize,
34 ToAnimatedZero,
35 ToResolvedValue,
36 ToShmem,
37)]
38#[repr(C)]
39pub struct NoCalcPercentage(CSSFloat);
40
41impl SpecifiedValueInfo for NoCalcPercentage {}
42
43impl ToCss for NoCalcPercentage {
44 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
45 where
46 W: Write,
47 {
48 serialize_percentage(self.0, dest)
49 }
50}
51
52impl ToTyped for NoCalcPercentage {
53 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
54 reify_percentage(self.0, dest)
55 }
56}
57
58impl NoCalcPercentage {
59 pub fn new(value: CSSFloat) -> Self {
61 Self(value)
62 }
63
64 #[inline]
66 pub fn zero() -> Self {
67 Self::new(0.)
68 }
69
70 #[inline]
72 pub fn hundred() -> Self {
73 Self::new(1.)
74 }
75
76 #[inline]
78 pub fn get(&self) -> CSSFloat {
79 self.0
80 }
81
82 pub fn unit(&self) -> &'static str {
84 "percent"
85 }
86
87 pub fn canonical_unit(&self) -> Option<&'static str> {
89 None
90 }
91
92 pub fn to(&self, unit: &str) -> Result<Self, ()> {
95 if !unit.eq_ignore_ascii_case("percent") {
96 return Err(());
97 }
98 Ok(*self)
99 }
100}
101
102impl ToComputedValue for NoCalcPercentage {
103 type ComputedValue = ComputedPercentage;
104
105 fn to_computed_value(&self, _: &Context) -> Self::ComputedValue {
106 ComputedPercentage(normalize(self.get()))
107 }
108
109 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
110 Self::new(computed.0)
111 }
112}
113
114#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
116pub struct Percentage(NumericUnion<(), f32, CalcNumeric>);
117
118impl ToCss for Percentage {
119 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
120 where
121 W: Write,
122 {
123 match self.0.unpack() {
124 Unpacked::Inline((), p) => NoCalcPercentage(p).to_css(dest),
125 Unpacked::Boxed(calc) => calc.to_css(dest),
126 }
127 }
128}
129
130impl ToTyped for Percentage {
131 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
132 match self.0.unpack() {
133 Unpacked::Inline((), p) => NoCalcPercentage(p).to_typed(dest),
134 Unpacked::Boxed(calc) => calc.to_typed(dest),
135 }
136 }
137}
138
139impl Percentage {
140 pub fn new(value: CSSFloat) -> Self {
142 Self(NumericUnion::inline((), value))
143 }
144
145 #[inline]
147 pub fn new_calc(val: Box<CalcNumeric>) -> Self {
148 Self(NumericUnion::boxed(val))
149 }
150
151 #[inline]
153 pub fn zero() -> Self {
154 Self::new(0.)
155 }
156
157 #[inline]
159 pub fn hundred() -> Self {
160 Self::new(1.)
161 }
162
163 #[inline]
165 pub fn is_calc(&self) -> bool {
166 self.0.is_boxed()
167 }
168
169 #[inline]
173 pub fn get(&self) -> Option<f32> {
174 match self.0.unpack() {
175 Unpacked::Inline((), f) => Some(f),
176 Unpacked::Boxed(..) => None,
177 }
178 }
179
180 #[inline]
184 pub fn resolve(&self) -> Option<CSSFloat> {
185 match self.0.unpack() {
186 Unpacked::Inline((), f) => Some(f),
187 Unpacked::Boxed(calc) => calc.as_percentage().map(|p| p.get()),
188 }
189 }
190
191 pub fn to_number(&self) -> Option<Number> {
193 Some(match self.0.unpack() {
194 Unpacked::Inline((), p) => Number::new(p),
195 Unpacked::Boxed(calc) => {
196 let p = calc.as_percentage()?.get();
197 Number::new_calc(Box::new(
198 calc.with_leaf_node(Leaf::Number(NoCalcNumber::new(p))),
199 ))
200 },
201 })
202 }
203
204 pub fn to_length_percentage(self) -> LengthPercentage {
206 match self.0.extract() {
207 Extracted::Inline((), p) => LengthPercentage::Percentage(NoCalcPercentage(p)),
208 Extracted::Boxed(calc) => LengthPercentage::Calc(Box::new(CalcLengthPercentage(*calc))),
209 }
210 }
211
212 pub fn reverse(&mut self) {
216 match self.0.unpack_mut() {
217 UnpackedMut::Inline(_, p) => {
218 *p = 1. - *p;
219 },
220 UnpackedMut::Boxed(calc) => {
221 let mut sum = smallvec::SmallVec::<[CalcNode; 2]>::new();
222 sum.push(CalcNode::Leaf(Leaf::Percentage(CalcPercentageLeaf::new(
223 1.,
224 Optional::None,
225 ))));
226 let mut node = calc.node.clone();
227 node.negate();
228 sum.push(node);
229 let mut diff = CalcNode::Sum(sum.into_boxed_slice().into());
230 diff.simplify_and_sort();
231 calc.node = diff;
232 },
233 }
234 }
235
236 pub fn parse_with_clamping_mode(
238 context: &ParserContext,
239 input: &mut Parser,
240 num_context: AllowedNumericType,
241 ) -> Result<Self, ParseError> {
242 Ok(Self(match *input.next()? {
243 Token::Percentage { unit_value, .. }
244 if num_context.is_ok(context.parsing_mode, unit_value) =>
245 {
246 NumericUnion::inline((), unit_value)
247 },
248 Token::Function(ref name) => {
249 let function = CalcNode::math_function(context, name)?;
250 let calc = CalcNode::parse_percentage(context, input, num_context, function)?;
251 NumericUnion::boxed(Box::new(calc))
252 },
253 _ => return Err(ParseError::unexpected_token()),
254 }))
255 }
256
257 pub fn parse_non_negative(
259 context: &ParserContext,
260 input: &mut Parser,
261 ) -> Result<Self, ParseError> {
262 Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
263 }
264
265 pub fn parse_zero_to_a_hundred(
268 context: &ParserContext,
269 input: &mut Parser,
270 ) -> Result<Self, ParseError> {
271 Self::parse_with_clamping_mode(context, input, AllowedNumericType::ZeroToOne)
272 }
273
274 #[inline]
276 pub fn clamp_to_hundred(&mut self) {
277 match self.0.unpack_mut() {
278 UnpackedMut::Inline((), p) => *p = p.min(1.),
279 UnpackedMut::Boxed(calc) => {
280 calc.clamping_mode = AllowedNumericType::ZeroToOne;
281 },
282 }
283 }
284}
285
286impl Parse for Percentage {
287 #[inline]
288 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
289 Self::parse_with_clamping_mode(context, input, AllowedNumericType::All)
290 }
291}
292
293impl ToComputedValue for Percentage {
294 type ComputedValue = ComputedPercentage;
295
296 #[inline]
297 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
298 match self.0.unpack() {
299 Unpacked::Inline((), p) => NoCalcPercentage(p).to_computed_value(context),
300 Unpacked::Boxed(calc) => {
301 let value = calc.resolve(context, |result| match result {
302 Ok(Leaf::Percentage(p)) => p.get(),
303 _ => {
304 debug_assert!(
305 false,
306 "Unexpected Percentage::Calc without resolved percentage"
307 );
308 f32::NAN
309 },
310 });
311 ComputedPercentage(crate::values::normalize(value).min(f32::MAX).max(f32::MIN))
312 },
313 }
314 }
315
316 #[inline]
317 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
318 Percentage::new(computed.0)
319 }
320}
321
322impl SpecifiedValueInfo for Percentage {}
323
324pub trait ToPercentage {
326 fn is_calc(&self) -> bool {
328 false
329 }
330 fn to_percentage(&self) -> Option<CSSFloat>;
333}
334
335impl ToPercentage for Percentage {
336 fn is_calc(&self) -> bool {
337 self.0.is_boxed()
338 }
339
340 fn to_percentage(&self) -> Option<CSSFloat> {
341 self.resolve()
342 }
343}
344
345pub type NonNegativePercentage = NonNegative<Percentage>;
347
348impl Parse for NonNegativePercentage {
349 #[inline]
350 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
351 Ok(NonNegative(Percentage::parse_non_negative(context, input)?))
352 }
353}
354
355impl NonNegativePercentage {
356 #[inline]
359 pub fn compute(&self) -> Option<ComputedPercentage> {
360 self.0
361 .resolve()
362 .map(|f| AllowedNumericType::NonNegative.clamp(f))
363 .map(ComputedPercentage)
364 }
365}