From ea5bb85e881626f77fc5908361c06b49572e29d8 Mon Sep 17 00:00:00 2001 From: Franz Dietrich Date: Fri, 24 Oct 2025 16:31:25 +0200 Subject: [PATCH] add a viewbox to the svg that has the whole drawing in frame --- turtle-lib/examples/export_svg.rs | 1 + turtle-lib/src/export_svg.rs | 108 ++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/turtle-lib/examples/export_svg.rs b/turtle-lib/examples/export_svg.rs index 826146d..b5db8a7 100644 --- a/turtle-lib/examples/export_svg.rs +++ b/turtle-lib/examples/export_svg.rs @@ -10,6 +10,7 @@ async fn main() { .right(90.0) .forward(100.0) .set_pen_color(macroquad::color::GRAY) + .set_pen_width(8.0) .circle_right(50.0, 90.0, 4) .begin_fill() .forward(100.0) diff --git a/turtle-lib/src/export_svg.rs b/turtle-lib/src/export_svg.rs index b8dfd68..c29771d 100644 --- a/turtle-lib/src/export_svg.rs +++ b/turtle-lib/src/export_svg.rs @@ -5,6 +5,7 @@ pub mod svg_export { use crate::commands::TurtleCommand; use crate::export::{DrawingExporter, ExportError}; use crate::state::{DrawCommand, TurtleWorld}; + use macroquad::prelude::Vec2; use std::fs::File; use svg::{ node::element::{Circle, Line, Polygon, Text as SvgText}, @@ -17,6 +18,25 @@ pub mod svg_export { fn export(&self, world: &TurtleWorld, filename: &str) -> Result<(), ExportError> { let mut doc = Document::new(); + let mut min_x = f32::INFINITY; + let mut max_x = f32::NEG_INFINITY; + let mut min_y = f32::INFINITY; + let mut max_y = f32::NEG_INFINITY; + + fn update_bounds( + min_x: &mut f32, + max_x: &mut f32, + min_y: &mut f32, + max_y: &mut f32, + x: f32, + y: f32, + ) { + *min_x = min_x.min(x); + *max_x = max_x.max(x); + *min_y = min_y.min(y); + *max_y = max_y.max(y); + } + for turtle in &world.turtles { for cmd in &turtle.commands { match cmd { @@ -26,6 +46,14 @@ pub mod svg_export { // Linie als let start = source.start_position; let end = source.end_position; + update_bounds( + &mut min_x, &mut max_x, &mut min_y, &mut max_y, start.x, + start.y, + ); + update_bounds( + &mut min_x, &mut max_x, &mut min_y, &mut max_y, end.x, + end.y, + ); let line = Line::new() .set("x1", start.x) .set("y1", start.y) @@ -51,6 +79,22 @@ pub mod svg_export { let center = geom.center; if (*angle - 360.0).abs() < 1e-3 { // Voller Kreis + update_bounds( + &mut min_x, + &mut max_x, + &mut min_y, + &mut max_y, + center.x - radius, + center.y - radius, + ); + update_bounds( + &mut min_x, + &mut max_x, + &mut min_y, + &mut max_y, + center.x + radius, + center.y + radius, + ); let circle = Circle::new() .set("cx", center.x) .set("cy", center.y) @@ -63,6 +107,23 @@ pub mod svg_export { // Kreisbogen als let start = source.start_position; let end = source.end_position; + // For arcs, include the full circle bounds to ensure complete visibility + update_bounds( + &mut min_x, + &mut max_x, + &mut min_y, + &mut max_y, + center.x - radius, + center.y - radius, + ); + update_bounds( + &mut min_x, + &mut max_x, + &mut min_y, + &mut max_y, + center.x + radius, + center.y + radius, + ); let large_arc = if *angle > 180.0 { 1 } else { 0 }; let sweep = match direction { crate::circle_geometry::CircleDirection::Left => 0, @@ -90,6 +151,14 @@ pub mod svg_export { TurtleCommand::EndFill => { // Fills werden als mit Konturen ausgegeben if let Some(contours) = &source.contours { + for contour in contours { + for point in contour { + update_bounds( + &mut min_x, &mut max_x, &mut min_y, &mut max_y, + point.x, point.y, + ); + } + } let mut d = String::new(); for (i, contour) in contours.iter().enumerate() { if !contour.is_empty() { @@ -119,6 +188,30 @@ pub mod svg_export { } } else { // Fallback: Dummy-Polygon + update_bounds( + &mut min_x, + &mut max_x, + &mut min_y, + &mut max_y, + source.start_position.x, + source.start_position.y, + ); + update_bounds( + &mut min_x, + &mut max_x, + &mut min_y, + &mut max_y, + source.start_position.x + 10.0, + source.start_position.y + 10.0, + ); + update_bounds( + &mut min_x, + &mut max_x, + &mut min_y, + &mut max_y, + source.start_position.x + 5.0, + source.start_position.y + 15.0, + ); let poly = Polygon::new() .set( "points", @@ -146,6 +239,10 @@ pub mod svg_export { source, .. } => { + update_bounds( + &mut min_x, &mut max_x, &mut min_y, &mut max_y, position.x, + position.y, + ); let txt = SvgText::new() .set("x", position.x) .set("y", position.y) @@ -157,6 +254,17 @@ pub mod svg_export { } } + // Set viewBox with 20px padding + if min_x.is_finite() && max_x.is_finite() && min_y.is_finite() && max_y.is_finite() { + let width = (max_x - min_x) + 40.0; + let height = (max_y - min_y) + 40.0; + let view_box = format!("{} {} {} {}", min_x - 20.0, min_y - 20.0, width, height); + doc = doc.set("viewBox", view_box); + } else { + // Default viewBox if no elements + doc = doc.set("viewBox", "0 0 400 400"); + } + let mut file = File::create(filename).map_err(ExportError::Io)?; svg::write(&mut file, &doc).map_err(ExportError::Io)?; Ok(())