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}