ramify/writer/
style.rs

1//! Diagram writer styles
2
3use std::{fmt, io};
4
5use super::{FmtWriter, IOWriter};
6
7/// Configuration for the appearance of the branch diagram.
8///
9/// This struct is used by [`IOWriter`]s and [`FmtWriter`]s.
10/// For more fine-grained configuration than is available here, one should manually
11/// implement [`DiagramWrite`](super::DiagramWrite).
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct Style {
14    /// A set of characters to use in the diagram.
15    pub(crate) charset: Charset,
16    /// The margin at the beginning of each annotation.
17    pub(crate) annotation_margin: usize,
18    /// The minimum left alignment of each annotation line.
19    pub(crate) annotation_justification: usize,
20    /// The gap between vertices.
21    pub(crate) gutter_width: usize,
22    /// Put merge lines on top.
23    pub(crate) merge_over: bool,
24}
25
26impl Style {
27    /// Initialize a new style from the provided character set.
28    #[inline]
29    pub const fn new(charset: Charset) -> Self {
30        Self {
31            charset,
32            annotation_margin: 1,
33            annotation_justification: 0,
34            gutter_width: 0,
35            merge_over: false,
36        }
37    }
38
39    /// Get an [`IOWriter`] using this style.
40    #[inline]
41    pub const fn io_writer<W: io::Write>(self, writer: W) -> IOWriter<W> {
42        IOWriter::new(self, writer)
43    }
44
45    /// Get a [`FmtWriter`] using this style.
46    #[inline]
47    pub const fn fmt_writer<W: fmt::Write>(self, writer: W) -> FmtWriter<W> {
48        FmtWriter::new(self, writer)
49    }
50
51    /// Initialize with the [rounded corners](Charset::rounded_corners) character set.
52    ///
53    /// The charset looks as follows.
54    /// ```txt
55    /// 0    
56    /// ├┬╮  
57    /// │1├╮
58    /// ││2│
59    /// │3│├╮
60    /// │╭╯││
61    /// ││╭┤│
62    /// │││4│
63    /// ││5╭╯
64    /// │6╭╯
65    /// 7╭╯  
66    ///  8   
67    /// ```
68    #[inline]
69    pub const fn rounded_corners() -> Self {
70        Self::new(Charset::rounded_corners())
71    }
72
73    /// Initialize with the [sharp corners](Charset::sharp_corners) character set.
74    ///
75    /// The charset looks as follows.
76    /// ```txt
77    /// 0
78    /// ├┬┐
79    /// │1├┐
80    /// ││2│
81    /// │3│├┐
82    /// │┌┘││
83    /// ││┌┤│
84    /// │││4│
85    /// ││5┌┘
86    /// │6┌┘
87    /// 7┌┘
88    ///  8
89    /// ```
90    #[inline]
91    pub const fn sharp_corners() -> Self {
92        Self::new(Charset::sharp_corners())
93    }
94
95    /// Initialize with the [doubled lines](Charset::doubled_lines) character set.
96    ///
97    /// The charset looks as follows.
98    /// ```txt
99    /// 0
100    /// ╠╦╗
101    /// ║1╠╗
102    /// ║║2║
103    /// ║3║╠╗
104    /// ║╔╝║║
105    /// ║║╔╣║
106    /// ║║║4║
107    /// ║║5╔╝
108    /// ║6╔╝
109    /// 7╔╝
110    ///  8
111    /// ```
112    #[inline]
113    pub const fn doubled_lines() -> Self {
114        Self::new(Charset::doubled_lines())
115    }
116
117    /// Reset the character set used in the style.
118    #[inline]
119    pub const fn charset(mut self, charset: Charset) -> Self {
120        self.charset = charset;
121        self
122    }
123
124    /// Invert the internal character set.
125    ///
126    /// This inverts vertically by swapping characters like `╯` and `╮`. Calling this method twice
127    /// will return the original character set. This makes the lines connect correctly when writing
128    /// the diagram lines in reverse order.
129    ///
130    /// Also see [`Config::inverted_annotations`](crate::Config::inverted_annotations) to modify the layout algorithm to
131    /// draw annotations correctly if you are also rendering annotations.
132    ///
133    /// ## Example
134    /// Inverting has the following effect (the third column has reversed lines).
135    /// ```txt
136    /// 0         0          8
137    /// ├┬╮       ├┴╯       7╰╮
138    /// │1├╮      │1├╯      │6╰╮
139    /// ││2│      ││2│      ││5╰╮
140    /// │3│├╮     │3│├╯     │││4│
141    /// │╭╯││  →  │╰╮││  ↺  ││╰┤│
142    /// ││╭┤│     ││╰┤│     │╰╮││
143    /// │││4│  →  │││4│  ↺  │3│├╯
144    /// ││5╭╯     ││5╰╮     ││2│
145    /// │6╭╯      │6╰╮      │1├╯
146    /// 7╭╯       7╰╮       ├┴╯
147    ///  8         8        0
148    /// ```
149    #[inline]
150    pub const fn invert(mut self) -> Self {
151        self.charset = self.charset.invert();
152        self
153    }
154
155    /// The margin at the beginning of each annotation.
156    ///
157    /// This is the number of characters written at the beginning of the annotation line to create
158    /// a gap between the annotation and the branch diagram lines.
159    ///
160    /// The default value is `1`.
161    ///
162    /// ## Example
163    /// Here is an example going from annotation margin `0` to `2`.
164    /// ```txt
165    /// 0             0
166    /// ├┬╮           ├┬╮
167    /// │1├╮L0    →   │1├╮  L0
168    /// ││││L1        ││││  L1
169    /// ││2│          ││2│
170    /// │3│├╮L0   →   │3│├╮  L0
171    /// │╭╯││         │╭╯││
172    /// ││╭┤│         ││╭┤│
173    /// │││4│L0   →   │││4│  L0
174    /// │││╭╯L1       │││╭╯  L1
175    /// ││││ L2       ││││   L2
176    /// ││5│          ││5│
177    /// │6╭╯          │6╭╯
178    /// 7╭╯           7╭╯
179    ///  8             8
180    /// ```
181    #[inline]
182    pub const fn annotation_margin(mut self, annotation_margin: usize) -> Self {
183        self.annotation_margin = annotation_margin;
184        self
185    }
186
187    /// The minimum left justification of the annotations.
188    ///
189    /// Annotation lines will never begin earlier than this value, but may begin later than this
190    /// value if the row width plus annotation margin exceeds the justification. Note that the
191    /// justficiation is the number of characters, rather than columns, and therefore may need to be
192    /// adjusted if the gutter width is not zero.
193    ///
194    /// The default value is `0`.
195    ///
196    /// ## Example
197    /// Here is an example going from justification 0 to 5, with an annotation margin of 1.
198    /// Observe that:
199    ///
200    /// - `L0` is not shifted, since the justification refers to the start of the line after the
201    ///   margin
202    /// - `L1` still starts 1 character to the right of the justification since the diagram is too wide.
203    /// - `L2` is shifted right by two characters, so that it begins 5 characters from the
204    ///   boundary.
205    /// ```txt
206    /// 0             0
207    /// ├┬╮           ├┬╮
208    /// │1├╮ L0   →   │1├╮ L0
209    /// ││2│          ││2│
210    /// │3│├╮         │3│├╮
211    /// │╭╯││         │╭╯││
212    /// ││╭┤│         ││╭┤│
213    /// │││4│ L1  →   │││4│ L1
214    /// ││5╭╯         ││5╭╯
215    /// │6╭╯          │6╭╯
216    /// 7╭╯           7╭╯
217    ///  8 L2     →    8   L2
218    ///               ---->
219    /// ```
220    #[inline]
221    pub const fn annotation_justification(mut self, annotation_justification: usize) -> Self {
222        self.annotation_justification = annotation_justification;
223        self
224    }
225
226    /// The gap between columns in the branch diagram.
227    ///
228    /// The default value is `0`.
229    ///
230    /// ## Example
231    /// Setting the gutter width to `1` has the following effect.
232    /// ```txt
233    /// 0         0     
234    /// ├┬╮       ├─┬─╮  
235    /// │1├╮      │ 1 ├─╮
236    /// ││2│      │ │ 2 │
237    /// │3│├╮     │ 3 │ ├─╮
238    /// │╭╯││  →  │ ╭─╯ │ │
239    /// ││╭┤│     │ │ ╭─┤ │
240    /// │││4│  →  │ │ │ 4 │
241    /// ││5╭╯     │ │ 5 ╭─╯
242    /// │6╭╯      │ 6 ╭─╯
243    /// 7╭╯       7 ╭─╯   
244    ///  8          8
245    ///            ^ ^ ^ ^ gutters
246    /// ```
247    #[inline]
248    pub const fn gutter_width(mut self, gutter_width: usize) -> Self {
249        self.gutter_width = gutter_width;
250        self
251    }
252
253    /// Whether merge lines should go above diagram lines.
254    ///
255    /// Setting this value to `true` typically requires a
256    /// [gutter width](Self::gutter_width) of at least 1 for diagram clarity.
257    ///
258    /// The default value is `false`.
259    ///
260    /// ## Example
261    /// With a gutter width of 1, setting this to `true` has the following effect:
262    /// ```txt
263    /// 0             0
264    /// ├─╮           ├─╮
265    /// │ 1           │ 1
266    /// │ ├─╮         │ ├─╮
267    /// │ 2 ╰───╮     │ 2 ╰───╮
268    /// │ ├─┬─╮ │     │ ├─┬─╮ │
269    /// ├─│─╯ │ │  →  ├───╯ │ │
270    /// 3 │ ╭─╯ │     3 │ ╭─╯ │
271    /// │ │ 4 ╭─╯     │ │ 4 ╭─╯
272    /// │ ╰─╮ │       │ ╰─╮ │
273    /// ├─╮ │ │       ├─╮ │ │
274    /// ├─│─╯ │    →  ├───╯ │
275    /// 5 │ ╭─╯       5 │ ╭─╯
276    /// ╭─┴─╯         ╭─┴─╯
277    /// 6             6
278    /// ```
279    #[inline]
280    pub const fn merge_over(mut self, merge_over: bool) -> Self {
281        self.merge_over = merge_over;
282        self
283    }
284}
285
286/// A set of characters to use in the diagram.
287///
288/// In most cases, you would use some combination of [box-drawing
289/// characters](https://en.wikipedia.org/wiki/Box-drawing_characters).
290///
291/// The [`rounded_corners`](Self::rounded_corners) character set often looks the best if there is
292/// font support, whereas the [`sharp_corners`](Self::sharp_corners) tends to have more universal
293/// font support.
294///
295/// When used in the [`Style`] struct, an [`IOWriter`] or a [`FmtWriter`] expects that these are
296/// characters with width exactly 1 when displayed to the terminal. Other characters,
297/// such as control characters or double-width characters (mainly those described in
298/// [Unicode Annex #11](https://www.unicode.org/reports/tr11/tr11-11.html)) will corrupt the
299/// branch diagram.
300#[derive(Debug, Clone, PartialEq, Eq)]
301pub struct Charset {
302    /// The ` ` character.
303    pub space: char,
304    /// The `╮` character.
305    pub down_and_left: char,
306    /// The `╭` character.
307    pub down_and_right: char,
308    /// The `╯` character.
309    pub up_and_left: char,
310    /// The `╰` character.
311    pub up_and_right: char,
312    /// The `─` character.
313    pub horizontal: char,
314    /// The `│` character.
315    pub vertical: char,
316    /// The `┬` character.
317    pub down_and_horizontal: char,
318    /// The `┴` character.
319    pub up_and_horizontal: char,
320    /// The `┤` character.
321    pub vertical_and_left: char,
322    /// The `├` character.
323    pub vertical_and_right: char,
324    /// The `┼` character.
325    pub vertical_and_horizontal: char,
326}
327
328impl Charset {
329    /// Using this character set in a diagram style.
330    #[inline]
331    pub const fn style(self) -> Style {
332        Style::new(self)
333    }
334
335    /// The rounded corners character set.
336    /// ```txt
337    /// ╯ ┴ ╰
338    /// ┤ ┼ ├ ─
339    /// ╮ ┬ ╭ │
340    /// ```
341    #[inline]
342    pub const fn rounded_corners() -> Self {
343        Self {
344            space: ' ',
345            down_and_left: '╮',
346            down_and_right: '╭',
347            up_and_left: '╯',
348            up_and_right: '╰',
349            horizontal: '─',
350            vertical: '│',
351            down_and_horizontal: '┬',
352            up_and_horizontal: '┴',
353            vertical_and_left: '┤',
354            vertical_and_right: '├',
355            vertical_and_horizontal: '┼',
356        }
357    }
358
359    /// The sharp corners character set.
360    /// ```txt
361    /// ┘ ┴ └
362    /// ┤ ┼ ├ ─
363    /// ┐ ┬ ┌ │
364    /// ```
365    #[inline]
366    pub const fn sharp_corners() -> Self {
367        Self {
368            down_and_left: '┐',
369            down_and_right: '┌',
370            up_and_left: '┘',
371            up_and_right: '└',
372            ..Self::rounded_corners()
373        }
374    }
375
376    /// The doubled lines character set.
377    /// ```txt
378    /// ╝ ╩ ╚
379    /// ╣ ╬ ╠ ═
380    /// ╗ ╦ ╔ ║
381    /// ```
382    #[inline]
383    pub const fn doubled_lines() -> Self {
384        Self {
385            space: ' ',
386            down_and_left: '╗',
387            down_and_right: '╔',
388            up_and_left: '╝',
389            up_and_right: '╚',
390            horizontal: '═',
391            vertical: '║',
392            down_and_horizontal: '╦',
393            up_and_horizontal: '╩',
394            vertical_and_left: '╣',
395            vertical_and_right: '╠',
396            vertical_and_horizontal: '╬',
397        }
398    }
399
400    /// Flip the orientation of the charset vertically.
401    ///
402    /// This swaps the `down_*` characters with the `up_*` characters. This is useful when you
403    /// want to render the branch diagram with the root at the bottom since
404    /// it will make the branch diagram lines connect correctly when the lines are written in
405    /// reverse order.
406    ///
407    /// Also see [`Config::inverted_annotations`](crate::Config::inverted_annotations) to modify the layout algorithm to
408    /// draw annotations correctly.
409    #[inline]
410    pub const fn invert(self) -> Self {
411        Self {
412            down_and_left: self.up_and_left,
413            down_and_right: self.up_and_right,
414            down_and_horizontal: self.up_and_horizontal,
415            up_and_left: self.down_and_left,
416            up_and_right: self.down_and_right,
417            up_and_horizontal: self.down_and_horizontal,
418            ..self
419        }
420    }
421}