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}