From bc925ffe26f469d04ddba29fdc29cba39e61908e Mon Sep 17 00:00:00 2001 From: Dietrich Date: Mon, 20 Jul 2026 13:39:42 +0200 Subject: [PATCH] svg text size and orientation --- turtle-lib/examples/breadboard.rs | 102 ++++++++++++++++++++++++++++++ turtle-lib/examples/export_svg.rs | 9 ++- turtle-lib/src/execution.rs | 2 + turtle-lib/src/export_svg.rs | 26 ++++++-- turtle-lib/src/state.rs | 2 + 5 files changed, 133 insertions(+), 8 deletions(-) create mode 100644 turtle-lib/examples/breadboard.rs diff --git a/turtle-lib/examples/breadboard.rs b/turtle-lib/examples/breadboard.rs new file mode 100644 index 0000000..0466417 --- /dev/null +++ b/turtle-lib/examples/breadboard.rs @@ -0,0 +1,102 @@ +use turtle_lib::*; +#[cfg(feature = "svg")] +#[macroquad::main("Export SVG")] + +async fn main() { + // Create turtle plan + let mut turtle = create_turtle_plan(); + + // Set instant mode so commands execute imqmediately + turtle.set_speed(1200).set_pen_width(0.5); + + breadboard(&mut turtle, 65); + + turtle.hide(); + let mut app = TurtleApp::new().with_commands(turtle.build()); + use macroquad::{ + input::{is_key_pressed, KeyCode}, + text::draw_text, + window::{clear_background, next_frame}, + }; + + loop { + clear_background(WHITE); + app.update(); + app.render(); + + draw_text("Drücke E für SVG-Export", 20.0, 40.0, 32.0, BLACK); + + if is_key_pressed(KeyCode::E) { + match app.export_drawing("test.svg", export::DrawingFormat::Svg) { + Ok(_) => println!("SVG exportiert nach test.svg"), + Err(e) => println!("Fehler beim Export: {:?}", e), + } + } + + next_frame().await; + } +} + +#[cfg(not(feature = "svg"))] +fn main() { + println!("SVG-Export ist nicht aktiviert. Baue mit --features svg"); +} + +fn pin(t: &mut TurtlePlan, size: f32) { + t.left(90.0).forward(size / 2.0); + for _ in 0..5 { + t.right(90.0).forward(size); + } + t.right(90.0).forward(size / 2.0).left(90.0); +} + +fn pin_reihe(t: &mut TurtlePlan, anzahl: usize) { + for x in 0..anzahl { + pin(t, 5.0); + if x < anzahl - 1 { + t.forward(5.0); + } + } +} + +fn pin_spalte(t: &mut TurtlePlan, anzahl: usize, x_coord: f32) { + for x in 0..anzahl { + t.pen_up().go_to(vec2(x_coord, x as f32 * 10.0)).pen_down(); + pin_reihe(t, 5); + } +} + +fn pin_seite(t: &mut TurtlePlan, anzahl: usize, x_coord: f32, color: Color) { + t.pen_up() + .go_to(vec2(x_coord, -2.5)) + .pen_down() + .set_pen_color(color) + .set_heading(90.0); + for x in 0..anzahl { + pin(t, 5.0); + if x < anzahl - 1 { + t.forward(5.0); + } + } +} + +fn breadboard(t: &mut TurtlePlan, anzahl_reihen: usize) { + pin_spalte(t, anzahl_reihen, 0.0); + pin_spalte(t, anzahl_reihen, 65.0); + pin_seite(t, anzahl_reihen, -15.0, BLUE); + pin_seite(t, anzahl_reihen, -25.0, RED); + pin_seite(t, anzahl_reihen, 125.0, BLUE); + pin_seite(t, anzahl_reihen, 135.0, RED); + + // draw outline + t.pen_up().go_to(vec2(-30.0, -5.0)).pen_down(); + t.set_pen_color(BLACK) + .forward(anzahl_reihen as f32 * 10.0 + 10.0) + .right(90.0) + .forward(170.0) + .right(90.0) + .forward(anzahl_reihen as f32 * 10.0 + 10.0) + .right(90.0) + .forward(170.0) + .right(90.0); +} diff --git a/turtle-lib/examples/export_svg.rs b/turtle-lib/examples/export_svg.rs index 9be8aac..1cf011d 100644 --- a/turtle-lib/examples/export_svg.rs +++ b/turtle-lib/examples/export_svg.rs @@ -9,6 +9,7 @@ async fn main() { let mut plan = create_turtle_plan(); plan.forward(100.0) .right(90.0) + .set_speed(950.0) .forward(100.0) .set_pen_color(macroquad::color::GRAY) .set_pen_width(8.0) @@ -17,9 +18,11 @@ async fn main() { .forward(100.0) .right(90.0) .forward(200.0) - .end_fill() - .circle_left(200., 180., 24) - .circle_left(90.0, 180.0, 36) + .end_fill(); + for size in 0..9 { + plan.circle_left(200., 20., 6).write_text("a", size * 10); + } + plan.circle_left(90.0, 180.0, 36) .begin_fill() .circle_left(90.0, 180.0, 36) .circle_left(45.0, 180.0, 26) diff --git a/turtle-lib/src/execution.rs b/turtle-lib/src/execution.rs index c8bead6..57c005f 100644 --- a/turtle-lib/src/execution.rs +++ b/turtle-lib/src/execution.rs @@ -190,6 +190,8 @@ pub(crate) fn execute_command_side_effects( svg_log.push(crate::state::SvgRecord::Text { text: text.clone(), position: params.position, + heading: params.heading, + font_size: *font_size, color: params.color, }); true diff --git a/turtle-lib/src/export_svg.rs b/turtle-lib/src/export_svg.rs index e64f504..71c1c79 100644 --- a/turtle-lib/src/export_svg.rs +++ b/turtle-lib/src/export_svg.rs @@ -174,16 +174,32 @@ pub mod svg_export { SvgRecord::Text { text, position, + heading, + font_size, color, + .. } => { + // Match the offset/rotation applied by the on-screen renderer + // (see draw_text_command in drawing.rs) so the SVG export lines up. + let font_size_val = f32::from(font_size.value()); + let offset_distance = font_size_val / 3.0; + let perpendicular_angle = *heading - std::f32::consts::PI / 2.0; + let text_x = position.x + offset_distance * perpendicular_angle.cos(); + let text_y = position.y + offset_distance * perpendicular_angle.sin(); update_bounds( - &mut min_x, &mut max_x, &mut min_y, &mut max_y, position.x, - position.y, + &mut min_x, &mut max_x, &mut min_y, &mut max_y, text_x, text_y, ); + + let rotation_deg = heading.to_degrees(); let txt = SvgText::new(text.clone()) - .set("x", position.x) - .set("y", position.y) - .set("fill", color_to_svg(*color)); + .set("x", text_x) + .set("y", text_y) + .set("fill", color_to_svg(*color)) + .set("font-size", font_size_val) + .set( + "transform", + format!("rotate({rotation_deg}, {text_x}, {text_y})"), + ); doc = doc.add(txt); } } diff --git a/turtle-lib/src/state.rs b/turtle-lib/src/state.rs index e383dfd..fb58ba6 100644 --- a/turtle-lib/src/state.rs +++ b/turtle-lib/src/state.rs @@ -343,6 +343,8 @@ pub(crate) enum SvgRecord { Text { text: String, position: Vec2, + heading: f32, + font_size: crate::general::FontSize, color: Color, }, }