wavedrom/lib.rs
1//! WaveDrom allows for the programmatic creation of beautiful [Diagram Timing Diagrams][dtd] in
2//! Rust. This is the crate that powers all the wavedrom tools including the [editor], the
3//! [command-line interface][cli], and the [mdbook preprocessor][mdbook-wavedrom].
4//!
5//! This crate is be used in two ways. It can be given [WaveJson][wavejson] which is a JSON format
6//! to describe [Diagram Timing Diagrams][dtd]. Alternatively, you can programmatically define a
7//! figure by building it using the [`Figure`] struct.
8//!
9//! # Getting Started
10//!
11//! Getting started with this crate is quite easy. Here, we have two examples. First, how to use
12//! [WaveJson][wavejson] as an input to your figures and second how to programmically define
13//! figures.
14//!
15//! ## WaveJson
16//!
17#![cfg_attr(feature = "json5", doc = r####"
18```
19use std::fs::File;
20
21let path = "path/to/file.svg";
22# let path = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/doc-root-wavejson.svg");
23let mut file = File::create(path)?;
24
25wavedrom::render_json5(r##"
26 { signal: [
27 { name: "clk", wave: "P......" },
28 { name: "bus", wave: "x.==.=x", data: ["head", "body", "tail", "data"] },
29 { name: "wire", wave: "0.1..0." }
30 ]}
31"##, &mut file)?;
32# <Result<(), wavedrom::RenderJson5Error>>::Ok(())
33```"####)]
34//!
35//! **Result:**
36//!
37#![doc=include_str!("../assets/doc-root-wavejson.svg")]
38//!
39//! ## Programmically defining a Figure
40//!
41//! ```
42//! use std::fs::File;
43//! use wavedrom::{Figure, Signal};
44//!
45//! let figure = Figure::new()
46//! .header_text("Timing Schema")
47//! .add_signals([
48//! Signal::with_cycle_str("p........").name("clk"),
49//! Signal::with_cycle_str("010......").name("req"),
50//! Signal::with_cycle_str("0......10").name("done"),
51//! Signal::with_cycle_str("0......10").name("done"),
52//! Signal::with_cycle_str("==.=.=.=.").name("state")
53//! .add_data_fields([
54//! "Idle", "Fetch", "Calculate", "Return", "Idle",
55//! ]),
56//! ]);
57//! let assembled_figure = figure.assemble();
58//!
59//! let path = "path/to/file.svg";
60//! # let path = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/doc-root-programatically.svg");
61//! let mut file = File::create(path)?;
62//!
63//! assembled_figure.write_svg(&mut file)?;
64//! # <Result<(), std::io::Error>>::Ok(())
65//! ```
66//!
67//! **Result:**
68//!
69#![doc=include_str!("../assets/doc-root-programatically.svg")]
70//!
71//! # Cargo Features
72//!
73//! There are a set of cargo features, most of which are enabled by default.
74//!
75//! * `serde`. Enabled by default. Adds the [`wavejson`] module, which defines the serialize and
76//! deserialize formats for a wave format for a wave.
77//! * `embed_font`. Enabled by default. Adds an embedded [Helvetica][helvetica] into the library
78//! which is used to find the dimensions of certain texts. When this is disabled, it is replaced by
79//! a width look-up table that is only accurate for ASCII and over-estimates the width for other
80//! UTF-8 characters.
81//! * `json5`. Enabled by default. The human friendly variant of JSON that can be used with the
82//! `serde` feature to deserialize a WaveJson file.
83//! * `serde_json`. Disabled by default. Formal version of JSON that can be used with the `serde`
84//! feature to deserialize a WaveJson file.
85//! * `skins`. Enabled by default. Adds the [`skin`] module, which defines the serialize and
86//! deserialize formats for WaveDrom skins. Also adds logic to merge a skin into an existing set of
87//! options.
88//!
89//! # Rendering Process
90//!
91//! The rendering process of this crate is done in 3 steps.
92//!
93//! **1. Create [`Figure`]**
94//!
95//! A [`Figure`] can be created in two ways. First, a [`Figure`] can be built programmatically with
96//! the [`Figure::new`] method and the builder pattern methods. Second, a [`Figure`] can be built
97//! by loading a [WaveJson][wavejson] file. This can be done with the [`Figure::from_json5`] or
98//! [`Figure::from_json`] methods.
99//!
100//! **2. Assemble [`Figure`] to [`AssembledFigure`]**
101//!
102//! A [`Figure`] needs to be assembled. This shapes the signal waves removes any invalid groups and
103//! edges. Assembling is done with the [`Figure::assemble`] and [`Figure::assemble_with_options`]
104//! methods.
105//!
106//! **3. Render [`AssembledFigure`] to SVG**
107//!
108//! An [`AssembledFigure`] can be rendered by calling the [`AssembledFigure::write_svg`] or
109//! [`AssembledFigure::write_svg_with_options`] methods. This will write an SVG into an
110//! [`io::Write`][std::io::Write] buffer. If a write to the [`io::Write`][std::io::Write] is
111//! expensive, it is recommended to wrap the [`io::Write`][std::io::Write] in a
112//! [`std::io::BufWriter`].
113//!
114//! [helvetica]: https://en.wikipedia.org/wiki/Helvetica
115//! [dtd]: https://en.wikipedia.org/wiki/Digital_timing_diagram
116//! [editor]: https://gburghoorn.com/wavedrom
117//! [cli]: https://github.com/coastalwhite/wavedrom-rs/tree/main/wavedrom-cli
118//! [mdbook-wavedrom]: https://github.com/coastalwhite/wavedrom-rs/tree/main/mdbook-wavedrom
119
120#![cfg_attr(docsrs, feature(doc_auto_cfg))]
121#![cfg_attr(all(feature = "serde_json", feature = "json5", feature = "serde", feature = "skins"), deny(rustdoc::broken_intra_doc_links))]
122#![deny(missing_docs)]
123
124#[cfg(feature = "json5")]
125pub use json5;
126
127#[cfg(feature = "serde_json")]
128pub use serde_json;
129
130#[cfg(feature = "skins")]
131pub mod skin;
132
133mod color;
134mod cycle_offset;
135pub mod edges;
136mod figure;
137mod path;
138mod shortcuts;
139mod signal;
140mod svg;
141
142pub use figure::Figure;
143pub use color::Color;
144pub use cycle_offset::{CycleOffset, InCycleOffset};
145pub use shortcuts::*;
146pub use signal::Signal;
147pub use svg::*;
148pub use path::*;
149
150pub mod markers;
151#[cfg(feature = "serde")]
152pub mod wavejson;
153
154use edges::{
155 EdgeArrowType, EdgeVariant, LineEdgeMarkers, SharpEdgeVariant,
156 SplineEdgeVariant,
157};
158
159use markers::{ClockEdge, CycleEnumerationMarker, GroupMarker};
160
161/// A section of the figure's signals
162#[derive(Debug, Clone)]
163pub enum FigureSection {
164 /// A [`Signal`]
165 Signal(Signal),
166 /// A group of [`Signal`]s
167 Group(FigureSectionGroup),
168}
169
170/// A section of the figure's group
171#[derive(Debug, Clone)]
172pub struct FigureSectionGroup(Option<String>, Vec<FigureSection>);
173
174/// A line of the [`AssembledFigure`].
175///
176/// This contains the shaped signal path, the group nesting depth and the name of the signal line.
177#[derive(Debug, Clone)]
178pub struct AssembledLine<'a> {
179 text: &'a str,
180 path: AssembledSignalPath,
181}
182
183impl From<Signal> for FigureSection {
184 fn from(wave: Signal) -> Self {
185 Self::Signal(wave)
186 }
187}
188
189#[derive(Default, Debug)]
190struct DefinitionTracker {
191 has_undefined: bool,
192 has_gaps: bool,
193 has_posedge_marker: bool,
194 has_negedge_marker: bool,
195}
196
197
198/// A [`Figure`] that has been assembled with the [`Figure::assemble`] or
199/// [`Figure::assemble_with_options`] methods.
200///
201/// An assembled figure contains all the information necessary to perform rendering.
202#[derive(Debug)]
203pub struct AssembledFigure<'a> {
204 num_cycles: u32,
205
206 hscale: u16,
207
208 definitions: DefinitionTracker,
209
210 group_label_at_depth: Vec<bool>,
211 max_group_depth: u32,
212
213 header_text: Option<&'a str>,
214 footer_text: Option<&'a str>,
215
216 top_cycle_marker: Option<CycleEnumerationMarker>,
217 bottom_cycle_marker: Option<CycleEnumerationMarker>,
218
219 path_assemble_options: PathAssembleOptions,
220
221 lines: Vec<AssembledLine<'a>>,
222 group_markers: Vec<GroupMarker<'a>>,
223
224 line_edge_markers: LineEdgeMarkers<'a>,
225}
226
227impl<'a> AssembledFigure<'a> {
228 #[inline]
229 fn amount_labels_below(&self, depth: u32) -> u32 {
230 self.group_label_at_depth
231 .iter()
232 .take(depth as usize)
233 .filter(|x| **x)
234 .count() as u32
235 }
236
237 /// Returns the maximum cycle width over all lines.
238 #[inline]
239 pub fn num_cycles(&self) -> u32 {
240 self.num_cycles
241 }
242
243 /// Returns the scaling factor for the horizontal axis.
244 #[inline]
245 pub fn horizontal_scale(&self) -> u16 {
246 self.hscale
247 }
248
249 /// Returns whether the [`AssembledFigure`] contains any [`CycleState::X`]
250 #[inline]
251 pub fn has_undefined(&self) -> bool {
252 self.definitions.has_undefined
253 }
254
255 /// Returns whether the [`AssembledFigure`] contains any [`CycleState::Gap`]
256 #[inline]
257 pub fn has_gaps(&self) -> bool {
258 self.definitions.has_gaps
259 }
260
261 /// Returns whether the [`AssembledFigure`] contains any [`CycleState::PosedgeClockMarked`]
262 #[inline]
263 pub fn has_posedge_marker(&self) -> bool {
264 self.definitions.has_posedge_marker
265 }
266
267 /// Returns whether the [`AssembledFigure`] contains any [`CycleState::NegedgeClockMarked`]
268 #[inline]
269 pub fn has_negedge_marker(&self) -> bool {
270 self.definitions.has_negedge_marker
271 }
272
273 /// Returns the whether there is a label at group nesting level `depth`.
274 #[inline]
275 pub fn has_group_label_at_depth(&self, depth: u32) -> bool {
276 let Ok(depth) = usize::try_from(depth) else {
277 return false;
278 };
279
280 self.group_label_at_depth
281 .get(depth)
282 .cloned()
283 .unwrap_or(false)
284 }
285
286 /// Returns the maximum depth of the group nesting.
287 #[inline]
288 pub fn group_nesting(&self) -> u32 {
289 self.max_group_depth
290 }
291
292 /// Returns the lines that the [`AssembledFigure`] contains
293 #[inline]
294 pub fn lines(&self) -> &[AssembledLine<'a>] {
295 &self.lines
296 }
297
298 /// Returns the markers for the group nestings
299 #[inline]
300 pub fn group_markers(&self) -> &[GroupMarker<'a>] {
301 &self.group_markers
302 }
303
304 /// Returns a potential header text of the [`AssembledFigure`]
305 #[inline]
306 pub fn header_text(&self) -> Option<&'a str> {
307 self.header_text
308 }
309
310 /// Returns a potential footer text of the [`AssembledFigure`]
311 #[inline]
312 pub fn footer_text(&self) -> Option<&'a str> {
313 self.footer_text
314 }
315
316 /// Returns a [`CycleEnumerationMarker`] above the signals of the [`AssembledFigure`]
317 #[inline]
318 pub fn top_cycle_marker(&self) -> Option<CycleEnumerationMarker> {
319 self.top_cycle_marker
320 }
321
322 /// Returns a [`CycleEnumerationMarker`] below the signals of the [`AssembledFigure`]
323 #[inline]
324 pub fn bottom_cycle_marker(&self) -> Option<CycleEnumerationMarker> {
325 self.bottom_cycle_marker
326 }
327}
328
329impl AssembledLine<'_> {
330 fn is_empty(&self) -> bool {
331 self.path.is_empty() && self.text.is_empty()
332 }
333}