rust_hdl/docs/vcd2svg/
display_metrics.rs

1use crate::docs::vcd2svg::renderable::Renderable;
2use crate::docs::vcd2svg::time_view::TimeView;
3use crate::docs::vcd2svg::timed_value::{changes, SignalType, TimedValue};
4use crate::docs::vcd2svg::utils::{line, rect, time_label};
5use crate::docs::vcd2svg::vcd_style::VCDStyle;
6use svg::node::element::path::Data;
7use svg::node::element::{Path, Text, SVG};
8
9pub struct DisplayMetrics {
10    pub signal_width: u32,
11    pub signal_height: u32,
12    pub timescale_height: u32,
13    pub tick_half_height: u32,
14    pub timescale_midline: u32,
15    pub canvas_width: u32,
16    pub canvas_height: u32,
17    pub shim: u32,
18    pub label_size: u32,
19    pub min_time: u64,
20    pub max_time: u64,
21    pub style: VCDStyle,
22}
23
24impl Default for DisplayMetrics {
25    fn default() -> Self {
26        Self {
27            signal_width: 200,
28            signal_height: 20,
29            timescale_height: 45,
30            tick_half_height: 6,
31            timescale_midline: 20,
32            canvas_width: 1000,
33            canvas_height: 400,
34            shim: 5,
35            label_size: 10,
36            min_time: 40,
37            max_time: 102,
38            style: VCDStyle::scansion(),
39        }
40    }
41}
42
43impl DisplayMetrics {
44    fn compute_major_tick_delta_t(&self) -> u64 {
45        let delta_t = (self.max_time - self.min_time) as f64;
46        let s = delta_t.log10() - 1.0;
47        let x = s.floor();
48        let e = s - x;
49        let d0 = (e - 0.0).abs();
50        let d1 = (e - 2.0_f64.log10()).abs();
51        let d2 = (e - 5.0_f64.log10()).abs();
52        let value = if d0 <= d1 && d0 <= d2 {
53            (10.0_f64.powf(x)) as u64
54        } else if d1 <= d0 && d1 <= d2 {
55            (2.0_f64 * 10.0_f64.powf(x)) as u64
56        } else {
57            (5.0_f64 * 10.0_f64.powf(x)) as u64
58        };
59        value
60    }
61
62    fn time_to_pixel(&self, time: u64) -> Option<u32> {
63        if time < self.min_time || time > self.max_time {
64            None
65        } else {
66            Some(
67                (self.signal_width as f64 + self.pixel_scale() * ((time - self.min_time) as f64))
68                    .round() as u32,
69            )
70        }
71    }
72
73    fn major_tick_distance(&self) -> u32 {
74        (self.compute_major_tick_delta_t() as f64 * self.pixel_scale()).round() as u32
75    }
76
77    fn minor_tick_delta_t(&self) -> f64 {
78        self.compute_major_tick_delta_t() as f64 / 5.0
79    }
80
81    fn pixel_scale(&self) -> f64 {
82        ((self.canvas_width - self.signal_width + 1) as f64)
83            / ((self.max_time - self.min_time) as f64)
84    }
85
86    fn time_view(&self) -> TimeView {
87        TimeView {
88            start_time: self.min_time,
89            end_time: self.max_time,
90            pixel_scale: self.pixel_scale(),
91        }
92    }
93
94    pub fn major_x0(&self, major: u64) -> Option<u32> {
95        let value = self.compute_major_tick_delta_t() * major;
96        self.time_to_pixel(value)
97    }
98
99    pub fn minor_x0(&self, major: u64, minor: u32) -> Option<u32> {
100        let value = self.compute_major_tick_delta_t() * major
101            + (self.minor_tick_delta_t() * (minor + 1) as f64) as u64;
102        self.time_to_pixel(value)
103    }
104    pub fn signal_baseline(&self, index: usize) -> u32 {
105        self.timescale_height + ((index as u32 + 1) * self.signal_height)
106    }
107    pub(crate) fn signal_rect(&self) -> Path {
108        rect(
109            0,
110            0,
111            self.signal_width,
112            self.canvas_height,
113            &self.style.signal_label_background_color,
114        )
115    }
116
117    pub(crate) fn background_rect(&self) -> Path {
118        rect(
119            0,
120            0,
121            self.canvas_width,
122            self.canvas_height,
123            &self.style.background_color,
124        )
125    }
126
127    pub(crate) fn timescale_header_rect(&self) -> Path {
128        rect(
129            self.signal_width,
130            0,
131            self.canvas_width,
132            self.timescale_height,
133            &self.style.timeline_background_color,
134        )
135    }
136
137    pub(crate) fn timescale_midline(&self) -> Path {
138        line(
139            self.signal_width,
140            self.timescale_midline,
141            self.canvas_width,
142            self.timescale_midline,
143            &self.style.timeline_line_color,
144        )
145    }
146
147    fn timescale_major_tick(&self, major: u64) -> Option<Path> {
148        if let Some(x0) = self.major_x0(major) {
149            Some(line(
150                x0,
151                self.timescale_midline - self.tick_half_height,
152                x0,
153                self.timescale_midline + self.tick_half_height,
154                &self.style.timeline_tick_color,
155            ))
156        } else {
157            None
158        }
159    }
160
161    fn timescale_major_gridline(&self, major: u64) -> Option<Path> {
162        if let Some(x0) = self.major_x0(major) {
163            if let Some(color) = self.style.grid_lines.as_ref() {
164                Some(line(
165                    x0,
166                    self.timescale_midline,
167                    x0,
168                    self.canvas_height,
169                    color,
170                ))
171            } else {
172                None
173            }
174        } else {
175            None
176        }
177    }
178
179    fn timescale_minor_tick(&self, major: u64, minor: u32) -> Option<Path> {
180        if let Some(x1) = self.minor_x0(major, minor) {
181            Some(line(
182                x1,
183                self.timescale_midline,
184                x1,
185                self.timescale_midline + self.tick_half_height,
186                &self.style.timeline_tick_color,
187            ))
188        } else {
189            None
190        }
191    }
192
193    fn timescale_major_label(&self, major: u64, value: &str) -> Option<Text> {
194        if let Some(x0) = self.major_x0(major) {
195            let label_width = value.len() as u32 * self.label_size;
196            if (x0 - label_width / 2) >= self.signal_width
197                && (x0 + label_width / 2) <= self.canvas_width
198            {
199                let txt = Text::new()
200                    .add(svg::node::Text::new(value))
201                    .set("x", x0)
202                    .set(
203                        "y",
204                        self.timescale_midline + self.tick_half_height + self.shim,
205                    )
206                    .set("text-anchor", "middle")
207                    .set("font-family", "sans-serif")
208                    .set("alignment-baseline", "hanging")
209                    .set("fill", self.style.timeline_tick_color.clone())
210                    .set("font-size", self.label_size);
211                Some(txt)
212            } else {
213                None
214            }
215        } else {
216            None
217        }
218    }
219
220    pub(crate) fn signal_label(&self, index: usize, signal: &str) -> Text {
221        Text::new()
222            .add(svg::node::Text::new(signal))
223            .set("x", self.shim)
224            .set("y", self.signal_baseline(index) - self.shim)
225            .set("text-anchor", "start")
226            .set("font-family", "sans-serif")
227            .set("alignment-baseline", "bottom")
228            .set("font-size", self.label_size)
229    }
230
231    pub(crate) fn signal_line(&self, index: usize) -> Path {
232        let y0 = self.signal_baseline(index);
233        line(
234            0,
235            y0,
236            self.signal_width,
237            y0,
238            &self.style.timeline_line_color,
239        )
240    }
241
242    pub(crate) fn timescale(&self, mut document: SVG) -> SVG {
243        let first_major_tick =
244            (self.min_time as f64 / self.compute_major_tick_delta_t() as f64).floor() as u64;
245        let last_major_tick =
246            (self.max_time as f64 / self.compute_major_tick_delta_t() as f64).ceil() as u64;
247        let delt = self.compute_major_tick_delta_t();
248        for major in first_major_tick..=last_major_tick {
249            if let Some(major_line) = self.timescale_major_gridline(major) {
250                document = document.add(major_line);
251            }
252            if let Some(major_tick) = self.timescale_major_tick(major) {
253                document = document.add(major_tick);
254            }
255            if let Some(label) = self.timescale_major_label(major, &time_label(delt * major)) {
256                document = document.add(label);
257            }
258            for minor in 0..4 {
259                if let Some(minor_tick) = self.timescale_minor_tick(major, minor) {
260                    document = document.add(minor_tick);
261                }
262            }
263        }
264        document
265    }
266
267    pub(crate) fn horiz_grid_line(&self, index: usize, doc: SVG) -> SVG {
268        if self.style.grid_lines.is_none() {
269            return doc;
270        }
271        doc.add(line(
272            self.signal_width,
273            self.signal_baseline(index),
274            self.canvas_width,
275            self.signal_baseline(index),
276            self.style.grid_lines.as_ref().unwrap(),
277        ))
278    }
279
280    pub(crate) fn vector_signal_plot<T: SignalType>(
281        &self,
282        index: usize,
283        values: &[TimedValue<T>],
284        mut doc: SVG,
285    ) -> SVG {
286        let values = changes(values);
287        let time_view = self.time_view();
288        let y0 = self.signal_baseline(index);
289        let y_lo = (y0 - self.signal_height + self.shim / 2) as f64;
290        let y_hi = (y0 - self.shim / 2) as f64;
291        let flip = |x| if x == y_lo { y_hi } else { y_lo };
292        let shim = 1.0;
293        let x0 = self.signal_width as f64;
294        let mut data_low = Data::new().move_to((x0, y_lo));
295        let mut data_high = Data::new().move_to((x0, y_hi));
296        let mut last_y1 = y_lo as f64;
297        for value in time_view
298            .intervals(&values)
299            .iter()
300            .filter(|x| !x.is_empty())
301        {
302            let x1 = x0 + value.start_x;
303            let y1 = flip(last_y1);
304            data_low = data_low.line_to((x1 - shim, last_y1));
305            data_high = data_high.line_to((x1 - shim, flip(last_y1)));
306            last_y1 = y1;
307            data_low = data_low.line_to((x1 + shim, y1));
308            data_high = data_high.line_to((x1 + shim, flip(y1)));
309            doc = doc.add(
310                Text::new()
311                    .add(svg::node::Text::new(value.label.to_string()))
312                    .set("x", x1 + 2.0 * shim)
313                    .set(
314                        "y",
315                        self.signal_baseline(index) - self.signal_height / 2 + 1,
316                    )
317                    .set("text-anchor", "start")
318                    .set("font-family", "monospace")
319                    //                    .set("font-family", "sans-serif")
320                    .set("alignment-baseline", "middle")
321                    .set("font-size", self.label_size - 2)
322                    .set("fill", "white"),
323            );
324        }
325        if let Some(x) = self.time_to_pixel(self.max_time) {
326            data_low = data_low.line_to((x, last_y1));
327            data_high = data_high.line_to((x, flip(last_y1)));
328        }
329        let doc = doc
330            .add(
331                Path::new()
332                    .set("fill", "none")
333                    .set("stroke", self.style.trace_color.clone())
334                    .set("stroke-width", 0.75)
335                    .set("d", data_low),
336            )
337            .add(
338                Path::new()
339                    .set("fill", "none")
340                    .set("stroke", self.style.trace_color.clone())
341                    .set("stroke-width", 0.75)
342                    .set("d", data_high),
343            );
344        doc
345    }
346    pub(crate) fn bit_signal_plot(&self, index: usize, values: &[TimedValue<bool>]) -> Path {
347        let values = changes(values);
348        let y0 = self.signal_baseline(index) - self.shim / 2;
349        let y1 = y0 - self.signal_height + self.shim;
350        let x0 = self.signal_width;
351        let mut data = Data::new().move_to((x0, y0));
352        let mut last_y1 = y0;
353        for value in values {
354            if let Some(x1) = self.time_to_pixel(value.time) {
355                let y = if value.value { y1 } else { y0 };
356                data = data.line_to((x1, last_y1));
357                last_y1 = y;
358                data = data.line_to((x1, y));
359            }
360        }
361        if let Some(x) = self.time_to_pixel(self.max_time) {
362            data = data.line_to((x, last_y1));
363        }
364        Path::new()
365            .set("fill", "none")
366            .set("stroke", self.style.trace_color.clone())
367            .set("stroke-width", 0.75)
368            .set("d", data)
369    }
370}