Skip to main content

libvisio_rs/
model.rs

1//! Common data model for Visio documents.
2//!
3//! Defines Shape, Page, Document, XForm, and other shared types used by both
4//! the .vsdx XML parser and the .vsd binary parser.
5
6use std::collections::HashMap;
7
8/// Transform data for a shape (position, size, rotation, flips).
9#[derive(Debug, Clone, Default)]
10pub struct XForm {
11    pub pin_x: f64,
12    pub pin_y: f64,
13    pub width: f64,
14    pub height: f64,
15    pub loc_pin_x: f64,
16    pub loc_pin_y: f64,
17    pub angle: f64,
18    pub flip_x: bool,
19    pub flip_y: bool,
20}
21
22/// Text block transform — positions text independently of shape.
23#[derive(Debug, Clone, Default)]
24pub struct TextXForm {
25    pub txt_pin_x: f64,
26    pub txt_pin_y: f64,
27    pub txt_width: f64,
28    pub txt_height: f64,
29    pub txt_loc_pin_x: f64,
30    pub txt_loc_pin_y: f64,
31    pub txt_angle: f64,
32}
33
34/// 1D connector endpoints.
35#[derive(Debug, Clone, Default)]
36pub struct XForm1D {
37    pub begin_x: f64,
38    pub begin_y: f64,
39    pub end_x: f64,
40    pub end_y: f64,
41}
42
43/// A cell value with optional formula.
44#[derive(Debug, Clone, Default)]
45pub struct CellValue {
46    pub v: String,
47    pub f: String,
48}
49
50impl CellValue {
51    pub fn new(v: impl Into<String>, f: impl Into<String>) -> Self {
52        Self {
53            v: v.into(),
54            f: f.into(),
55        }
56    }
57    pub fn val(v: impl Into<String>) -> Self {
58        Self {
59            v: v.into(),
60            f: String::new(),
61        }
62    }
63    pub fn as_f64(&self) -> f64 {
64        self.v.parse().unwrap_or(0.0)
65    }
66    pub fn as_f64_or(&self, default: f64) -> f64 {
67        self.v.parse().unwrap_or(default)
68    }
69    pub fn is_empty(&self) -> bool {
70        self.v.is_empty() && self.f.is_empty()
71    }
72}
73
74/// A single row in a geometry section.
75#[derive(Debug, Clone)]
76pub struct GeomRow {
77    pub row_type: String,
78    pub ix: String,
79    pub cells: HashMap<String, CellValue>,
80}
81
82impl GeomRow {
83    pub fn new(row_type: &str) -> Self {
84        Self {
85            row_type: row_type.to_string(),
86            ix: String::new(),
87            cells: HashMap::new(),
88        }
89    }
90    pub fn cell_f64(&self, name: &str) -> f64 {
91        self.cells.get(name).map(|c| c.as_f64()).unwrap_or(0.0)
92    }
93}
94
95/// A geometry section (may contain multiple rows).
96#[derive(Debug, Clone, Default)]
97pub struct GeomSection {
98    pub no_fill: bool,
99    pub no_line: bool,
100    pub no_show: bool,
101    pub ix: String,
102    pub rows: Vec<GeomRow>,
103}
104
105/// Character formatting for a text run.
106#[derive(Debug, Clone)]
107pub struct CharFormat {
108    pub size: String,
109    pub color: String,
110    pub style: String,
111    pub font: String,
112}
113
114impl Default for CharFormat {
115    fn default() -> Self {
116        Self {
117            size: "0.1111".to_string(),
118            color: String::new(),
119            style: "0".to_string(),
120            font: String::new(),
121        }
122    }
123}
124
125/// Paragraph formatting.
126#[derive(Debug, Clone, Default)]
127pub struct ParaFormat {
128    pub horiz_align: String,
129    pub indent_first: String,
130    pub indent_left: String,
131    pub indent_right: String,
132    pub bullet: String,
133    pub bullet_str: String,
134    pub sp_line: String,
135    pub sp_before: String,
136    pub sp_after: String,
137}
138
139/// A part of text with formatting references.
140#[derive(Debug, Clone)]
141pub struct TextPart {
142    pub text: String,
143    pub cp: String,
144    pub pp: String,
145}
146
147/// Foreign data info (embedded images).
148#[derive(Debug, Clone, Default)]
149pub struct ForeignDataInfo {
150    pub foreign_type: String,
151    pub compression: String,
152    pub data: Option<String>,
153    pub rel_id: Option<String>,
154}
155
156/// Hyperlink data.
157#[derive(Debug, Clone, Default)]
158pub struct Hyperlink {
159    pub description: String,
160    pub address: String,
161    pub sub_address: String,
162    pub frame: String,
163}
164
165/// Gradient stop.
166#[derive(Debug, Clone)]
167pub struct GradientStop {
168    pub position: f64,
169    pub color: String,
170}
171
172/// Gradient definition for SVG output.
173#[derive(Debug, Clone)]
174pub struct GradientDef {
175    pub id: String,
176    pub start: String,
177    pub end: String,
178    pub mid: Option<String>,
179    pub dir: f64,
180    pub radial: bool,
181    pub stops: Vec<GradientStop>,
182    pub cx: f64,
183    pub cy: f64,
184    pub fx: f64,
185    pub fy: f64,
186    pub r: f64,
187}
188
189impl Default for GradientDef {
190    fn default() -> Self {
191        Self {
192            id: String::new(),
193            start: "#FFFFFF".to_string(),
194            end: "#000000".to_string(),
195            mid: None,
196            dir: 0.0,
197            radial: false,
198            stops: Vec::new(),
199            cx: 50.0,
200            cy: 50.0,
201            fx: 50.0,
202            fy: 50.0,
203            r: 50.0,
204        }
205    }
206}
207
208/// Fill pattern definition for hatching.
209#[derive(Debug, Clone)]
210pub struct FillPatternDef {
211    pub id: String,
212    pub fg: String,
213    pub bg: String,
214    pub pattern_type: i32,
215}
216
217/// A parsed Visio shape.
218#[derive(Debug, Clone)]
219pub struct Shape {
220    pub id: String,
221    pub name: String,
222    pub name_u: String,
223    pub shape_type: String,
224    pub master: String,
225    pub master_shape: String,
226    pub cells: HashMap<String, CellValue>,
227    pub geometry: Vec<GeomSection>,
228    pub text: String,
229    pub text_parts: Vec<TextPart>,
230    pub char_formats: HashMap<String, CharFormat>,
231    pub para_formats: HashMap<String, ParaFormat>,
232    pub sub_shapes: Vec<Shape>,
233    pub controls: HashMap<String, HashMap<String, String>>,
234    pub connections: HashMap<String, HashMap<String, CellValue>>,
235    pub user: HashMap<String, HashMap<String, String>>,
236    pub foreign_data: Option<ForeignDataInfo>,
237    pub hyperlinks: Vec<Hyperlink>,
238    pub line_style: String,
239    pub fill_style: String,
240    pub text_style: String,
241    pub gradient_stops: Vec<Vec<GradientStop>>,
242    pub has_text_elem: bool,
243    pub master_w: f64,
244    pub master_h: f64,
245    pub has_own_geometry: bool,
246    pub theme_text_color: Option<String>,
247}
248
249impl Default for Shape {
250    fn default() -> Self {
251        Self {
252            id: String::new(),
253            name: String::new(),
254            name_u: String::new(),
255            shape_type: "Shape".to_string(),
256            master: String::new(),
257            master_shape: String::new(),
258            cells: HashMap::new(),
259            geometry: Vec::new(),
260            text: String::new(),
261            text_parts: Vec::new(),
262            char_formats: HashMap::new(),
263            para_formats: HashMap::new(),
264            sub_shapes: Vec::new(),
265            controls: HashMap::new(),
266            connections: HashMap::new(),
267            user: HashMap::new(),
268            foreign_data: None,
269            hyperlinks: Vec::new(),
270            line_style: String::new(),
271            fill_style: String::new(),
272            text_style: String::new(),
273            gradient_stops: Vec::new(),
274            has_text_elem: false,
275            master_w: 0.0,
276            master_h: 0.0,
277            has_own_geometry: false,
278            theme_text_color: None,
279        }
280    }
281}
282
283impl Shape {
284    pub fn cell_val(&self, name: &str) -> &str {
285        self.cells.get(name).map(|c| c.v.as_str()).unwrap_or("")
286    }
287
288    pub fn cell_f64(&self, name: &str) -> f64 {
289        self.cells.get(name).map(|c| c.as_f64()).unwrap_or(0.0)
290    }
291
292    pub fn cell_f64_or(&self, name: &str, default: f64) -> f64 {
293        self.cells
294            .get(name)
295            .map(|c| c.as_f64_or(default))
296            .unwrap_or(default)
297    }
298}
299
300/// A connection between shapes on a page.
301#[derive(Debug, Clone)]
302pub struct Connect {
303    pub from_sheet: String,
304    pub from_cell: String,
305    pub to_sheet: String,
306    pub to_cell: String,
307}
308
309/// Layer definition on a page.
310#[derive(Debug, Clone)]
311pub struct LayerDef {
312    pub name: String,
313    pub visible: bool,
314}
315
316/// A page in a Visio document.
317#[derive(Debug, Clone)]
318pub struct Page {
319    pub name: String,
320    pub index: usize,
321    pub width: f64,
322    pub height: f64,
323    pub shapes: Vec<Shape>,
324    pub connects: Vec<Connect>,
325    pub layers: HashMap<String, LayerDef>,
326    pub background: bool,
327}
328
329impl Default for Page {
330    fn default() -> Self {
331        Self {
332            name: String::new(),
333            index: 0,
334            width: 8.5,
335            height: 11.0,
336            shapes: Vec::new(),
337            connects: Vec::new(),
338            layers: HashMap::new(),
339            background: false,
340        }
341    }
342}
343
344/// Stylesheet data from document.xml.
345#[derive(Debug, Clone, Default)]
346pub struct StyleSheet {
347    pub cells: HashMap<String, CellValue>,
348    pub line_style: String,
349    pub fill_style: String,
350    pub text_style: String,
351}
352
353/// A parsed Visio document.
354#[derive(Debug, Clone, Default)]
355pub struct Document {
356    pub pages: Vec<Page>,
357    pub masters: HashMap<String, HashMap<String, Shape>>,
358    pub theme_colors: HashMap<String, String>,
359    pub media: HashMap<String, Vec<u8>>,
360    pub stylesheets: HashMap<String, StyleSheet>,
361    pub background_map: HashMap<usize, usize>,
362}
363
364/// Page information returned by get_page_info.
365#[derive(Debug, Clone)]
366pub struct PageInfo {
367    pub name: String,
368    pub index: usize,
369    pub width: f64,
370    pub height: f64,
371}