lib.rs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. use std::fmt::{self, Display, Formatter};
  2. use std::fs::{self, OpenOptions};
  3. use std::io::{self, Write};
  4. use std::path::Path;
  5. mod xml;
  6. use xml::XMLWriter;
  7. const MAX_OUTPUT_FILES: u32 = 500;
  8. /// An SVG document, which has a fixed size (computed in inches) as
  9. /// well as a collection of drawn figures.
  10. pub struct SVG {
  11. stuff: Vec<Box<AsSVG>>,
  12. size: (f64, f64),
  13. }
  14. #[derive(Copy, Clone)]
  15. struct Inches { amt: f64 }
  16. impl Display for Inches {
  17. fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
  18. self.amt.fmt(f)?;
  19. write!(f, "in")
  20. }
  21. }
  22. fn inches(amt: f64) -> Inches { Inches { amt } }
  23. /// Create a new empty SVG document of the specified width and height
  24. pub fn svg(w: f64, h: f64) -> SVG {
  25. SVG {
  26. stuff: Vec::new(),
  27. size: (w, h),
  28. }
  29. }
  30. impl SVG {
  31. /// Print this SVG document to stdout
  32. pub fn to_stdout(self) -> io::Result<()> {
  33. self.write_svg(&mut std::io::stdout())
  34. }
  35. pub fn to_bytes(self) -> io::Result<Vec<u8>> {
  36. let mut buf = Vec::new();
  37. self.write_svg(&mut buf)?;
  38. Ok(buf)
  39. }
  40. /// Print this SVG document to stdout
  41. pub fn output(self, p: &str) -> io::Result<()> {
  42. let output_dir = Path::new("output");
  43. if !output_dir.is_dir() {
  44. fs::create_dir(output_dir)?;
  45. }
  46. let mut file = {
  47. let mut n = 0;
  48. let mut path = format!("output/{}{:05}.svg", p, n);
  49. while Path::new(&path).exists() {
  50. if n > MAX_OUTPUT_FILES {
  51. return Err(io::Error::new(
  52. io::ErrorKind::Other,
  53. "gunpowder_treason: too many output files",
  54. ));
  55. }
  56. n += 1;
  57. path = format!("output/{}{:05}.svg", p, n);
  58. }
  59. eprintln!("writing to {}", path);
  60. OpenOptions::new().write(true).create_new(true).open(&path)?
  61. };
  62. self.write_svg(&mut file)
  63. }
  64. pub fn write_svg<W: Write>(self, buf: &mut W) -> io::Result<()> {
  65. let (w, h) = self.size;
  66. let mut xml = XMLWriter::start(buf)?;
  67. xml.block(
  68. "svg",
  69. &[("xmlns", &"http://www.w3.org/2000/svg"),
  70. ("version", &"1.1"),
  71. ("width", &inches(w)),
  72. ("height", &inches(h)),
  73. ("viewBox", &format!("0 0 {} {}", w, h)),
  74. ("stroke-width", &"0.0001in"),
  75. ],
  76. |mut b| {
  77. for elem in self.stuff {
  78. elem.draw_svg(&mut b)?;
  79. }
  80. Ok(())
  81. }
  82. )
  83. }
  84. /// Add a new drawable thing to this SVG document
  85. pub fn add<T: AsSVG>(&mut self, t: T) {
  86. self.stuff.push(t.consume())
  87. }
  88. }
  89. /// The AsSVG trait represents things which can be rendered to an SVG
  90. /// canvas.
  91. pub trait AsSVG {
  92. fn draw_svg(&self, buf: &mut XMLWriter) -> io::Result<()>;
  93. // This is a bit of a hack to make sure that all of our types live
  94. // long enough: it's now on the implementer of AsSVG to box it up
  95. // and make sure that we can keep a trait object around.
  96. fn consume(self) -> Box<AsSVG>;
  97. }
  98. /// A Line is a sequence of points. It'll be represented as a single
  99. /// dot if it has only one point, and otherwise will be drawn as a
  100. /// line between them.
  101. pub struct Line {
  102. start: (f64, f64),
  103. points: Vec<(f64, f64)>,
  104. }
  105. impl AsSVG for Line {
  106. fn draw_svg(&self, buf: &mut XMLWriter) -> io::Result<()> {
  107. if self.points.len() == 0 {
  108. // if there are no later points, then we just draw a
  109. // 'point' at the starting position, which is just a tiny
  110. // circle
  111. let (x, y) = self.start;
  112. buf.tag("circle",
  113. &[("cx", &x),
  114. ("cy", &y),
  115. ("r", &"0.01"),
  116. ("fill", &"black"),
  117. ],
  118. )
  119. } else {
  120. // Otherwise, we draw a path, which mean assembling this
  121. // somewhat wonky path field
  122. let path = {
  123. let mut path = Vec::new();
  124. let (x, y) = self.start;
  125. write!(&mut path, "M{} {}", x, y)?;
  126. for &(x, y) in self.points.iter() {
  127. write!(&mut path, " L{} {}", x, y)?;
  128. }
  129. String::from_utf8(path).unwrap()
  130. };
  131. buf.tag("path",
  132. &[("d", &path),
  133. ("stroke", &"black"),
  134. ("fill", &"none"),
  135. ],
  136. )
  137. }
  138. }
  139. fn consume(self) -> Box<AsSVG> {
  140. Box::new(self)
  141. }
  142. }
  143. /// Create a new line at the given starting point
  144. pub fn line(x: f64, y: f64) -> Line {
  145. Line::new(x, y)
  146. }
  147. impl Line {
  148. /// Create a new line at the given starting point
  149. pub fn new(x: f64, y: f64) -> Line {
  150. Line {
  151. start: (x, y),
  152. points: Vec::new(),
  153. }
  154. }
  155. /// Draw a line segment from this point to another point
  156. pub fn to(mut self, x: f64, y: f64) -> Line {
  157. self.points.push((x, y));
  158. self
  159. }
  160. }
  161. pub struct Rect {
  162. position: (f64, f64),
  163. size: (f64, f64),
  164. }
  165. pub fn rect(position: (f64, f64), size: (f64, f64)) -> Rect {
  166. Rect { position, size }
  167. }
  168. impl AsSVG for Rect {
  169. fn draw_svg(&self, buf: &mut XMLWriter) -> io::Result<()> {
  170. let (x, y) = self.position;
  171. let (w, h) = self.size;
  172. buf.tag("rect",
  173. &[("x", &x),
  174. ("y", &y),
  175. ("width", &w),
  176. ("height", &h),
  177. ("stroke", &"black"),
  178. ("fill", &"none"),
  179. ]
  180. )
  181. }
  182. fn consume(self) -> Box<AsSVG> {
  183. Box::new(self)
  184. }
  185. }
  186. pub struct Circle {
  187. position: (f64, f64),
  188. radius: f64,
  189. }
  190. pub fn circle(position: (f64, f64), radius: f64) -> Circle {
  191. Circle { position, radius }
  192. }
  193. impl AsSVG for Circle {
  194. fn draw_svg(&self, buf: &mut XMLWriter) -> io::Result<()> {
  195. let (x, y) = self.position;
  196. let r = self.radius;
  197. buf.tag(
  198. "circle",
  199. &[("cx", &x),
  200. ("cy", &y),
  201. ("r", &r),
  202. ("stroke", &"black"),
  203. ("fill", &"none"),
  204. ]
  205. )
  206. }
  207. fn consume(self) -> Box<AsSVG> {
  208. Box::new(self)
  209. }
  210. }