Line caps on exported SVG lines and arcs are now configured to be round
(stroke-linecap="round"), matching the on-screen Lyon tessellation.
Summary of Changes
turtle-lib/src/export_svg.rs:
Added .set("stroke-linecap", "round") to SVG <line> elements.
Added .set("stroke-linecap", "round") to partial arc <path> elements.
Extracted SvgExporter::to_svg_document(&TurtleWorld) -> Document to
allow in-memory SVG inspection and testing.
Added unit tests verifying that both lines and arcs export with
stroke-linecap="round".
turtle-lib/src/state.rs:
Added #[allow(clippy::unused_self)] to SvgLog::clear for clean clippy
passes when compiling without the svg feature.
This commit is contained in:
@@ -9,11 +9,11 @@ fn draw(turtle: &mut TurtlePlan) {
|
|||||||
turtle.set_pen_width(0.5);
|
turtle.set_pen_width(0.5);
|
||||||
|
|
||||||
// Draw a 5-pointed star pattern repeatedly
|
// Draw a 5-pointed star pattern repeatedly
|
||||||
for _i in 0..50000 {
|
for _i in 0..500 {
|
||||||
turtle.forward(200.0);
|
turtle.forward(200.0);
|
||||||
turtle.circle_left(10.0, 72.0, 1000);
|
turtle.circle_left(10.0, 72.0, 100);
|
||||||
turtle.circle_right(5.0, 360.0, 1000);
|
turtle.circle_right(5.0, 360.0, 100);
|
||||||
turtle.circle_left(10.0, 72.0, 1000);
|
turtle.circle_left(10.0, 72.0, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set animation speed
|
// Set animation speed
|
||||||
|
|||||||
@@ -27,9 +27,11 @@ pub mod svg_export {
|
|||||||
|
|
||||||
pub struct SvgExporter;
|
pub struct SvgExporter;
|
||||||
|
|
||||||
impl DrawingExporter for SvgExporter {
|
impl SvgExporter {
|
||||||
|
/// Generate an SVG [`Document`] from the given [`TurtleWorld`].
|
||||||
|
#[must_use]
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
fn export(&self, world: &TurtleWorld, filename: &str) -> Result<(), ExportError> {
|
pub fn to_svg_document(world: &TurtleWorld) -> Document {
|
||||||
let mut doc = Document::new();
|
let mut doc = Document::new();
|
||||||
|
|
||||||
let mut min_x = f32::INFINITY;
|
let mut min_x = f32::INFINITY;
|
||||||
@@ -58,7 +60,8 @@ pub mod svg_export {
|
|||||||
.set("x2", end.x)
|
.set("x2", end.x)
|
||||||
.set("y2", end.y)
|
.set("y2", end.y)
|
||||||
.set("stroke", color_to_svg(*color))
|
.set("stroke", color_to_svg(*color))
|
||||||
.set("stroke-width", *pen_width);
|
.set("stroke-width", *pen_width)
|
||||||
|
.set("stroke-linecap", "round");
|
||||||
doc = doc.add(line);
|
doc = doc.add(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,6 +135,7 @@ pub mod svg_export {
|
|||||||
.set("d", d)
|
.set("d", d)
|
||||||
.set("stroke", color_to_svg(*color))
|
.set("stroke", color_to_svg(*color))
|
||||||
.set("stroke-width", *pen_width)
|
.set("stroke-width", *pen_width)
|
||||||
|
.set("stroke-linecap", "round")
|
||||||
.set("fill", "none");
|
.set("fill", "none");
|
||||||
doc = doc.add(path);
|
doc = doc.add(path);
|
||||||
}
|
}
|
||||||
@@ -218,6 +222,13 @@ pub mod svg_export {
|
|||||||
doc = doc.set("viewBox", "0 0 400 400");
|
doc = doc.set("viewBox", "0 0 400 400");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
doc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DrawingExporter for SvgExporter {
|
||||||
|
fn export(&self, world: &TurtleWorld, filename: &str) -> Result<(), ExportError> {
|
||||||
|
let doc = Self::to_svg_document(world);
|
||||||
let mut file = File::create(filename).map_err(ExportError::Io)?;
|
let mut file = File::create(filename).map_err(ExportError::Io)?;
|
||||||
svg::write(&mut file, &doc).map_err(ExportError::Io)?;
|
svg::write(&mut file, &doc).map_err(ExportError::Io)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -234,4 +245,55 @@ pub mod svg_export {
|
|||||||
format!("rgb({r},{g},{b})")
|
format!("rgb({r},{g},{b})")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::circle_geometry::CircleDirection;
|
||||||
|
use crate::general::{Color, Coordinate, Degrees};
|
||||||
|
use crate::state::{SvgRecord, Turtle};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_svg_export_line_has_round_line_caps() {
|
||||||
|
let mut world = TurtleWorld::new();
|
||||||
|
let mut turtle = Turtle::default();
|
||||||
|
turtle.svg_log.push(SvgRecord::Line {
|
||||||
|
start: Coordinate::new(0.0, 0.0),
|
||||||
|
end: Coordinate::new(100.0, 0.0),
|
||||||
|
color: Color::new(0.0, 0.0, 0.0, 1.0),
|
||||||
|
pen_width: 2.0,
|
||||||
|
});
|
||||||
|
world.turtles.push(turtle);
|
||||||
|
|
||||||
|
let doc = SvgExporter::to_svg_document(&world);
|
||||||
|
let svg_string = doc.to_string();
|
||||||
|
assert!(
|
||||||
|
svg_string.contains(r#"stroke-linecap="round""#),
|
||||||
|
"SVG export of lines should have round line caps: {svg_string}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_svg_export_arc_has_round_line_caps() {
|
||||||
|
let mut world = TurtleWorld::new();
|
||||||
|
let mut turtle = Turtle::default();
|
||||||
|
turtle.svg_log.push(SvgRecord::Arc {
|
||||||
|
start_position: Coordinate::new(0.0, 0.0),
|
||||||
|
start_heading: 0.0,
|
||||||
|
radius: 50.0,
|
||||||
|
angle: Degrees::new(90.0),
|
||||||
|
direction: CircleDirection::Right,
|
||||||
|
color: Color::new(0.0, 0.0, 0.0, 1.0),
|
||||||
|
pen_width: 2.0,
|
||||||
|
});
|
||||||
|
world.turtles.push(turtle);
|
||||||
|
|
||||||
|
let doc = SvgExporter::to_svg_document(&world);
|
||||||
|
let svg_string = doc.to_string();
|
||||||
|
assert!(
|
||||||
|
svg_string.contains(r#"stroke-linecap="round""#),
|
||||||
|
"SVG export of partial arcs should have round line caps: {svg_string}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ pub(crate) struct SvgLog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SvgLog {
|
impl SvgLog {
|
||||||
|
#[allow(clippy::unused_self)]
|
||||||
pub(crate) fn clear(&mut self) {
|
pub(crate) fn clear(&mut self) {
|
||||||
#[cfg(feature = "svg")]
|
#[cfg(feature = "svg")]
|
||||||
self.records.clear();
|
self.records.clear();
|
||||||
|
|||||||
Reference in New Issue
Block a user