gdritter repos gunpowder_treason / master src / lib.rs
master

Tree @master (Download .tar.gz)

lib.rs @master

5b72ea1
419e315
5b72ea1
419e315
ebcf35e
60a15ed
 
 
ab1ab67
 
5df598d
 
ebcf35e
db72b16
ebcf35e
 
 
ec7c38d
5df598d
ec7c38d
 
5b72ea1
ec7c38d
 
 
 
 
 
 
ebcf35e
 
 
 
 
 
 
 
 
 
5e66bb3
 
ebcf35e
 
5e66bb3
 
 
 
ebcf35e
 
5b72ea1
ed0f021
419e315
 
 
 
 
5b72ea1
ab1ab67
5b72ea1
419e315
 
 
 
 
 
5b72ea1
 
 
 
5df598d
419e315
5b72ea1
ed0f021
5b72ea1
 
 
ed0f021
5df598d
84babcf
5df598d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ebcf35e
 
 
 
 
 
 
 
 
 
 
 
 
5df598d
ebcf35e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5df598d
ebcf35e
 
 
 
 
5df598d
ebcf35e
 
5e66bb3
ebcf35e
 
5e66bb3
ebcf35e
 
 
 
 
 
 
5e66bb3
db72b16
5e66bb3
ebcf35e
 
 
 
5df598d
5e66bb3
 
 
 
 
ebcf35e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ec7c38d
ebcf35e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5df598d
ebcf35e
 
5df598d
ebcf35e
 
 
 
 
60a15ed
ebcf35e
5e66bb3
ebcf35e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5df598d
ebcf35e
 
5df598d
 
ebcf35e
 
 
 
60a15ed
ebcf35e
5e66bb3
ebcf35e
 
 
 
 
 
use std::fmt::{self, Display, Formatter};
use std::fs::{self, OpenOptions};
use std::io::{self, Write};
use std::path::Path;

mod xml;
use xml::XMLWriter;

const MAX_OUTPUT_FILES: u32 = 500;

/// An SVG document, which has a fixed size (computed in inches) as
/// well as a collection of drawn figures.
pub struct SVG {
    stuff: Vec<Box<AsSVG>>,
    size: (f64, f64),
}

#[derive(Copy, Clone)]
struct Inches { amt: f64 }

impl Display for Inches {
    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
        self.amt.fmt(f)?;
        write!(f, "in")
    }
}

fn inches(amt: f64) -> Inches { Inches { amt } }

/// Create a new empty SVG document of the specified width and height
pub fn svg(w: f64, h: f64) -> SVG {
    SVG {
        stuff: Vec::new(),
        size: (w, h),
    }
}

impl SVG {
    /// Print this SVG document to stdout
    pub fn to_stdout(self) -> io::Result<()> {
        self.write_svg(&mut std::io::stdout())
    }

    pub fn to_bytes(self) -> io::Result<Vec<u8>> {
        let mut buf = Vec::new();
        self.write_svg(&mut buf)?;
        Ok(buf)
    }

    /// Print this SVG document to stdout
    pub fn output(self, p: &str) -> io::Result<()> {
        let output_dir = Path::new("output");
        if !output_dir.is_dir() {
            fs::create_dir(output_dir)?;
        }

        let mut file = {
            let mut n = 0;
            let mut path = format!("output/{}{:05}.svg", p, n);
            while Path::new(&path).exists() {
                if n > MAX_OUTPUT_FILES {
                    return Err(io::Error::new(
                        io::ErrorKind::Other,
                        "gunpowder_treason: too many output files",
                    ));
                }
                n += 1;
                path = format!("output/{}{:05}.svg", p, n);
            }
            eprintln!("writing to {}", path);
            OpenOptions::new().write(true).create_new(true).open(&path)?
        };
        self.write_svg(&mut file)
    }


