1use super::angle::{impl_try_from_angle, Angle};
4use super::calc::{Calc, MathFunction};
5use super::number::CSSNumber;
6use crate::error::{ParserError, PrinterError};
7use crate::printer::Printer;
8use crate::traits::private::AddInternal;
9use crate::traits::{impl_op, private::TryAdd, Op, Parse, Sign, ToCss, TryMap, TryOp, TrySign, Zero};
10#[cfg(feature = "visitor")]
11use crate::visitor::Visit;
12use cssparser::*;
13
14#[derive(Debug, Clone, PartialEq)]
19#[cfg_attr(feature = "visitor", derive(Visit))]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(transparent))]
21#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
22#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
23pub struct Percentage(pub CSSNumber);
24
25impl<'i> Parse<'i> for Percentage {
26 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
27 match input.try_parse(Calc::parse) {
28 Ok(Calc::Value(v)) => return Ok(*v),
29 Ok(_) => return Err(input.new_custom_error(ParserError::InvalidValue)),
31 _ => {}
32 }
33
34 let percent = input.expect_percentage()?;
35 Ok(Percentage(percent))
36 }
37}
38
39impl ToCss for Percentage {
40 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
41 where
42 W: std::fmt::Write,
43 {
44 use cssparser::ToCss;
45 let v = self.0 * 100.0;
46 let int_value = if (v).fract() == 0.0 { Some(v as i32) } else { None };
47 let percent = Token::Percentage {
48 has_sign: self.0 < 0.0,
49 unit_value: self.0,
50 int_value,
51 };
52 if self.0 != 0.0 && self.0.abs() < 0.01 {
53 let mut s = String::new();
54 percent.to_css(&mut s)?;
55 if self.0 < 0.0 {
56 dest.write_char('-')?;
57 dest.write_str(s.trim_start_matches("-0"))
58 } else {
59 dest.write_str(s.trim_start_matches('0'))
60 }
61 } else {
62 percent.to_css(dest)?;
63 Ok(())
64 }
65 }
66}
67
68impl std::convert::Into<Calc<Percentage>> for Percentage {
69 fn into(self) -> Calc<Percentage> {
70 Calc::Value(Box::new(self))
71 }
72}
73
74impl std::convert::TryFrom<Calc<Percentage>> for Percentage {
75 type Error = ();
76
77 fn try_from(calc: Calc<Percentage>) -> Result<Percentage, Self::Error> {
78 match calc {
79 Calc::Value(v) => Ok(*v),
80 _ => Err(()),
81 }
82 }
83}
84
85impl std::ops::Mul<CSSNumber> for Percentage {
86 type Output = Self;
87
88 fn mul(self, other: CSSNumber) -> Percentage {
89 Percentage(self.0 * other)
90 }
91}
92
93impl AddInternal for Percentage {
94 fn add(self, other: Self) -> Self {
95 self + other
96 }
97}
98
99impl std::cmp::PartialOrd<Percentage> for Percentage {
100 fn partial_cmp(&self, other: &Percentage) -> Option<std::cmp::Ordering> {
101 self.0.partial_cmp(&other.0)
102 }
103}
104
105impl Op for Percentage {
106 fn op<F: FnOnce(f32, f32) -> f32>(&self, to: &Self, op: F) -> Self {
107 Percentage(op(self.0, to.0))
108 }
109
110 fn op_to<T, F: FnOnce(f32, f32) -> T>(&self, rhs: &Self, op: F) -> T {
111 op(self.0, rhs.0)
112 }
113}
114
115impl TryMap for Percentage {
116 fn try_map<F: FnOnce(f32) -> f32>(&self, _: F) -> Option<Self> {
117 None
121 }
122}
123
124impl Zero for Percentage {
125 fn zero() -> Self {
126 Percentage(0.0)
127 }
128
129 fn is_zero(&self) -> bool {
130 self.0.is_zero()
131 }
132}
133
134impl Sign for Percentage {
135 fn sign(&self) -> f32 {
136 self.0.sign()
137 }
138}
139
140impl_op!(Percentage, std::ops::Rem, rem);
141impl_op!(Percentage, std::ops::Add, add);
142
143impl_try_from_angle!(Percentage);
144
145#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
147#[cfg_attr(feature = "visitor", derive(Visit))]
148#[cfg_attr(
149 feature = "serde",
150 derive(serde::Serialize, serde::Deserialize),
151 serde(tag = "type", content = "value", rename_all = "kebab-case")
152)]
153#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
154#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
155pub enum NumberOrPercentage {
156 Number(CSSNumber),
158 Percentage(Percentage),
160}
161
162impl std::convert::Into<CSSNumber> for &NumberOrPercentage {
163 fn into(self) -> CSSNumber {
164 match self {
165 NumberOrPercentage::Number(a) => *a,
166 NumberOrPercentage::Percentage(a) => a.0,
167 }
168 }
169}
170
171#[derive(Debug, Clone, PartialEq)]
176#[cfg_attr(feature = "visitor", derive(Visit))]
177#[cfg_attr(
178 feature = "serde",
179 derive(serde::Serialize, serde::Deserialize),
180 serde(tag = "type", content = "value", rename_all = "kebab-case")
181)]
182#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
183#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
184pub enum DimensionPercentage<D> {
185 Dimension(D),
187 Percentage(Percentage),
189 #[cfg_attr(feature = "visitor", skip_type)]
191 Calc(Box<Calc<DimensionPercentage<D>>>),
192}
193
194impl<
195 'i,
196 D: Parse<'i>
197 + std::ops::Mul<CSSNumber, Output = D>
198 + TryAdd<D>
199 + Clone
200 + TryOp
201 + TryMap
202 + Zero
203 + TrySign
204 + TryFrom<Angle>
205 + TryInto<Angle>
206 + PartialOrd<D>
207 + std::fmt::Debug,
208 > Parse<'i> for DimensionPercentage<D>
209{
210 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
211 match input.try_parse(Calc::parse) {
212 Ok(Calc::Value(v)) => return Ok(*v),
213 Ok(calc) => return Ok(DimensionPercentage::Calc(Box::new(calc))),
214 _ => {}
215 }
216
217 if let Ok(length) = input.try_parse(|input| D::parse(input)) {
218 return Ok(DimensionPercentage::Dimension(length));
219 }
220
221 if let Ok(percent) = input.try_parse(|input| Percentage::parse(input)) {
222 return Ok(DimensionPercentage::Percentage(percent));
223 }
224
225 Err(input.new_error_for_next_token())
226 }
227}
228
229impl<D: std::ops::Mul<CSSNumber, Output = D>> std::ops::Mul<CSSNumber> for DimensionPercentage<D> {
230 type Output = Self;
231
232 fn mul(self, other: CSSNumber) -> DimensionPercentage<D> {
233 match self {
234 DimensionPercentage::Dimension(l) => DimensionPercentage::Dimension(l * other),
235 DimensionPercentage::Percentage(p) => DimensionPercentage::Percentage(Percentage(p.0 * other)),
236 DimensionPercentage::Calc(c) => DimensionPercentage::Calc(Box::new(*c * other)),
237 }
238 }
239}
240
241impl<D: TryAdd<D> + Clone + Zero + TrySign + std::fmt::Debug> std::ops::Add<DimensionPercentage<D>>
242 for DimensionPercentage<D>
243{
244 type Output = DimensionPercentage<D>;
245
246 fn add(self, other: DimensionPercentage<D>) -> DimensionPercentage<D> {
247 let a = unwrap_calc(self);
250 let b = unwrap_calc(other);
251 let res = AddInternal::add(a, b);
252 match res {
253 DimensionPercentage::Calc(c) => match *c {
254 Calc::Value(l) => *l,
255 Calc::Function(f) if !matches!(*f, MathFunction::Calc(_)) => {
256 DimensionPercentage::Calc(Box::new(Calc::Function(f)))
257 }
258 c => DimensionPercentage::Calc(Box::new(Calc::Function(Box::new(MathFunction::Calc(c))))),
259 },
260 _ => res,
261 }
262 }
263}
264
265fn unwrap_calc<D>(v: DimensionPercentage<D>) -> DimensionPercentage<D> {
266 match v {
267 DimensionPercentage::Calc(c) => match *c {
268 Calc::Function(f) => match *f {
269 MathFunction::Calc(c) => DimensionPercentage::Calc(Box::new(c)),
270 c => DimensionPercentage::Calc(Box::new(Calc::Function(Box::new(c)))),
271 },
272 _ => DimensionPercentage::Calc(c),
273 },
274 _ => v,
275 }
276}
277
278impl<D: TryAdd<D> + Clone + Zero + TrySign + std::fmt::Debug> AddInternal for DimensionPercentage<D> {
279 fn add(self, other: Self) -> Self {
280 match self.add_recursive(&other) {
281 Some(r) => r,
282 None => self.add(other),
283 }
284 }
285}
286
287impl<D: TryAdd<D> + Clone + Zero + TrySign + std::fmt::Debug> DimensionPercentage<D> {
288 fn add_recursive(&self, other: &DimensionPercentage<D>) -> Option<DimensionPercentage<D>> {
289 match (self, other) {
290 (DimensionPercentage::Dimension(a), DimensionPercentage::Dimension(b)) => {
291 if let Some(res) = a.try_add(b) {
292 Some(DimensionPercentage::Dimension(res))
293 } else {
294 None
295 }
296 }
297 (DimensionPercentage::Percentage(a), DimensionPercentage::Percentage(b)) => {
298 Some(DimensionPercentage::Percentage(Percentage(a.0 + b.0)))
299 }
300 (DimensionPercentage::Calc(a), other) => match &**a {
301 Calc::Value(v) => v.add_recursive(other),
302 Calc::Sum(a, b) => {
303 if let Some(res) = DimensionPercentage::Calc(Box::new(*a.clone())).add_recursive(other) {
304 return Some(res.add(DimensionPercentage::from(*b.clone())));
305 }
306
307 if let Some(res) = DimensionPercentage::Calc(Box::new(*b.clone())).add_recursive(other) {
308 return Some(DimensionPercentage::from(*a.clone()).add(res));
309 }
310
311 None
312 }
313 _ => None,
314 },
315 (other, DimensionPercentage::Calc(b)) => match &**b {
316 Calc::Value(v) => other.add_recursive(&*v),
317 Calc::Sum(a, b) => {
318 if let Some(res) = other.add_recursive(&DimensionPercentage::Calc(Box::new(*a.clone()))) {
319 return Some(res.add(DimensionPercentage::from(*b.clone())));
320 }
321
322 if let Some(res) = other.add_recursive(&DimensionPercentage::Calc(Box::new(*b.clone()))) {
323 return Some(DimensionPercentage::from(*a.clone()).add(res));
324 }
325
326 None
327 }
328 _ => None,
329 },
330 _ => None,
331 }
332 }
333
334 fn add(self, other: DimensionPercentage<D>) -> DimensionPercentage<D> {
335 let mut a = self;
336 let mut b = other;
337
338 if a.is_zero() {
339 return b;
340 }
341
342 if b.is_zero() {
343 return a;
344 }
345
346 if a.is_sign_negative() && b.is_sign_positive() {
347 std::mem::swap(&mut a, &mut b);
348 }
349
350 match (a, b) {
351 (DimensionPercentage::Calc(a), DimensionPercentage::Calc(b)) => {
352 DimensionPercentage::Calc(Box::new(a.add(*b).unwrap()))
353 }
354 (DimensionPercentage::Calc(calc), b) => {
355 if let Calc::Value(a) = *calc {
356 a.add(b)
357 } else {
358 DimensionPercentage::Calc(Box::new(Calc::Sum(Box::new((*calc).into()), Box::new(b.into()))))
359 }
360 }
361 (a, DimensionPercentage::Calc(calc)) => {
362 if let Calc::Value(b) = *calc {
363 a.add(*b)
364 } else {
365 DimensionPercentage::Calc(Box::new(Calc::Sum(Box::new(a.into()), Box::new((*calc).into()))))
366 }
367 }
368 (a, b) => DimensionPercentage::Calc(Box::new(Calc::Sum(Box::new(a.into()), Box::new(b.into())))),
369 }
370 }
371}
372
373impl<D> std::convert::Into<Calc<DimensionPercentage<D>>> for DimensionPercentage<D> {
374 fn into(self) -> Calc<DimensionPercentage<D>> {
375 match self {
376 DimensionPercentage::Calc(c) => *c,
377 b => Calc::Value(Box::new(b)),
378 }
379 }
380}
381
382impl<D> std::convert::From<Calc<DimensionPercentage<D>>> for DimensionPercentage<D> {
383 fn from(calc: Calc<DimensionPercentage<D>>) -> DimensionPercentage<D> {
384 DimensionPercentage::Calc(Box::new(calc))
385 }
386}
387
388impl<D: std::cmp::PartialOrd<D>> std::cmp::PartialOrd<DimensionPercentage<D>> for DimensionPercentage<D> {
389 fn partial_cmp(&self, other: &DimensionPercentage<D>) -> Option<std::cmp::Ordering> {
390 match (self, other) {
391 (DimensionPercentage::Dimension(a), DimensionPercentage::Dimension(b)) => a.partial_cmp(b),
392 (DimensionPercentage::Percentage(a), DimensionPercentage::Percentage(b)) => a.partial_cmp(b),
393 _ => None,
394 }
395 }
396}
397
398impl<D: TryOp> TryOp for DimensionPercentage<D> {
399 fn try_op<F: FnOnce(f32, f32) -> f32>(&self, rhs: &Self, op: F) -> Option<Self> {
400 match (self, rhs) {
401 (DimensionPercentage::Dimension(a), DimensionPercentage::Dimension(b)) => {
402 a.try_op(b, op).map(DimensionPercentage::Dimension)
403 }
404 (DimensionPercentage::Percentage(a), DimensionPercentage::Percentage(b)) => {
405 Some(DimensionPercentage::Percentage(Percentage(op(a.0, b.0))))
406 }
407 _ => None,
408 }
409 }
410
411 fn try_op_to<T, F: FnOnce(f32, f32) -> T>(&self, rhs: &Self, op: F) -> Option<T> {
412 match (self, rhs) {
413 (DimensionPercentage::Dimension(a), DimensionPercentage::Dimension(b)) => a.try_op_to(b, op),
414 (DimensionPercentage::Percentage(a), DimensionPercentage::Percentage(b)) => Some(op(a.0, b.0)),
415 _ => None,
416 }
417 }
418}
419
420impl<D: TryMap> TryMap for DimensionPercentage<D> {
421 fn try_map<F: FnOnce(f32) -> f32>(&self, op: F) -> Option<Self> {
422 match self {
423 DimensionPercentage::Dimension(v) => v.try_map(op).map(DimensionPercentage::Dimension),
424 _ => None,
425 }
426 }
427}
428
429impl<E, D: TryFrom<Angle, Error = E>> TryFrom<Angle> for DimensionPercentage<D> {
430 type Error = E;
431
432 fn try_from(value: Angle) -> Result<Self, Self::Error> {
433 Ok(DimensionPercentage::Dimension(D::try_from(value)?))
434 }
435}
436
437impl<E, D: TryInto<Angle, Error = E>> TryInto<Angle> for DimensionPercentage<D> {
438 type Error = ();
439
440 fn try_into(self) -> Result<Angle, Self::Error> {
441 match self {
442 DimensionPercentage::Dimension(d) => d.try_into().map_err(|_| ()),
443 _ => Err(()),
444 }
445 }
446}
447
448impl<D: Zero> Zero for DimensionPercentage<D> {
449 fn zero() -> Self {
450 DimensionPercentage::Dimension(D::zero())
451 }
452
453 fn is_zero(&self) -> bool {
454 match self {
455 DimensionPercentage::Dimension(d) => d.is_zero(),
456 DimensionPercentage::Percentage(p) => p.is_zero(),
457 _ => false,
458 }
459 }
460}
461
462impl<D: TrySign> TrySign for DimensionPercentage<D> {
463 fn try_sign(&self) -> Option<f32> {
464 match self {
465 DimensionPercentage::Dimension(d) => d.try_sign(),
466 DimensionPercentage::Percentage(p) => p.try_sign(),
467 DimensionPercentage::Calc(c) => c.try_sign(),
468 }
469 }
470}
471
472impl<D: ToCss + std::ops::Mul<CSSNumber, Output = D> + TrySign + Clone + std::fmt::Debug> ToCss
473 for DimensionPercentage<D>
474{
475 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
476 where
477 W: std::fmt::Write,
478 {
479 match self {
480 DimensionPercentage::Dimension(length) => length.to_css(dest),
481 DimensionPercentage::Percentage(percent) => percent.to_css(dest),
482 DimensionPercentage::Calc(calc) => calc.to_css(dest),
483 }
484 }
485}