cjk_align/
lib.rs

1//! This library provides a wrapper struct [CJKAlign] to align CJK and emoji characters
2//! correctly on terminals. Despite its name, it works for other Unicode characters too
3//! as supported by the unicode-width crate.
4//!
5//! ```
6//! use cjk_align::CJKAlign;
7//!
8//! assert_eq!(format!("{:6}", CJKAlign("你好")), "你好  ");
9//! assert_eq!(format!("{:>6}", CJKAlign("你好")), "  你好");
10//! assert_eq!(format!("{:^6}", CJKAlign("你好")), " 你好 ");
11//! assert_eq!(format!("{:^7}", CJKAlign("你好")), "  你好 ");
12//! ```
13//!
14//! To treat East Asian ambiguous width characters as double width, use
15//! [CJKAlignWide] instead:
16//!
17//! ```
18//! use cjk_align::{CJKAlign, CJKAlignWide};
19//!
20//! assert_eq!(format!("{:8}", CJKAlign("“……”")), "“……”    ");
21//! assert_eq!(format!("{:8}", CJKAlignWide("“……”")), "“……”");
22//! ```
23
24use std::fmt::{Display, Formatter, Error, Alignment};
25
26use unicode_width::UnicodeWidthStr;
27
28pub struct CJKAlign<'a>(pub &'a str);
29impl<'a> Display for CJKAlign<'a> {
30  fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
31    StringWidth::fmt(self, f)
32  }
33}
34
35pub struct CJKAlignWide<'a>(pub &'a str);
36impl<'a> Display for CJKAlignWide<'a> {
37  fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
38    StringWidth::fmt(self, f)
39  }
40}
41
42trait StringWidth {
43  fn width(&self) -> usize;
44  fn str(&self) -> &str;
45
46  fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
47    let w = f.width().unwrap_or(0);
48    if w == 0 {
49      write!(f, "{}", self.str())
50    } else {
51      let self_width = self.width();
52      let pad = w.saturating_sub(self_width);
53      match f.align() {
54        Some(Alignment::Left) | None => write!(f, "{}{}", self.str(), " ".repeat(pad)),
55        Some(Alignment::Right) => write!(f, "{}{}", " ".repeat(pad), self.str()),
56        Some(Alignment::Center) => {
57          write!(f, "{}{}{}", " ".repeat(pad - pad / 2), self.str(), " ".repeat(pad / 2))
58        },
59      }
60    }
61  }
62}
63
64impl<'a> StringWidth for CJKAlign<'a> {
65  fn width(&self) -> usize {
66    UnicodeWidthStr::width(self.0)
67  }
68
69  fn str(&self) -> &str {
70    self.0
71  }
72}
73
74impl<'a> StringWidth for CJKAlignWide<'a> {
75  fn width(&self) -> usize {
76    UnicodeWidthStr::width_cjk(self.0)
77  }
78
79  fn str(&self) -> &str {
80    self.0
81  }
82}