dessin/export.rs
1//! Documentation to export a dessin in a specific format.
2//!
3//! You should probably head to [`Exporter`][crate::export::Exporter]
4//!
5//!
6//! ## Examples
7//! Examples can be found for [PDF](https://docs.rs/dessin-pdf/) or [SVG](https://docs.rs/dessin-svg/)
8use crate::prelude::*;
9use nalgebra::Transform2;
10
11/// Orchestrator of the export
12///
13/// The Export walks the dessin graph and orchestrate an [`Exporter`] of a given format.
14/// Unless you need a specific export behavious, you should not need to implement this trait as it is already imiplemented for [`Shape`][crate::shapes::Shape].
15pub trait Export<E>
16where
17 E: Exporter,
18{
19 /// Start the export of a dessin.
20 ///
21 /// Example in a Dummy [`Exporter`]:
22 /// ```
23 /// # fn main() {
24 /// # use dessin::{prelude::*, export::*};
25 /// struct MyDummyExporter;
26 /// impl Exporter for MyDummyExporter { // Hidden implementation
27 /// # type Error = ();
28 /// # fn start_style(&mut self, style: StylePosition) -> Result<(), Self::Error> { Ok(()) }
29 /// # fn end_style(&mut self) -> Result<(), Self::Error> { Ok(()) }
30 /// # fn export_image(&mut self, image: ImagePosition) -> Result<(), Self::Error> { Ok(()) }
31 /// # fn export_ellipse(&mut self, ellipse: EllipsePosition) -> Result<(), Self::Error> { Ok(()) }
32 /// # fn export_curve(&mut self, curve: CurvePosition, StylePosition {fill,stroke,} : StylePosition,) -> Result<(), Self::Error> { Ok(()) }
33 /// # fn export_text(&mut self, text: TextPosition) -> Result<(), Self::Error> { Ok(()) }
34 /// }
35 ///
36 /// fn export_shape(shape: Shape) {
37 /// let mut my_dummy_exporter = MyDummyExporter;
38 ///
39 /// shape.write_into_exporter( // Start walking the dessin
40 /// &mut my_dummy_exporter,
41 /// &Default::default(),
42 /// StylePosition {fill: None,stroke: None,},
43 /// );
44 /// }
45 ///
46 /// export_shape(dessin!());
47 /// # }
48 /// ```
49 fn write_into_exporter(
50 &self,
51 exporter: &mut E,
52 parent_transform: &Transform2<f32>,
53 style_position: StylePosition,
54 ) -> Result<(), <E as Exporter>::Error>;
55}
56
57impl<E> Export<E> for Shape
58where
59 E: Exporter,
60{
61 fn write_into_exporter(
62 &self,
63 exporter: &mut E,
64 parent_transform: &Transform2<f32>,
65 StylePosition { fill, stroke }: StylePosition,
66 ) -> Result<(), <E as Exporter>::Error> {
67 match self {
68 Shape::Group(Group {
69 local_transform,
70 shapes,
71 metadata,
72 }) => {
73 exporter.start_block(metadata.as_slice())?;
74
75 let parent_transform = parent_transform * local_transform;
76 for shape in shapes {
77 shape.write_into_exporter(
78 exporter,
79 &parent_transform,
80 StylePosition { fill, stroke },
81 )?;
82 }
83
84 exporter.end_block(metadata.as_slice())?;
85
86 Ok(())
87 }
88 Shape::Style {
89 fill,
90 stroke,
91 shape,
92 } => {
93 let style = StylePosition {
94 fill: fill.clone(),
95 stroke: stroke.clone().map(|v| *parent_transform * v),
96 };
97
98 exporter.start_style(style)?;
99 shape.write_into_exporter(exporter, parent_transform, style)?;
100 exporter.end_style()
101 }
102 Shape::Image(image) => exporter.export_image(image.position(parent_transform)),
103 Shape::Ellipse(ellipse) => {
104 if E::CAN_EXPORT_ELLIPSE {
105 exporter.export_ellipse(
106 ellipse.position(parent_transform),
107 StylePosition { fill, stroke },
108 )
109 } else {
110 exporter.export_curve(
111 ellipse.as_curve().position(parent_transform),
112 StylePosition { fill, stroke },
113 )
114 }
115 }
116 Shape::Curve(curve) => exporter.export_curve(
117 curve.position(parent_transform),
118 StylePosition { fill, stroke },
119 ),
120 Shape::Text(text) => exporter.export_text(
121 text.position(parent_transform),
122 StylePosition { fill, stroke },
123 ),
124 Shape::Dynamic {
125 local_transform,
126 shaper,
127 } => {
128 let shape = shaper();
129 let parent_transform = parent_transform * local_transform;
130 shape.write_into_exporter(
131 exporter,
132 &parent_transform,
133 StylePosition { fill, stroke },
134 )
135 }
136 }
137 }
138}
139
140/// Writer to a given format
141///
142/// Implementation hint:
143/// The implementation is a bit opiniated as you don't have the control about when a given will be called by the [`Export`].
144/// You should probably store a state inside the [`Exporter`] and mutate it as each function is called.
145///
146/// As a user isn't expect to call the method in this module directly, you still have a bit of control over the export.
147///
148/// Here is an idea of how the export in [SVG](https://docs.rs/dessin-svg/) is done:
149/// We need to add the closing tag `</svg>` after exporting everything.
150/// ```
151/// # use dessin::{prelude::*, export::*};
152/// struct SVGExport { state: String }
153/// impl SVGExport {
154/// fn new() -> Self {
155/// SVGExport { state: "<svg>".to_string() }
156/// }
157///
158/// fn finish(self) -> String { // Add the closing tag and give the accumulated state
159/// format!("{}</svg>", self.state) // Closing tag
160/// }
161/// }
162/// impl Exporter for SVGExport { // Hidden implementation
163/// # type Error = ();
164/// # fn start_style(&mut self, style: StylePosition) -> Result<(), Self::Error> { Ok(()) }
165/// # fn end_style(&mut self) -> Result<(), Self::Error> { Ok(()) }
166/// # fn export_image(&mut self, image: ImagePosition) -> Result<(), Self::Error> { Ok(()) }
167/// # fn export_ellipse(&mut self, ellipse: EllipsePosition) -> Result<(), Self::Error> { Ok(()) }
168/// # fn export_curve(&mut self, curve: CurvePosition, StylePosition {fill,stroke,}: StylePosition,) -> Result<(), Self::Error> { Ok(()) }
169/// # fn export_text(&mut self, text: TextPosition) -> Result<(), Self::Error> { Ok(()) }
170/// }
171///
172/// trait ToSVG {
173/// fn to_svg(&self) -> String;
174/// }
175/// impl ToSVG for Shape {
176/// fn to_svg(&self) -> String {
177/// let mut exporter = SVGExport::new();
178///
179/// self.write_into_exporter( // Start walking the dessin
180/// &mut exporter,
181/// &Default::default(), // In the real implementation, we need to mirror the Y axis, as the positive side is in the DOWN side
182/// StylePosition {fill: None,stroke: None,},
183/// ).unwrap();
184///
185/// exporter.finish()
186/// }
187/// }
188///
189/// fn main() {
190/// let svg = dessin!().to_svg();
191/// }
192/// ```
193pub trait Exporter {
194 /// Export error
195 type Error;
196
197 ///
198 const CAN_EXPORT_ELLIPSE: bool = true;
199
200 /// Enter a scope of style
201 ///
202 /// All [`Shape`][crate::shapes::Shape] between [`start_style`][Exporter::start_style] and [`end_style`][Exporter::end_style] must have this style applied to them.
203 fn start_style(&mut self, style: StylePosition) -> Result<(), Self::Error>;
204 /// End a scope of style
205 fn end_style(&mut self) -> Result<(), Self::Error>;
206
207 /// Start of a block, with custom metadata attached
208 fn start_block(&mut self, _metadata: &[(String, String)]) -> Result<(), Self::Error> {
209 Ok(())
210 }
211 /// End a scope of block
212 fn end_block(&mut self, _metadata: &[(String, String)]) -> Result<(), Self::Error> {
213 Ok(())
214 }
215
216 /// Export an [`Image`][crate::shapes::image::Image]
217 fn export_image(&mut self, image: ImagePosition) -> Result<(), Self::Error>;
218 /// Export an [`Ellipse`][crate::shapes::ellipse::Ellipse]
219 fn export_ellipse(
220 &mut self,
221 _ellipse: EllipsePosition,
222 _style: StylePosition,
223 ) -> Result<(), Self::Error> {
224 Ok(())
225 }
226 /// Export a [`Curve`][crate::shapes::curve::Curve]
227 fn export_curve(
228 &mut self,
229 curve: CurvePosition,
230 style_position: StylePosition,
231 ) -> Result<(), Self::Error>;
232 /// Export a [`Text`][crate::shapes::text::Text]
233 fn export_text(&mut self, text: TextPosition, style: StylePosition) -> Result<(), Self::Error>;
234}