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(_) => unreachable!(),
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 int_value = if (self.0 * 100.0).fract() == 0.0 {
46 Some(self.0 as i32)
47 } else {
48 None
49 };
50 let percent = Token::Percentage {
51 has_sign: self.0 < 0.0,
52 unit_value: self.0,
53 int_value,
54 };
55 if self.0 != 0.0 && self.0.abs() < 0.01 {
56 let mut s = String::new();
57 percent.to_css(&mut s)?;
58 if self.0 < 0.0 {
59 dest.write_char('-')?;
60 dest.write_str(s.trim_start_matches("-0"))
61 } else {
62 dest.write_str(s.trim_start_matches('0'))
63 }
64 } else {
65 percent.to_css(dest)?;
66 Ok(())
67 }
68 }
69}
70
71impl std::convert::Into<Calc<Percentage>> for Percentage {
72 fn into(self) -> Calc<Percentage> {
73 Calc::Value(Box::new(self))
74 }
75}
76
77impl std::convert::From<Calc<Percentage>> for Percentage {
78 fn from(calc: Calc<Percentage>) -> Percentage {
79 match calc {
80 Calc::Value(v) => *v,
81 _ => unreachable!(),
82 }
83 }
84}
85
86impl std::ops::Mul<CSSNumber> for Percentage {
87 type Output = Self;
88
89 fn mul(self, other: CSSNumber) -> Percentage {
90 Percentage(self.0 * other)
91 }
92}
93
94impl AddInternal for Percentage {
95 fn add(self, other: Self) -> Self {
96 self + other
97 }
98}
99
100impl std::cmp::PartialOrd<Percentage> for Percentage {
101 fn partial_cmp(&self, other: &Percentage) -> Option<std::cmp::Ordering> {
102 self.0.partial_cmp(&other.0)
103 }
104}
105
106impl Op for Percentage {
107 fn op<F: FnOnce(f32, f32) -> f32>(&self, to: &Self, op: F) -> Self {
108 Percentage(op(self.0, to.0))
109 }
110
111 fn op_to<T, F: FnOnce(f32, f32) -> T>(&self, rhs: &Self, op: F) -> T {
112 op(self.0, rhs.0)
113 }
114}
115
116impl TryMap for Percentage {
117 fn try_map<F: FnOnce(f32) -> f32>(&self, _: F) -> Option<Self> {
118 None
122 }
123}
124
125impl Zero for Percentage {
126 fn zero() -> Self {
127 Percentage(0.0)
128 }
129
130 fn is_zero(&self) -> bool {
131 self.0.is_zero()
132 }
133}
134
135impl Sign for Percentage {
136 fn sign(&self) -> f32 {
137 self.0.sign()
138 }
139}
140
141impl_op!(Percentage, std::ops::Rem, rem);
142impl_op!(Percentage, std::ops::Add, add);
143
144impl_try_from_angle!(Percentage);
145
146#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
148#[cfg_attr(feature = "visitor", derive(Visit))]
149#[cfg_attr(
150 feature = "serde",
151 derive(serde::Serialize, serde::Deserialize),
152 serde(tag = "type", content = "value", rename_all = "kebab-case")
153)]
154#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
155#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
156pub enum NumberOrPercentage {
157 Number(CSSNumber),
159 Percentage(Percentage),
161}
162
163impl std::convert::Into<CSSNumber> for &NumberOrPercentage {
164 fn into(self) -> CSSNumber {
165 match self {
166 NumberOrPercentage::Number(a) => *a,
167 NumberOrPercentage::Percentage(a) => a.0,
168 }
169 }
170}
171
172#[derive(Debug, Clone, PartialEq)]
177#[cfg_attr(feature = "visitor", derive(Visit))]
178#[cfg_attr(
179 feature = "serde",
180 derive(serde::Serialize, serde::Deserialize),
181 serde(tag = "type", content = "value", rename_all = "kebab-case")
182)]
183#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
184#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
185pub enum DimensionPercentage<D> {
186 Dimension(D),
188 Percentage(Percentage),
190 #[cfg_attr(feature = "visitor", skip_type)]
192 Calc(Box<Calc<DimensionPercentage<D>>>),
193}
194
195impl<
196 'i,
197 D: Parse<'i>
198 + std::ops::Mul<CSSNumber, Output = D>
199 + TryAdd<D>
200 + Clone
201 + TryOp
202 + TryMap
203 + Zero
204 + TrySign
205 + TryFrom<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)))
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<D: Zero> Zero for DimensionPercentage<D> {
438 fn zero() -> Self {
439 DimensionPercentage::Dimension(D::zero())
440 }
441
442 fn is_zero(&self) -> bool {
443 match self {
444 DimensionPercentage::Dimension(d) => d.is_zero(),
445 DimensionPercentage::Percentage(p) => p.is_zero(),
446 _ => false,
447 }
448 }
449}
450
451impl<D: TrySign> TrySign for DimensionPercentage<D> {
452 fn try_sign(&self) -> Option<f32> {
453 match self {
454 DimensionPercentage::Dimension(d) => d.try_sign(),
455 DimensionPercentage::Percentage(p) => p.try_sign(),
456 DimensionPercentage::Calc(c) => c.try_sign(),
457 }
458 }
459}
460
461impl<D: ToCss + std::ops::Mul<CSSNumber, Output = D> + TrySign + Clone + std::fmt::Debug> ToCss
462 for DimensionPercentage<D>
463{
464 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
465 where
466 W: std::fmt::Write,
467 {
468 match self {
469 DimensionPercentage::Dimension(length) => length.to_css(dest),
470 DimensionPercentage::Percentage(percent) => percent.to_css(dest),
471 DimensionPercentage::Calc(calc) => calc.to_css(dest),
472 }
473 }
474}