1use std::fmt;
2
3#[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#[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#[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#[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#[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#[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#[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#[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#[derive(Clone, PartialEq)]
478pub struct Path {
479 pub ops: Vec<PathOp>,
480 pub style: Style,
481 pub comment: Option<Comment>,
482}
483
484#[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#[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#[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#[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#[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
767pub 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}