microcad_export/svg/
writer.rs

1// Copyright © 2024-2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! Scalable Vector Graphics (SVG) file writer
5
6use microcad_core::*;
7
8use crate::svg::{SvgTagAttributes, canvas::Canvas};
9
10/// SVG writer.
11pub struct SvgWriter {
12    /// The writer (e.g. a file).
13    writer: Box<dyn std::io::Write>,
14    /// Indentation level.
15    level: usize,
16    /// The canvas.
17    canvas: Canvas,
18}
19
20impl SvgWriter {
21    /// Create new SvgWriter
22    /// # Arguments
23    /// - `w`: Output writer
24    /// - `size`: Size of the canvas.
25    /// - `scale`: Scale of the output
26    pub fn new_canvas(
27        mut writer: Box<dyn std::io::Write>,
28        size: Option<Size2>,
29        content_rect: Rect,
30        scale: Option<Scalar>,
31    ) -> std::io::Result<Self> {
32        let size = match size {
33            Some(size) => size,
34            None => Size2 {
35                width: content_rect.width(),
36                height: content_rect.height(),
37            },
38        };
39        let x = 0;
40        let y = 0;
41        let w = size.width;
42        let h = size.height;
43        let canvas = Canvas::new_centered_content(size, content_rect, scale);
44
45        writeln!(&mut writer, "<?xml version='1.0' encoding='UTF-8'?>")?;
46        writeln!(
47            &mut writer,
48            "<svg version='1.1' xmlns='http://www.w3.org/2000/svg' viewBox='{x} {y} {w} {h}' width='{w}mm' height='{h}mm'>",
49        )?;
50        writeln!(
51            &mut writer,
52            r#"
53  <defs>
54    <!-- A marker to be used as an arrowhead -->
55    <marker
56      id="arrow"
57      viewBox="0 0 16 16"
58      refX="8"
59      refY="8"
60      markerWidth="9"
61      markerHeight="9"
62      orient="auto-start-reverse">
63      <path d="M 0 0 L 16 8 L 0 16 z" stroke="none" fill="context-fill" />
64    </marker>
65  </defs>
66            "#
67        )?;
68
69        Ok(Self {
70            writer: Box::new(writer),
71            level: 1,
72            canvas,
73        })
74    }
75
76    /// Return reference to canvas.
77    pub fn canvas(&self) -> &Canvas {
78        &self.canvas
79    }
80
81    fn tag_inner(tag: &str, attr: &SvgTagAttributes) -> String {
82        format!(
83            "{tag}{attr}",
84            attr = if attr.is_empty() {
85                String::new()
86            } else {
87                format!(" {attr}")
88            }
89        )
90    }
91
92    /// Write something into the SVG and consider indentation.
93    pub fn with_indent(&mut self, s: &str) -> std::io::Result<()> {
94        writeln!(self.writer, "{:indent$}{s}", "", indent = 2 * self.level)
95    }
96
97    /// Write a single tag `<tag>`.
98    pub fn tag(&mut self, tag: &str, attr: &SvgTagAttributes) -> std::io::Result<()> {
99        self.with_indent(&format!(
100            "<{tag_inner}/>",
101            tag_inner = Self::tag_inner(tag, attr)
102        ))
103    }
104
105    /// Open a tag `<tag>`
106    pub fn open_tag(&mut self, tag: &str, attr: &SvgTagAttributes) -> std::io::Result<()> {
107        self.with_indent(&format!(
108            "<{tag_inner}>",
109            tag_inner = Self::tag_inner(tag, attr)
110        ))?;
111
112        self.level += 1;
113        Ok(())
114    }
115
116    /// Close a tag `</tag>`
117    pub fn close_tag(&mut self, tag: &str) -> std::io::Result<()> {
118        self.level -= 1;
119        self.with_indent(format!("</{tag}>").as_str())
120    }
121
122    /// Begin a new group `<g>`.
123    pub fn begin_group(&mut self, attr: &SvgTagAttributes) -> std::io::Result<()> {
124        self.open_tag("g", attr)
125    }
126
127    /// End a group `</g>`.
128    pub fn end_group(&mut self) -> std::io::Result<()> {
129        self.close_tag("g")
130    }
131
132    /// Defs tag.
133    pub fn defs(&mut self, inner: &str) -> std::io::Result<()> {
134        self.open_tag("defs", &Default::default())?;
135        self.with_indent(inner)?;
136        self.close_tag("defs")
137    }
138
139    /// Style tag.
140    pub fn style(&mut self, inner: &str) -> std::io::Result<()> {
141        self.open_tag("style", &Default::default())?;
142        self.with_indent(inner)?;
143        self.close_tag("style")
144    }
145
146    /// Finish this SVG. This method is also called in the Drop trait implementation.
147    pub fn finish(&mut self) -> std::io::Result<()> {
148        writeln!(self.writer, "</svg>")
149    }
150}
151
152impl Drop for SvgWriter {
153    fn drop(&mut self) {
154        self.finish().expect("No error")
155    }
156}