1#![feature(proc_macro_hygiene)]
2extern crate proc_macro;
3use proc_macro::TokenStream;
4use quote::quote;
5
6#[proc_macro]
7pub fn svgpath(input: TokenStream) -> TokenStream {
8 let (ident, path) = get_params(input).expect("syntax error");
9 let mut path: Path = path.parse().expect("parsing the path failed");
10 simplify(&mut path)
11 .map(|segment| match segment {
12 PathSegment::MoveTo { x, y, .. } => quote! { #ident.move_to(#x, #y); },
13 PathSegment::LineTo { x, y, .. } => quote! { #ident.line_to(#x, #y); },
14 PathSegment::CurveTo {
15 x1,
16 y1,
17 x2,
18 y2,
19 x,
20 y,
21 ..
22 } => quote! { #ident.curve_to(#x1, #y1, #x2, #y2, #x, #y); },
23 PathSegment::ClosePath { .. } => quote! { #ident.close_path(); },
24 _ => unreachable!(),
25 })
26 .map(Into::<proc_macro::TokenStream>::into)
27 .collect()
28}
29
30fn get_params(input: TokenStream) -> Option<(proc_macro2::Ident, String)> {
31 let input: proc_macro2::TokenStream = input.into();
32 let mut input = input.into_iter();
33 let ident = input.next()?;
34 match input.next()? {
35 proc_macro2::TokenTree::Punct(punct) => {
36 if punct.as_char() != ',' {
37 return None;
38 }
39 }
40 _ => return None,
41 }
42
43 let ident = match ident {
44 proc_macro2::TokenTree::Ident(ident) => ident,
45 _ => return None,
46 };
47
48 let path = input.next()?;
49 if let Some(_) = input.next() {
51 return None;
52 };
53
54 let path = match path {
55 proc_macro2::TokenTree::Literal(literal) => syn::Lit::new(literal),
56 _ => return None,
57 };
58
59 let path = match path {
60 syn::Lit::Str(string) => string.value(),
61 _ => return None,
62 };
63
64 Some((ident, path))
65}
66
67use svgtypes::{Path, PathSegment};
68
69fn simplify<'a>(path: &'a mut Path) -> impl Iterator<Item = PathSegment> + 'a {
73 path.conv_to_absolute();
74 let mut pmx = 0.0;
76 let mut pmy = 0.0;
77
78 let mut px = 0.0;
80 let mut py = 0.0;
81
82 let mut ptx = 0.0;
84 let mut pty = 0.0;
85
86 let mut previous = None;
87
88 path.iter().flat_map(move |&seg| {
89 let segs = match seg {
90 PathSegment::MoveTo { .. } => vec![seg],
91 PathSegment::LineTo { .. } => vec![seg],
92 PathSegment::HorizontalLineTo { x, .. } => vec![PathSegment::LineTo {
93 x,
94 y: py,
95 abs: true,
96 }],
97 PathSegment::VerticalLineTo { y, .. } => vec![PathSegment::LineTo {
98 x: px,
99 y,
100 abs: true,
101 }],
102 PathSegment::CurveTo { .. } => vec![seg],
103 PathSegment::SmoothCurveTo { x2, y2, x, y, .. } => {
104 let (x1, y1) = if let Some(prev_seg) = previous {
105 match prev_seg {
106 PathSegment::CurveTo { x2, y2, x, y, .. }
107 | PathSegment::SmoothCurveTo { x2, y2, x, y, .. } => {
108 (x * 2.0 - x2, y * 2.0 - y2)
109 }
110 _ => (px, py),
111 }
112 } else {
113 (px, py)
114 };
115
116 vec![PathSegment::CurveTo {
117 x1,
118 y1,
119 x2,
120 y2,
121 x,
122 y,
123 abs: true,
124 }]
125 }
126 PathSegment::Quadratic { x1, y1, x, y, .. } => vec![handle_quadratic(px, py, x1, y1, x, y)],
127 PathSegment::SmoothQuadratic { x, y, .. } => {
128 let (x1, y1) = if let Some(prev_seg) = previous {
129 match prev_seg {
130 PathSegment::Quadratic { x1, y1, x, y, .. } => (x * 2.0 - x1, y * 2.0 - y1),
131 PathSegment::SmoothQuadratic { x, y, .. } => (x * 2.0 - ptx, y * 2.0 - pty),
132 _ => (px, py),
133 }
134 } else {
135 (px, py)
136 };
137
138 ptx = x1;
139 pty = y1;
140
141 vec![handle_quadratic(px, py, x1, y1, x, y)]
142 }
143 PathSegment::EllipticalArc { rx, ry, x_axis_rotation, large_arc, sweep, x, y, .. } => {
144 let arc = lyon_geom::SvgArc {
145 from: [px, py].into(),
146 to: [x, y].into(),
147 radii: [rx, ry].into(),
148 x_rotation: euclid::Angle::degrees(x_axis_rotation),
149 flags: lyon_geom::ArcFlags { large_arc, sweep },
150 };
151
152 let mut curves = vec![];
153 arc.for_each_cubic_bezier(&mut |curve| {
154 curves.push(PathSegment::CurveTo {
155 x1: curve.ctrl1.x,
156 y1: curve.ctrl1.y,
157 x2: curve.ctrl2.x,
158 y2: curve.ctrl2.y,
159 x: curve.to.x,
160 y: curve.to.y,
161 abs: true,
162 });
163 });
164 curves
165 }
166 PathSegment::ClosePath { .. } => {
167 if let Some(PathSegment::ClosePath { .. }) = previous {
168 vec![]
170 } else {
171 vec![seg]
172 }
173 }
174 };
175
176 if let Some(&seg) = segs.last() {
177 match seg {
178 PathSegment::MoveTo { x, y, .. } => {
179 px = x;
180 py = y;
181 pmx = x;
182 pmy = y;
183 }
184 PathSegment::LineTo { x, y, .. }
185 | PathSegment::CurveTo { x, y, .. } => {
186 px = x;
187 py = y;
188 }
189 PathSegment::ClosePath { .. } => {
190 px = pmx;
191 py = pmy;
192 }
193 _ => unreachable!(),
194 }
195 }
196
197 previous = Some(seg);
198 segs.into_iter()
199 })
200}
201
202fn handle_quadratic(px: f64, py: f64, x1: f64, y1: f64, x: f64, y: f64) -> PathSegment {
203 PathSegment::CurveTo {
204 x,
205 y,
206 x1: 2.0 / 3.0 * x1 + 1.0 / 3.0 * px,
207 y1: 2.0 / 3.0 * y1 + 1.0 / 3.0 * py,
208 x2: 2.0 / 3.0 * x1 + 1.0 / 3.0 * x,
209 y2: 2.0 / 3.0 * y1 + 1.0 / 3.0 * y,
210 abs: true,
211 }
212}