svg_fmt/
svg.rs

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