123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- 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)
- }
- }
|