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;
13use crate::values::specified::calc::{CalcNode, CalcNumeric, 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, false, 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.clone())
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
101#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
103pub struct Percentage(NumericUnion<(), f32, CalcNumeric>);
104
105impl ToCss for Percentage {
106 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
107 where
108 W: Write,
109 {
110 match self.0.unpack() {
111 Unpacked::Inline((), p) => NoCalcPercentage(p).to_css(dest),
112 Unpacked::Boxed(calc) => calc.to_css(dest),
113 }
114 }
115}
116
117impl ToTyped for Percentage {
118 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
119 match self.0.unpack() {
120 Unpacked::Inline((), p) => NoCalcPercentage(p).to_typed(dest),
121 Unpacked::Boxed(calc) => calc.to_typed(dest),
122 }
123 }
124}
125
126impl Percentage {
127 pub fn new(value: CSSFloat) -> Self {
129 Self(NumericUnion::inline((), value))
130 }
131
132 #[inline]
134 pub fn new_calc(val: Box<CalcNumeric>) -> Self {
135 Self(NumericUnion::boxed(val))
136 }
137
138 #[inline]
140 pub fn zero() -> Self {
141 Self::new(0.)
142 }
143
144 #[inline]
146 pub fn hundred() -> Self {
147 Self::new(1.)
148 }
149
150 #[inline]
154 pub fn get(&self) -> Option<f32> {
155 match self.0.unpack() {
156 Unpacked::Inline((), f) => Some(f),
157 Unpacked::Boxed(..) => None,
158 }
159 }
160
161 #[inline]
165 pub fn resolve(&self) -> Option<CSSFloat> {
166 match self.0.unpack() {
167 Unpacked::Inline((), f) => Some(f),
168 Unpacked::Boxed(calc) => calc.as_percentage().map(|p| p.get()),
169 }
170 }
171
172 pub fn to_number(&self) -> Option<Number> {
174 Some(match self.0.unpack() {
175 Unpacked::Inline((), p) => Number::new(p),
176 Unpacked::Boxed(ref calc) => {
177 let p = calc.as_percentage()?.get();
178 Number::new_calc(Box::new(
179 calc.with_leaf_node(Leaf::Number(NoCalcNumber::new(p))),
180 ))
181 },
182 })
183 }
184
185 pub fn to_length_percentage(self) -> LengthPercentage {
187 match self.0.extract() {
188 Extracted::Inline((), p) => LengthPercentage::Percentage(NoCalcPercentage(p)),
189 Extracted::Boxed(calc) => LengthPercentage::Calc(Box::new(CalcLengthPercentage(*calc))),
190 }
191 }
192
193 pub fn reverse(&mut self) {
197 match self.0.unpack_mut() {
198 UnpackedMut::Inline(_, p) => {
199 *p = 1. - *p;
200 },
201 UnpackedMut::Boxed(calc) => {
202 let mut sum = smallvec::SmallVec::<[CalcNode; 2]>::new();
203 sum.push(CalcNode::Leaf(
204 Leaf::Percentage(NoCalcPercentage::hundred()),
205 ));
206 let mut node = calc.node.clone();
207 node.negate();
208 sum.push(node);
209 let mut diff = CalcNode::Sum(sum.into_boxed_slice().into());
210 diff.simplify_and_sort();
211 calc.node = diff;
212 },
213 }
214 }
215
216 pub fn parse_with_clamping_mode<'i, 't>(
218 context: &ParserContext,
219 input: &mut Parser<'i, 't>,
220 num_context: AllowedNumericType,
221 ) -> Result<Self, ParseError<'i>> {
222 let location = input.current_source_location();
223 Ok(Self(match *input.next()? {
224 Token::Percentage { unit_value, .. }
225 if num_context.is_ok(context.parsing_mode, unit_value) =>
226 {
227 NumericUnion::inline((), unit_value)
228 },
229 Token::Function(ref name) => {
230 let function = CalcNode::math_function(context, name, location)?;
231 let calc = CalcNode::parse_percentage(context, input, num_context, function)?;
232 NumericUnion::boxed(Box::new(calc))
233 },
234 ref t => return Err(location.new_unexpected_token_error(t.clone())),
235 }))
236 }
237
238 pub fn parse_non_negative<'i, 't>(
240 context: &ParserContext,
241 input: &mut Parser<'i, 't>,
242 ) -> Result<Self, ParseError<'i>> {
243 Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
244 }
245
246 pub fn parse_zero_to_a_hundred<'i, 't>(
249 context: &ParserContext,
250 input: &mut Parser<'i, 't>,
251 ) -> Result<Self, ParseError<'i>> {
252 Self::parse_with_clamping_mode(context, input, AllowedNumericType::ZeroToOne)
253 }
254
255 #[inline]
257 pub fn clamp_to_hundred(&mut self) {
258 match self.0.unpack_mut() {
259 UnpackedMut::Inline((), p) => *p = p.min(1.),
260 UnpackedMut::Boxed(calc) => {
261 calc.clamping_mode = AllowedNumericType::ZeroToOne;
262 },
263 }
264 }
265}
266
267impl Parse for Percentage {
268 #[inline]
269 fn parse<'i, 't>(
270 context: &ParserContext,
271 input: &mut Parser<'i, 't>,
272 ) -> Result<Self, ParseError<'i>> {
273 Self::parse_with_clamping_mode(context, input, AllowedNumericType::All)
274 }
275}
276
277impl ToComputedValue for Percentage {
278 type ComputedValue = ComputedPercentage;
279
280 #[inline]
281 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
282 match self.0.unpack() {
283 Unpacked::Inline((), p) => NoCalcPercentage(p).to_computed_value(context),
284 Unpacked::Boxed(ref calc) => {
285 let value = calc.resolve(context, |result| match result {
286 Ok(Leaf::Percentage(p)) => p.get(),
287 _ => {
288 debug_assert!(
289 false,
290 "Unexpected Percentage::Calc without resolved percentage"
291 );
292 f32::NAN
293 },
294 });
295 ComputedPercentage(crate::values::normalize(value).min(f32::MAX).max(f32::MIN))
296 },
297 }
298 }
299
300 #[inline]
301 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
302 Percentage::new(computed.0)
303 }
304}
305
306impl SpecifiedValueInfo for Percentage {}
307
308pub trait ToPercentage {
310 fn is_calc(&self) -> bool {
312 false
313 }
314 fn to_percentage(&self) -> Option<CSSFloat>;
317}
318
319impl ToPercentage for Percentage {
320 fn is_calc(&self) -> bool {
321 self.0.is_boxed()
322 }
323
324 fn to_percentage(&self) -> Option<CSSFloat> {
325 self.resolve()
326 }
327}
328
329pub type NonNegativePercentage = NonNegative<Percentage>;
331
332impl Parse for NonNegativePercentage {
333 #[inline]
334 fn parse<'i, 't>(
335 context: &ParserContext,
336 input: &mut Parser<'i, 't>,
337 ) -> Result<Self, ParseError<'i>> {
338 Ok(NonNegative(Percentage::parse_non_negative(context, input)?))
339 }
340}
341
342impl NonNegativePercentage {
343 #[inline]
346 pub fn compute(&self) -> Option<ComputedPercentage> {
347 self.0
348 .resolve()
349 .map(|f| AllowedNumericType::NonNegative.clamp(f))
350 .map(ComputedPercentage)
351 }
352}