#![cfg_attr(
feature = "std",
doc = r##"
Making sure the string is displayed in exactly number of columns by
combining padding and truncating.
```rust
use unicode_truncate::UnicodeTruncateStr;
use unicode_truncate::Alignment;
use unicode_width::UnicodeWidthStr;
let rv = "你好吗".unicode_pad(5, Alignment::Left, true);
assert_eq!(rv, "你好 ");
assert_eq!(rv.width(), 5);
```
"##
)]
#![deny(missing_docs, unsafe_code)]
#![cfg_attr(not(feature = "std"), no_std)]
use unicode_width::UnicodeWidthChar;
use unicode_width::UnicodeWidthStr;
pub trait UnicodeTruncateStr {
fn unicode_truncate(&self, width: usize) -> (&str, usize);
#[cfg(feature = "std")]
fn unicode_pad(
&self,
width: usize,
align: Alignment,
truncate: bool,
) -> std::borrow::Cow<'_, str>;
}
impl UnicodeTruncateStr for str {
#[inline]
fn unicode_truncate(&self, width: usize) -> (&str, usize) {
if width == 0 {
return (self.get(..0).unwrap(), 0);
}
let mut new_width = self.width();
if new_width <= width {
return (self, new_width);
}
for (bidx, c) in self.char_indices().rev() {
new_width = new_width - c.width().unwrap_or(0);
if new_width <= width {
return (self.get(..bidx).unwrap(), new_width);
}
}
(self.get(..0).unwrap(), 0)
}
#[cfg(feature = "std")]
#[inline]
fn unicode_pad(
&self,
width: usize,
align: Alignment,
truncate: bool,
) -> std::borrow::Cow<'_, str> {
pad::unicode_pad(self, width, align, truncate)
}
}
#[cfg(feature = "std")]
mod pad {
use super::*;
pub use std::borrow::Cow;
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub enum Alignment {
Left,
Center,
Right,
}
pub fn unicode_pad(s: &str, width: usize, align: Alignment, truncate: bool) -> Cow<'_, str> {
let mut cols = s.width();
let mut cs = Cow::Borrowed(s);
if cols >= width {
if !truncate {
return Cow::Borrowed(s);
}
{
let (new_s, new_cols) = s.unicode_truncate(width);
cs = Cow::Borrowed(new_s);
cols = new_cols;
}
if cols == width {
return cs;
}
}
let diff = width.saturating_sub(cols);
let (left_pad, right_pad) = match align {
Alignment::Left => (0, diff),
Alignment::Right => (diff, 0),
Alignment::Center => (diff / 2, diff.saturating_sub(diff / 2)),
};
let mut rv = String::new();
rv.reserve(left_pad + cs.len() + right_pad);
for _ in 0..left_pad {
rv.push(' ');
}
rv.push_str(&cs);
for _ in 0..right_pad {
rv.push(' ');
}
Cow::Owned(rv)
}
}
#[cfg(feature = "std")]
pub use pad::Alignment;
#[cfg(test)]
mod tests {
mod truncate {
use super::super::*;
#[test]
fn empty() {
let (rv, rw) = "".unicode_truncate(4);
assert_eq!(rv, "");
assert_eq!(rw, 0);
}
#[test]
fn zero_width() {
let (rv, rw) = "ab".unicode_truncate(0);
assert_eq!(rv, "");
assert_eq!(rw, 0);
let (rv, rw) = "你好".unicode_truncate(0);
assert_eq!(rv, "");
assert_eq!(rw, 0);
}
#[test]
fn less_than_limit() {
let (rv, rw) = "abc".unicode_truncate(4);
assert_eq!(rv, "abc");
assert_eq!(rw, 3);
let (rv, rw) = "你".unicode_truncate(4);
assert_eq!(rv, "你");
assert_eq!(rw, 2);
}
#[test]
fn at_boundary() {
let (rv, rw) = "boundary".unicode_truncate(5);
assert_eq!(rv, "bound");
assert_eq!(rw, 5);
let (rv, rw) = "你好吗".unicode_truncate(4);
assert_eq!(rv, "你好");
assert_eq!(rw, 4);
}
#[test]
fn not_boundary() {
let (rv, rw) = "你好吗".unicode_truncate(3);
assert_eq!(rv, "你");
assert_eq!(rw, 2);
let (rv, rw) = "你好吗".unicode_truncate(1);
assert_eq!(rv, "");
assert_eq!(rw, 0);
}
}
#[cfg(feature = "std")]
mod pad {
use super::super::*;
#[test]
fn zero_width() {
let rv = "你好".unicode_pad(0, Alignment::Left, true);
assert_eq!(&rv, "");
let rv = "你好".unicode_pad(0, Alignment::Left, false);
assert_eq!(&rv, "你好");
}
#[test]
fn less_than_limit() {
let rv = "你".unicode_pad(4, Alignment::Left, true);
assert_eq!(&rv, "你 ");
let rv = "你".unicode_pad(4, Alignment::Left, false);
assert_eq!(&rv, "你 ");
}
#[test]
fn width_at_boundary() {
let rv = "你好吗".unicode_pad(4, Alignment::Left, true);
assert_eq!(&rv, "你好");
let rv = "你好吗".unicode_pad(4, Alignment::Left, false);
assert_eq!(&rv, "你好吗");
}
#[test]
fn width_not_boundary() {
let rv = "你好吗".unicode_pad(3, Alignment::Left, true);
assert_eq!(&rv, "你 ");
let rv = "你好吗".unicode_pad(1, Alignment::Left, true);
assert_eq!(&rv, " ");
let rv = "你好吗".unicode_pad(3, Alignment::Left, false);
assert_eq!(&rv, "你好吗");
}
}
}