Skip to main content

document_svg/cad/dxf/
types.rs

1//! Data structures representing parsed DXF elements and drawing hierarchy.
2
3use std::collections::HashMap;
4
5#[derive(Clone, Debug, PartialEq)]
6pub struct Layer {
7    pub name: String,
8    pub color_hex: String,
9    pub linetype: String,
10    pub is_off: bool,
11    pub is_frozen: bool,
12    pub line_weight: Option<f64>,
13}
14
15impl Default for Layer {
16    fn default() -> Self {
17        Self {
18            name: "0".into(),
19            color_hex: "#000000".into(),
20            linetype: "CONTINUOUS".into(),
21            is_off: false,
22            is_frozen: false,
23            line_weight: None,
24        }
25    }
26}
27
28#[derive(Clone, Debug, PartialEq)]
29pub struct LineType {
30    pub name: String,
31    pub description: String,
32    /// Positive values: dash lengths. Negative values: space lengths. Zero: dot.
33    pub pattern: Vec<f64>,
34}
35
36#[derive(Clone, Copy, Debug, PartialEq)]
37pub struct LwVertex {
38    pub x: f64,
39    pub y: f64,
40    pub bulge: f64,
41}
42
43#[derive(Clone, Debug, PartialEq)]
44#[allow(dead_code)]
45pub enum HatchBoundary {
46    Polyline {
47        vertices: Vec<LwVertex>,
48        is_closed: bool,
49    },
50    Edges(Vec<Entity>),
51}
52
53#[derive(Clone, Debug, PartialEq)]
54pub enum Entity {
55    Line {
56        start: (f64, f64),
57        end: (f64, f64),
58        layer: String,
59        color: Option<String>,
60        line_weight: Option<f64>,
61        linetype: Option<String>,
62    },
63    Point {
64        pt: (f64, f64),
65        layer: String,
66        color: Option<String>,
67    },
68    Circle {
69        center: (f64, f64),
70        radius: f64,
71        layer: String,
72        color: Option<String>,
73        line_weight: Option<f64>,
74        linetype: Option<String>,
75    },
76    Arc {
77        center: (f64, f64),
78        radius: f64,
79        start_deg: f64,
80        end_deg: f64,
81        layer: String,
82        color: Option<String>,
83        line_weight: Option<f64>,
84        linetype: Option<String>,
85    },
86    Ellipse {
87        center: (f64, f64),
88        major_axis: (f64, f64),
89        axis_ratio: f64,
90        start_param: f64,
91        end_param: f64,
92        layer: String,
93        color: Option<String>,
94        line_weight: Option<f64>,
95        linetype: Option<String>,
96    },
97    LwPolyline {
98        vertices: Vec<LwVertex>,
99        is_closed: bool,
100        layer: String,
101        color: Option<String>,
102        line_weight: Option<f64>,
103        linetype: Option<String>,
104    },
105    Spline {
106        degree: usize,
107        control_points: Vec<(f64, f64)>,
108        knots: Vec<f64>,
109        is_closed: bool,
110        layer: String,
111        color: Option<String>,
112        line_weight: Option<f64>,
113    },
114    Solid {
115        points: [(f64, f64); 4],
116        layer: String,
117        color: Option<String>,
118    },
119    Text {
120        text: String,
121        insert: (f64, f64),
122        height: f64,
123        rotation_deg: f64,
124        layer: String,
125        color: Option<String>,
126        h_align: u8,
127        v_align: u8,
128    },
129    MText {
130        text: String,
131        insert: (f64, f64),
132        height: f64,
133        rotation_deg: f64,
134        layer: String,
135        color: Option<String>,
136        attachment: u8,
137    },
138    Insert {
139        block_name: String,
140        insert: (f64, f64),
141        scale: (f64, f64),
142        rotation_deg: f64,
143        layer: String,
144        color: Option<String>,
145        line_weight: Option<f64>,
146    },
147    Hatch {
148        boundaries: Vec<HatchBoundary>,
149        is_solid: bool,
150        pattern_name: String,
151        layer: String,
152        color: Option<String>,
153    },
154    Dimension {
155        block_name: Option<String>,
156        text: String,
157        insert: (f64, f64),
158        layer: String,
159        color: Option<String>,
160    },
161    Leader {
162        vertices: Vec<(f64, f64)>,
163        layer: String,
164        color: Option<String>,
165    },
166}
167
168impl Entity {
169    #[must_use]
170    pub fn layer(&self) -> &str {
171        match self {
172            Self::Line { layer, .. }
173            | Self::Point { layer, .. }
174            | Self::Circle { layer, .. }
175            | Self::Arc { layer, .. }
176            | Self::Ellipse { layer, .. }
177            | Self::LwPolyline { layer, .. }
178            | Self::Spline { layer, .. }
179            | Self::Solid { layer, .. }
180            | Self::Text { layer, .. }
181            | Self::MText { layer, .. }
182            | Self::Insert { layer, .. }
183            | Self::Hatch { layer, .. }
184            | Self::Dimension { layer, .. }
185            | Self::Leader { layer, .. } => layer.as_str(),
186        }
187    }
188}
189
190#[derive(Clone, Debug, PartialEq)]
191pub struct Block {
192    pub name: String,
193    pub base_point: (f64, f64),
194    pub entities: Vec<Entity>,
195}
196
197#[derive(Clone, Debug, Default)]
198pub struct DxfDocument {
199    pub layers: HashMap<String, Layer>,
200    pub linetypes: HashMap<String, LineType>,
201    pub blocks: HashMap<String, Block>,
202    pub entities: Vec<Entity>,
203    pub ext_min: Option<(f64, f64)>,
204    pub ext_max: Option<(f64, f64)>,
205    pub ins_units: u16,
206}
207
208impl DxfDocument {
209    /// Finds a layer by name, ignoring ASCII case according to DXF specification.
210    #[must_use]
211    pub fn find_layer(&self, name: &str) -> Option<&Layer> {
212        if let Some(l) = self.layers.get(name) {
213            return Some(l);
214        }
215        let lower = name.to_ascii_lowercase();
216        self.layers
217            .iter()
218            .find(|(k, _)| k.eq_ignore_ascii_case(&lower))
219            .map(|(_, v)| v)
220    }
221
222    /// Finds a block by name, ignoring ASCII case.
223    #[must_use]
224    pub fn find_block(&self, name: &str) -> Option<&Block> {
225        if let Some(b) = self.blocks.get(name) {
226            return Some(b);
227        }
228        let lower = name.to_ascii_lowercase();
229        self.blocks
230            .iter()
231            .find(|(k, _)| k.eq_ignore_ascii_case(&lower))
232            .map(|(_, v)| v)
233    }
234
235    /// Finds a linetype by name, ignoring ASCII case.
236    #[must_use]
237    pub fn find_linetype(&self, name: &str) -> Option<&LineType> {
238        if let Some(lt) = self.linetypes.get(name) {
239            return Some(lt);
240        }
241        let lower = name.to_ascii_lowercase();
242        self.linetypes
243            .iter()
244            .find(|(k, _)| k.eq_ignore_ascii_case(&lower))
245            .map(|(_, v)| v)
246    }
247}