Skip to main content

css_ast/types/
shape_command.rs

1use super::prelude::*;
2use crate::{Angle, CalcableValue, LengthPercentage};
3use css_parse::Box;
4
5/// <https://drafts.csswg.org/css-shapes/#typedef-shape-command>
6///
7/// ```text,ignore
8/// <shape-command> = <move-command> | <line-command> | close |
9///                   <horizontal-line-command> | <vertical-line-command> |
10///                   <curve-command> | <smooth-command> | <arc-command>
11/// ```
12#[syntax(
13	" <move-command> | <line-command> | close | <horizontal-line-command> | <vertical-line-command> | <curve-command> | <smooth-command> | <arc-command> "
14)]
15#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
17#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
18#[derive(csskit_derives::NodeWithMetadata)]
19pub enum ShapeCommand<'a> {}
20
21/// <https://drafts.csswg.org/css-shapes/#typedef-shape-coordinate-pair>
22///
23/// ```text,ignore
24/// <coordinate-pair> = <length-percentage>{2}
25/// ```
26#[syntax(" <length-percentage>{2} ")]
27#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
28#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
29#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
30#[derive(csskit_derives::NodeWithMetadata)]
31pub struct CoordinatePair<'a>;
32
33/// <https://drafts.csswg.org/css-shapes/#typedef-shape-command-end-point>
34///
35/// ```text,ignore
36/// <command-end-point> = [ to <position> | by <coordinate-pair> ]
37/// ```
38#[syntax(" to <position> | by <coordinate-pair> ")]
39#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
40#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
41#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
42#[derive(csskit_derives::NodeWithMetadata)]
43pub enum CommandEndPoint<'a> {}
44
45/// <https://drafts.csswg.org/css-shapes/#typedef-shape-control-point>
46///
47/// ```text,ignore
48/// <control-point> = [ <position> | <relative-control-point> ]
49/// ```
50#[syntax(" <position> | <relative-control-point> ")]
51#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
52#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
53#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
54#[derive(csskit_derives::NodeWithMetadata)]
55pub enum ControlPoint<'a> {}
56
57/// <https://drafts.csswg.org/css-shapes/#typedef-shape-relative-control-point>
58///
59/// ```text,ignore
60/// <relative-control-point> = <coordinate-pair> [ from [ start | end | origin ] ]?
61/// ```
62#[syntax(" <coordinate-pair> [ from [ start | end | origin ] ]? ")]
63#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
64#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
65#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
66#[derive(csskit_derives::NodeWithMetadata)]
67pub struct RelativeControlPoint<'a>;
68
69/// <https://drafts.csswg.org/css-shapes/#typedef-shape-arc-sweep>
70///
71/// ```text,ignore
72/// <arc-sweep> = cw | ccw
73/// ```
74#[syntax(" cw | ccw ")]
75#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
76#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
77#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
78#[derive(csskit_derives::NodeWithMetadata)]
79pub enum ArcSweep<'a> {}
80
81/// <https://drafts.csswg.org/css-shapes/#typedef-shape-arc-size>
82///
83/// ```text,ignore
84/// <arc-size> = large | small
85/// ```
86#[syntax(" large | small ")]
87#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
88#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
89#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
90#[derive(csskit_derives::NodeWithMetadata)]
91pub enum ArcSize<'a> {}
92
93/// <https://drafts.csswg.org/css-shapes/#typedef-shape-move-command>
94///
95/// ```text,ignore
96/// <move-command> = move <command-end-point>
97/// ```
98///
99/// Written by hand (rather than via `#[syntax]`) because a struct whose
100/// entire body is a single leading literal keyword hits a bug in the
101/// `#[syntax]` macro's generated `ToSpan`/`Parse` impls for its synthesized
102/// keyword-marker type.
103#[node]
104#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
105#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
106#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
107#[derive(csskit_derives::NodeWithMetadata)]
108pub struct MoveCommand<'a> {
109	#[atom(CssAtomSet::Move)]
110	pub keyword: T![Ident],
111	pub point: CommandEndPoint<'a>,
112}
113
114/// <https://drafts.csswg.org/css-shapes/#typedef-shape-line-command>
115///
116/// ```text,ignore
117/// <line-command> = line <command-end-point>
118/// ```
119#[node]
120#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
121#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
122#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
123#[derive(csskit_derives::NodeWithMetadata)]
124pub struct LineCommand<'a> {
125	#[atom(CssAtomSet::Line)]
126	pub keyword: T![Ident],
127	pub point: CommandEndPoint<'a>,
128}
129
130/// <https://drafts.csswg.org/css-shapes/#typedef-shape-horizontal-line-command>
131///
132/// ```text,ignore
133/// <horizontal-line-command> = hline
134///         [ to [ <length-percentage> | left | center | right | x-start | x-end ]
135///         | by <length-percentage> ]
136/// ```
137#[syntax(" <length-percentage> | left | center | right | x-start | x-end ")]
138#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
139#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
140#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
141#[derive(csskit_derives::NodeWithMetadata)]
142pub enum HorizontalLineValue<'a> {}
143
144#[syntax(" to <horizontal-line-value> | by <length-percentage> ")]
145#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
146#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
147#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
148#[derive(csskit_derives::NodeWithMetadata)]
149pub enum HorizontalLineClause<'a> {}
150
151#[node]
152#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
153#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
154#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
155#[derive(csskit_derives::NodeWithMetadata)]
156pub struct HorizontalLineCommand<'a> {
157	#[atom(CssAtomSet::Hline)]
158	pub keyword: T![Ident],
159	pub clause: HorizontalLineClause<'a>,
160}
161
162/// <https://drafts.csswg.org/css-shapes/#typedef-shape-vertical-line-command>
163///
164/// ```text,ignore
165/// <vertical-line-command> = vline
166///         [ to [ <length-percentage> | top | center | bottom | y-start | y-end ]
167///         | by <length-percentage> ]
168/// ```
169///
170#[syntax(" <length-percentage> | top | center | bottom | y-start | y-end ")]
171#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
172#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
173#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
174#[derive(csskit_derives::NodeWithMetadata)]
175pub enum VerticalLineValue<'a> {}
176
177#[syntax(" to <vertical-line-value> | by <length-percentage> ")]
178#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
179#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
180#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
181#[derive(csskit_derives::NodeWithMetadata)]
182pub enum VerticalLineClause<'a> {}
183
184#[node]
185#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
186#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
187#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
188#[derive(csskit_derives::NodeWithMetadata)]
189pub struct VerticalLineCommand<'a> {
190	#[atom(CssAtomSet::Vline)]
191	pub keyword: T![Ident],
192	pub clause: VerticalLineClause<'a>,
193}
194
195/// <https://drafts.csswg.org/css-shapes/#typedef-shape-curve-command>
196///
197/// ```text,ignore
198/// <curve-command> = curve
199///         [ [ to <position> with <control-point> [ / <control-point> ]? ]
200///         | [ by <coordinate-pair> with <relative-control-point> [ / <relative-control-point> ]? ] ]
201/// ```
202#[syntax(
203	" to <position> with <control-point> [ / <control-point> ]? | by <coordinate-pair> with <relative-control-point> [ / <relative-control-point> ]? "
204)]
205#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
206#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
207#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
208#[derive(csskit_derives::NodeWithMetadata)]
209pub enum CurveTarget<'a> {}
210
211#[node]
212#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
213#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
214#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
215#[derive(csskit_derives::NodeWithMetadata)]
216pub struct CurveCommand<'a> {
217	#[atom(CssAtomSet::Curve)]
218	pub keyword: T![Ident],
219	pub target: Box<'a, CurveTarget<'a>>,
220}
221
222/// <https://drafts.csswg.org/css-shapes/#typedef-shape-smooth-command>
223///
224/// ```text,ignore
225/// <smooth-command> = smooth
226///         [ [ to <position> [ with <control-point> ]? ]
227///         | [ by <coordinate-pair> [ with <relative-control-point> ]? ] ]
228/// ```
229#[syntax(" to <position> [ with <control-point> ]? | by <coordinate-pair> [ with <relative-control-point> ]? ")]
230#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
231#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
232#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
233#[derive(csskit_derives::NodeWithMetadata)]
234pub enum SmoothTarget<'a> {}
235
236#[node]
237#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
238#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
239#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
240#[derive(csskit_derives::NodeWithMetadata)]
241pub struct SmoothCommand<'a> {
242	#[atom(CssAtomSet::Smooth)]
243	pub keyword: T![Ident],
244	pub target: Box<'a, SmoothTarget<'a>>,
245}
246
247/// <https://drafts.csswg.org/css-shapes/#typedef-shape-arc-command>
248///
249/// ```text,ignore
250/// <arc-command> = arc <command-end-point>
251///             [ [ of <length-percentage>{1,2} ]
252///               && <arc-sweep>? && <arc-size>? && [rotate <angle>]? ]
253/// ```
254#[node]
255#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
256#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
257#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
258#[derive(csskit_derives::NodeWithMetadata)]
259pub struct ArcRadii<'a> {
260	#[atom(CssAtomSet::Of)]
261	pub keyword: T![Ident],
262	pub horizontal: CalcableValue<'a, LengthPercentage>,
263	pub vertical: Option<CalcableValue<'a, LengthPercentage>>,
264}
265
266#[node]
267#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
268#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
269#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
270#[derive(csskit_derives::NodeWithMetadata)]
271pub struct ArcRotate<'a> {
272	#[atom(CssAtomSet::Rotate)]
273	pub keyword: T![Ident],
274	pub angle: CalcableValue<'a, Angle>,
275}
276
277#[node]
278#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
279#[parse(all_must_occur)]
280#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
281#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
282#[derive(csskit_derives::NodeWithMetadata)]
283pub struct ArcCommandParams<'a> {
284	pub radii: ArcRadii<'a>,
285	pub sweep: Option<ArcSweep<'a>>,
286	pub size: Option<ArcSize<'a>>,
287	pub rotate: Option<ArcRotate<'a>>,
288}
289
290#[node]
291#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
292#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
293#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
294#[derive(csskit_derives::NodeWithMetadata)]
295pub struct ArcCommand<'a> {
296	#[atom(CssAtomSet::Arc)]
297	pub keyword: T![Ident],
298	pub point: CommandEndPoint<'a>,
299	pub params: Box<'a, ArcCommandParams<'a>>,
300}
301
302#[cfg(test)]
303mod tests {
304	use super::*;
305	use crate::CssAtomSet;
306	use css_parse::{assert_parse, assert_parse_error, assert_peek_false};
307
308	#[test]
309	fn test_parses() {
310		assert_parse!(CssAtomSet::ATOMS, ShapeCommand, "close");
311		assert_parse!(CssAtomSet::ATOMS, ShapeCommand, "move to 10px 10px");
312		assert_parse!(CssAtomSet::ATOMS, ShapeCommand, "line by 10px 10px");
313		assert_parse!(CssAtomSet::ATOMS, ShapeCommand, "hline to 50%");
314		assert_parse!(CssAtomSet::ATOMS, ShapeCommand, "hline by 10px");
315		assert_parse!(CssAtomSet::ATOMS, ShapeCommand, "vline to bottom");
316		assert_parse!(CssAtomSet::ATOMS, ShapeCommand, "curve to 100% 5% with 100% 0%");
317		assert_parse!(CssAtomSet::ATOMS, ShapeCommand, "curve by 10px 10px with 5px 5px / 8px 8px");
318		assert_parse!(CssAtomSet::ATOMS, ShapeCommand, "smooth to 100% 5%");
319		assert_parse!(CssAtomSet::ATOMS, ShapeCommand, "smooth by 10px 10px with 5px 5px");
320		assert_parse!(CssAtomSet::ATOMS, ShapeCommand, "arc to 10px 10px of 5px 5px");
321		assert_parse!(CssAtomSet::ATOMS, ShapeCommand, "arc to 10px 10px of 5px cw large rotate 10deg");
322		assert_parse!(CssAtomSet::ATOMS, ShapeCommand, "arc to 10px 10px of calc(5px) rotate var(--a)");
323	}
324
325	#[test]
326	fn test_errors() {
327		assert_peek_false!(CssAtomSet::ATOMS, ShapeCommand, "foo");
328		assert_parse_error!(CssAtomSet::ATOMS, ShapeCommand, "move");
329		assert_parse_error!(CssAtomSet::ATOMS, ShapeCommand, "arc to 10px 10px");
330	}
331}