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