Skip to main content

geo_aid_svg/
lib.rs

1//! Geo-AID is capable of outputting figures as a simple svg file. This file may not be possible
2//! to display everywhere, but it should be suitable for most cases.
3
4use std::io::{self, Seek, Write};
5
6use geo_aid_figure::{
7    CircleItem, Figure, Item, LineItem, PointItem, Position, Style, TwoPointItem,
8};
9
10/// The SVG format writer.
11#[derive(Debug)]
12pub struct Svg<W: Write + Seek> {
13    /// Writer stream
14    writer: W,
15}
16
17impl<W: Write + Seek> Svg<W> {
18    /// Get the figure in SVG format.
19    pub fn draw(figure: &Figure, writer: W) -> io::Result<()> {
20        let mut svg = Self { writer };
21
22        svg.begin(figure)?;
23
24        for item in &figure.items {
25            match item {
26                Item::Point(point) => svg.draw_point(point)?,
27                Item::Line(line) => svg.draw_line(line)?,
28                Item::Ray(ray) => svg.draw_ray(ray)?,
29                Item::Segment(segment) => svg.draw_segment(segment)?,
30                Item::Circle(circle) => svg.draw_circle(circle)?,
31            }
32        }
33
34        svg.end()
35    }
36
37    /// The width of a line made with the given [`Style`]
38    fn get_style_width(style: Style) -> &'static str {
39        match style {
40            Style::Dashed | Style::Dotted => "0.5",
41            Style::Bold => "2",
42            Style::Solid => "1",
43        }
44    }
45
46    /// Get parameters for line based on its [`Style`]
47    fn get_style_dashing(style: Style) -> &'static str {
48        match style {
49            Style::Dotted => "0.8,1",
50            Style::Dashed => "2,2",
51            Style::Bold | Style::Solid => "1,0",
52        }
53    }
54
55    /// Draw a styled segment delimited by two points.
56    fn draw_simple_segment(
57        &mut self,
58        (p1, p2): (Position, Position),
59        style: Style,
60    ) -> io::Result<()> {
61        write!(
62            &mut self.writer,
63            r#"
64                <line stroke-width="{}" stroke-dasharray="{}" stroke="black" x1="{}" x2="{}" y1="{}" y2="{}"/>
65            "#,
66            Self::get_style_width(style),
67            Self::get_style_dashing(style),
68            p1.x,
69            p2.x,
70            p1.y,
71            p2.y
72        )
73    }
74
75    fn begin(&mut self, figure: &Figure) -> io::Result<()> {
76        write!(
77            &mut self.writer,
78            r#"
79                <svg height="{}" width="{}" xmlns="http://www.w3.org/2000/svg">
80                    <font>
81                        <font-face font-family="New Computer Modern">
82                        </font-face>
83                    </font>
84                <g transform="translate(0,{})">
85                <g transform="scale(1,-1)">
86            "#,
87            figure.width, figure.height, figure.width,
88        )
89    }
90
91    fn draw_point(&mut self, point: &PointItem) -> io::Result<()> {
92        let pos = point.position;
93        write!(
94            &mut self.writer,
95            r#"<circle cx="{}" cy="{}" fill="black" r="1"/>"#,
96            pos.x, pos.y
97        )?;
98
99        if let Some(label) = &point.label {
100            write!(
101                &mut self.writer,
102                r#"
103                <text transform="scale(1,-1)"
104                    text-anchor="middle" dominant-baseline="middle"
105                    style="font-family: 'Computer Modern'" font-size="10px"
106                    stroke="black" stroke-width="0" x="{}" y="-{}">{}
107                </text>
108            "#,
109                label.position.x, label.position.y, label.content
110            )?;
111        }
112
113        Ok(())
114    }
115
116    fn draw_line(&mut self, line: &LineItem) -> io::Result<()> {
117        self.draw_simple_segment(line.points, line.style)
118    }
119
120    fn draw_ray(&mut self, ray: &TwoPointItem) -> io::Result<()> {
121        self.draw_simple_segment(ray.points, ray.style)
122    }
123
124    fn draw_segment(&mut self, segment: &TwoPointItem) -> io::Result<()> {
125        self.draw_simple_segment(segment.points, segment.style)
126    }
127
128    // fn draw_angle(&mut self, angle: &RenderedAngle) {
129    //     let x: u32 = 45;
130    //     self.content += &format!(
131    //         r#"
132    //             <g transform="translate({}, {}) rotate({}, 0, 0)" fill="transparent">
133    //                 <path stroke-dasharray="{}" d="M {}, 0 A 45, 45, 0, 0, 0, {}, {}" stroke="black" stroke-width="{}"/>
134    //             </g>
135    //         "#,
136    //         angle.points.1.real,
137    //         angle.points.1.imaginary,
138    //         geometry::get_line(angle.points.1, angle.points.0)
139    //             .direction
140    //             .arg()
141    //             .to_degrees(),
142    //         Self::get_style_dashing(angle.item),
143    //         x, // It should probably be a constant. For now, we will leave it like this.
144    //         angle.angle_value.cos() * 45.0,
145    //         -angle.angle_value.sin() * 45.0,
146    //         style_width(angle.style),
147    //     );
148    // }
149
150    fn draw_circle(&mut self, circle: &CircleItem) -> io::Result<()> {
151        write!(
152            &mut self.writer,
153            r#"
154                <circle cx="{}" cy="{}" r="{}" stroke="black" stroke-width="{}" stroke-dasharray="{}" fill="transparent"/>
155            "#,
156            circle.center.x,
157            circle.center.y,
158            circle.radius,
159            Self::get_style_width(circle.style),
160            Self::get_style_dashing(circle.style),
161        )
162    }
163
164    fn end(&mut self) -> io::Result<()> {
165        write!(&mut self.writer, "</g> </g> </svg>")
166    }
167}