svg_fmt/
svg.rs

1use std::fmt;
2
3/// `rgb({r},{g},{b})`
4#[derive(Copy, Clone, PartialEq)]
5pub struct Color {
6    pub r: u8,
7    pub g: u8,
8    pub b: u8,
9}
10
11impl Default for Color {
12    fn default() -> Self {
13        black()
14    }
15}
16
17impl fmt::Display for Color {
18    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
19        write!(f, "rgb({},{},{})", self.r, self.g, self.b)
20    }
21}
22
23pub fn rgb(r: u8, g: u8, b: u8) -> Color {
24    Color { r, g, b }
25}
26pub fn black() -> Color {
27    rgb(0, 0, 0)
28}
29pub fn white() -> Color {
30    rgb(255, 255, 255)
31}
32pub fn red() -> Color {
33    rgb(255, 0, 0)
34}
35pub fn green() -> Color {
36    rgb(0, 255, 0)
37}
38pub fn blue() -> Color {
39    rgb(0, 0, 255)
40}
41
42/// `fill:{self}`
43#[derive(Copy, Clone, PartialEq)]
44pub enum Fill {
45    Color(Color),
46    None,
47}
48
49impl Default for Fill {
50    fn default() -> Self {
51        Fill::None
52    }
53}
54
55/// `stroke:{self}`
56#[derive(Copy, Clone, PartialEq)]
57pub enum Stroke {
58    Color(Color, f32),
59    None,
60}
61
62impl Default for Stroke {
63    fn default() -> Self {
64        Stroke::Color(black(), 1.0)
65    }
66}
67
68/// `fill:{fill};stroke:{stroke};fill-opacity:{opacity};`
69#[derive(Copy, Clone, PartialEq)]
70pub struct Style {
71    pub fill: Fill,
72    pub stroke: Stroke,
73    pub opacity: f32,
74    pub stroke_opacity: f32,
75}
76
77impl fmt::Display for Style {
78    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
79        write!(
80            f,
81            "{};{};fill-opacity:{};stroke-opacity:{};",
82            self.fill, self.stroke, self.opacity, self.stroke_opacity,
83        )
84    }
85}
86
87impl Default for Style {
88    fn default() -> Self {
89        Style {
90            fill: Fill::Color(black()),
91            stroke: Stroke::None,
92            opacity: 1.0,
93            stroke_opacity: 1.0,
94        }
95    }
96}
97
98impl From<Fill> for Style {
99    fn from(fill: Fill) -> Style {
100        Style {
101            fill,
102            stroke: Stroke::None,
103            .. Default::default()
104        }
105    }
106}
107
108impl From<Stroke> for Style {
109    fn from(stroke: Stroke) -> Style {
110        Style {
111            fill: Fill::None,
112            stroke,
113            .. Default::default()
114        }
115    }
116}
117
118impl fmt::Display for Fill {
119    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
120        match self {
121            Fill::Color(color) => write!(f, "fill:{}", color),
122            Fill::None => write!(f, "fill:none"),
123        }
124    }
125}
126
127impl fmt::Display for Stroke {
128    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
129        match self {
130            Stroke::Color(color, radius) => write!(f, "stroke:{};stroke-width:{}", color, radius),
131            Stroke::None => write!(f, "stroke:none"),
132        }
133    }
134}
135
136impl Into<Fill> for Color {
137    fn into(self) -> Fill {
138        Fill::Color(self)
139    }
140}
141
142impl Into<Stroke> for Color {
143    fn into(self) -> Stroke {
144        Stroke::Color(self, 1.0)
145    }
146}
147
148/// `<rect x="{x}" y="{y}" width="{w}" height="{h}" ... />`,
149#[derive(Clone, PartialEq)]
150pub struct Rectangle {
151    pub x: f32,
152    pub y: f32,
153    pub w: f32,
154    pub h: f32,
155    pub style: Style,
156    pub border_radius: f32,
157    pub comment: Option<Comment>,
158}
159
160pub fn rectangle(x: f32, y: f32, w: f32, h: f32) -> Rectangle {
161    Rectangle {
162        x,
163        y,
164        w,
165        h,
166        style: Style::default(),
167        border_radius: 0.0,
168        comment: None,
169    }
170}
171
172impl Rectangle {
173    pub fn fill<F>(mut self, fill: F) -> Self
174    where
175        F: Into<Fill>,
176    {
177        self.style.fill = fill.into();
178        self
179    }
180
181    pub fn stroke<S>(mut self, stroke: S) -> Self
182    where
183        S: Into<Stroke>,
184    {
185        self.style.stroke = stroke.into();
186        self
187    }
188
189    pub fn opacity(mut self, opacity: f32) -> Self {
190        self.style.opacity = opacity;
191        self
192    }
193
194    pub fn stroke_opacity(mut self, opacity: f32) -> Self {
195        self.style.stroke_opacity = opacity;
196        self
197    }
198
199    pub fn style(mut self, style: Style) -> Self {
200        self.style = style;
201        self
202    }
203
204    pub fn border_radius(mut self, r: f32) -> Self {
205        self.border_radius = r;
206        self
207    }
208
209    pub fn offset(mut self, dx: f32, dy: f32) -> Self {
210        self.x += dx;
211        self.y += dy;
212        self
213    }
214
215    pub fn inflate(mut self, dx: f32, dy: f32) -> Self {
216        self.x -= dx;
217        self.y -= dy;
218        self.w += 2.0 * dx;
219        self.h += 2.0 * dy;
220        self
221    }
222
223    pub fn comment(mut self, text: &str) -> Self {
224        self.comment = Some(comment(text));
225        self
226    }
227}
228
229impl fmt::Display for Rectangle {
230    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
231        write!(
232            f,
233            r#"<rect x="{}" y="{}" width="{}" height="{}" ry="{}" style="{}""#,
234            self.x, self.y, self.w, self.h, self.border_radius, self.style,
235        )?;
236        if let Some(comment) = &self.comment {
237            write!(f, r#">{}</rect>"#, comment)?;
238        } else {
239            write!(f, r#" />"#)?;
240        }
241        Ok(())
242    }
243}
244
245/// `<circle cx="{x}" cy="{y}" r="{radius}" .../>`
246#[derive(Clone, PartialEq)]
247pub struct Circle {
248    pub x: f32,
249    pub y: f32,
250    pub radius: f32,
251    pub style: Style,
252    pub comment: Option<Comment>,
253}
254
255impl Circle {
256    pub fn fill<F>(mut self, fill: F) -> Self
257    where
258        F: Into<Fill>,
259    {
260        self.style.fill = fill.into();
261        self
262    }
263
264    pub fn stroke<S>(mut self, stroke: S) -> Self
265    where
266        S: Into<Stroke>,
267    {
268        self.style.stroke = stroke.into();
269        self
270    }
271
272    pub fn style(mut self, style: Style) -> Self {
273        self.style = style;
274        self
275    }
276
277    pub fn opacity(mut self, opacity: f32) -> Self {
278        self.style.opacity = opacity;
279        self
280    }
281
282    pub fn stroke_opacity(mut self, opacity: f32) -> Self {
283        self.style.stroke_opacity = opacity;
284        self
285    }
286
287    pub fn offset(mut self, dx: f32, dy: f32) -> Self {
288        self.x += dx;
289        self.y += dy;
290        self
291    }
292
293    pub fn inflate(mut self, by: f32) -> Self {
294        self.radius += by;
295        self
296    }
297
298    pub fn comment(mut self, text: &str) -> Self {
299        self.comment = Some(comment(text));
300        self
301    }
302}
303
304impl fmt::Display for Circle {
305    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
306        write!(
307            f,
308            r#"<circle cx="{}" cy="{}" r="{}" style="{}""#,
309            self.x, self.y, self.radius, self.style,
310        )?;
311        if let Some(comment) = &self.comment {
312            write!(f, r#">{}</circle>"#, comment)?;
313        } else {
314            write!(f, r#" />"#)?;
315        }
316        Ok(())
317    }
318}
319
320/// `<path d="..." style="..."/>`
321#[derive(Clone, PartialEq)]
322pub struct Polygon {
323    pub points: Vec<[f32; 2]>,
324    pub closed: bool,
325    pub style: Style,
326    pub comment: Option<Comment>,
327}
328
329impl fmt::Display for Polygon {
330    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
331        write!(f, r#"<path d="#)?;
332        if self.points.len() > 0 {
333            write!(f, "M {} {} ", self.points[0][0], self.points[0][1])?;
334            for &p in &self.points[1..] {
335                write!(f, "L {} {} ", p[0], p[1])?;
336            }
337            if self.closed {
338                write!(f, "Z")?;
339            }
340        }
341        write!(f, r#"" style="{}"#, self.style)?;
342        if let Some(comment) = &self.comment {
343            write!(f, r#">{}</path>"#, comment)?;
344        } else {
345            write!(f, r#" />"#)?;
346        }
347        Ok(())
348    }
349}
350
351pub fn polygon<T: Copy + Into<[f32; 2]>>(pts: &[T]) -> Polygon {
352    let mut points = Vec::with_capacity(pts.len());
353    for p in pts {
354        points.push((*p).into());
355    }
356    Polygon {
357        points,
358        closed: true,
359        style: Style::default(),
360        comment: None,
361    }
362}
363
364pub fn triangle(x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) -> Polygon {
365    polygon(&[[x1, y1], [x2, y2], [x3, y3]])
366}
367
368impl Polygon {
369    pub fn open(mut self) -> Self {
370        self.closed = false;
371        self
372    }
373
374    pub fn fill<F>(mut self, fill: F) -> Self
375    where
376        F: Into<Fill>,
377    {
378        self.style.fill = fill.into();
379        self
380    }
381
382    pub fn stroke<S>(mut self, stroke: S) -> Self
383    where
384        S: Into<Stroke>,
385    {
386        self.style.stroke = stroke.into();
387        self
388    }
389
390    pub fn opacity(mut self, opacity: f32) -> Self {
391        self.style.opacity = opacity;
392        self
393    }
394
395    pub fn stroke_opacity(mut self, opacity: f32) -> Self {
396        self.style.stroke_opacity = opacity;
397        self
398    }
399
400    pub fn style(mut self, style: Style) -> Self {
401        self.style = style;
402        self
403    }
404
405    pub fn comment(mut self, text: &str) -> Self {
406        self.comment = Some(comment(text));
407        self
408    }
409}
410
411/// `<path d="M {x1} {y1} L {x2} {y2}" ... />`
412#[derive(Clone, PartialEq)]
413pub struct LineSegment {
414    pub x1: f32,
415    pub x2: f32,
416    pub y1: f32,
417    pub y2: f32,
418    pub color: Color,
419    pub width: f32,
420    pub comment: Option<Comment>,
421}
422
423impl fmt::Display for LineSegment {
424    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
425        write!(
426            f,
427            r#"<path d="M {} {} L {} {}" style="stroke:{};stroke-width:{}""#,
428            self.x1, self.y1, self.x2, self.y2, self.color, self.width
429        )?;
430        if let Some(comment) = &self.comment {
431            write!(f, r#">{}</path>"#, comment)?;
432        } else {
433            write!(f, r#" />"#)?;
434        }
435        Ok(())
436    }
437}
438
439pub fn line_segment(x1: f32, y1: f32, x2: f32, y2: f32) -> LineSegment {
440    LineSegment {
441        x1,
442        y1,
443        x2,
444        y2,
445        color: black(),
446        width: 1.0,
447        comment: None,
448    }
449}
450
451impl LineSegment {
452    pub fn color(mut self, color: Color) -> Self {
453        self.color = color;
454        self
455    }
456
457    pub fn width(mut self, width: f32) -> Self {
458        self.width = width;
459        self
460    }
461
462    pub fn offset(mut self, dx: f32, dy: f32) -> Self {
463        self.x1 += dx;
464        self.y1 += dy;
465        self.x2 += dx;
466        self.y2 += dy;
467        self
468    }
469
470    pub fn comment(mut self, text: &str) -> Self {
471        self.comment = Some(comment(text));
472        self
473    }
474}
475
476/// `<path d="..." />`
477#[derive(Clone, PartialEq)]
478pub struct Path {
479    pub ops: Vec<PathOp>,
480    pub style: Style,
481    pub comment: Option<Comment>,
482}
483
484/// `M {} {} L {} {} ...`
485#[derive(Copy, Clone, PartialEq)]
486pub enum PathOp {
487    MoveTo {
488        x: f32,
489        y: f32,
490    },
491    LineTo {
492        x: f32,
493        y: f32,
494    },
495    QuadraticTo {
496        ctrl_x: f32,
497        ctrl_y: f32,
498        x: f32,
499        y: f32,
500    },
501    CubicTo {
502        ctrl1_x: f32,
503        ctrl1_y: f32,
504        ctrl2_x: f32,
505        ctrl2_y: f32,
506        x: f32,
507        y: f32,
508    },
509    Close,
510}
511impl fmt::Display for PathOp {
512    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
513        match *self {
514            PathOp::MoveTo { x, y } => write!(f, "M {} {} ", x, y),
515            PathOp::LineTo { x, y } => write!(f, "L {} {} ", x, y),
516            PathOp::QuadraticTo {
517                ctrl_x,
518                ctrl_y,
519                x,
520                y,
521            } => write!(f, "Q {} {} {} {} ", ctrl_x, ctrl_y, x, y),
522            PathOp::CubicTo {
523                ctrl1_x,
524                ctrl1_y,
525                ctrl2_x,
526                ctrl2_y,
527                x,
528                y,
529            } => write!(
530                f,
531                "C {} {} {} {} {} {} ",
532                ctrl1_x, ctrl1_y, ctrl2_x, ctrl2_y, x, y
533            ),
534            PathOp::Close => write!(f, "Z "),
535        }
536    }
537}
538
539impl fmt::Display for Path {
540    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
541        write!(f, r#"<path d=""#)?;
542        for op in &self.ops {
543            op.fmt(f)?;
544        }
545        write!(f, r#"" style="{}""#, self.style)?;
546        if let Some(comment) = &self.comment {
547            write!(f, r#">{}</path>"#, comment)?;
548        } else {
549            write!(f, r#"/>"#)?;
550        }
551        Ok(())
552    }
553}
554
555impl Path {
556    pub fn move_to(mut self, x: f32, y: f32) -> Self {
557        self.ops.push(PathOp::MoveTo { x, y });
558        self
559    }
560
561    pub fn line_to(mut self, x: f32, y: f32) -> Self {
562        self.ops.push(PathOp::LineTo { x, y });
563        self
564    }
565
566    pub fn quadratic_bezier_to(mut self, ctrl_x: f32, ctrl_y: f32, x: f32, y: f32) -> Self {
567        self.ops.push(PathOp::QuadraticTo {
568            ctrl_x,
569            ctrl_y,
570            x,
571            y,
572        });
573        self
574    }
575
576    pub fn cubic_bezier_to(
577        mut self,
578        ctrl1_x: f32,
579        ctrl1_y: f32,
580        ctrl2_x: f32,
581        ctrl2_y: f32,
582        x: f32,
583        y: f32,
584    ) -> Self {
585        self.ops.push(PathOp::CubicTo {
586            ctrl1_x,
587            ctrl1_y,
588            ctrl2_x,
589            ctrl2_y,
590            x,
591            y,
592        });
593        self
594    }
595
596    pub fn close(mut self) -> Self {
597        self.ops.push(PathOp::Close);
598        self
599    }
600
601    pub fn fill<F>(mut self, fill: F) -> Self
602    where
603        F: Into<Fill>,
604    {
605        self.style.fill = fill.into();
606        self
607    }
608
609    pub fn stroke<S>(mut self, stroke: S) -> Self
610    where
611        S: Into<Stroke>,
612    {
613        self.style.stroke = stroke.into();
614        self
615    }
616
617    pub fn opacity(mut self, opacity: f32) -> Self {
618        self.style.opacity = opacity;
619        self
620    }
621
622    pub fn stroke_opacity(mut self, opacity: f32) -> Self {
623        self.style.stroke_opacity = opacity;
624        self
625    }
626
627    pub fn style(mut self, style: Style) -> Self {
628        self.style = style;
629        self
630    }
631}
632
633pub fn path() -> Path {
634    Path {
635        ops: Vec::new(),
636        style: Style::default(),
637        comment: None,
638    }
639}
640
641/// `<text x="{x}" y="{y}" ... > {text} </text>`
642#[derive(Clone, PartialEq)]
643pub struct Text {
644    pub x: f32,
645    pub y: f32,
646    pub text: String,
647    pub color: Color,
648    pub align: Align,
649    pub size: f32,
650    pub comment: Option<Comment>,
651}
652
653impl fmt::Display for Text {
654    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
655        write!(
656            f,
657            r#"<text x="{}" y="{}" style="font-size:{}px;fill:{};{}">"#,
658            self.x, self.y, self.size, self.color, self.align,
659        )?;
660        if let Some(comment) = &self.comment {
661            write!(f, r#" {}"#, comment)?;
662        }
663        write!(f, r#" {} </text>"#, self.text)
664    }
665}
666
667pub fn text<T: Into<String>>(x: f32, y: f32, txt: T) -> Text {
668    Text {
669        x,
670        y,
671        text: txt.into(),
672        color: black(),
673        align: Align::Left,
674        size: 10.0,
675        comment: None,
676    }
677}
678
679impl Text {
680    pub fn color(mut self, color: Color) -> Self {
681        self.color = color;
682        self
683    }
684
685    pub fn size(mut self, size: f32) -> Self {
686        self.size = size;
687        self
688    }
689
690    pub fn align(mut self, align: Align) -> Self {
691        self.align = align;
692        self
693    }
694
695    pub fn offset(mut self, dx: f32, dy: f32) -> Self {
696        self.x += dx;
697        self.y += dy;
698        self
699    }
700
701    pub fn comment(mut self, text: &str) -> Self {
702        self.comment = Some(comment(text));
703        self
704    }
705}
706
707#[derive(Clone, PartialEq)]
708pub struct Comment {
709    pub text: String,
710}
711
712pub fn comment<T: Into<String>>(text: T) -> Comment {
713    Comment { text: text.into() }
714}
715
716impl fmt::Display for Comment {
717    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
718        write!(f, "<!-- {} -->", self.text)
719    }
720}
721
722/// `text-align:{self}`
723#[derive(Copy, Clone, PartialEq)]
724pub enum Align {
725    Left,
726    Right,
727    Center,
728}
729
730impl fmt::Display for Align {
731    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
732        match *self {
733            Align::Left => write!(f, "text-anchor:start;text-align:left;"),
734            Align::Right => write!(f, "text-anchor:end;text-align:right;"),
735            Align::Center => write!(f, "text-anchor:middle;text-align:center;"),
736        }
737    }
738}
739
740/// `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {w} {y}">`
741#[derive(Copy, Clone, PartialEq)]
742pub struct BeginSvg {
743    pub w: f32,
744    pub h: f32,
745}
746
747impl fmt::Display for BeginSvg {
748    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
749        write!(
750            f,
751            r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {} {}">"#,
752            self.w, self.h,
753        )
754    }
755}
756
757/// `</svg>`
758#[derive(Copy, Clone, PartialEq)]
759pub struct EndSvg;
760
761impl fmt::Display for EndSvg {
762    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
763        write!(f, "</svg>")
764    }
765}
766
767/// `"    "`
768pub struct Indentation {
769    pub n: u32,
770}
771
772pub fn indent(n: u32) -> Indentation {
773    Indentation { n }
774}
775
776impl Indentation {
777    pub fn push(&mut self) {
778        self.n += 1;
779    }
780
781    pub fn pop(&mut self) {
782        self.n -= 1;
783    }
784}
785
786impl fmt::Display for Indentation {
787    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
788        for _ in 0..self.n {
789            write!(f, "    ")?;
790        }
791        Ok(())
792    }
793}
794
795#[test]
796fn foo() {
797    println!("{}", BeginSvg { w: 800.0, h: 600.0 });
798    println!(
799        "    {}",
800        rectangle(20.0, 50.0, 200.0, 100.0)
801            .fill(red())
802            .stroke(Stroke::Color(black(), 3.0))
803            .border_radius(5.0)
804    );
805    println!(
806        "    {}",
807        text(25.0, 100.0, "Foo!").size(42.0).color(white())
808    );
809    println!("{}", EndSvg);
810}