123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- use std::fmt::Display;
- use std::io::Write;
- /// An SVG document
- pub struct SVG {
- stuff: Vec<Box<dyn AsSVG>>,
- size: (f64, f64),
- }
- fn xml_tag(w: &mut Vec<u8>, name: &str, attrs: &[(&str, &dyn Display)]) {
- write!(w, "<{}", name);
- for (k, v) in attrs {
- write!(w, " {}=\"{}\"", k, v);
- }
- writeln!(w, "/>");
- }
- fn xml_open(w: &mut Vec<u8>, name: &str, attrs: &[(&str, &dyn Display)]) {
- write!(w, "<{}", name);
- for (k, v) in attrs {
- write!(w, " {}=\"{}\"", k, v);
- }
- writeln!(w, ">");
- }
- fn xml_close(w: &mut Vec<u8>, name: &str) {
- write!(w, "<{}/>", name);
- }
- /// 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) {
- let buf = self.to_bytes();
- println!("{}", String::from_utf8(buf).unwrap());
- }
- /// Write this SVG document to a file
- pub fn to_file<W: Write>(self, w: &mut W) -> Result<(), std::io::Error> {
- let buf = self.to_bytes();
- w.write(&buf)?;
- Ok(())
- }
- pub fn to_bytes(self) -> Vec<u8> {
- let mut buf = Vec::new();
- let (w, h) = self.size;
- writeln!(buf, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
- xml_open(
- &mut buf, "svg",
- &[("xmlns", &"http://www.w3.org/2000/svg"),
- ("version", &"1.1"),
- ("width", &w),
- ("height", &h),
- ],
- );
- for elem in self.stuff {
- elem.draw_svg(&mut buf);
- }
- xml_close(&mut buf, "svg");
- buf
- }
- /// 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 Vec<u8>);
- // 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 Vec<u8>) {
- 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;
- xml_tag(
- buf, "circle",
- &[("cx", &x),
- ("cy", &y),
- ("r", &"0.5"),
- ("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()
- };
- xml_tag(
- buf, "path",
- &[("d", &path), ("stroke", &"black")],
- );
- }
- }
- 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 draw_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 Vec<u8>) {
- let (x, y) = self.position;
- let (w, h) = self.size;
- xml_tag(
- buf, "rect",
- &[("x", &x),
- ("y", &y),
- ("width", &w),
- ("height", &h),
- ("stroke", &"black"),
- ("fill", &"white"),
- ]
- );
- }
- 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 Vec<u8>) {
- let (x, y) = self.position;
- let r = self.radius;
- xml_tag(
- buf, "circle",
- &[("cx", &x),
- ("cy", &y),
- ("r", &r),
- ("stroke", &"black"),
- ("fill", &"white"),
- ]
- );
- }
- fn consume(self) -> Box<AsSVG> {
- Box::new(self)
- }
- }
|