Skip to main content

geop_lib_sheet/
dxf.rs

1//! A [`Sheet`] as an ASCII DXF file of release 12 — the oldest and most
2//! widely read: lines, arcs, circles, polylines, solids and text, one layer
3//! per [`super::Layer`] it draws on, dashed by its [`LineType`].
4
5use std::fmt::Write;
6
7use super::{Anchor, LineType, Shape, Sheet};
8
9/// Text as DXF writes it: its special characters as `%%` codes.
10fn text(s: &str) -> String {
11    s.replace('⌀', "%%c")
12        .replace('°', "%%d")
13        .replace('±', "%%p")
14}
15
16struct Writer(String);
17
18impl Writer {
19    fn pair(&mut self, code: i32, value: impl std::fmt::Display) {
20        let _ = write!(self.0, "{code}\n{value}\n");
21    }
22
23    fn point(&mut self, base: i32, p: [f64; 2]) {
24        self.pair(base, p[0]);
25        self.pair(base + 10, p[1]);
26        self.pair(base + 20, 0.0);
27    }
28}
29
30/// The sheet as a DXF document.
31pub fn to_dxf(sheet: &Sheet) -> String {
32    let mut w = Writer(String::new());
33    w.pair(0, "SECTION");
34    w.pair(2, "HEADER");
35    w.pair(9, "$ACADVER");
36    w.pair(1, "AC1009");
37    w.pair(9, "$EXTMIN");
38    w.point(10, [0.0, 0.0]);
39    w.pair(9, "$EXTMAX");
40    w.point(10, [sheet.width, sheet.height]);
41    w.pair(0, "ENDSEC");
42
43    w.pair(0, "SECTION");
44    w.pair(2, "TABLES");
45    w.pair(0, "TABLE");
46    w.pair(2, "LTYPE");
47    w.pair(70, LineType::ALL.len());
48    for line_type in LineType::ALL {
49        let dashes = line_type.dashes();
50        w.pair(0, "LTYPE");
51        w.pair(2, line_type.name());
52        w.pair(70, 0);
53        w.pair(3, line_type.description());
54        w.pair(72, 65);
55        w.pair(73, dashes.len());
56        w.pair(40, dashes.iter().map(|d| d.abs()).sum::<f64>());
57        for d in dashes {
58            w.pair(49, d);
59        }
60    }
61    w.pair(0, "ENDTAB");
62    // The layers it draws on, each once.
63    let layers: Vec<_> = (sheet.layers().into_iter())
64        .filter(|&layer| {
65            sheet.strokes.iter().any(|s| s.layer == layer)
66                || sheet.labels.iter().any(|l| l.layer == layer)
67        })
68        .collect();
69    w.pair(0, "TABLE");
70    w.pair(2, "LAYER");
71    w.pair(70, layers.len());
72    for layer in layers {
73        w.pair(0, "LAYER");
74        w.pair(2, layer.name);
75        w.pair(70, 0);
76        w.pair(62, layer.color);
77        w.pair(6, layer.line_type.name());
78    }
79    w.pair(0, "ENDTAB");
80    w.pair(0, "ENDSEC");
81
82    w.pair(0, "SECTION");
83    w.pair(2, "ENTITIES");
84    for stroke in &sheet.strokes {
85        let layer = stroke.layer.name;
86        match &stroke.shape {
87            Shape::Line(a, b) => {
88                w.pair(0, "LINE");
89                w.pair(8, layer);
90                w.point(10, *a);
91                w.point(11, *b);
92            }
93            Shape::Circle { center, radius } => {
94                w.pair(0, "CIRCLE");
95                w.pair(8, layer);
96                w.point(10, *center);
97                w.pair(40, radius);
98            }
99            Shape::Arc {
100                center,
101                radius,
102                start,
103                end,
104            } => {
105                w.pair(0, "ARC");
106                w.pair(8, layer);
107                w.point(10, *center);
108                w.pair(40, radius);
109                w.pair(50, start.to_degrees());
110                w.pair(51, end.to_degrees());
111            }
112            Shape::Polyline(points) => {
113                w.pair(0, "POLYLINE");
114                w.pair(8, layer);
115                w.pair(66, 1);
116                w.point(10, [0.0, 0.0]);
117                w.pair(70, 0);
118                for p in points {
119                    w.pair(0, "VERTEX");
120                    w.pair(8, layer);
121                    w.point(10, *p);
122                }
123                w.pair(0, "SEQEND");
124                w.pair(8, layer);
125            }
126            Shape::Filled(points) => {
127                // A solid takes four corners, the third and fourth crossed:
128                // a triangle repeats its last.
129                let corner = |k: usize| points[k.min(points.len() - 1)];
130                w.pair(0, "SOLID");
131                w.pair(8, layer);
132                w.point(10, corner(0));
133                w.point(11, corner(1));
134                w.point(12, corner(3));
135                w.point(13, corner(2));
136            }
137        }
138    }
139    for label in &sheet.labels {
140        w.pair(0, "TEXT");
141        w.pair(8, label.layer.name);
142        w.point(10, label.at);
143        w.pair(40, label.height);
144        w.pair(1, text(&label.text));
145        if label.angle != 0.0 {
146            w.pair(50, label.angle);
147        }
148        let justify = match label.anchor {
149            Anchor::Start => 0,
150            Anchor::Middle => 1,
151            Anchor::End => 2,
152        };
153        if justify != 0 {
154            w.pair(72, justify);
155            w.point(11, label.at);
156        }
157    }
158    w.pair(0, "ENDSEC");
159    w.pair(0, "EOF");
160    w.0
161}