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