diff --git a/Cargo.toml b/Cargo.toml index 800f3b6..0aeda20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,23 @@ edition = "2024" repository = "https://github.com/sameer/svg2gcode" license = "MIT" +[workspace.lints.clippy] +pedantic = { level = "warn", priority = -1 } + +similar_names = "allow" +doc_markdown = "allow" +must_use_candidate = "allow" +too_many_lines = "allow" +cast_possible_truncation = "allow" +cast_possible_wrap = "allow" +cast_precision_loss = "allow" +cast_sign_loss = "allow" +struct_excessive_bools = "allow" +return_self_not_must_use = "allow" +missing_errors_doc = "allow" +missing_panics_doc = "allow" +items_after_statements = "allow" + [workspace.dependencies] codespan-reporting = "0.13" g-code = "0.6.0" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index bc169b5..7e0272f 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -7,6 +7,9 @@ edition.workspace = true repository.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] svg2gcode = { path = "../g_code", version = "0.4.0", features = ["serde"] } env_logger = "0.11" diff --git a/cli/src/main.rs b/cli/src/main.rs index 6961bca..4284544 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -236,9 +236,9 @@ fn main() -> io::Result<()> { let config_json_bytes = serde_json::to_vec_pretty(&settings)?; if export_path.to_string_lossy() == "-" { return io::stdout().write_all(&config_json_bytes); - } else { - return File::create(export_path)?.write_all(&config_json_bytes); } + + return File::create(export_path)?.write_all(&config_json_bytes); } let options = { @@ -266,20 +266,17 @@ fn main() -> io::Result<()> { ConversionOptions { dimensions } }; - let input = match opt.file { - Some(filename) => { - let mut f = File::open(filename)?; - let len = f.metadata()?.len(); - let mut input = String::with_capacity(len as usize + 1); - f.read_to_string(&mut input)?; - input - } - None => { - info!("Reading from standard input"); - let mut input = String::new(); - io::stdin().read_to_string(&mut input)?; - input - } + let input = if let Some(filename) = opt.file { + let mut f = File::open(filename)?; + let len = f.metadata()?.len(); + let mut input = String::with_capacity(len as usize + 1); + f.read_to_string(&mut input)?; + input + } else { + info!("Reading from standard input"); + let mut input = String::new(); + io::stdin().read_to_string(&mut input)?; + input }; let snippets = [ diff --git a/g_code/Cargo.toml b/g_code/Cargo.toml index 818d84e..326e43f 100644 --- a/g_code/Cargo.toml +++ b/g_code/Cargo.toml @@ -7,6 +7,9 @@ edition.workspace = true repository.workspace = true license.workspace = true +[lints] +workspace = true + [features] default = ["image"] serde = ["dep:serde", "g-code/serde", "svg2star/serde"] diff --git a/g_code/src/config/mod.rs b/g_code/src/config/mod.rs index 0e87da4..728f3a6 100644 --- a/g_code/src/config/mod.rs +++ b/g_code/src/config/mod.rs @@ -124,7 +124,7 @@ pub struct GCodeConfig { impl Default for GCodeConfig { fn default() -> Self { Self { - inner: Default::default(), + inner: ConversionConfig::default(), tolerance: 0.002, feedrate: 300.0, } @@ -146,11 +146,11 @@ pub struct SupportedFunctionality { #[derive(Debug, Default, Clone, PartialEq)] /// Operations performed after G-Code generation. pub struct PostprocessConfig { - /// Convenience field for [g_code::emit::FormatOptions] field + /// Convenience field for [`g_code::emit::FormatOptions`] field pub checksums: bool, - /// Convenience field for [g_code::emit::FormatOptions] field + /// Convenience field for [`g_code::emit::FormatOptions`] field pub line_numbers: bool, - /// Convenience field for [g_code::emit::FormatOptions] field + /// Convenience field for [`g_code::emit::FormatOptions`] field pub newline_before_comment: bool, } diff --git a/g_code/src/machine.rs b/g_code/src/machine.rs index 44c6bb3..ff6f13c 100644 --- a/g_code/src/machine.rs +++ b/g_code/src/machine.rs @@ -51,8 +51,8 @@ impl<'input> Machine<'input> { program_begin_sequence: program_begin_sequence.unwrap_or_else(|| empty_snippet.clone()), program_end_sequence: program_end_sequence.unwrap_or_else(|| empty_snippet.clone()), empty_snippet, - tool_state: Default::default(), - distance_mode: Default::default(), + tool_state: None, + distance_mode: None, } } diff --git a/g_code/src/tests.rs b/g_code/src/tests.rs index effe08c..129befa 100644 --- a/g_code/src/tests.rs +++ b/g_code/src/tests.rs @@ -16,7 +16,7 @@ fn get_actual( circular_interpolation: bool, dimensions: [Option; 2], ) -> Vec> { - let gcode_config = Default::default(); + let gcode_config = GCodeConfig::default(); let options = ConversionOptions { dimensions }; let document = roxmltree::Document::parse_with_options( input, @@ -115,7 +115,7 @@ fn square_transformed_produces_expected_gcode() { .collect::>(); let actual = get_actual(square_transformed, false, [None; 2]); - assert_close(actual, expected) + assert_close(actual, expected); } #[test] @@ -128,7 +128,7 @@ fn square_transformed_nested_produces_expected_gcode() { .collect::>(); let actual = get_actual(square_transformed, false, [None; 2]); - assert_close(actual, expected) + assert_close(actual, expected); } #[test] @@ -153,7 +153,7 @@ fn circular_interpolation_produces_expected_gcode() { .collect::>(); let actual = get_actual(circular_interpolation, true, [None; 2]); - assert_close(actual, expected) + assert_close(actual, expected); } #[test] @@ -191,7 +191,7 @@ fn shapes_produces_expected_gcode() { .collect::>(); let actual = get_actual(shapes, false, [None; 2]); - assert_close(actual, expected) + assert_close(actual, expected); } #[test] @@ -203,7 +203,7 @@ fn use_defs_produces_expected_gcode() { .collect::>(); let actual = get_actual(svg, false, [None; 2]); - assert_close(actual, expected) + assert_close(actual, expected); } #[test] @@ -215,7 +215,7 @@ fn use_xlink_href_produces_expected_gcode() { .collect::>(); let actual = get_actual(svg, false, [None; 2]); - assert_close(actual, expected) + assert_close(actual, expected); } #[test] @@ -238,7 +238,7 @@ fn transform_origin_produces_expected_gcode() { .iter_emit_tokens() .collect::>(); let actual = get_actual(svg, false, [None; 2]); - assert_close(actual, expected) + assert_close(actual, expected); } /// `transform-origin="5 5"` with `rotate(90)` should be identical to the @@ -255,10 +255,10 @@ fn transform_origin_matches_manual_equivalent() { false, [None; 2], ); - assert_close(with_origin, manual) + assert_close(with_origin, manual); } -/// Regression test for https://github.com/sameer/svg2gcode/issues/105 +/// Regression test for #[test] fn issue_105_optimize_path_order_does_not_shrink_output() { let svg = include_str!("../tests/square.svg"); @@ -281,7 +281,7 @@ fn issue_105_optimize_path_order_does_not_shrink_output() { ); let normal = svg_to_gcode( &document, - &Default::default(), + &GCodeConfig::default(), ConversionOptions::default(), machine.clone(), ); diff --git a/g_code/src/turtle.rs b/g_code/src/turtle.rs index 5c38437..aecf948 100644 --- a/g_code/src/turtle.rs +++ b/g_code/src/turtle.rs @@ -123,7 +123,7 @@ impl<'input> GCodeTurtle<'input> { } } -impl<'input> Turtle for GCodeTurtle<'input> { +impl Turtle for GCodeTurtle<'_> { fn begin(&mut self) { self.program .append(&mut command!(UnitsMillimeters {}).into_token_vec()); @@ -181,22 +181,23 @@ impl<'input> Turtle for GCodeTurtle<'input> { .supported_functionality() .circular_interpolation { - FlattenWithArcs::flattened(&svg_arc, self.arc_flattening_tolerance()) - .into_iter() - .for_each(|segment| match segment { + for segment in + FlattenWithArcs::flattened(&svg_arc, self.arc_flattening_tolerance()) + { + match segment { ArcOrLineSegment::Arc(arc) => { - self.program.append(&mut self.circular_interpolation(arc)) + self.program.append(&mut self.circular_interpolation(arc)); } ArcOrLineSegment::Line(line) => { self.line_to(line.to); } - }); + } + } } else { - svg_arc - .to_arc() - .flattened(self.flattening_tolerance()) - .for_each(|point| self.program.append(&mut self.line_to(point))); - }; + for point in svg_arc.to_arc().flattened(self.flattening_tolerance()) { + self.program.append(&mut self.line_to(point)); + } + } } DrawCommand::CubicBezier(cbs) => { if self @@ -204,20 +205,23 @@ impl<'input> Turtle for GCodeTurtle<'input> { .supported_functionality() .circular_interpolation { - FlattenWithArcs::::flattened(&cbs, self.arc_flattening_tolerance()) - .into_iter() - .for_each(|segment| match segment { + for segment in + FlattenWithArcs::::flattened(&cbs, self.arc_flattening_tolerance()) + { + match segment { ArcOrLineSegment::Arc(arc) => { - self.program.append(&mut self.circular_interpolation(arc)) + self.program.append(&mut self.circular_interpolation(arc)); } ArcOrLineSegment::Line(line) => { - self.program.append(&mut self.line_to(line.to)) + self.program.append(&mut self.line_to(line.to)); } - }); + } + } } else { - cbs.flattened(self.flattening_tolerance()) - .for_each(|point| self.program.append(&mut self.line_to(point))); - }; + for point in cbs.flattened(self.flattening_tolerance()) { + self.program.append(&mut self.line_to(point)); + } + } } DrawCommand::QuadraticBezier(qbs) => { if self @@ -225,23 +229,24 @@ impl<'input> Turtle for GCodeTurtle<'input> { .supported_functionality() .circular_interpolation { - FlattenWithArcs::::flattened( + for segment in FlattenWithArcs::::flattened( &qbs.to_cubic(), self.arc_flattening_tolerance(), - ) - .into_iter() - .for_each(|segment| match segment { - ArcOrLineSegment::Arc(arc) => { - self.program.append(&mut self.circular_interpolation(arc)) - } - ArcOrLineSegment::Line(line) => { - self.program.append(&mut self.line_to(line.to)) + ) { + match segment { + ArcOrLineSegment::Arc(arc) => { + self.program.append(&mut self.circular_interpolation(arc)); + } + ArcOrLineSegment::Line(line) => { + self.program.append(&mut self.line_to(line.to)); + } } - }); + } } else { - qbs.flattened(self.flattening_tolerance()) - .for_each(|point| self.program.append(&mut self.line_to(point))); - }; + for point in qbs.flattened(self.flattening_tolerance()) { + self.program.append(&mut self.line_to(point)); + } + } } DrawCommand::Comment(comment) => { self.program.push(Token::Comment { diff --git a/star/Cargo.toml b/star/Cargo.toml index 3b4bb4b..399e4dc 100644 --- a/star/Cargo.toml +++ b/star/Cargo.toml @@ -7,6 +7,9 @@ edition.workspace = true repository.workspace = true license.workspace = true +[lints] +workspace = true + [features] serde = ["dep:serde"] image = ["dep:image", "dep:base64"] diff --git a/star/src/lower/length_serde.rs b/star/src/lower/length_serde.rs index e2518bb..7d4708f 100644 --- a/star/src/lower/length_serde.rs +++ b/star/src/lower/length_serde.rs @@ -1,5 +1,5 @@ //! Makes it possible to serialize an array of two optional [`svgtypes::Length`]s, -//! used for [super::ConversionOptions::dimensions] +//! used for [`super::ConversionOptions::dimensions`] use serde::{ Deserialize, Deserializer, Serialize, Serializer, diff --git a/star/src/lower/mod.rs b/star/src/lower/mod.rs index f9c337f..d02c35b 100644 --- a/star/src/lower/mod.rs +++ b/star/src/lower/mod.rs @@ -68,7 +68,7 @@ impl Default for ConversionConfig { /// Options are specific to this conversion. /// -/// This is separate from [ConversionConfig] to support bulk processing in the web interface. +/// This is separate from [`ConversionConfig`] to support bulk processing in the web interface. #[derive(Debug, Clone, PartialEq, Default)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct ConversionOptions { @@ -96,7 +96,7 @@ struct ConversionVisitor<'a, 'input, T: Turtle> { css_rules: HashMap<&'input str, HashMap<&'input str, &'input str>>, } -impl<'a, 'input, T: Turtle> ConversionVisitor<'a, 'input, T> { +impl ConversionVisitor<'_, '_, T> { fn comment(&mut self) { let mut comment = String::new(); self.name_stack @@ -269,7 +269,7 @@ fn svg_to_optimized_strokes( minimize_travel_time(strokes, starting_point) } -fn node_name(node: &Node, attr_to_print: &Option) -> String { +fn node_name(node: &Node, attr_to_print: Option<&str>) -> String { let mut name = node.tag_name().name().to_string(); if let Some(id) = node.attribute("id") { name += "#"; diff --git a/star/src/lower/selector.rs b/star/src/lower/selector.rs index f8795a2..e656e22 100644 --- a/star/src/lower/selector.rs +++ b/star/src/lower/selector.rs @@ -217,7 +217,7 @@ impl RelativeSelectorList { } Some(Combinator::Child) => node .children() - .filter(|n| n.is_element()) + .filter(roxmltree::Node::is_element) .any(|d| sel.matches(d)), } }) @@ -251,8 +251,8 @@ impl CombinatorSelector { match combinator { // Find first matching ancestor Combinator::Descendant => { - let found = std::iter::successors(current.parent(), |n| n.parent()) - .filter(|n| n.is_element()) + let found = std::iter::successors(current.parent(), roxmltree::Node::parent) + .filter(roxmltree::Node::is_element) .find(|n| target.matches(*n)); match found { Some(ancestor) => current = ancestor, @@ -319,8 +319,7 @@ impl AttributeSelector { match (&self.op, value) { (None, Some(_)) => true, - (None, None) => false, - (Some(_), None) => false, + (None | Some(_), None) => false, (Some((op, expected)), Some(val)) => match op { AttributeOp::Equal => val == expected, AttributeOp::Includes => val.split_whitespace().any(|w| w == expected), diff --git a/star/src/lower/transform.rs b/star/src/lower/transform.rs index c4ba1da..5c26bbe 100644 --- a/star/src/lower/transform.rs +++ b/star/src/lower/transform.rs @@ -55,7 +55,7 @@ pub fn get_viewport_transform( } pub fn svg_transform_into_euclid_transform(svg_transform: TransformListToken) -> Transform2D { - use TransformListToken::*; + use TransformListToken::{Matrix, Rotate, Scale, SkewX, SkewY, Translate}; match svg_transform { Matrix { a, b, c, d, e, f } => Transform2D::new(a, b, c, d, e, f), Translate { tx, ty } => Transform2D::translation(tx, ty), diff --git a/star/src/lower/units.rs b/star/src/lower/units.rs index 0f40b75..20805d1 100644 --- a/star/src/lower/units.rs +++ b/star/src/lower/units.rs @@ -20,7 +20,7 @@ pub enum DimensionHint { Other, } -impl<'a, 'input, T: Turtle> ConversionVisitor<'a, 'input, T> { +impl ConversionVisitor<'_, '_, T> { /// Convenience function for converting a length attribute to user units pub fn length_attr_to_user_units(&self, node: &Node, attr: &str) -> Option { let l = node @@ -47,8 +47,11 @@ impl<'a, 'input, T: Turtle> ConversionVisitor<'a, 'input, T> { /// /// A default DPI of 96 is used as per [CSS 4 ยง7.4](https://www.w3.org/TR/css-values/#resolution) pub fn length_to_user_units(&self, l: Length, hint: DimensionHint) -> f64 { - use svgtypes::LengthUnit::*; - use uom::si::{f64::Length, length::*}; + use svgtypes::LengthUnit::{Cm, Em, Ex, In, Mm, None, Pc, Percent, Pt, Px}; + use uom::si::{ + f64::Length, + length::{centimeter, inch, millimeter, pica_computer, point_computer}, + }; match l.unit { Cm => Length::new::(l.number).get::() * CSS_DEFAULT_DPI, diff --git a/star/src/lower/visit.rs b/star/src/lower/visit.rs index 4b74776..4db76ab 100644 --- a/star/src/lower/visit.rs +++ b/star/src/lower/visit.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, str::FromStr}; +use std::{collections::HashMap, result::Result, str::FromStr}; use euclid::default::Transform2D; use log::{debug, warn}; @@ -67,7 +67,7 @@ fn calculate_presentation_attr<'input>( return v; } } - node.attribute(name).map(|v| v.trim()) + node.attribute(name).map(str::trim) } /// Barebones CSS class rules parser. @@ -169,16 +169,18 @@ pub fn depth_first_visit(doc: &Document, visitor: &mut impl XmlVisitor) { .for_each(|child| visit_node(doc, child, visitor, false)); } -impl<'a, 'input, T: Turtle> XmlVisitor for ConversionVisitor<'a, 'input, T> { +impl XmlVisitor for ConversionVisitor<'_, '_, T> { fn visit_enter(&mut self, node: Node) { - use PathSegment::*; + use PathSegment::{ + ClosePath, EllipticalArc, HorizontalLineTo, LineTo, MoveTo, VerticalLineTo, + }; let mut view_box_opt = None; let mut viewport_pos_opt = [None, None]; let mut viewport_size_opt = [1., 1.]; if node.tag_name().name() == CLIP_PATH_TAG_NAME { - warn!("Clip paths are not supported: {:?}", node); + warn!("Clip paths are not supported: {node:?}"); } let mut flattened_transform = if let Some(transform) = node.attribute("transform") { @@ -194,16 +196,12 @@ impl<'a, 'input, T: Turtle> XmlVisitor for ConversionVisitor<'a, 'input, T> { // https://www.w3.org/TR/css-transforms-1/#transform-origin-property if let Some(to_str) = node.attribute("transform-origin") { let mut parser = LengthListParser::from(to_str); - let ox = parser - .next() - .and_then(|r| r.ok()) - .map(|l| self.length_to_user_units(l, DimensionHint::Horizontal)) - .unwrap_or(0.); - let oy = parser - .next() - .and_then(|r| r.ok()) - .map(|l| self.length_to_user_units(l, DimensionHint::Vertical)) - .unwrap_or(0.); + let ox = parser.next().and_then(Result::ok).map_or(0., |l| { + self.length_to_user_units(l, DimensionHint::Horizontal) + }); + let oy = parser.next().and_then(Result::ok).map_or(0., |l| { + self.length_to_user_units(l, DimensionHint::Vertical) + }); if ox != 0. || oy != 0. { // https://www.w3.org/TR/css-transforms-1/#transformation-matrix-computation // Steps 2 & 4 @@ -247,7 +245,7 @@ impl<'a, 'input, T: Turtle> XmlVisitor for ConversionVisitor<'a, 'input, T> { let intrinsic_aspect_ratio = match (view_box, viewport_size) { (None, [Some(ref width), Some(ref height)]) => Some(*width / *height), (Some(ref view_box), _) => Some(view_box.w / view_box.h), - (None, [None, None] | [None, Some(_)] | [Some(_), None]) => None, + (None, [None | Some(_), None] | [None, Some(_)]) => None, }; // https://www.w3.org/TR/css-images-3/#default-sizing @@ -277,8 +275,7 @@ impl<'a, 'input, T: Turtle> XmlVisitor for ConversionVisitor<'a, 'input, T> { self.viewport_dim_stack .push(match (view_box.as_ref(), &viewport_size) { - (Some(ViewBox { w, h, .. }), _) => [*w, *h], - (None, [w, h]) => [*w, *h], + (Some(ViewBox { w, h, .. }), _) | (None, [w, h]) => [*w, *h], }); if let Some(view_box) = view_box { @@ -350,8 +347,10 @@ impl<'a, 'input, T: Turtle> XmlVisitor for ConversionVisitor<'a, 'input, T> { } self.terrarium.push_transform(flattened_transform); - self.name_stack - .push(node_name(&node, &self.config.extra_attribute_name)); + self.name_stack.push(node_name( + &node, + self.config.extra_attribute_name.as_deref(), + )); if node.has_tag_name(SVG_TAG_NAME) || node.has_tag_name(SYMBOL_TAG_NAME) { let viewport_box = if let Some(vb) = view_box_opt { @@ -372,23 +371,20 @@ impl<'a, 'input, T: Turtle> XmlVisitor for ConversionVisitor<'a, 'input, T> { return; } - let has_fill_attr = calculate_presentation_attr(node, "fill", &self.css_rules) - .map(|v| v != "none") - .unwrap_or(true); + let has_fill_attr = + calculate_presentation_attr(node, "fill", &self.css_rules) != Some("none"); let fill_rule = calculate_presentation_attr(node, "fill-rule", &self.css_rules) - .map(|v| { + .map_or_default(|v| { if v == "evenodd" { FillRule::EvenOdd } else { FillRule::NonZero } - }) - .unwrap_or_default(); + }); // TODO: changing the default here to false would be breaking, but is technically correct from an SVG perspective. - let has_stroke_attr = calculate_presentation_attr(node, "stroke", &self.css_rules) - .map(|v| v != "none") - .unwrap_or(true); + let has_stroke_attr = + calculate_presentation_attr(node, "stroke", &self.css_rules) != Some("none"); match node.tag_name().name() { PATH_TAG_NAME => { @@ -662,22 +658,22 @@ impl<'a, 'input, T: Turtle> XmlVisitor for ConversionVisitor<'a, 'input, T> { .length_attr_to_user_units(&node, "height") .unwrap_or(0.); - let preserve_aspect_ratio = node - .attribute("preserveAspectRatio") - .map(|attr| { - AspectRatio::from_str(attr).expect("could not parse preserveAspectRatio") - }) - .unwrap_or(svgtypes::AspectRatio { + let preserve_aspect_ratio = node.attribute("preserveAspectRatio").map_or( + svgtypes::AspectRatio { defer: false, align: svgtypes::Align::XMidYMid, slice: false, - }); + }, + |attr| { + AspectRatio::from_str(attr).expect("could not parse preserveAspectRatio") + }, + ); let view_box = svgtypes::ViewBox { x: 0., y: 0., - w: image.width() as f64, - h: image.height() as f64, + w: f64::from(image.width()), + h: f64::from(image.height()), }; let image_to_user = get_viewport_transform( view_box, diff --git a/star/src/turtle/dpi.rs b/star/src/turtle/dpi.rs index f124e13..42e0e88 100644 --- a/star/src/turtle/dpi.rs +++ b/star/src/turtle/dpi.rs @@ -94,11 +94,11 @@ impl DpiConvertingTurtle { impl Turtle for DpiConvertingTurtle { fn begin(&mut self) { - self.inner.begin() + self.inner.begin(); } fn end(&mut self) { - self.inner.end() + self.inner.end(); } fn stroke(&mut self, stroke: Stroke) { @@ -110,7 +110,7 @@ impl Turtle for DpiConvertingTurtle { self.inner.image(super::elements::RasterImage { dimensions: self.box_to_mm(img.dimensions), image: img.image, - }) + }); } fn fill_polygon(&mut self, polygon: FillPolygon) { @@ -121,6 +121,6 @@ impl Turtle for DpiConvertingTurtle { .into_iter() .map(|s| self.stroke_to_mm(s)) .collect(), - }) + }); } } diff --git a/star/src/turtle/elements/arc.rs b/star/src/turtle/elements/arc.rs index 77464b0..21bcf65 100644 --- a/star/src/turtle/elements/arc.rs +++ b/star/src/turtle/elements/arc.rs @@ -102,7 +102,8 @@ where if (inner_bezier.to - inner_bezier.from).square_length() < S::EPSILON { continue; - } else if inner_bezier.is_linear(tolerance) { + } + if inner_bezier.is_linear(tolerance) { acc.push(ArcOrLineSegment::Line(inner_bezier.baseline())); continue; } diff --git a/star/src/turtle/elements/fill.rs b/star/src/turtle/elements/fill.rs index 76d0ffc..4f87e48 100644 --- a/star/src/turtle/elements/fill.rs +++ b/star/src/turtle/elements/fill.rs @@ -147,7 +147,7 @@ fn stroke_contains_stroke(outer: &Stroke, inner: &Stroke) -> bool { /// `fill_rule` is flattened here: /// - `EvenOdd`: even = outer /// - `NonZero`: 0 cumulative winding = outer -pub(crate) fn into_fill_polygons(subpaths: Vec, fill_rule: FillRule) -> Vec { +pub(crate) fn into_fill_polygons(subpaths: &[Stroke], fill_rule: FillRule) -> Vec { if subpaths.is_empty() { return vec![]; } @@ -165,7 +165,7 @@ pub(crate) fn into_fill_polygons(subpaths: Vec, fill_rule: FillRule) -> // TODO: EvenOdd fill for overlapping but non-nested subpaths isn't handled. // We need to XOR without flattening somehow. if fill_rule == FillRule::EvenOdd { - let bboxes: Vec<_> = subpaths.iter().map(|s| s.bounding_box()).collect(); + let bboxes: Vec<_> = subpaths.iter().map(super::Stroke::bounding_box).collect(); for i in 0..subpaths.len() { for j in (i + 1)..subpaths.len() { let boxes_overlap = bboxes[i].intersects(&bboxes[j]); @@ -243,6 +243,8 @@ pub(crate) fn into_fill_polygons(subpaths: Vec, fill_rule: FillRule) -> #[cfg(test)] mod tests { + use std::slice; + use lyon_geom::Point; use super::*; @@ -319,8 +321,7 @@ mod tests { ], ); - let polygons = - into_fill_polygons(vec![s0.clone(), s1.clone(), s2.clone()], FillRule::NonZero); + let polygons = into_fill_polygons(&[s0.clone(), s1.clone(), s2.clone()], FillRule::NonZero); // For NonZero, since all are CCW, the winding numbers are 1, 2, and 3. // All are non-zero, so the whole area should be filled. @@ -378,7 +379,7 @@ mod tests { ], ); - let polygons = into_fill_polygons(vec![s0.clone(), s1.clone()], FillRule::NonZero); + let polygons = into_fill_polygons(&[s0.clone(), s1.clone()], FillRule::NonZero); // Since they overlap but are not nested (neither bbox is inside the other), // they should be classified as two independent outer contours. @@ -390,8 +391,8 @@ mod tests { /// The path visits the five outer tips in "skip-one" order, crossing itself five times /// and enclosing an inner pentagon whose fill depends on the fill rule: /// - /// - EvenOdd: hole in the center (incorrect currently) - /// - NonZero: filled + /// - `EvenOdd`: hole in the center (incorrect currently) + /// - `NonZero`: filled #[test] #[ignore = "self-intersecting single subpaths are not yet handled"] fn test_self_intersecting_pentagram() { @@ -421,7 +422,7 @@ mod tests { ], ); - let evenodd = into_fill_polygons(vec![star.clone()], FillRule::EvenOdd); + let evenodd = into_fill_polygons(slice::from_ref(&star), FillRule::EvenOdd); assert_eq!(evenodd.len(), 1, "EvenOdd: expected one outer contour"); assert_eq!( evenodd[0].holes.len(), @@ -429,7 +430,7 @@ mod tests { "EvenOdd: expected one hole (the inner pentagon)" ); - let nonzero = into_fill_polygons(vec![star], FillRule::NonZero); + let nonzero = into_fill_polygons(&[star], FillRule::NonZero); assert_eq!(nonzero.len(), 1, "NonZero: expected one outer contour"); assert_eq!( nonzero[0].holes.len(), diff --git a/star/src/turtle/elements/image_ops.rs b/star/src/turtle/elements/image_ops.rs index 2621f71..787b6ec 100644 --- a/star/src/turtle/elements/image_ops.rs +++ b/star/src/turtle/elements/image_ops.rs @@ -31,7 +31,7 @@ pub fn transform_image( let orig_w = image.width(); let orig_h = image.height(); - let pixel_bounds = Box2D::new(point(0., 0.), point(orig_w as f64, orig_h as f64)); + let pixel_bounds = Box2D::new(point(0., 0.), point(f64::from(orig_w), f64::from(orig_h))); let user_bounds = image_to_user.outer_transformed_box(&pixel_bounds); let image_to_final = image_to_user.then(user_to_final); let transformed_box = image_to_final.outer_transformed_box(&pixel_bounds); @@ -47,7 +47,7 @@ pub fn transform_image( }; let aspect_ratios_match = { - let orig_aspect_ratio = orig_w as f64 / orig_h as f64; + let orig_aspect_ratio = f64::from(orig_w) / f64::from(orig_h); let final_aspect_ratio = user_bounds.width() / user_bounds.height(); (orig_aspect_ratio - final_aspect_ratio).abs() < EPSILON }; @@ -87,8 +87,8 @@ pub fn transform_image( (scale_x, scale_y) }; - let new_w = (orig_w as f64 * resample_scale_x).ceil() as u32; - let new_h = (orig_h as f64 * resample_scale_y).ceil() as u32; + let new_w = (f64::from(orig_w) * resample_scale_x).ceil() as u32; + let new_h = (f64::from(orig_h) * resample_scale_y).ceil() as u32; let mut new_img = DynamicImage::new(new_w, new_h, image.color()); @@ -101,13 +101,13 @@ pub fn transform_image( // Map new pixel to target space let target_pt = min + vector( - (new_x as f64 / new_w as f64) * tb_w, - (new_y as f64 / new_h as f64) * tb_h, + (f64::from(new_x) / f64::from(new_w)) * tb_w, + (f64::from(new_y) / f64::from(new_h)) * tb_h, ); let orig_pt = inv.transform_point(target_pt); - if (0.0..orig_w as f64).contains(&orig_pt.x) - && (0.0..orig_h as f64).contains(&orig_pt.y) + if (0.0..f64::from(orig_w)).contains(&orig_pt.x) + && (0.0..f64::from(orig_h)).contains(&orig_pt.y) && let Some(pixel) = sample_lanczos3(&image, orig_pt.x, orig_pt.y, orig_w, orig_h) { @@ -146,14 +146,14 @@ pub fn crop_image_to_bounds( let crop_offset = cropped_bbox.min - img_bbox.min; - let mut pixel_x = (crop_offset.x / orig_w * src_w as f64).round() as u32; - let mut pixel_y = (crop_offset.y / orig_h * src_h as f64).round() as u32; + let mut pixel_x = (crop_offset.x / orig_w * f64::from(src_w)).round() as u32; + let mut pixel_y = (crop_offset.y / orig_h * f64::from(src_h)).round() as u32; pixel_x = pixel_x.min(src_w - 1); pixel_y = pixel_y.min(src_h - 1); - let pixel_w = ((w_crop / orig_w) * src_w as f64).round() as u32; - let pixel_h = ((h_crop / orig_h) * src_h as f64).round() as u32; + let pixel_w = ((w_crop / orig_w) * f64::from(src_w)).round() as u32; + let pixel_h = ((h_crop / orig_h) * f64::from(src_h)).round() as u32; let pixel_w = pixel_w.clamp(1, src_w - pixel_x); let pixel_h = pixel_h.clamp(1, src_h - pixel_y); @@ -233,7 +233,7 @@ fn sample_lanczos3( continue; } let py = y_floor - 2 + dy_idx as i64; - let clamped_py = py.clamp(0, orig_h as i64 - 1) as u32; + let clamped_py = py.clamp(0, i64::from(orig_h) - 1) as u32; for (dx_idx, &wx) in wx_arr.iter().enumerate() { let w = wx * wy; @@ -241,13 +241,13 @@ fn sample_lanczos3( continue; } let px = x_floor - 2 + dx_idx as i64; - let clamped_px = px.clamp(0, orig_w as i64 - 1) as u32; + let clamped_px = px.clamp(0, i64::from(orig_w) - 1) as u32; let pixel = image.get_pixel(clamped_px, clamped_py); - r_sum += pixel.0[0] as f64 * w; - g_sum += pixel.0[1] as f64 * w; - b_sum += pixel.0[2] as f64 * w; - a_sum += pixel.0[3] as f64 * w; + r_sum += f64::from(pixel.0[0]) * w; + g_sum += f64::from(pixel.0[1]) * w; + b_sum += f64::from(pixel.0[2]) * w; + a_sum += f64::from(pixel.0[3]) * w; w_sum += w; } } diff --git a/star/src/turtle/elements/mod.rs b/star/src/turtle/elements/mod.rs index 9e4f3be..97a24d3 100644 --- a/star/src/turtle/elements/mod.rs +++ b/star/src/turtle/elements/mod.rs @@ -143,11 +143,11 @@ impl Stroke { .unwrap_or(self.start_point) } - /// Reverses the stroke so it runs from [Self::end_point] to [Self::start_point]. + /// Reverses the stroke so it runs from [`Self::end_point`] to [`Self::start_point`]. pub fn reversed(&mut self) { self.start_point = self.end_point(); self.commands.reverse(); - self.commands.iter_mut().for_each(|c| c.reverse()); + self.commands.iter_mut().for_each(DrawCommand::reverse); } pub fn start_point(&self) -> Point { diff --git a/star/src/turtle/elements/tsp.rs b/star/src/turtle/elements/tsp.rs index fc90b9f..e572013 100644 --- a/star/src/turtle/elements/tsp.rs +++ b/star/src/turtle/elements/tsp.rs @@ -20,8 +20,8 @@ fn dist(a: Point, b: Point) -> f64 { /// Reorder (and optionally reverse) strokes to minimise total tool-off travel distance. /// -/// Uses [nearest_neighbor_greedy] for the initial ordering, then refines -/// with tabu search using the Relocate, 2-Opt, and LinkSwap operators. +/// Uses [`nearest_neighbor_greedy`] for the initial ordering, then refines +/// with tabu search using the Relocate, 2-Opt, and `LinkSwap` operators. /// /// Based off of the code in raster2svg. /// @@ -42,7 +42,7 @@ pub fn minimize_travel_time(strokes: Vec, starting_point: [Option; /// Greedy nearest-neighbour ordering with flips. /// -/// Repeatedly chooses the [Stroke] or [Stroke::reversed] closest to the current point until none remain. +/// Repeatedly chooses the [Stroke] or [`Stroke::reversed`] closest to the current point until none remain. fn nearest_neighbor_greedy( mut remaining: Vec, the_starting_point: Point, @@ -120,7 +120,7 @@ fn stroke_distances(path: &[Stroke]) -> Vec { .collect() } -/// Reverse a series of strokes in-place and also calls [Stroke::reversed] on each [Stroke] to preserve the path. +/// Reverse a series of strokes in-place and also calls [`Stroke::reversed`] on each [Stroke] to preserve the path. fn reverse_and_flip(strokes: &mut [Stroke]) { strokes.reverse(); for s in strokes.iter_mut() { @@ -168,12 +168,11 @@ fn local_improvement_with_tabu_search( if tabu.is_empty() { debug!("TSP: stuck after {idx} iterations, no more local improvements"); break; - } else { - // Try to unstick by clearing tabu. - tabu.clear(); - tabu_set.clear(); - stuck_operators.clear(); } + // Try to unstick by clearing tabu. + tabu.clear(); + tabu_set.clear(); + stuck_operators.clear(); } let operator: Operator = rng.random(); @@ -215,9 +214,8 @@ fn local_improvement_with_tabu_search( if diff <= 0.0 { stuck_operators.insert(operator); continue; - } else { - stuck_operators.clear(); } + stuck_operators.clear(); let mut stroke = current.remove(i); if reversed { stroke.reversed(); @@ -262,9 +260,8 @@ fn local_improvement_with_tabu_search( if diff <= 0.0 { stuck_operators.insert(operator); continue; - } else { - stuck_operators.clear(); } + stuck_operators.clear(); tabu.extend([this.1, other.0]); tabu_set.extend([this.1, other.0]); reverse_and_flip(&mut current[this.1..=other.0]); @@ -318,9 +315,8 @@ fn local_improvement_with_tabu_search( if diff <= 0.0 { stuck_operators.insert(operator); continue; - } else { - stuck_operators.clear(); } + stuck_operators.clear(); // Apply prefix reversal (options 1 and 2). if opt != 0 { @@ -346,7 +342,7 @@ fn local_improvement_with_tabu_search( + dist(the_starting_point, current[0].start_point()); if current_sum < best_sum { - best = current.clone(); + best.clone_from(¤t); best_sum = current_sum; } diff --git a/star/src/turtle/mod.rs b/star/src/turtle/mod.rs index d7636a7..c28adc7 100644 --- a/star/src/turtle/mod.rs +++ b/star/src/turtle/mod.rs @@ -134,17 +134,17 @@ impl Terrarium { /// fn close(&mut self) -> Option { // See https://www.w3.org/TR/SVG/paths.html#Segment-CompletingClosePath - let command = if !(self.current_position - self.initial_position) + let command = if (self.current_position - self.initial_position) .abs() .lower_than(vector(f64::EPSILON, f64::EPSILON)) .all() { + None + } else { Some(DrawCommand::LineTo { from: self.current_position, to: self.initial_position, }) - } else { - None }; self.current_position = self.initial_position; self.previous_quadratic_control = None; @@ -161,24 +161,20 @@ impl Terrarium { .inverse() .expect("transform is invertible"); let original_current_position = inverse_transform.transform_point(self.current_position); - let x = x - .map(|x| { - if abs { - x - } else { - original_current_position.x + x - } - }) - .unwrap_or(original_current_position.x); - let y = y - .map(|y| { - if abs { - y - } else { - original_current_position.y + y - } - }) - .unwrap_or(original_current_position.y); + let x = x.map_or(original_current_position.x, |x| { + if abs { + x + } else { + original_current_position.x + x + } + }); + let y = y.map_or(original_current_position.y, |y| { + if abs { + y + } else { + original_current_position.y + y + } + }); let from = self.current_position; let to = self.current_transform.transform_point(point(x, y)); @@ -357,7 +353,7 @@ impl Terrarium { .transform_point(self.current_position); if !abs { - to = from + to.to_vector() + to = from + to.to_vector(); } let svg_arc = SvgArc { from, @@ -421,7 +417,7 @@ impl Terrarium { } /// Push a generic transform onto the stack - /// Could be any valid CSS transform https://drafts.csswg.org/css-transforms-1/#typedef-transform-function + /// Could be any valid CSS transform /// pub fn push_transform(&mut self, trans: Transform2D) { self.transform_stack.push(self.current_transform); @@ -442,9 +438,12 @@ impl Terrarium { self.comment = Some(comment); } - /// Maps [PathSegments](PathSegment) into concrete operations. + /// Maps [`PathSegments`](PathSegment) into concrete operations. pub fn apply_path(&mut self, path: impl IntoIterator) { - use PathSegment::*; + use PathSegment::{ + ClosePath, CurveTo, EllipticalArc, HorizontalLineTo, LineTo, MoveTo, Quadratic, + SmoothCurveTo, SmoothQuadratic, VerticalLineTo, + }; self.begin(); self.reset(); @@ -574,7 +573,7 @@ impl Terrarium { }; sub.apply_path(segments); let segments = sub.finish().into_strokes(); - for polygon in self::elements::fill::into_fill_polygons(segments, fill_rule) { + for polygon in self::elements::fill::into_fill_polygons(&segments, fill_rule) { self.turtle.fill_polygon(polygon); } } diff --git a/star/src/turtle/preprocess.rs b/star/src/turtle/preprocess.rs index 9676ad3..c22fa01 100644 --- a/star/src/turtle/preprocess.rs +++ b/star/src/turtle/preprocess.rs @@ -4,7 +4,7 @@ use lyon_geom::Box2D; use super::{Turtle, elements::FillPolygon}; -/// Generates a bounding box for all draw operations, used to properly apply [crate::lower::ConversionConfig::origin] +/// Generates a bounding box for all draw operations, used to properly apply [`crate::lower::ConversionConfig::origin`] #[derive(Debug, Default)] pub struct PreprocessTurtle { bounding_box: Box2D, @@ -34,7 +34,7 @@ impl Turtle for PreprocessTurtle { fn fill_polygon(&mut self, polygon: FillPolygon) { self.bounding_box = iter::once(polygon.outer) .chain(polygon.holes) - .flat_map(|s| s.into_commands()) + .flat_map(super::elements::Stroke::into_commands) .filter_map(|c| c.bounding_box()) .fold(self.bounding_box, |acc, b| { Box2D::from_points([acc.min, acc.max, b.min, b.max]) diff --git a/star/src/turtle/svg_preview.rs b/star/src/turtle/svg_preview.rs index a55b4a8..2659125 100644 --- a/star/src/turtle/svg_preview.rs +++ b/star/src/turtle/svg_preview.rs @@ -26,8 +26,9 @@ impl SvgPreviewTurtle { self.bounding_box = Some( self.bounding_box // Box2D::union discards empty boxes - .map(|existing| Box2D::from_points([existing.min, existing.max, bb.min, bb.max])) - .unwrap_or(bb), + .map_or(bb, |existing| { + Box2D::from_points([existing.min, existing.max, bb.min, bb.max]) + }), ); } @@ -100,8 +101,8 @@ impl Turtle for SvgPreviewTurtle { svg_arc.radii.x, svg_arc.radii.y, svg_arc.x_rotation.to_degrees(), - if svg_arc.flags.large_arc { 1 } else { 0 }, - if svg_arc.flags.sweep { 1 } else { 0 }, + i32::from(svg_arc.flags.large_arc), + i32::from(svg_arc.flags.sweep), svg_arc.to.x, svg_arc.to.y, ) diff --git a/web/Cargo.toml b/web/Cargo.toml index a192008..7b23942 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -8,6 +8,9 @@ edition.workspace = true repository.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] getrandom = { version = "0.4", features = ["wasm_js"] } wasm-bindgen = "0.2" diff --git a/web/src/forms/inputs.rs b/web/src/forms/inputs.rs index 37058bc..41f6cb3 100644 --- a/web/src/forms/inputs.rs +++ b/web/src/forms/inputs.rs @@ -6,7 +6,7 @@ use yewdux::functional::{use_store, use_store_value}; use crate::{ state::{AppState, FormState}, - ui::*, + ui::{FormGroup, Input}, }; macro_rules! form_input { diff --git a/web/src/forms/mod.rs b/web/src/forms/mod.rs index 90bc5e1..3d47e1f 100644 --- a/web/src/forms/mod.rs +++ b/web/src/forms/mod.rs @@ -1,4 +1,4 @@ -use std::{convert::TryInto, path::Path}; +use std::{convert::TryInto, path::Path, result::Result}; use gloo_file::{ callbacks::{FileReader, read_as_bytes}, @@ -24,8 +24,8 @@ use crate::{ mod editors; mod inputs; -use editors::*; -use inputs::*; +use editors::{BeginSequenceInput, EndSequenceInput, ToolOffSequenceInput, ToolOnSequenceInput}; +use inputs::{DpiInput, FeedrateInput, OriginXInput, OriginYInput, ToleranceInput}; #[function_component(SettingsForm)] pub fn settings_form() -> Html { @@ -38,27 +38,20 @@ pub fn settings_form() -> Html { || form_state .origin .iter() - .all(|opt| opt.as_ref().is_some_and(|r| r.is_err())) + .all(|opt| opt.as_ref().is_some_and(Result::is_err)) || form_state .tool_on_sequence .as_ref() - .map(Result::is_err) - .unwrap_or(false) + .is_some_and(Result::is_err) || form_state .tool_off_sequence .as_ref() - .map(Result::is_err) - .unwrap_or(false) + .is_some_and(Result::is_err) || form_state .begin_sequence .as_ref() - .map(Result::is_err) - .unwrap_or(false) - || form_state - .end_sequence - .as_ref() - .map(Result::is_err) - .unwrap_or(false); + .is_some_and(Result::is_err) + || form_state.end_sequence.as_ref().is_some_and(Result::is_err); let close_ref = use_node_ref(); @@ -276,10 +269,10 @@ pub fn import_export_modal() -> Html { &gloo_file::File::from(file), move |res| { let res = res - .map_err(|err| format!("Error reading {}: {}", filename, err)) + .map_err(|err| format!("Error reading {filename}: {err}")) .and_then(|bytes| { serde_json::from_slice::(&bytes) - .map_err(|err| format!("Error parsing {}: {}", filename, err)) + .map_err(|err| format!("Error parsing {filename}: {err}")) }); match res { @@ -359,7 +352,7 @@ pub fn import_export_modal() -> Html { button={html_nested!(