unicode_line_stacker/lib.rs
1//! Module for stacking line-drawing characters on top of each other.
2//!
3//! # Examples
4//!
5//! ```
6//! let combo = unicode_line_stacker::stack('┌', '┴');
7//! assert_eq!(Some('┼'), combo);
8//!
9//! // Bit string format: for each of the four directions, clockwise starting from
10//! // top (least significant to most significant), 1 means "on" and 0 means "off."
11//! let c = unicode_line_stacker::bits_to_char(0b1011);
12//! assert_eq!('┴', c);
13//! ```
14
15/// Stack two line-drawing characters on top of each other and return the result.
16///
17/// Returns `None` if one or both of the input characters are unsupported.
18///
19/// # Examples
20///
21/// ```
22/// let a = '─';
23/// let b = '│';
24/// let result = unicode_line_stacker::stack(a, b);
25/// assert_eq!(Some('┼'), result);
26/// ```
27pub fn stack(a: char, b: char) -> Option<char> {
28 let look_for_a = char_to_bits(a);
29 let look_for_b = char_to_bits(b);
30
31 if look_for_b.is_none() || look_for_b.is_none() {
32 None
33 } else {
34 Some(LINE_DRAWING_CHARS[look_for_a.unwrap() | look_for_b.unwrap()])
35 }
36}
37
38/// Convert a line-drawing char to a bitset (or None if the char is unsupported).
39///
40/// # Examples
41///
42/// ```
43/// let c = '┬';
44/// let result = unicode_line_stacker::char_to_bits(c);
45/// assert_eq!(Some(0b1110), result);
46/// ```
47#[inline]
48pub fn char_to_bits(c: char) -> Option<usize> {
49 // It's likely faster to iterate through 16 chars than it is to try some
50 // HashMap trickery.
51 LINE_DRAWING_CHARS
52 .iter()
53 .enumerate()
54 .find(|&(_, &c2)| c == c2)
55 .map(|tup| tup.0)
56}
57
58/// Convert a bitset to a line-drawing char.
59///
60/// This crate's representation of each line-drawing char is a `u32`
61/// representing a bitset: starting from least significant bit, the bits
62/// represent up, right, down, left, in that order.
63///
64/// # Examples
65///
66/// ```
67/// assert_eq!('┤', unicode_line_stacker::bits_to_char(0b1101));
68/// ```
69///
70/// # Panics
71///
72/// Panics if `bits >= 16`.
73#[inline]
74pub fn bits_to_char(bits: u32) -> char {
75 if bits >= 16 {
76 panic!(
77 "Bit set must be between 0 and 15 inclusive but got {}",
78 bits
79 );
80 }
81
82 LINE_DRAWING_CHARS[bits as usize]
83}
84
85const LINE_DRAWING_CHARS: [char; 16] = [
86 ' ', // 0000
87 '\u{2575}', // 0001
88 '\u{2576}', // 0010
89 '\u{2514}', // 0011
90 '\u{2577}', // 0100
91 '\u{2502}', // 0101
92 '\u{250c}', // 0110
93 '\u{251c}', // 0111
94 '\u{2574}', // 1000
95 '\u{2518}', // 1001
96 '\u{2500}', // 1010
97 '\u{2534}', // 1011
98 '\u{2510}', // 1100
99 '\u{2524}', // 1101
100 '\u{252c}', // 1110
101 '\u{253c}', // 1111
102];
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn stack_with_empty() {
110 let base_char = '\u{2514}'; // light up and right
111
112 let result = stack(' ', base_char);
113
114 assert_eq!(Some(base_char), result);
115 }
116
117 #[test]
118 #[should_panic(expected = "but got 16")]
119 fn bits_to_char_panics_on_input_16() {
120 bits_to_char(16);
121 }
122}