css_ast/types/
shadow.rs

1use crate::{Color, Length, diagnostics};
2use css_parse::{Cursor, Parse, Parser, Peek, Result as ParserResult, T, ToSpan};
3use csskit_derives::{ToCursors, ToSpan, Visitable};
4
5// https://drafts.csswg.org/css-backgrounds-3/#typedef-shadow
6// <shadow> = <color>? && [<length>{2} <length [0,∞]>? <length>?] && inset?
7#[derive(ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
9#[visit]
10pub struct Shadow {
11	pub color: Option<Color>,
12	pub offset: (Length, Length),
13	pub blur_radius: Option<Length>,
14	pub spread_radius: Option<Length>,
15	#[visit(skip)]
16	pub inset: Option<T![Ident]>,
17}
18
19impl<'a> Peek<'a> for Shadow {
20	fn peek(p: &Parser<'a>, c: Cursor) -> bool {
21		Color::peek(p, c) || Length::peek(p, c)
22	}
23}
24
25impl<'a> Parse<'a> for Shadow {
26	fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
27		let color = p.parse_if_peek::<Color>()?;
28
29		let x = p.parse::<Length>()?;
30		let y = p.parse::<Length>()?;
31
32		let blur_radius = p.parse_if_peek::<Length>()?;
33		if let Some(blur) = blur_radius {
34			if 0.0f32 > blur.into() {
35				Err(diagnostics::NumberTooSmall(0.0f32, blur.to_span()))?
36			}
37		}
38
39		let spread_radius = p.parse_if_peek::<Length>()?;
40
41		let inset = p.parse_if_peek::<T![Ident]>()?;
42		if let Some(ident) = inset {
43			if !p.eq_ignore_ascii_case(ident.into(), "inset") {
44				let c: Cursor = x.into();
45				let source_cursor = p.to_source_cursor(c);
46				Err(diagnostics::UnexpectedIdent(source_cursor.to_string(), c))?
47			}
48		}
49
50		Ok(Self { color, offset: (x, y), blur_radius, spread_radius, inset })
51	}
52}
53
54#[cfg(test)]
55mod tests {
56	use super::*;
57	use crate::assert_visits;
58	use css_parse::{assert_parse, assert_parse_error};
59
60	#[test]
61	fn size_test() {
62		assert_eq!(std::mem::size_of::<Shadow>(), 224);
63	}
64
65	#[test]
66	fn test_writes() {
67		assert_parse!(Shadow, "10px 20px");
68		assert_parse!(Shadow, "10px 20px 5px");
69		assert_parse!(Shadow, "10px 20px 5px 3px");
70		assert_parse!(Shadow, "red 10px 20px");
71		assert_parse!(Shadow, "#ff0000 10px 20px 5px");
72		assert_parse!(Shadow, "rgba(255,0,0,0.5)10px 20px 5px 3px");
73		assert_parse!(Shadow, "10px 20px inset");
74		assert_parse!(Shadow, "10px 20px 5px inset");
75		assert_parse!(Shadow, "10px 20px 5px 3px inset");
76		assert_parse!(Shadow, "red 10px 20px inset");
77		assert_parse!(Shadow, "blue 10px 20px 5px 3px inset");
78		assert_parse!(Shadow, "-10px -20px");
79		assert_parse!(Shadow, "red -10px -20px 5px");
80		assert_parse!(Shadow, "0 0");
81		assert_parse!(Shadow, "0 0 0");
82		assert_parse!(Shadow, "0 0 0 0");
83		assert_parse!(Shadow, "1em 2em");
84		assert_parse!(Shadow, "1rem 2rem 0.5rem");
85	}
86
87	#[test]
88	fn test_errors() {
89		assert_parse_error!(Shadow, "");
90		assert_parse_error!(Shadow, "10% 20%");
91		assert_parse_error!(Shadow, "10px");
92		assert_parse_error!(Shadow, "red");
93		assert_parse_error!(Shadow, "inset");
94		assert_parse_error!(Shadow, "10px 20px -5px");
95		assert_parse_error!(Shadow, "10px 20px 5px 3px 7px");
96		assert_parse_error!(Shadow, "10px 20px notinset");
97		assert_parse_error!(Shadow, "10px 20px 5px inset 3px");
98		assert_parse_error!(Shadow, "10px 20px 5px 3px inset extra");
99	}
100
101	#[test]
102	fn test_visits() {
103		assert_visits!("10px 20px", Shadow, Length, Length);
104		assert_visits!("red 10px 20px", Shadow, Color, Length, Length);
105		assert_visits!("10px 20px 5px", Shadow, Length, Length, Length);
106		assert_visits!("blue 10px 20px 5px 3px", Shadow, Color, Length, Length, Length, Length);
107	}
108}