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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
//! Utility functions for TUI rendering.
//!
//! This module provides utility functions for working with text and layout
//! in TUI applications.
//!
//! # Text Utilities
//!
//! - [`truncate_to_width`]: Truncate a string to fit within a maximum display width.
//! - [`wrapped_line_count`]: Count visual lines when text is wrapped at a given width.
//!
//! # Layout Utilities
//!
//! - [`centered_rect`]: Calculate a centered rectangle within a given area.
//!
//! # Example
//!
//! ```rust
//! use envision::util::{truncate_to_width, wrapped_line_count};
//!
//! // ASCII text
//! assert_eq!(truncate_to_width("hello world", 5), "hello");
//!
//! // CJK characters are 2 columns wide
//! assert_eq!(truncate_to_width("世界", 3), "世");
//!
//! // Wrapped line counting
//! assert_eq!(wrapped_line_count("hello world", 5), 3);
//! assert_eq!(wrapped_line_count("hello\nworld", 20), 2);
//! ```
use Rect;
use UnicodeWidthChar;
/// Truncates a string to fit within `max_width` display columns.
///
/// Returns the longest prefix of `s` whose display width does not exceed
/// `max_width`. The returned slice is always a valid UTF-8 substring of `s`
/// and never splits a character.
///
/// Wide characters (e.g., CJK ideographs, some emoji) count as 2 display
/// columns. If a wide character would cause the accumulated width to exceed
/// `max_width`, it is excluded entirely.
///
/// # Examples
///
/// ```rust
/// use envision::util::truncate_to_width;
///
/// // Fits entirely
/// assert_eq!(truncate_to_width("hi", 10), "hi");
///
/// // Exact fit
/// assert_eq!(truncate_to_width("hello", 5), "hello");
///
/// // Truncated
/// assert_eq!(truncate_to_width("hello world", 5), "hello");
///
/// // Empty string
/// assert_eq!(truncate_to_width("", 5), "");
///
/// // Zero width
/// assert_eq!(truncate_to_width("hello", 0), "");
///
/// // CJK characters (2 columns each)
/// assert_eq!(truncate_to_width("世界", 4), "世界");
/// assert_eq!(truncate_to_width("世界", 3), "世");
/// assert_eq!(truncate_to_width("世界", 1), "");
/// ```
/// Counts the number of visual lines when `s` is wrapped at `width` columns.
///
/// This function handles both explicit newlines (`\n`) and character-level
/// wrapping within each line. Wide characters (CJK, some emoji) that would
/// exceed the line width are bumped to the next line.
///
/// # Special cases
///
/// - Returns `0` if `width` is `0` (no columns available to display text).
/// - Returns `1` for an empty string (an empty line still occupies one row).
/// - A trailing newline adds an additional line.
///
/// # Examples
///
/// ```rust
/// use envision::util::wrapped_line_count;
///
/// // Single line fits
/// assert_eq!(wrapped_line_count("hello", 10), 1);
///
/// // Empty string
/// assert_eq!(wrapped_line_count("", 10), 1);
///
/// // Explicit newlines
/// assert_eq!(wrapped_line_count("a\nb\nc", 10), 3);
///
/// // Character-level wrapping
/// assert_eq!(wrapped_line_count("hello world", 5), 3);
///
/// // Zero width
/// assert_eq!(wrapped_line_count("hello", 0), 0);
///
/// // CJK wrapping (2 columns each)
/// assert_eq!(wrapped_line_count("世界你好", 5), 2);
/// ```
/// Calculates a centered rectangle within the given area.
///
/// Returns a `Rect` of the given `width` and `height` centered within `area`.
/// The dimensions are clamped to fit within the area if they exceed it.
///
/// # Examples
///
/// ```rust
/// use ratatui::prelude::Rect;
/// use envision::util::centered_rect;
///
/// let area = Rect::new(0, 0, 80, 24);
/// let centered = centered_rect(40, 10, area);
/// assert_eq!(centered.x, 20);
/// assert_eq!(centered.y, 7);
/// assert_eq!(centered.width, 40);
/// assert_eq!(centered.height, 10);
/// ```