0
# Display and Formatting
1
2
Unicode-aware text formatting for terminal display, including width calculation and justification functions that handle fullwidth characters and zero-width characters correctly.
3
4
## Capabilities
5
6
### Character Width Calculation
7
8
Functions for calculating the display width of Unicode characters in monospaced terminals.
9
10
```python { .api }
11
def character_width(char: str) -> int:
12
"""
13
Determine display width of character in monospaced terminal.
14
15
Returns width in terminal columns: 0 for zero-width, 1 for normal,
16
2 for wide characters (CJK), -1 for control/non-printable chars.
17
Uses wcwidth library for accurate Unicode width calculation.
18
19
Args:
20
char: Single Unicode character
21
22
Returns:
23
Display width (0, 1, 2, or -1)
24
25
Examples:
26
>>> character_width('A')
27
1
28
>>> character_width('車') # Wide CJK character
29
2
30
>>> character_width('\\u200b') # Zero-width space
31
0
32
>>> character_width('\\x1b') # Control character
33
-1
34
"""
35
36
def monospaced_width(text: str) -> int:
37
"""
38
Calculate total display width of text in monospaced terminal.
39
40
Sums the display widths of all characters, handling wide characters,
41
zero-width characters, and control sequences properly.
42
43
Args:
44
text: Unicode string
45
46
Returns:
47
Total display width in terminal columns, or -1 if contains
48
control characters that make width undetermined
49
50
Examples:
51
>>> monospaced_width("hello")
52
5
53
>>> monospaced_width("café")
54
4
55
>>> monospaced_width("hello世界") # Mixed ASCII and wide chars
56
9
57
>>> monospaced_width("hello\\x1b[31m") # Contains control chars
58
-1
59
"""
60
```
61
62
### Text Justification
63
64
Unicode-aware text justification functions that properly handle character width for terminal display.
65
66
```python { .api }
67
def display_ljust(text: str, width: int, fillchar: str = " ") -> str:
68
"""
69
Left-justify text in field of given width, accounting for Unicode display width.
70
71
Unlike str.ljust(), correctly handles wide characters (CJK), zero-width
72
characters, and combining characters for proper terminal alignment.
73
74
Args:
75
text: String to justify
76
width: Target display width in terminal columns
77
fillchar: Character to pad with (default space)
78
79
Returns:
80
Left-justified string
81
82
Examples:
83
>>> display_ljust("hello", 10)
84
'hello '
85
>>> display_ljust("café", 10, '-')
86
'café------'
87
>>> display_ljust("世界", 6) # Wide chars count as 2
88
'世界 '
89
"""
90
91
def display_rjust(text: str, width: int, fillchar: str = " ") -> str:
92
"""
93
Right-justify text in field of given width, accounting for Unicode display width.
94
95
Unicode-aware version of str.rjust() that handles wide characters,
96
zero-width characters, and combining characters correctly.
97
98
Args:
99
text: String to justify
100
width: Target display width in terminal columns
101
fillchar: Character to pad with (default space)
102
103
Returns:
104
Right-justified string
105
106
Examples:
107
>>> display_rjust("hello", 10)
108
' hello'
109
>>> display_rjust("世界", 6) # Wide chars handled correctly
110
' 世界'
111
"""
112
113
def display_center(text: str, width: int, fillchar: str = " ") -> str:
114
"""
115
Center text in field of given width, accounting for Unicode display width.
116
117
Unicode-aware version of str.center() that properly centers text
118
containing wide characters, zero-width characters, and combining chars.
119
120
Args:
121
text: String to center
122
width: Target display width in terminal columns
123
fillchar: Character to pad with (default space)
124
125
Returns:
126
Centered string
127
128
Examples:
129
>>> display_center("hello", 11)
130
' hello '
131
>>> display_center("世界", 8) # Wide chars centered correctly
132
' 世界 '
133
"""
134
```
135
136
## Usage Examples
137
138
### Basic Width Calculation
139
140
```python
141
from ftfy.formatting import character_width, monospaced_width
142
143
# Check individual character widths
144
print(character_width('A')) # 1 - normal ASCII
145
print(character_width('世')) # 2 - wide CJK
146
print(character_width('\u0300')) # 0 - combining accent
147
print(character_width('\t')) # -1 - control character
148
149
# Calculate total text width
150
text = "Hello 世界!"
151
width = monospaced_width(text)
152
print(f"'{text}' displays as {width} columns") # 9 columns
153
```
154
155
### Terminal-Aware Text Alignment
156
157
```python
158
from ftfy.formatting import display_ljust, display_rjust, display_center
159
160
texts = ["hello", "café", "世界", "mixed 世界 text"]
161
width = 20
162
163
print("Left justified:")
164
for text in texts:
165
justified = display_ljust(text, width, '.')
166
print(f"'{justified}'")
167
168
print("\nRight justified:")
169
for text in texts:
170
justified = display_rjust(text, width, '.')
171
print(f"'{justified}'")
172
173
print("\nCentered:")
174
for text in texts:
175
justified = display_center(text, width, '.')
176
print(f"'{justified}'")
177
```
178
179
### Table Formatting
180
181
```python
182
from ftfy.formatting import display_ljust, display_rjust, monospaced_width
183
184
def format_table(data, headers, widths):
185
"""Format table with proper Unicode alignment."""
186
187
# Print headers
188
header_row = " | ".join(
189
display_ljust(header, width)
190
for header, width in zip(headers, widths)
191
)
192
print(header_row)
193
print("-" * monospaced_width(header_row))
194
195
# Print data rows
196
for row in data:
197
formatted_row = " | ".join(
198
display_ljust(str(cell), width)
199
for cell, width in zip(row, widths)
200
)
201
print(formatted_row)
202
203
# Example with mixed character widths
204
headers = ["Name", "City", "Score"]
205
widths = [15, 10, 8]
206
data = [
207
["Alice Smith", "NYC", "95.5"],
208
["田中太郎", "東京", "87.2"], # Japanese name and city
209
["José García", "México", "92.1"] # Accented characters
210
]
211
212
format_table(data, headers, widths)
213
```
214
215
### Progress Bar with Unicode
216
217
```python
218
from ftfy.formatting import display_ljust, monospaced_width
219
220
def unicode_progress_bar(current, total, width=40, fill='█', empty='░'):
221
"""Create progress bar that handles Unicode fill characters."""
222
223
# Calculate fill amount based on actual character widths
224
fill_width = monospaced_width(fill)
225
empty_width = monospaced_width(empty)
226
227
# Adjust for character widths
228
if fill_width > 1:
229
width = width // fill_width * fill_width
230
231
percent = current / total
232
filled_chars = int(width * percent // fill_width)
233
empty_chars = (width - filled_chars * fill_width) // empty_width
234
235
bar = fill * filled_chars + empty * empty_chars
236
237
return f"[{bar}] {current}/{total} ({percent:.1%})"
238
239
# Example with Unicode characters
240
print(unicode_progress_bar(7, 10)) # [██████████████████████████████░░░░░░░░░░] 7/10 (70.0%)
241
print(unicode_progress_bar(3, 5)) # [████████████████████████░░░░░░░░░░░░░░░░] 3/5 (60.0%)
242
```
243
244
### Command Line Output Formatting
245
246
```python
247
from ftfy.formatting import display_ljust, display_center, monospaced_width
248
249
def print_status_table(statuses):
250
"""Print status table with proper alignment."""
251
252
# Calculate column widths based on actual display widths
253
name_width = max(monospaced_width(s['name']) for s in statuses) + 2
254
status_width = max(monospaced_width(s['status']) for s in statuses) + 2
255
256
print(display_center("System Status", name_width + status_width))
257
print("=" * (name_width + status_width))
258
259
for item in statuses:
260
name_col = display_ljust(item['name'], name_width)
261
status_col = display_ljust(item['status'], status_width)
262
print(f"{name_col}{status_col}")
263
264
# Example with international text
265
statuses = [
266
{'name': 'Database', 'status': '✓ Running'},
267
{'name': 'サーバー', 'status': '✓ 動作中'}, # Japanese
268
{'name': 'Señales', 'status': '⚠ Alerta'}, # Spanish with warning
269
{'name': 'Система', 'status': '✗ Ошибка'} # Russian with error
270
]
271
272
print_status_table(statuses)
273
```
274
275
### Width-Aware Text Processing
276
277
```python
278
from ftfy.formatting import monospaced_width, display_ljust
279
280
def wrap_text_unicode(text, max_width):
281
"""Wrap text accounting for Unicode display width."""
282
words = text.split()
283
lines = []
284
current_line = []
285
current_width = 0
286
287
for word in words:
288
word_width = monospaced_width(word)
289
space_width = 1 if current_line else 0
290
291
if current_width + word_width + space_width <= max_width:
292
current_line.append(word)
293
current_width += word_width + space_width
294
else:
295
if current_line:
296
lines.append(' '.join(current_line))
297
current_line = [word]
298
current_width = word_width
299
300
if current_line:
301
lines.append(' '.join(current_line))
302
303
return lines
304
305
# Example with mixed character widths
306
mixed_text = "This text contains 中文字符 and עברית characters with different display widths"
307
wrapped = wrap_text_unicode(mixed_text, 30)
308
for line in wrapped:
309
print(f"'{line}' ({monospaced_width(line)} columns)")
310
```