use std::fmt; /// `rgb({r},{g},{b})` #[derive(Copy, Clone, PartialEq)] pub struct Color { pub r: u8, pub g: u8, pub b: u8, } impl fmt::Display for Color { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "rgb({},{},{})", self.r, self.g, self.b) } } pub fn rgb(r: u8, g: u8, b: u8) -> Color { Color { r, g, b } } pub fn black() -> Color { rgb(0, 0, 0) } pub fn white() -> Color { rgb(255, 255, 255) } pub fn red() -> Color { rgb(255, 0, 0) } pub fn green() -> Color { rgb(0, 255, 0) } pub fn blue() -> Color { rgb(0, 0, 255) } /// `fill:{self}` #[derive(Copy, Clone, PartialEq)] pub enum Fill { Color(Color), None, } /// `stroke:{self}` #[derive(Copy, Clone, PartialEq)] pub enum Stroke { Color(Color, f32), None, } /// `fill:{fill};stroke:{stroke};fill-opacity:{opacity};` #[derive(Copy, Clone, PartialEq)] pub struct Style { pub fill: Fill, pub stroke: Stroke, pub opacity: f32, pub stroke_opacity: f32, } impl fmt::Display for Style { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{};{};fill-opacity:{};stroke-opacity:{};", self.fill, self.stroke, self.opacity, self.stroke_opacity, ) } } impl Style { pub fn default() -> Self { Style { fill: Fill::Color(black()), stroke: Stroke::None, opacity: 1.0, stroke_opacity: 1.0, } } } impl fmt::Display for Fill { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Fill::Color(color) => write!(f, "fill:{}", color), Fill::None => write!(f, "fill:none"), } } } impl fmt::Display for Stroke { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Stroke::Color(color, radius) => write!(f, "stroke:{};stroke-width:{}", color, radius), Stroke::None => write!(f, "stroke:none"), } } } impl Into for Color { fn into(self) -> Fill { Fill::Color(self) } } impl Into for Color { fn into(self) -> Stroke { Stroke::Color(self, 1.0) } } /// ``, #[derive(Copy, Clone, PartialEq)] pub struct Rectangle { pub x: f32, pub y: f32, pub w: f32, pub h: f32, pub style: Style, pub border_radius: f32, } pub fn rectangle(x: f32, y: f32, w: f32, h: f32) -> Rectangle { Rectangle { x, y, w, h, style: Style::default(), border_radius: 0.0, } } impl Rectangle { pub fn fill(mut self, fill: F) -> Self where F: Into { self.style.fill = fill.into(); self } pub fn stroke(mut self, stroke: S) -> Self where S: Into { self.style.stroke = stroke.into(); self } pub fn opacity(mut self, opacity: f32) -> Self { self.style.opacity = opacity; self } pub fn stroke_opacity(mut self, opacity: f32) -> Self { self.style.stroke_opacity = opacity; self } pub fn style(mut self, style: Style) -> Self { self.style = style; self } pub fn border_radius(mut self, r: f32) -> Self { self.border_radius = r; self } pub fn offset(mut self, dx: f32, dy: f32) -> Self { self.x += dx; self.y += dy; self } pub fn inflate(mut self, dx: f32, dy: f32) -> Self { self.x -= dx; self.y -= dy; self.w += 2.0 * dx; self.h += 2.0 * dy; self } } impl fmt::Display for Rectangle { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, r#"""#, self.x, self.y, self.w, self.h, self.border_radius, self.style, ) } } /// `` #[derive(Copy, Clone, PartialEq)] pub struct Circle { pub x: f32, pub y: f32, pub radius: f32, pub style: Style, } impl Circle { pub fn fill(mut self, fill: F) -> Self where F: Into { self.style.fill = fill.into(); self } pub fn stroke(mut self, stroke: S) -> Self where S: Into { self.style.stroke = stroke.into(); self } pub fn style(mut self, style: Style) -> Self { self.style = style; self } pub fn opacity(mut self, opacity: f32) -> Self { self.style.opacity = opacity; self } pub fn stroke_opacity(mut self, opacity: f32) -> Self { self.style.stroke_opacity = opacity; self } pub fn offset(mut self, dx: f32, dy: f32) -> Self { self.x += dx; self.y += dy; self } pub fn inflate(mut self, by: f32) -> Self { self.radius += by; self } } impl fmt::Display for Circle { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, r#"""#, self.x, self.y, self.radius, self.style, ) } } /// `` #[derive(Clone, PartialEq)] pub struct Polygon { pub points: Vec<[f32; 2]>, pub closed: bool, pub style: Style, } impl fmt::Display for Polygon { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, r#""#, self.style) } } pub fn polygon>(pts: &[T]) -> Polygon { let mut points = Vec::with_capacity(pts.len()); for p in pts { points.push((*p).into()); } Polygon { points, closed: true, style: Style::default(), } } pub fn triangle(x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) -> Polygon { polygon(&[[x1, y1], [x2, y2], [x3, y3]]) } impl Polygon { pub fn open(mut self) -> Self { self.closed = false; self } pub fn fill(mut self, fill: F) -> Self where F: Into { self.style.fill = fill.into(); self } pub fn stroke(mut self, stroke: S) -> Self where S: Into { self.style.stroke = stroke.into(); self } pub fn opacity(mut self, opacity: f32) -> Self { self.style.opacity = opacity; self } pub fn stroke_opacity(mut self, opacity: f32) -> Self { self.style.stroke_opacity = opacity; self } pub fn style(mut self, style: Style) -> Self { self.style = style; self } } /// `` #[derive(Copy, Clone, PartialEq)] pub struct LineSegment { pub x1: f32, pub x2: f32, pub y1: f32, pub y2: f32, pub color: Color, pub width: f32, } impl fmt::Display for LineSegment { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, r#""#, self.x1, self.y1, self.x2, self.y2, self.color, self.width, ) } } pub fn line_segment(x1: f32, y1: f32, x2: f32, y2: f32) -> LineSegment { LineSegment { x1, y1, x2, y2, color: black(), width: 1.0, } } impl LineSegment { pub fn color(mut self, color: Color) -> Self { self.color = color; self } pub fn width(mut self, width: f32) -> Self { self.width = width; self } pub fn offset(mut self, dx: f32, dy: f32) -> Self { self.x1 += dx; self.y1 += dy; self.x2 += dx; self.y2 += dy; self } } /// `` #[derive(Clone, PartialEq)] pub struct Path { pub ops: Vec, pub style: Style, } /// `M {} {} L {} {} ...` #[derive(Copy, Clone, PartialEq)] pub enum PathOp { MoveTo { x: f32, y: f32 }, LineTo { x: f32, y: f32 }, QuadraticTo { ctrl_x: f32, ctrl_y: f32, x: f32, y: f32 }, CubicTo { ctrl1_x: f32, ctrl1_y: f32, ctrl2_x: f32, ctrl2_y: f32, x: f32, y: f32 }, Close, } impl fmt::Display for PathOp { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { PathOp::MoveTo { x, y } => write!(f, "M {} {} ", x, y), PathOp::LineTo { x, y } => write!(f, "L {} {} ", x, y), PathOp::QuadraticTo { ctrl_x, ctrl_y, x, y } => write!(f, "Q {} {} {} {} ", ctrl_x, ctrl_y, x, y), PathOp::CubicTo { ctrl1_x, ctrl1_y, ctrl2_x, ctrl2_y, x, y } => write!(f, "C {} {} {} {} {} {} ", ctrl1_x, ctrl1_y, ctrl2_x, ctrl2_y, x, y), PathOp::Close => write!(f, "Z "), } } } impl fmt::Display for Path { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, r#""#, self.style) } } impl Path { pub fn move_to(mut self, x: f32, y: f32) -> Self { self.ops.push(PathOp::MoveTo { x, y }); self } pub fn line_to(mut self, x: f32, y: f32) -> Self { self.ops.push(PathOp::LineTo { x, y }); self } pub fn quadratic_bezier_to( mut self, ctrl_x: f32, ctrl_y: f32, x: f32, y: f32, ) -> Self { self.ops.push(PathOp::QuadraticTo { ctrl_x, ctrl_y, x, y }); self } pub fn cubic_bezier_to( mut self, ctrl1_x: f32, ctrl1_y: f32, ctrl2_x: f32, ctrl2_y: f32, x: f32, y: f32, ) -> Self { self.ops.push(PathOp::CubicTo { ctrl1_x, ctrl1_y, ctrl2_x, ctrl2_y, x, y }); self } pub fn close(mut self) -> Self { self.ops.push(PathOp::Close); self } pub fn fill(mut self, fill: F) -> Self where F: Into { self.style.fill = fill.into(); self } pub fn stroke(mut self, stroke: S) -> Self where S: Into { self.style.stroke = stroke.into(); self } pub fn opacity(mut self, opacity: f32) -> Self { self.style.opacity = opacity; self } pub fn stroke_opacity(mut self, opacity: f32) -> Self { self.style.stroke_opacity = opacity; self } pub fn style(mut self, style: Style) -> Self { self.style = style; self } } pub fn path() -> Path { Path { ops: Vec::new(), style: Style::default(), } } /// ` {text} ` #[derive(Clone, PartialEq)] pub struct Text { pub x: f32, pub y: f32, pub text: String, pub color: Color, pub align: Align, pub size: f32, } impl fmt::Display for Text { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, r#" {} "#, self.x, self.y, self.size, self.color, self.align, self.text, ) } } pub fn text>(x: f32, y: f32, txt: T) -> Text { Text { x, y, text: txt.into(), color: black(), align: Align::Left, size: 10.0, } } impl Text { pub fn color(mut self, color: Color) -> Self { self.color = color; self } pub fn size(mut self, size: f32) -> Self { self.size = size; self } pub fn align(mut self, align: Align) -> Self { self.align = align; self } pub fn offset(mut self, dx: f32, dy: f32) -> Self { self.x += dx; self.y += dy; self } } pub struct Comment { pub text: String, } pub fn comment>(text: T) -> Comment { Comment { text: text.into() } } impl fmt::Display for Comment { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "", self.text) } } /// `text-align:{self}` #[derive(Copy, Clone, PartialEq)] pub enum Align { Left, Right, Center } impl fmt::Display for Align { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Align::Left => write!(f, "text-anchor:start;text-align:left;"), Align::Right => write!(f, "text-anchor:end;text-align:right;"), Align::Center => write!(f, "text-anchor:middle;text-align:center;"), } } } /// `` #[derive(Copy, Clone, PartialEq)] pub struct BeginSvg { pub w: f32, pub h: f32, } impl fmt::Display for BeginSvg { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, r#""#, self.w, self.h, ) } } /// `` #[derive(Copy, Clone, PartialEq)] pub struct EndSvg; impl fmt::Display for EndSvg { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "") } } /// `" "` pub struct Indentation { pub n: u32, } pub fn indent(n: u32) -> Indentation { Indentation { n } } impl Indentation { pub fn push(&mut self) { self.n += 1; } pub fn pop(&mut self) { self.n -= 1; } } impl fmt::Display for Indentation { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { for _ in 0..self.n { write!(f, " ")?; } Ok(()) } } #[test] fn foo() { println!("{}", BeginSvg { w: 800.0, h: 600.0 }); println!(" {}", rectangle(20.0, 50.0, 200.0, 100.0) .fill(red()) .stroke(Stroke::Color(black(), 3.0)) .border_radius(5.0) ); println!(" {}", text(25.0, 100.0, "Foo!").size(42.0).color(white())); println!("{}", EndSvg); }