1use std::f32::consts::PI;
2use std::io::{self, Write};
3use std::ops::{Add, Neg};
4
5#[derive(Copy, Clone, Debug)]
6pub struct Position(f32, f32);
7
8impl Position {
9 pub fn origin() -> Position {
10 Position(0.0, 0.0)
11 }
12
13 pub fn min(&self, other: &Position) -> Position {
14 Position(self.0.min(other.0), self.1.min(other.1))
15 }
16
17 pub fn max(&self, other: &Position) -> Position {
18 Position(self.0.max(other.0), self.1.max(other.1))
19 }
20
21 pub fn min_max(&self, min_max: &(Position, Position)) -> (Position, Position) {
22 (self.min(&min_max.0), self.max(&min_max.1))
23 }
24}
25
26struct Bounds {
27 min_max: Option<(Position, Position)>,
28}
29
30impl Bounds {
31 fn new() -> Bounds {
32 Bounds { min_max: None }
33 }
34
35 fn add_position(&mut self, pos: Position) {
36 let mm = match self.min_max {
37 None => (pos, pos),
38 Some(ref a) => pos.min_max(a),
39 };
40
41 self.min_max = Some(mm);
42 }
43
44 #[allow(dead_code)]
45 fn is_bounded(&self) -> bool {
46 self.min_max.is_some()
47 }
48
49 fn width(&self) -> f32 {
50 let (min, max) = self.min_max.unwrap();
51 (max.0 - min.0).abs()
52 }
53
54 fn height(&self) -> f32 {
55 let (min, max) = self.min_max.unwrap();
56 (max.1 - min.1).abs()
57 }
58
59 fn min_x(&self) -> f32 {
60 let (min, _) = self.min_max.unwrap();
61 min.0
62 }
63 fn min_y(&self) -> f32 {
64 let (min, _) = self.min_max.unwrap();
65 min.1
66 }
67
68 fn max_x(&self) -> f32 {
69 let (_, max) = self.min_max.unwrap();
70 max.0
71 }
72 fn max_y(&self) -> f32 {
73 let (_, max) = self.min_max.unwrap();
74 max.1
75 }
76}
77
78impl Add<Position> for Position {
79 type Output = Position;
80 fn add(self, other: Position) -> Self::Output {
81 Position(self.0 + other.0, self.1 + other.1)
82 }
83}
84
85#[derive(Copy, Clone, Debug)]
86pub struct Degree(pub f32);
87
88#[derive(Copy, Clone, Debug)]
89pub struct Radiant(pub f32);
90
91impl Into<Degree> for Radiant {
92 fn into(self) -> Degree {
93 Degree(self.0 * 180.0 / PI)
94 }
95}
96
97impl Into<Degree> for f32 {
98 fn into(self) -> Degree {
99 Degree(self)
100 }
101}
102
103impl Into<Radiant> for Degree {
104 fn into(self) -> Radiant {
105 Radiant(self.0 * PI / 180.0)
106 }
107}
108
109#[derive(Copy, Clone, Debug)]
110pub struct Distance(f32);
111
112impl Into<Distance> for f32 {
113 fn into(self) -> Distance {
114 Distance(self)
115 }
116}
117
118impl Neg for Distance {
119 type Output = Distance;
120 fn neg(self) -> Self::Output {
121 Distance(-self.0)
122 }
123}
124
125impl Neg for Degree {
126 type Output = Degree;
127 fn neg(self) -> Self::Output {
128 Degree(-self.0)
129 }
130}
131
132pub trait Turtle {
133 fn forward<T: Into<Distance>>(&mut self, distance: T);
135
136 fn backward<T: Into<Distance>>(&mut self, distance: T) {
138 self.forward(-distance.into())
139 }
140
141 fn move_forward<T: Into<Distance>>(&mut self, distance: T);
143
144 fn rotate<T: Into<Degree>>(&mut self, angle: T);
148
149 fn right<T: Into<Degree>>(&mut self, angle: T) {
151 self.rotate(-angle.into());
152 }
153
154 fn left<T: Into<Degree>>(&mut self, angle: T) {
156 self.rotate(angle.into());
157 }
158
159 fn is_pen_down(&self) -> bool;
161
162 fn is_pen_up(&self) -> bool {
164 !self.is_pen_down()
165 }
166
167 fn pen_down(&mut self);
169
170 fn pen_up(&mut self);
172
173 fn goto(&mut self, pos: Position);
174
175 fn home(&mut self) {
176 self.goto(Position::origin());
177 }
178
179 fn push(&mut self);
181
182 fn pop(&mut self);
184}
185
186#[derive(Clone)]
187struct TurtleState {
188 pos: Position,
189 angle: Degree,
190 pendown: bool,
191}
192
193pub struct Canvas {
194 states: Vec<TurtleState>,
195 paths: Vec<Vec<Position>>,
196}
197
198impl Canvas {
199 pub fn new() -> Canvas {
200 let init_pos = Position::origin();
201 let init_state = TurtleState {
202 pos: init_pos,
203 angle: Degree(0.0), pendown: true, };
207 Canvas {
208 states: vec![init_state],
209 paths: vec![vec![init_pos]],
210 }
211 }
212
213 #[inline]
214 fn current_state_mut(&mut self) -> &mut TurtleState {
215 self.states.last_mut().unwrap()
216 }
217
218 #[inline]
219 fn current_state(&self) -> &TurtleState {
220 self.states.last().unwrap()
221 }
222
223 #[inline]
224 fn direction(&self, distance: Distance) -> (f32, f32) {
225 let state = self.current_state();
226 let rad: Radiant = state.angle.into();
227 let (sin, cos) = rad.0.sin_cos();
228 let dx = -sin * distance.0;
229 let dy = cos * distance.0;
230 (dx, dy)
231 }
232
233 fn line_to(&mut self, dst: Position) {
234 self.paths.last_mut().unwrap().push(dst);
235 }
236
237 fn move_to(&mut self, dst: Position) {
238 if self.paths.is_empty() {
239 self.paths.push(vec![dst]);
240 } else {
241 let begin_new_path = self.paths.last().unwrap().len() > 1;
242 if begin_new_path {
243 self.paths.push(vec![dst]);
244 } else {
245 self.paths.last_mut().unwrap()[0] = dst;
247 }
248 }
249 }
250
251 fn foreach_position<F: FnMut(Position)>(&self, mut f: F, scale_x: f32, scale_y: f32) {
252 for path in self.paths.iter() {
253 for pos in path.iter() {
254 f(Position(pos.0 * scale_x, pos.1 * scale_y));
255 }
256 }
257 }
258
259 pub fn save_eps<W: Write>(&self, wr: &mut W) -> io::Result<()> {
261 let mut bounds = Bounds::new();
263
264 self.foreach_position(|pos| bounds.add_position(pos), 1.0, 1.0);
266
267 let (min_width, min_height) = (100.0, 100.0);
268 let width = bounds.width().max(min_width);
269 let height = bounds.height().max(min_height);
270 let border_percent = 0.1;
271
272 let scale = 1.0 + 2.0 * border_percent;
273
274 writeln!(
275 wr,
276 r#"%%!PS-Adobe-3.0 EPSF-3.0
277%%Creator: https://github.com/mneumann/turtle-graphics-rs
278%%DocumentData: Clean7Bit
279%%Origin: 0 0
280%%BoundingBox: {} {} {} {}
281%%LanguageLevel: 2
282%%Pages: 1
283%%Page: 1 1
284"#,
285 bounds.min_x() - border_percent * width,
286 bounds.min_y() - border_percent * height,
287 bounds.max_x() + border_percent * width,
288 bounds.max_y() + border_percent * height
289 )?;
290
291 let stroke_width = scale * width.max(height) / 1000.0;
293 writeln!(wr, r#"{} setlinewidth"#, stroke_width)?;
294
295 for path in self.paths.iter() {
296 if let Some((head, tail)) = path.split_first() {
297 writeln!(wr, "newpath")?;
298 writeln!(wr, " {} {} moveto", head.0, head.1)?;
299 for pos in tail {
300 writeln!(wr, r#" {} {} lineto"#, pos.0, pos.1)?;
301 }
302 writeln!(wr, r#"stroke"#)?;
303 }
304 }
305 writeln!(wr, "%%EOF")
306 }
307
308 pub fn save_svg<W: Write>(&self, wr: &mut W) -> io::Result<()> {
310 let mut bounds = Bounds::new();
312
313 self.foreach_position(|pos| bounds.add_position(pos), 1.0, -1.0);
317
318 let (min_width, min_height) = (100.0, 100.0);
319 let width = bounds.width().max(min_width);
320 let height = bounds.height().max(min_height);
321 let border_percent = 0.1;
322
323 let top_left = Position(
324 bounds.min_x() - border_percent * width,
325 bounds.min_y() - border_percent * height,
326 );
327
328 let scale = 1.0 + 2.0 * border_percent;
329
330 writeln!(
331 wr,
332 r#"<?xml version="1.0" encoding="UTF-8"?>
333 <svg xmlns="http://www.w3.org/2000/svg"
334 version="1.1" baseProfile="full"
335 viewBox="{} {} {} {}">"#,
336 top_left.0,
337 top_left.1,
338 scale * width,
339 scale * height
340 )?;
341
342 let stroke_width = scale * width.max(height) / 1000.0;
344 writeln!(
345 wr,
346 r#"<g stroke="black" stroke-width="{}" fill="none">"#,
347 stroke_width
348 )?;
349
350 for path in self.paths.iter() {
351 if let Some((head, tail)) = path.split_first() {
352 let head = Position(head.0, -1.0 * head.1);
354
355 write!(wr, r#"<path d="M{} {}"#, head.0, head.1)?;
356 for pos in tail {
357 let pos = Position(pos.0, -1.0 * pos.1);
358 write!(wr, r#" L{} {}"#, pos.0, pos.1)?;
359 }
360 writeln!(wr, r#"" />"#)?;
361 }
362 }
363 writeln!(wr, r#"</g>"#)?;
364
365 writeln!(wr, "</svg>")
366 }
367}
368
369impl Turtle for Canvas {
370 fn forward<T: Into<Distance>>(&mut self, distance: T) {
372 let (dx, dy) = self.direction(distance.into());
373 let src: Position = self.current_state().pos;
374 let dst = Position(src.0 + dx, src.1 + dy);
375 if self.is_pen_down() {
376 self.line_to(dst);
377 }
378 self.current_state_mut().pos = dst;
379 }
380
381 fn rotate<T: Into<Degree>>(&mut self, angle: T) {
382 let angle: Degree = angle.into();
383 self.current_state_mut().angle.0 += angle.0;
384 }
385
386 fn move_forward<T: Into<Distance>>(&mut self, distance: T) {
387 let (dx, dy) = self.direction(distance.into());
388 let src: Position = self.current_state().pos;
389 let dst = Position(src.0 + dx, src.1 + dy);
390 self.move_to(dst);
391 self.current_state_mut().pos = dst;
392 }
393
394 fn is_pen_down(&self) -> bool {
395 self.current_state().pendown
396 }
397
398 fn pen_down(&mut self) {
400 let pos = self.current_state().pos;
401 self.move_to(pos);
402 self.current_state_mut().pendown = true;
403 }
404
405 fn pen_up(&mut self) {
407 self.current_state_mut().pendown = false;
408 }
409
410 fn goto(&mut self, position: Position) {
412 self.current_state_mut().pos = position;
413 self.move_to(position);
414 }
415
416 fn push(&mut self) {
418 let state = self.current_state_mut().clone();
419 self.states.push(state);
420 }
421
422 fn pop(&mut self) {
424 self.states.pop();
425 let pos = self.current_state().pos;
426 self.move_to(pos);
427 }
428}