Browse Source

Initial slightly wonky API for gunpowder_treason

Getty Ritter 5 years ago
commit
ebcf35e4bf
3 changed files with 226 additions and 0 deletions
  1. 3 0
      .gitignore
  2. 6 0
      Cargo.toml
  3. 217 0
      src/lib.rs

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+/target
+**/*.rs.bk
+Cargo.lock

+ 6 - 0
Cargo.toml

@@ -0,0 +1,6 @@
+[package]
+name = "gunpowder_treason"
+version = "0.1.0"
+authors = ["Getty Ritter <gettylefou@gmail.com>"]
+
+[dependencies]

+ 217 - 0
src/lib.rs

@@ -0,0 +1,217 @@
+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)
+    }
+}