fmtastic/tally_marks.rs
1use crate::integer::IntegerImpl;
2use crate::UnsignedInteger;
3use core::fmt::{self, Write};
4
5/// Formats an unsigned integer as tally marks.
6///
7/// You may need to install an extra font such as [Noto Sans Symbols 2]
8/// since most other fonts do not support these digits.
9///
10/// [Noto Sans Symbols 2]: https://fonts.google.com/noto/specimen/Noto+Sans+Symbols+2
11///
12/// ```
13/// use fmtastic::TallyMarks;
14///
15/// assert_eq!("", TallyMarks(0_u32).to_string());
16/// assert_eq!("π·", TallyMarks(1_u32).to_string());
17/// assert_eq!("π·π·", TallyMarks(2_u32).to_string());
18/// assert_eq!("π·π·π·", TallyMarks(3_u32).to_string());
19/// assert_eq!("π·π·π·π·", TallyMarks(4_u32).to_string());
20/// assert_eq!("πΈ", TallyMarks(5_u32).to_string());
21/// assert_eq!("πΈπ·", TallyMarks(6_u32).to_string());
22/// assert_eq!("πΈπΈπΈπ·π·", TallyMarks(17_u32).to_string());
23/// ```
24#[derive(Debug, Clone, Copy, Eq, PartialEq)]
25pub struct TallyMarks<T>(pub T);
26
27impl<T> From<T> for TallyMarks<T>
28where
29 T: UnsignedInteger,
30{
31 fn from(value: T) -> Self {
32 TallyMarks(value)
33 }
34}
35
36impl<T> fmt::Display for TallyMarks<T>
37where
38 T: UnsignedInteger,
39{
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 fmt_tally_marks(self.0.to_impl(), f)
42 }
43}
44
45fn fmt_tally_marks<T: IntegerImpl>(n: T, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 const TALLY_MARK_ONE: char = '\u{1D377}';
47 const TALLY_MARK_FIVE: char = '\u{1D378}';
48 let (fives, ones) = (n / T::FIVE, n % T::FIVE);
49 T::range(T::ZERO, fives).try_for_each(|_| f.write_char(TALLY_MARK_FIVE))?;
50 T::range(T::ZERO, ones).try_for_each(|_| f.write_char(TALLY_MARK_ONE))?;
51 Ok(())
52}