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("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}