Files
turtle/turtle-lib/examples/sierpinski_triangle.rs
T
dietrich d855750970 Headless SVG Export Macroquad Panic Clarification
Clarified and improved error reporting when drawing routines invoke
Macroquad window or rendering functions (such as `screen_width()` or
`screen_height()`) during headless SVG export (`--export-svg`).

[export.rs](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/src/export.rs)
- Added `ExportError::Execution(String)` variant to represent drawing
  execution failures and panics.
- Implemented `std::fmt::Display` and `std::error::Error` for
  `ExportError`.
- Created `PanicHookGuard` RAII struct ensuring any installed panic
  hooks are automatically restored after execution.
- In `run_headless_svg_export`:
  - Chains onto the existing panic hook to preserve standard panic
    backtrace and line number information.
  - Detects if the panic originated from uninitialized Macroquad context
    (`THREAD_ID.is_some()`).
  - Emits a clear, prominent diagnostic banner explaining that
    window/GUI functions are unavailable in headless mode.
  - Catches the panic via `std::panic::catch_unwind` and returns
    `Err(ExportError::Execution(...))`.
  - In `handle_svg_export`, uses `{e}` (Display) rather than `{e:?}`
    (Debug) for cleaner error reporting.

[lib.rs](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib-macros/src/lib.rs)
- Updated the expanded `main` function generated by `#[turtle_main]` to
  format errors with `{}` (Display) instead of `{:?}` (Debug).

[sierpinski_triangle.rs](file:///home/dietrich/Projekte/Source/turtlers/turtle-lib/examples/sierpinski_triangle.rs)
- Added documentation notes to the module header and
  `sierpinski_triangle_auto` noting that the example requires an active
  graphics window and cannot be exported to SVG headlessly because it
  queries window dimensions.

---

Executed:
```bash
RUST_BACKTRACE=1 cargo run --example sierpinski_triangle --features svg
-- --export-svg sier.svg
```

Output:
```
thread 'main' (49702) panicked at
/home/dietrich/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/macroquad-0.4.16/src/lib.rs:172:13:
assertion failed: THREAD_ID.is_some()
stack backtrace:
0: __rustc::rust_begin_unwind
...
5: macroquad::window::screen_width
6: sierpinski_triangle::sierpinski_triangle_auto
at ./turtle-lib/examples/sierpinski_triangle.rs:100:20
7: sierpinski_triangle::draw_sierpinski
at ./turtle-lib/examples/sierpinski_triangle.rs:39:5
...
================================================================================
Headless SVG Export Note:
A Macroquad window/rendering function (e.g. `screen_width()`,
`screen_height()`,
or input check) was called while running in headless export mode.
Headless export does not initialize a graphics window. To resolve this:
  - Use relative turtle commands or fixed coordinates instead of window
    queries, or
  - Run the program in windowed GUI mode without the `--export-svg`
    flag.
    ================================================================================

Error exporting SVG: execution error: Drawing function called Macroquad
window/GUI functions (e.g. `screen_width()`, `screen_height()`) which
are unavailable in headless SVG export mode.
```
- Process exited cleanly with exit code 1.

Executed:
```bash
cargo run --example koch --features svg -- --export-svg /tmp/koch.svg
```
Output:
```
SVG exported successfully to: /tmp/koch.svg
```
Exit code 0.

```bash
cargo test --package turtle-lib
cargo clippy --package turtle-lib --features svg -- -Wclippy::pedantic
-Aclippy::cast_precision_loss -Aclippy::cast_sign_loss
-Aclippy::cast_possible_truncation
```
- 17 unit tests + 34 doctests passed (1 ignored doctest).
- Clippy completed with zero warnings.
2026-09-20 11:11:59 +02:00

106 lines
3.9 KiB
Rust

//! Draws a Sierpiński triangle with automatic positioning and sizing.
//!
//! The Sierpiński triangle is a fairly simple self-similar fractal geometric shape: it consists of
//! many nested equilateral triangles. More formally, such a triangle is itself three triangles of
//! one level below and a size divided by two. Level zero means a simple equilateral triangle. The
//! drawing procedure is as follows, for a given level and size:
//!
//! * If level is 0
//! * Draw an equilateral triangle of the given size.
//! * otherwise
//! * Draw the half-sized level - 1 triangle at the bottom left.
//! * Go the start of the bottom-right slot.
//! * Draw a half-sized level - 1 triangle.
//! * Go to the start of the top slot.
//! * Draw a half-sized level - 1 triangle.
//!
//! That is relatively easy to implement, as long as you follow these steps and let recursion do
//! the rest. Another little bonus this example provides is the ability to customize the drawing
//! size: the triangle will stay correctly sized and positioned automatically.
//!
//! Note: This example queries `screen_width()` and `screen_height()` to calculate positioning
//! relative to the window. Consequently, it requires an active graphics window and does not
//! support headless SVG export (`--export-svg`).
use macroquad::window::{screen_height, screen_width};
use turtle_lib::*;
/// The number of levels to draw following the recursive procedure.
const LEVELS: u8 = 9;
/// Triangle size (adjust to fit nicely in window)
const TRIANGLE_SIZE: f32 = 300.0;
#[turtle_main("Sierpiński Triangle")]
fn draw_sierpinski(turtle: &mut TurtlePlan) {
turtle.set_speed(1500); // Fast drawing
turtle.set_pen_width(0.2);
// Auto-sized procedure
sierpinski_triangle_auto(turtle, LEVELS);
// Hide turtle when done drawing in order to fully reveal the result
turtle.hide();
}
/// Recursive function drawing a Sierpiński triangle.
///
/// It will do it with the given `turtle` and start at its current position and heading. `level`
/// is the depth of the drawing to be done, zero meaning a simple triangle. `size` is the length
/// of the outermost triangle's sides.
fn sierpinski_triangle(turtle: &mut TurtlePlan, level: u8, size: f32) {
// When level 0 is reached, just draw an equilateral triangle.
if level == 0 {
turtle.pen_down();
for _ in 0..3 {
turtle.forward(size);
turtle.left(120.0);
}
turtle.pen_up();
} else {
// Parameters for subsequent calls are the same.
let next_level = level - 1;
let next_size = size / 2.0;
// Bottom-left triangle.
sierpinski_triangle(turtle, next_level, next_size);
turtle.forward(next_size);
// Bottom-right triangle.
sierpinski_triangle(turtle, next_level, next_size);
turtle.left(120.0);
turtle.forward(next_size);
turtle.right(120.0);
// Top triangle.
sierpinski_triangle(turtle, next_level, next_size);
// Go back to the start.
turtle.right(120.0);
turtle.forward(next_size);
turtle.left(120.0);
}
}
/// Draws a Sierpiński triangle with automatic size and start point.
///
/// `level` is still required, it can't be computed automatically. However, given the used
/// canvas size, it will compute the appropriate size and start point so the triangle gets
/// centered and occupies as much drawing space as possible while staying in bounds.
///
/// Note: Because this function queries window dimensions, it requires an active graphics
/// window and cannot be run in headless SVG export mode.
fn sierpinski_triangle_auto(turtle: &mut TurtlePlan, level: u8) {
let size = TRIANGLE_SIZE;
turtle.pen_up();
turtle.go_to((-screen_width() / 2.0 + 20.0, -screen_height() / 2.0 + 20.0));
turtle.set_heading(0.0); // 0 = East (pointing right)
// The drawing itself.
sierpinski_triangle(turtle, level, size);
}