   pub fn write_svg<W: Write>(self, buf: &mut W) -> io::Result<()> {
       let (w, h) = self.size;
       let mut xml = XMLWriter::start(buf)?;
       xml.block(
           "svg",
           &[("xmlns", &"http://www.w3.org/2000/svg"),
             ("version", &"1.1"),
             ("width", &inches(w)),
             ("height", &inches(h)),
             ("viewBox", &format!("0 0 {} {}", w, h)),
             ("stroke-width", &"0.0001in"),
           ],
           |mut b| {
               for elem in self.stuff {
                   elem.draw_svg(&mut b)?;
               }
               Ok(())
           }
       )
    }

    /// Add a new drawable thing to this SVG document
    pub fn add<T: AsSVG>(&mut self, t: T) {
        self.stuff.push(t.consume())
    }
}



/// The AsSVG trait represents things which can be rendered to an SVG
/// canvas.
pub trait AsSVG {
    fn draw_svg(&self, buf: &mut XMLWriter) -> io::Result<()>;
    // This is a bit of a hack to make sure that all of our types live
    // long enough: it's now on the implementer of AsSVG to box it up
    // and make sure that we can keep a trait object around.
    fn consume(self) -> Box<AsSVG>;
}

/// A Line is a sequence of points. It'll be represented as a single
/// dot if it has only one point, and otherwise will be drawn as a
/// line between them.
pub struct Line {
    start: (f64, f64),
    points: Vec<(f64, f64)>,
}

impl AsSVG for Line {
    fn draw_svg(&self, buf: &mut XMLWriter) -> io::Result<()> {
        if self.points.len() == 0 {
            // if there are no later points, then we just draw a
            // 'point' at the starting position, which is just a tiny
            // circle
            let (x, y) = self.start;
            buf.tag("circle",
                &[("cx", &x),
                  ("cy", &y),
                  ("r", &"0.01"),
                  ("fill", &"black"),
                ],
            )
        } else {
            // Otherwise, we draw a path, which mean assembling this
            // somewhat wonky path field

            let path = {
                let mut path = Vec::new();
                let (x, y) = self.start;
                write!(&mut path, "M{} {}", x, y)?;
                for &(x, y) in self.points.iter() {
                    write!(&mut path, " L{} {}", x, y)?;
                }
                String::from_utf8(path).unwrap()
            };

            buf.tag("path",
                &[("d", &path),
                  ("stroke", &"black"),
                  ("fill", &"none"),
                ],
            )
        }
    }

    fn consume(self) -> Box<AsSVG> {
        Box::new(self)
    }
}

/// Create a new line at the given starting point
pub fn line(x: f64, y: f64) -> Line {
    Line::new(x, y)
}

impl Line {
    /// Create a new line at the given starting point
    pub fn new(x: f64, y: f64) -> Line {
        Line {
            start: (x, y),
            points: Vec::new(),
        }
    }

    /// Draw a line segment from this point to another point
    pub fn to(mut self, x: f64, y: f64) -> Line {
        self.points.push((x, y));
        self
    }
}

pub struct Rect {
    position: (f64, f64),
    size: (f64, f64),
}

pub fn rect(position: (f64, f64), size: (f64, f64)) -> Rect {
    Rect { position, size }
}

impl AsSVG for Rect {
    fn draw_svg(&self, buf: &mut XMLWriter) -> io::Result<()> {
        let (x, y) = self.position;
        let (w, h) = self.size;
        buf.tag("rect",
            &[("x", &x),
              ("y", &y),
              ("width", &w),
              ("height", &h),
              ("stroke", &"black"),
              ("fill", &"none"),
            ]
        )
    }

    fn consume(self) -> Box<AsSVG> {
        Box::new(self)
    }
}


pub struct Circle {
    position: (f64, f64),
    radius: f64,
}

pub fn circle(position: (f64, f64), radius: f64) -> Circle {
    Circle { position, radius }
}

impl AsSVG for Circle {
    fn draw_svg(&self, buf: &mut XMLWriter) -> io::Result<()> {
        let (x, y) = self.position;
        let r = self.radius;
        buf.tag(
            "circle",
            &[("cx", &x),
              ("cy", &y),
              ("r", &r),
              ("stroke", &"black"),
              ("fill", &"none"),
            ]
        )
    }

    fn consume(self) -> Box<AsSVG> {
        Box::new(self)
    }
}