recaman_svg/
lib.rs

1// Copyright 2018, Joren Van Onder
2// This program is free software: you can redistribute it and/or modify
3// it under the terms of the GNU General Public License as published by
4// the Free Software Foundation, either version 3 of the License, or
5// (at your option) any later version.
6
7// This program is distributed in the hope that it will be useful,
8// but WITHOUT ANY WARRANTY; without even the implied warranty of
9// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10// GNU General Public License for more details.
11
12// You should have received a copy of the GNU General Public License
13// along with this program.  If not, see <http://www.gnu.org/licenses/>.
14extern crate svg;
15
16use std::process;
17use std::collections::HashSet;
18
19use svg::node::element::path::Data;
20use svg::node::element::Path;
21use svg::Document;
22
23pub fn recaman_sequence(n: u32) -> Vec<u32> {
24    if n == 0 {
25        return vec![];
26    }
27
28    let mut used_numbers = HashSet::new();
29    let mut seq: Vec<u32> = vec![0];
30    while seq.len() < n as usize {
31        let seq_len = seq.len() as i32;
32        let last = *seq.last().unwrap() as i32;
33        let subtract_result = last - seq_len;
34
35        let a: i32;
36        if subtract_result > 0 && !used_numbers.contains(&(subtract_result as u32)) {
37            a = subtract_result;
38        } else {
39            a = last + seq_len;
40        }
41
42        seq.push(a as u32);
43        used_numbers.insert(a as u32);
44    }
45
46    seq
47}
48
49/// Calculates the line height that will center the image.
50///
51/// Every n will jump one position farther than the last
52/// one. Therefore every arc will have a radius exactly 1 bigger than
53/// the last one. Because unit arcs (radius=1) are used the required
54/// line height at which we have to draw is 0.5 * n.
55fn get_line_height(recaman_sequence: &Vec<u32>) -> f32 {
56    0.5 * recaman_sequence.len() as f32
57}
58
59pub fn generate_svg_document(recaman_sequence: &Vec<u32>, stroke_width: f32) -> svg::Document {
60    const ARC_LARGE_FLAG: u32 = 0;
61    const ARC_ANGLE: u32 = 0;
62    const ARC_RX: u32 = 1;
63    const ARC_RY: u32 = 1;
64    const X_SCALE: u32 = 2;
65
66    let scale = |x: f32| x * X_SCALE as f32;
67    let line_height = scale(get_line_height(recaman_sequence));
68
69    let mut data = Data::new().move_to((0, line_height));
70    let mut should_go_up = false;
71    let mut last_x = 0;
72    let mut max_x = u32::min_value();
73    for x in recaman_sequence {
74        let x = *x;
75        let sweep_flag = if x > last_x {
76            !should_go_up
77        } else {
78            // invert if going backwards
79            should_go_up
80        };
81
82        data = data.elliptical_arc_to((
83            ARC_RX,
84            ARC_RY,
85            ARC_ANGLE,
86            ARC_LARGE_FLAG,
87            sweep_flag as u32,
88            scale(x as f32),
89            line_height,
90        ));
91
92        last_x = x;
93        should_go_up = !should_go_up;
94        max_x = max_x.max(x);
95    }
96
97    const MARGIN: f32 = 2.0;
98    let document = Document::new().set(
99        "viewBox",
100        (
101            scale(0.0 - MARGIN),
102            scale(0.0 - MARGIN),
103            scale(max_x as f32 + MARGIN * 2.0),
104            line_height * 2.0 + scale(MARGIN * 2.0),
105        ),
106    );
107    let path = Path::new()
108        .set("fill", "none")
109        .set("stroke", "black")
110        .set("stroke-width", stroke_width)
111        .set("d", data);
112
113    document.add(path)
114}
115
116pub fn write_svg_document(document: svg::Document, filename: &str) {
117    svg::save(filename, &document).unwrap_or_else(|err| {
118        eprintln!("Couldn't write file: {}", err.to_string().to_lowercase());
119        process::exit(1);
120    });
121}