1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
use std::collections::VecDeque;
use crate::{ansi, ansi::AnsiString};
#[derive(Debug, Clone)]
pub enum Justify {
Left,
Right,
Center,
}
pub struct JustedString<'a> {
input: &'a str,
overflow: ansi::Overflow,
}
impl<'a> JustedString<'a> {
pub fn truncating(input: &'a str) -> Self {
Self {
input,
overflow: ansi::Overflow::Truncate,
}
}
pub fn wrapping(input: &'a str) -> Self {
Self {
input,
overflow: ansi::Overflow::WordWrap,
}
}
/// Processes input text into justified lines that fit within specified dimensions.
///
/// This is the main entry point for text justification. It first processes the input
/// text to handle ANSI codes and line breaking, then applies the specified alignment
/// to each resulting line.
///
/// # Arguments
/// * `hspace` - Maximum characters per line (excluding ANSI codes)
/// * `vspace` - Maximum number of lines to generate
/// * `pad` - Text alignment strategy (left, right, center)
///
/// # Returns
/// A queue of fully formatted strings, each representing a justified line
pub fn justify(&self, hspace: usize, vspace: usize, pad: Justify) -> VecDeque<String> {
let ansi_strings = ansi::build_string(self.input, hspace, vspace, &self.overflow);
ansi_strings
.into_iter()
.map(|ansi_string| self.wrap_with_paddings(ansi_string, hspace, &pad))
.collect::<VecDeque<_>>()
}
/// Formats a single line of text with proper alignment and ANSI code handling.
///
/// Takes an ANSI-formatted string and applies the specified justification within
/// the given horizontal space, ensuring proper padding and ANSI code continuation.
///
/// # Arguments
/// * `astr` - The ANSI string containing text, formatting codes, and metadata
/// * `hspace` - Total horizontal space available for the formatted text
/// * `pad` - Justification strategy (left, right, center)
///
/// # Returns
/// A properly formatted string with alignment padding and ANSI codes
fn wrap_with_paddings(&self, astr: AnsiString, hspace: usize, pad: &Justify) -> String {
let slice = astr.slice;
let needs_padding = hspace - astr.len;
if needs_padding == 0 && astr.c2c.is_none() && !astr.needs_rst {
return astr.slice.to_string();
}
let mut result = String::with_capacity(
astr.c2c.as_ref().map(|s| s.len()).unwrap_or(0)
+ astr.len
+ needs_padding
+ (astr.needs_rst as usize * ansi::RST_CODE.len()),
);
if let Some(c2c) = astr.c2c {
result.push_str(&c2c);
}
match pad {
Justify::Left => {
result.push_str(slice);
for _ in 0..needs_padding {
result.push(' ');
}
}
Justify::Right => {
for _ in 0..needs_padding {
result.push(' ');
}
result.push_str(slice);
}
Justify::Center => {
let left_padding = needs_padding / 2;
let right_padding = needs_padding - left_padding;
for _ in 0..left_padding {
result.push(' ');
}
result.push_str(slice);
for _ in 0..right_padding {
result.push(' ');
}
}
}
if astr.needs_rst {
result.push_str(ansi::RST_CODE);
}
result
}
}