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}