1#![allow(dead_code)]
4
5#[allow(dead_code)]
9pub struct EpsOptions {
10 pub title: String,
11 pub width_pt: f32,
12 pub height_pt: f32,
13 pub line_width: f32,
14}
15
16impl Default for EpsOptions {
17 fn default() -> Self {
18 EpsOptions {
19 title: "OxiHuman Export".to_string(),
20 width_pt: 595.0,
21 height_pt: 842.0,
22 line_width: 1.0,
23 }
24 }
25}
26
27#[allow(dead_code)]
29pub struct EpsPath {
30 pub points: Vec<[f32; 2]>,
31 pub closed: bool,
32 pub stroke_rgb: [f32; 3],
33 pub fill_rgb: Option<[f32; 3]>,
34}
35
36#[allow(dead_code)]
38pub struct EpsDocument {
39 pub options: EpsOptions,
40 pub paths: Vec<EpsPath>,
41}
42
43#[allow(dead_code)]
45pub fn new_eps_document(options: EpsOptions) -> EpsDocument {
46 EpsDocument {
47 options,
48 paths: Vec::new(),
49 }
50}
51
52#[allow(dead_code)]
54pub fn add_eps_path(doc: &mut EpsDocument, path: EpsPath) {
55 doc.paths.push(path);
56}
57
58#[allow(dead_code)]
60pub fn export_eps(doc: &EpsDocument) -> String {
61 let mut out = String::new();
62 out.push_str("%!PS-Adobe-3.0 EPSF-3.0\n");
63 out.push_str(&format!(
64 "%%BoundingBox: 0 0 {} {}\n",
65 doc.options.width_pt as i32, doc.options.height_pt as i32
66 ));
67 out.push_str(&format!("%%Title: {}\n", doc.options.title));
68 out.push_str("%%EndComments\n");
69 out.push_str(&format!("{} setlinewidth\n", doc.options.line_width));
70 for path in &doc.paths {
71 if path.points.is_empty() {
72 continue;
73 }
74 let [r, g, b] = path.stroke_rgb;
75 out.push_str(&format!("{:.4} {:.4} {:.4} setrgbcolor\n", r, g, b));
76 let p0 = path.points[0];
77 out.push_str(&format!("{:.4} {:.4} moveto\n", p0[0], p0[1]));
78 for &p in &path.points[1..] {
79 out.push_str(&format!("{:.4} {:.4} lineto\n", p[0], p[1]));
80 }
81 if path.closed {
82 out.push_str("closepath\n");
83 if let Some([fr, fg, fb]) = path.fill_rgb {
84 out.push_str("gsave\n");
85 out.push_str(&format!("{:.4} {:.4} {:.4} setrgbcolor\n", fr, fg, fb));
86 out.push_str("fill\ngrestore\n");
87 }
88 }
89 out.push_str("stroke\n");
90 }
91 out.push_str("%%EOF\n");
92 out
93}
94
95#[allow(dead_code)]
97pub fn eps_path_count(doc: &EpsDocument) -> usize {
98 doc.paths.len()
99}
100
101#[allow(dead_code)]
103pub fn eps_bounding_box(doc: &EpsDocument) -> ([f32; 2], [f32; 2]) {
104 let mut mn = [f32::INFINITY; 2];
105 let mut mx = [f32::NEG_INFINITY; 2];
106 for path in &doc.paths {
107 for &p in &path.points {
108 mn[0] = mn[0].min(p[0]);
109 mn[1] = mn[1].min(p[1]);
110 mx[0] = mx[0].max(p[0]);
111 mx[1] = mx[1].max(p[1]);
112 }
113 }
114 if mn[0].is_infinite() {
115 mn = [0.0; 2];
116 mx = [0.0; 2];
117 }
118 (mn, mx)
119}
120
121#[allow(dead_code)]
123pub fn edges_to_eps_paths(
124 positions_2d: &[[f32; 2]],
125 edges: &[[u32; 2]],
126 stroke: [f32; 3],
127) -> Vec<EpsPath> {
128 edges
129 .iter()
130 .map(|&[a, b]| EpsPath {
131 points: vec![positions_2d[a as usize], positions_2d[b as usize]],
132 closed: false,
133 stroke_rgb: stroke,
134 fill_rgb: None,
135 })
136 .collect()
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 fn simple_path() -> EpsPath {
144 EpsPath {
145 points: vec![[0.0, 0.0], [100.0, 0.0], [100.0, 100.0]],
146 closed: false,
147 stroke_rgb: [0.0, 0.0, 0.0],
148 fill_rgb: None,
149 }
150 }
151
152 #[test]
153 fn eps_has_header() {
154 let doc = new_eps_document(EpsOptions::default());
155 let eps = export_eps(&doc);
156 assert!(eps.contains("%!PS-Adobe"));
157 }
158
159 #[test]
160 fn eps_has_bounding_box() {
161 let doc = new_eps_document(EpsOptions::default());
162 let eps = export_eps(&doc);
163 assert!(eps.contains("BoundingBox"));
164 }
165
166 #[test]
167 fn eps_has_eof() {
168 let doc = new_eps_document(EpsOptions::default());
169 let eps = export_eps(&doc);
170 assert!(eps.contains("%%EOF"));
171 }
172
173 #[test]
174 fn add_path_increases_count() {
175 let mut doc = new_eps_document(EpsOptions::default());
176 add_eps_path(&mut doc, simple_path());
177 assert_eq!(eps_path_count(&doc), 1);
178 }
179
180 #[test]
181 fn eps_contains_moveto() {
182 let mut doc = new_eps_document(EpsOptions::default());
183 add_eps_path(&mut doc, simple_path());
184 let eps = export_eps(&doc);
185 assert!(eps.contains("moveto"));
186 }
187
188 #[test]
189 fn eps_contains_lineto() {
190 let mut doc = new_eps_document(EpsOptions::default());
191 add_eps_path(&mut doc, simple_path());
192 let eps = export_eps(&doc);
193 assert!(eps.contains("lineto"));
194 }
195
196 #[test]
197 fn bounding_box_empty() {
198 let doc = new_eps_document(EpsOptions::default());
199 let (mn, mx) = eps_bounding_box(&doc);
200 assert_eq!(mn, [0.0, 0.0]);
201 assert_eq!(mx, [0.0, 0.0]);
202 }
203
204 #[test]
205 fn bounding_box_correct() {
206 let mut doc = new_eps_document(EpsOptions::default());
207 add_eps_path(&mut doc, simple_path());
208 let (mn, mx) = eps_bounding_box(&doc);
209 assert!((mn[0] - 0.0).abs() < 1e-5);
210 assert!((mx[0] - 100.0).abs() < 1e-5);
211 }
212
213 #[test]
214 fn edges_to_eps_paths_count() {
215 let pts = vec![[0.0f32, 0.0], [100.0, 0.0], [100.0, 100.0]];
216 let edges = vec![[0u32, 1], [1, 2]];
217 let paths = edges_to_eps_paths(&pts, &edges, [0.0, 0.0, 0.0]);
218 assert_eq!(paths.len(), 2);
219 }
220
221 #[test]
222 fn closed_path_has_closepath() {
223 let mut doc = new_eps_document(EpsOptions::default());
224 let p = EpsPath {
225 points: vec![[0.0, 0.0], [50.0, 0.0], [25.0, 50.0]],
226 closed: true,
227 stroke_rgb: [0.0, 0.0, 0.0],
228 fill_rgb: None,
229 };
230 add_eps_path(&mut doc, p);
231 let eps = export_eps(&doc);
232 assert!(eps.contains("closepath"));
233 }
234
235 #[test]
236 fn title_in_eps() {
237 let opts = EpsOptions {
238 title: "MyTest".to_string(),
239 ..Default::default()
240 };
241 let doc = new_eps_document(opts);
242 let eps = export_eps(&doc);
243 assert!(eps.contains("MyTest"));
244 }
245}