Skip to main content

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}