1mod cleanup;
14pub mod filter;
15mod mixed;
16mod relative;
17
18pub use crate::convert::cleanup::{cleanup, cleanup_unpositioned};
19pub use crate::convert::filter::filter;
20pub use crate::convert::mixed::{mixed, to_absolute};
21pub use crate::convert::relative::relative;
22use crate::geometry::MakeArcs;
23use crate::math::to_fixed;
24use crate::{command, Path};
25
26bitflags! {
27 #[derive(Debug)]
32 pub struct StyleInfo: usize {
33 const has_marker_mid = 0b0_0001;
35 const maybe_has_stroke = 0b0010;
37 const maybe_has_linecap = 0b100;
40 const is_safe_to_use_z = 0b1000;
43 const has_marker = 0b_0001_0000;
45 }
46}
47
48bitflags! {
49 #[derive(Debug)]
51 pub struct Flags: usize {
52 const remove_useless_flag= 0b0000_0000_0000_0001;
54 const smart_arc_rounding_flag= 0b_0000_0000_0010;
56 const straight_curves_flag = 0b00_0000_0000_0100;
58 const convert_to_q_flag = 0b_0000_0000_0000_1000;
60 const line_shorthands_flag = 0b00_0000_0001_0000;
62 const collapse_repeated_flag = 0b_0000_0010_0000;
64 const curve_smooth_shorthands_flag = 0b0100_0000;
66 const convert_to_z_flag = 0b_0000_0000_1000_0000;
68 const force_absolute_path_flag = 0b001_0000_0000;
70 const negative_extra_space_flag = 0b10_0000_0000;
72 const utilize_absolute_flag = 0b0_0100_0000_0000;
74 }
75}
76
77#[cfg_attr(feature = "napi", napi)]
78#[derive(Debug, Copy, Clone, Default)]
79pub enum Precision {
81 #[default]
83 None,
84 Disabled,
88 Enabled(i32),
90}
91
92#[derive(Debug, Default)]
93pub struct Options {
95 pub flags: Flags,
97 pub make_arcs: MakeArcs,
99 pub precision: Precision,
101}
102
103pub fn run(path: &mut Path, options: &Options, style_info: &StyleInfo) {
125 let includes_vertices = path
126 .0
127 .iter()
128 .any(|c| !matches!(c, command::Data::MoveBy(_) | command::Data::MoveTo(_)));
129 log::debug!("convert::run: converting path: {path}");
132 let mut positioned_path = relative(std::mem::take(path));
133 let mut state = filter::State::new(&positioned_path, options, style_info);
134 positioned_path = filter(positioned_path, options, &mut state, style_info);
135 if options.flags.utilize_absolute() {
136 positioned_path = mixed(positioned_path, options);
137 }
138 positioned_path = cleanup(positioned_path);
139 for command in &mut positioned_path.0 {
140 if command.command.is_by() {
141 options.round_data(command.command.args_mut(), options.error());
142 } else {
143 options.round_absolute_command_data(
144 command.command.args_mut(),
145 options.error(),
146 &command.start.0,
147 );
148 }
149 }
150
151 *path = positioned_path.take();
152 let has_marker = style_info.contains(StyleInfo::has_marker);
153 let is_markers_only_path = has_marker
154 && includes_vertices
155 && path
156 .0
157 .iter()
158 .all(|c| matches!(c, command::Data::MoveBy(_) | command::Data::MoveTo(_)));
159 if is_markers_only_path {
160 path.0.push(command::Data::ClosePath);
161 }
162 log::debug!("convert::run: done: {path}");
163}
164
165impl StyleInfo {
166 pub fn conservative() -> Self {
170 let mut result = Self::all();
171 result.set(Self::is_safe_to_use_z, false);
172 result
173 }
174}
175
176impl Default for StyleInfo {
177 fn default() -> Self {
178 Self::empty()
179 }
180}
181
182impl Flags {
183 fn remove_useless(&self) -> bool {
184 self.contains(Self::remove_useless_flag)
185 }
186
187 fn smart_arc_rounding(&self) -> bool {
188 self.contains(Self::smart_arc_rounding_flag)
189 }
190
191 fn straight_curves(&self) -> bool {
192 self.contains(Self::straight_curves_flag)
193 }
194
195 fn convert_to_q(&self) -> bool {
196 self.contains(Self::convert_to_q_flag)
197 }
198
199 fn line_shorthands(&self) -> bool {
200 self.contains(Self::line_shorthands_flag)
201 }
202
203 fn collapse_repeated(&self) -> bool {
204 self.contains(Self::collapse_repeated_flag)
205 }
206
207 fn curve_smooth_shorthands(&self) -> bool {
208 self.contains(Self::curve_smooth_shorthands_flag)
209 }
210
211 fn convert_to_z(&self) -> bool {
212 self.contains(Self::convert_to_z_flag)
213 }
214
215 fn force_absolute_path(&self) -> bool {
216 self.contains(Self::force_absolute_path_flag)
217 }
218
219 fn negative_extra_space(&self) -> bool {
220 self.contains(Self::negative_extra_space_flag)
221 }
222
223 fn utilize_absolute(&self) -> bool {
224 self.contains(Self::utilize_absolute_flag)
225 }
226}
227
228impl Default for Flags {
229 fn default() -> Self {
230 let mut flags = Self::all();
231 flags.set(Self::force_absolute_path_flag, false);
232 flags
233 }
234}
235
236impl Options {
237 pub fn error(&self) -> f64 {
239 match self.precision.inner() {
240 Some(precision) => {
241 let trunc_by = f64::powi(10.0, precision);
242 f64::trunc(f64::powi(0.1, precision) * trunc_by) / trunc_by
243 }
244 None => 1e-2,
245 }
246 }
247
248 pub fn round(&self, data: f64, error: f64) -> f64 {
250 let precision = self.precision.unwrap_or(0);
251 if precision > 0 && precision < 20 {
252 let fixed = to_fixed(data, precision);
253 if (fixed - data).abs() == 0.0 {
254 return data;
255 }
256 let rounded = to_fixed(data, precision - 1);
257 if to_fixed((rounded - data).abs(), precision + 1) >= error {
258 fixed
259 } else {
260 rounded
261 }
262 } else {
263 data.round()
264 }
265 }
266
267 pub fn round_data(&self, data: &mut [f64], error: f64) {
269 data.iter_mut().enumerate().for_each(|(i, d)| {
270 let result = self.round(*d, error);
271 if i > 4 && result == 0.0 {
272 return;
274 }
275 *d = result;
276 });
277 }
278
279 pub fn round_absolute_command_data(&self, data: &mut [f64], error: f64, start: &[f64; 2]) {
281 data.iter_mut().enumerate().for_each(|(i, d)| {
282 let result = self.round(*d, error);
283 if (i == 5 && result == start[0]) || (i == 6 && result == start[1]) {
284 return;
286 }
287 *d = result;
288 });
289 }
290
291 pub fn round_path(&self, path: &mut Path, error: f64) {
293 path.0
294 .iter_mut()
295 .for_each(|c| self.round_data(c.args_mut(), error));
296 }
297
298 pub fn conservative() -> Self {
300 Self {
301 flags: Flags::default(),
302 make_arcs: MakeArcs::default(),
303 precision: Precision::conservative(),
304 }
305 }
306}
307
308impl Precision {
309 fn is_disabled(self) -> bool {
310 matches!(self, Self::Disabled)
311 }
312
313 fn unwrap_or(self, default: i32) -> i32 {
314 match self.inner() {
315 Some(x) => x,
316 None => default,
317 }
318 }
319
320 fn inner(self) -> Option<i32> {
321 match self {
322 Self::Enabled(x) => Some(x),
323 Self::None => Some(3),
324 Self::Disabled => None,
325 }
326 }
327
328 pub fn conservative() -> Self {
330 Self::Enabled(19)
331 }
332}
333
334#[test]
335fn test_convert() {
336 use crate::Path;
337 use oxvg_parse::Parse as _;
338
339 let mut path = Path::parse_string("m 1208.23,1821.01 c 74.07,14.24 196.57,17.09 293.43,-14.24 122.5,-42.74 22.79,-199.42 48.43,-207.97 25.64,-8.55 59.83,108.25 287.73,96.86 230.75,-11.39 256.39,-113.95 287.73,-96.86 31.34,17.09 -31.34,284.88 313.37,222.21 0,0 -361.8,96.86 -344.71,-165.23 0,0 -207.96,159.53 -498.54,17.09 2.85,0 76.92,245 -387.44,148.14").unwrap();
340 run(&mut path, &Options::default(), &StyleInfo::default());
341 assert_eq!(
342 String::from(path),
343 String::from("M1208.23 1821.01c74.07 14.24 196.57 17.09 293.43-14.24 122.5-42.74 22.79-199.42 48.43-207.97s59.83 108.25 287.73 96.86c230.75-11.39 256.39-113.95 287.73-96.86s-31.34 284.88 313.37 222.21c0 0-361.8 96.86-344.71-165.23 0 0-207.96 159.53-498.54 17.09 2.85 0 76.92 245-387.44 148.14")
344 );
345}