runmat_plot/export/
vector.rs

1//! High-quality vector graphics export (SVG, PDF, etc.)
2//!
3//! Production-ready vector output using same rendering pipeline data.
4
5use crate::core::RenderData;
6use crate::plots::Figure;
7use std::fmt::Write;
8use std::path::Path;
9
10/// High-quality vector graphics exporter
11pub struct VectorExporter {
12    /// Export settings
13    settings: VectorExportSettings,
14}
15
16/// Vector export configuration
17#[derive(Debug, Clone)]
18pub struct VectorExportSettings {
19    /// Output width in user units
20    pub width: f32,
21    /// Output height in user units
22    pub height: f32,
23    /// Background color [R, G, B, A] (0.0-1.0)
24    pub background_color: [f32; 4],
25    /// Stroke width for lines
26    pub stroke_width: f32,
27    /// Include metadata in output
28    pub include_metadata: bool,
29    /// Enable anti-aliasing
30    pub anti_aliasing: bool,
31}
32
33/// Supported vector formats
34#[derive(Debug, Clone, Copy, PartialEq)]
35pub enum VectorFormat {
36    Svg,
37    Pdf,
38    Eps,
39}
40
41impl Default for VectorExportSettings {
42    fn default() -> Self {
43        Self {
44            width: 800.0,
45            height: 600.0,
46            background_color: [1.0, 1.0, 1.0, 1.0], // White background
47            stroke_width: 1.0,
48            include_metadata: true,
49            anti_aliasing: true,
50        }
51    }
52}
53
54impl VectorExporter {
55    /// Create a new vector exporter
56    pub fn new() -> Self {
57        Self {
58            settings: VectorExportSettings::default(),
59        }
60    }
61
62    /// Create exporter with custom settings
63    pub fn with_settings(settings: VectorExportSettings) -> Self {
64        Self { settings }
65    }
66
67    /// Export figure to SVG file
68    pub fn export_svg<P: AsRef<Path>>(&self, figure: &mut Figure, path: P) -> Result<(), String> {
69        let svg_content = self.render_to_svg(figure)?;
70        std::fs::write(path, svg_content).map_err(|e| format!("Failed to write SVG file: {e}"))?;
71        println!("DEBUG: SVG export completed successfully");
72        Ok(())
73    }
74
75    /// Export figure to PDF file (placeholder)
76    pub fn export_pdf<P: AsRef<Path>>(&self, _figure: &mut Figure, _path: P) -> Result<(), String> {
77        // TODO: Implement PDF export using a PDF library
78        Err("PDF export not yet implemented".to_string())
79    }
80
81    /// Render figure to SVG string using same rendering pipeline data
82    pub fn render_to_svg(&self, _figure: &mut Figure) -> Result<String, String> {
83        println!("DEBUG: Starting SVG vector export render");
84
85        let mut svg = String::new();
86
87        // SVG header
88        writeln!(
89            &mut svg,
90            r#"<?xml version="1.0" encoding="UTF-8"?>
91<svg width="{}" height="{}" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">"#,
92            self.settings.width, self.settings.height
93        ).map_err(|e| format!("SVG write error: {e}"))?;
94
95        // Background
96        if self.settings.background_color[3] > 0.0 {
97            writeln!(
98                &mut svg,
99                r#"  <rect width="100%" height="100%" fill="{}"/>"#,
100                self.color_to_hex(&self.settings.background_color)
101            )
102            .map_err(|e| format!("SVG write error: {e}"))?;
103        }
104
105        // Add metadata if requested
106        if self.settings.include_metadata {
107            writeln!(
108                &mut svg,
109                "  <metadata>\n    <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n      <rdf:Description>\n        <dc:creator xmlns:dc=\"http://purl.org/dc/elements/1.1/\">RunMat Plot System</dc:creator>\n      </rdf:Description>\n    </rdf:RDF>\n  </metadata>"
110            ).map_err(|e| format!("SVG write error: {e}"))?;
111        }
112
113        // Render each plot element using the same render data
114        // TODO: Implement proper figure element iteration once Figure API is finalized
115        writeln!(&mut svg, "  <!-- Plot data will be rendered here -->")
116            .map_err(|e| format!("SVG write error: {e}"))?;
117
118        // Note: add_render_data_to_svg and related methods are part of the public API
119        // and will be used once the Figure iteration is implemented
120
121        // SVG footer
122        writeln!(&mut svg, "</svg>").map_err(|e| format!("SVG write error: {e}"))?;
123
124        println!(
125            "DEBUG: SVG render completed, {} characters generated",
126            svg.len()
127        );
128        Ok(svg)
129    }
130
131    /// Add render data to SVG using same pipeline data
132    /// Note: Will be used when Figure iteration is implemented
133    #[allow(dead_code)]
134    fn add_render_data_to_svg(
135        &self,
136        svg: &mut String,
137        render_data: &RenderData,
138    ) -> Result<(), String> {
139        match render_data.pipeline_type {
140            crate::core::PipelineType::Lines => {
141                self.add_lines_to_svg(svg, render_data)?;
142            }
143            crate::core::PipelineType::Points => {
144                self.add_points_to_svg(svg, render_data)?;
145            }
146            crate::core::PipelineType::Triangles => {
147                self.add_triangles_to_svg(svg, render_data)?;
148            }
149            crate::core::PipelineType::PointCloud => {
150                self.add_points_to_svg(svg, render_data)?;
151            }
152        }
153        Ok(())
154    }
155
156    /// Add line data to SVG
157    #[allow(dead_code)]
158    fn add_lines_to_svg(&self, svg: &mut String, render_data: &RenderData) -> Result<(), String> {
159        if render_data.vertices.len() < 2 {
160            return Ok(());
161        }
162
163        // Convert vertices to SVG path
164        writeln!(svg, "  <g>").map_err(|e| format!("SVG write error: {e}"))?;
165
166        for chunk in render_data.vertices.chunks(2) {
167            if chunk.len() == 2 {
168                let start = &chunk[0];
169                let end = &chunk[1];
170
171                let start_screen = self.world_to_screen(start.position);
172                let end_screen = self.world_to_screen(end.position);
173
174                writeln!(
175                    svg,
176                    r#"    <line x1="{}" y1="{}" x2="{}" y2="{}" stroke="{}" stroke-width="{}"/>"#,
177                    start_screen[0],
178                    start_screen[1],
179                    end_screen[0],
180                    end_screen[1],
181                    self.color_to_hex(&start.color),
182                    self.settings.stroke_width
183                )
184                .map_err(|e| format!("SVG write error: {e}"))?;
185            }
186        }
187
188        writeln!(svg, "  </g>").map_err(|e| format!("SVG write error: {e}"))?;
189        Ok(())
190    }
191
192    /// Add point data to SVG
193    #[allow(dead_code)]
194    fn add_points_to_svg(&self, svg: &mut String, render_data: &RenderData) -> Result<(), String> {
195        writeln!(svg, "  <g>").map_err(|e| format!("SVG write error: {e}"))?;
196
197        for vertex in &render_data.vertices {
198            let screen_pos = self.world_to_screen(vertex.position);
199            let radius = self.settings.stroke_width * 2.0;
200
201            writeln!(
202                svg,
203                r#"    <circle cx="{}" cy="{}" r="{}" fill="{}"/>"#,
204                screen_pos[0],
205                screen_pos[1],
206                radius,
207                self.color_to_hex(&vertex.color)
208            )
209            .map_err(|e| format!("SVG write error: {e}"))?;
210        }
211
212        writeln!(svg, "  </g>").map_err(|e| format!("SVG write error: {e}"))?;
213        Ok(())
214    }
215
216    /// Add triangle data to SVG
217    #[allow(dead_code)]
218    fn add_triangles_to_svg(
219        &self,
220        svg: &mut String,
221        render_data: &RenderData,
222    ) -> Result<(), String> {
223        writeln!(svg, "  <g>").map_err(|e| format!("SVG write error: {e}"))?;
224
225        for triangle in render_data.vertices.chunks(3) {
226            if triangle.len() == 3 {
227                let p1 = self.world_to_screen(triangle[0].position);
228                let p2 = self.world_to_screen(triangle[1].position);
229                let p3 = self.world_to_screen(triangle[2].position);
230
231                writeln!(
232                    svg,
233                    r#"    <polygon points="{},{} {},{} {},{}" fill="{}"/>"#,
234                    p1[0],
235                    p1[1],
236                    p2[0],
237                    p2[1],
238                    p3[0],
239                    p3[1],
240                    self.color_to_hex(&triangle[0].color)
241                )
242                .map_err(|e| format!("SVG write error: {e}"))?;
243            }
244        }
245
246        writeln!(svg, "  </g>").map_err(|e| format!("SVG write error: {e}"))?;
247        Ok(())
248    }
249
250    /// Convert world coordinates to screen coordinates
251    #[allow(dead_code)]
252    fn world_to_screen(&self, world_pos: [f32; 3]) -> [f32; 2] {
253        // Simple orthographic projection for 2D plots
254        // TODO: Add proper camera transformation for 3D plots
255        [
256            (world_pos[0] + 1.0) * 0.5 * self.settings.width,
257            (1.0 - world_pos[1]) * 0.5 * self.settings.height,
258        ]
259    }
260
261    /// Convert color array to hex string
262    fn color_to_hex(&self, color: &[f32; 4]) -> String {
263        format!(
264            "#{:02x}{:02x}{:02x}",
265            (color[0] * 255.0) as u8,
266            (color[1] * 255.0) as u8,
267            (color[2] * 255.0) as u8
268        )
269    }
270
271    /// Update export settings
272    pub fn set_settings(&mut self, settings: VectorExportSettings) {
273        self.settings = settings;
274    }
275
276    /// Get current export settings
277    pub fn settings(&self) -> &VectorExportSettings {
278        &self.settings
279    }
280}
281
282impl Default for VectorExporter {
283    fn default() -> Self {
284        Self::new()
285    }
286}