@@ -32,13 +32,18 @@ Some examples of **incorrect results**:
3232 >> > ' café' .center(6 , ' X' )
3333 ' caféX'
3434
35+ >> > # result consumes 4 total cells, 2 expected
36+ >> > ' 🇿🇼' .ljust(2 , ' X' )
37+ ' 🇿🇼 '
38+
3539 >> > # result consumes 2 total cells, 4 expected.
3640 >> > print (' 👨👩👧' .center(4 , ' X' ))
3741 👨👩👧
3842
3943 Solution
4044--------
4145
46+
4247The base functions of this library are the POSIX.1-2001 and POSIX.1-2008 `wcwidth(3) `_ and
4348`wcswidth(3) `_, which this library precisely copies by interface as `wcwidth() `_ and `wcswidth() `_.
4449These functions return -1 when C0 and C1 control codes are present.
@@ -48,7 +53,8 @@ the displayed width of most C0 and C1 control codes, and measuring many kinds of
4853like colors, bold, tabstops, and horizontal cursor movement. This is aided by the
4954`iter_sequences() `_ function that provides an iterator over terminal sequences.
5055
51- A ``iter_graphemes() `` iterator function is provided to help format strings for a terminal.
56+ To solve the justification problem, this library provides `ljust() `_, `rjust() `_, and `center() `_
57+ functions that properly handle Unicode character widths and terminal escape sequences.
5258
5359Discrepancies
5460-------------
@@ -78,23 +84,8 @@ Measures width of a single codepoint,
7884 >> > wcwidth.wcwidth(' \u2640 ' )
7985 1
8086
81- Use function ``wcwidth() `` to determine the length of a *single unicode character *.
82-
83- See `Specification <Specification_from_pypi _>`_ of character measurements. More
84- briefly, return values of function ``wcwidth() `` are:
85-
86- ``-1 ``
87- Indeterminate (not printable) control codes (C0 and C1).
88-
89- ``0 ``
90- Does not advance the cursor, such as NULL or Combining.
91-
92- ``2 ``
93- Characters of category East Asian Wide (W) or East Asian
94- Full-width (F) which are displayed using two terminal cells.
95-
96- ``1 ``
97- All others.
87+ Use function `wcwidth() `_ to determine the length of a *single unicode character *.
88+ See `Specification <Specification_from_pypi _>`_ of character measurements.
9889
9990wcswidth()
10091----------
@@ -107,11 +98,97 @@ Measures width of a string, returns -1 for control codes.
10798 >> > wcwidth.wcswidth(' ♀️' )
10899 2
109100
110- Use function ``wcswidth() `` to determine the length of many, a *string of unicode characters *
101+ Use function `wcswidth() `_ to determine the length of many, a *string of unicode characters *
102+
103+ See `Specification <Specification_from_pypi _>`_ of character measurements. Note that
104+ ``-1 `` is returned if control codes occurs anywhere in the string.
105+
106+ width()
107+ -------
108+
109+ Measures width of a string with improved handling of ``control_codes ``
110+
111+ .. code-block :: python
112+
113+ >> > # same support as wcswidth(), eg. regional indicator flag:
114+ >> > wcwidth.width(' \U0001F1FF\U0001F1FC ' )
115+ 2
116+ >> > # SGR colored text, 'WARN', followed by SGR reset
117+ >> > wcwidth.width(' \x1b [38;2;255;150;100mWARN\x1b [0m' )
118+ 4
119+ >> > # customized tabsize and location
120+ >> > wcwidth.width(' \t ' , tabsize = 4 , column = 1 )
121+ 3
122+ >> > # tab and all other control characters ignored
123+ >> > wcwidth.width(' \t ' , control_codes = ' ignore' )
124+ 0
125+ >> > # "vertical" movement and control characters are always ignored
126+ >> > wcwidth.width(' \n ' )
127+ 0
128+ >> > # as well as sequences with "indeterminate" effects like Home + Clear
129+ >> > wcwidth.width(' \x1b [H\x1b [2J' )
130+ 0
131+ >> > # ValueError may raise when control_codes='strict'
132+ >> > wcwidth.width(' \n ' , control_codes = ' strict' )
133+ Traceback (most recent call last):
134+ ...
135+ ValueError : Vertical movement character 0x a at position 0
136+ >> > wcwidth.width(' \x1b [H\x1b [2J' , control_codes = ' strict' )
137+ Traceback (most recent call last):
138+ ...
139+ ValueError : Indeterminate cursor sequence at position 0
140+
141+ iter_sequences()
142+ ----------------
143+
144+ Iterates through text, yielding segments with escape sequence identification.
145+
146+ .. code-block :: python
147+
148+ >> > list (wcwidth.iter_sequences(' hello' ))
149+ [(' hello' , False )]
150+ >> > list (wcwidth.iter_sequences(' \x1b [31mred\x1b [0m' ))
151+ [(' \x1b [31m' , True ), (' red' , False ), (' \x1b [0m' , True )]
152+
153+ Use `iter_sequences() `_ to split text into segments of plain text and escape sequences. Each tuple
154+ contains the segment string and a boolean indicating whether it is an escape sequence (``True ``) or
155+ plain text (``False ``).
156+
157+ ljust()
158+ -------
159+
160+ Use `ljust() `_ as replacement of `str.ljust() `_:
161+
162+ .. code-block :: python
163+
164+ >> > ' コンニチハ' .ljust(11 , ' *' ) # don't do this
165+ ' コンニチハ******'
166+ >> > wcwidth.ljust(' コンニチハ' , 11 , ' *' ) # do this!
167+ ' コンニチハ*'
168+
169+ rjust()
170+ -------
171+
172+ Use `rjust() `_ as replacement of `str.rjust() `_:
173+
174+ .. code-block :: python
175+
176+ >> > ' コンニチハ' .rjust(11 , ' *' ) # don't do this
177+ ' ******コンニチハ'
178+ >> > wcwidth.rjust(' コンニチハ' , 11 , ' *' ) # do this!
179+ ' *コンニチハ'
180+
181+ center()
182+ --------
183+
184+ Use `center() `_ as replacement of `str.center() `_:
185+
186+ .. code-block :: python
111187
112- See `Specification <Specification_from_pypi _>`_ of character measurements. More briefly, return
113- values of function ``wcswidth() `` is the sum of ``wcwidth() `` with some additional account for some
114- kinds of sequences. Similarly, ``-1 `` is returned if control codes occurs anywhere in the string.
188+ >> > ' cafe\u0301 ' .center(6 , ' *' ) # don't do this
189+ ' café*'
190+ >> > wcwidth.center(' cafe\u0301 ' , 6 , ' *' )
191+ ' *café*' # do this!
115192
116193 width()
117194-------
@@ -293,6 +370,8 @@ History
293370
2943710.2.15 **next version **
295372 * **New ** Function `iter_graphemes() `_. `PR #165 `_.
373+ * **New ** Functions `width() `_ and `iter_sequences() `_. `PR #166 `_.
374+ * **New ** Functions `ljust() `_, `rjust() `_, `center() `_. `PR #168 `_.
296375
2973760.2.14 *2025-09-22 *
298377 * **Drop Support ** for Python 2.7 and 3.5. `PR #117 `_.
@@ -425,6 +504,8 @@ https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c::
425504.. _`PR #146` : https://github.com/jquast/wcwidth/pull/146
426505.. _`PR #149` : https://github.com/jquast/wcwidth/pull/149
427506.. _`PR #165` : https://github.com/jquast/wcwidth/pull/165
507+ .. _`PR #166` : https://github.com/jquast/wcwidth/pull/166
508+ .. _`PR #168` : https://github.com/jquast/wcwidth/pull/168
428509.. _`Issue #101` : https://github.com/jquast/wcwidth/issues/101
429510.. _`jquast/blessed` : https://github.com/jquast/blessed
430511.. _`selectel/pyte` : https://github.com/selectel/pyte
@@ -461,7 +542,12 @@ https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c::
461542.. _`str.ljust()` : https://docs.python.org/3/library/stdtypes.html#str.ljust
462543.. _`str.rjust()` : https://docs.python.org/3/library/stdtypes.html#str.rjust
463544.. _`str.center()` : https://docs.python.org/3/library/stdtypes.html#str.center
464- .. _`Annex #29` : https://www.unicode.org/reports/tr29/
545+ .. _`wcwidth()` : https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.wcwidth
546+ .. _`wcswidth()` : https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.wcswidth
547+ .. _`iter_sequences()` : https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.iter_sequences
548+ .. _`ljust()` : https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.ljust
549+ .. _`rjust()` : https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.rjust
550+ .. _`center()` : https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.center
465551.. _`General Tabulated Summary` : https://ucs-detect.readthedocs.io/results.html
466552.. |pypi_downloads | image :: https://img.shields.io/pypi/dm/wcwidth.svg?logo=pypi
467553 :alt: Downloads
0 commit comments