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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
|
Metadata-Version: 2.4
Name: wcwidth
Version: 0.8.2
Summary: Measures the displayed width of unicode strings in a terminal
Project-URL: Homepage, https://github.com/jquast/wcwidth
Author-email: Jeff Quast <[email protected]>
License-Expression: MIT
License-File: LICENSE
Keywords: cjk,combining,console,eastasian,emoji,emulator,terminal,unicode,wcswidth,wcwidth,xterm
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Natural Language :: English
Classifier: Operating System :: POSIX
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Classifier: Topic :: Software Development :: Internationalization
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Localization
Classifier: Topic :: Terminals
Classifier: Typing :: Typed
Requires-Python: >=3.8
Description-Content-Type: text/x-rst
|pypi_downloads| |codecov| |license|
============
Introduction
============
This library is mainly for CLI/TUI programs that carefully produce output for Terminals.
Installation
------------
The stable version of this package is maintained on pypi, install or upgrade, using pip::
pip install --upgrade wcwidth
Problem
-------
All Python string-formatting functions, `textwrap.wrap()`_, `str.ljust()`_, `str.rjust()`_, and
`str.center()`_ **incorrectly** measure the displayed width of a string as equal to the number of
their codepoints.
Some examples of **incorrect results**:
.. code-block:: python
>>> # result consumes 16 total cells, 11 expected,
>>> 'コンニチハ'.rjust(11, 'X')
'XXXXXXコンニチハ'
>>> # result consumes 5 total cells, 6 expected,
>>> 'café'.center(6, 'X')
'caféX'
Solution
--------
The lowest-level functions in this library are derived from POSIX.1-2001 and POSIX.1-2008
`wcwidth(3)`_ and `wcswidth(3)`_, which this library precisely copies by interface as `wcwidth()`_
and `wcswidth()`_. These functions return -1 when C0 and C1 control codes are present.
An easy-to-use `width()`_ function is provided as a wrapper of `wcswidth()`_ that is also capable of
measuring most terminal control codes and sequences, like colors, bold, tabstops, and horizontal
cursor movement. `width()`_ argument ``term_program`` may provide more accurate terminal measurement
Corrections_ as a wrapper of `wcstwidth()`_.
Text-justification is solved by the sequence-aware functions `ljust()`_, `rjust()`_, `center()`_,
and the grapheme-aware function `wrap()`_, serving as drop-in replacements to python standard
functions.
The `clip()`_ function extracts substrings by their displayed column positions, and
`strip_sequences()`_ removes terminal escape sequences from text altogether.
The iterator functions `iter_graphemes()`_ and `iter_sequences()`_ allow for careful navigation of
grapheme and terminal control sequence boundaries as required by editors or REPLs with cursor
control. `iter_graphemes_reverse()`_ and `grapheme_boundary_before()`_ are necessary for backward
cursor control over complex unicode.
Discrepancies
-------------
You may find that support *varies* for complex unicode sequences or codepoints.
This library may be considered to presume the terminal is enabled for DEC Private Mode 2027
("Grapheme Clustering") by default, which may require to be enabled by a TUI application but
is often the default mode for those terminals that support it: Windows Terminal, WezTerm, ghostty,
contour, and foot. This library does support any specific "legacy width" measurement, but does
provide Corrections_ for those terminals without grapheme support.
See also:
- `Grapheme Clusters and Terminal Emulators`_
- `terminal-unicode-core.tex`_
- `State of Terminal Emulators in 2025`_
- `Perfecting Terminal Character Width Using Correction Tables (2026)`_
The `jquast/ucs-detect`_ project publishes automatic results of compliance to our standard for Wide
character, Languages, grapheme clustering, complex or combining scripts, emojis, zero-width joiner,
variations, and regional indicator (flags) as a `General Tabulated Summary`_ by terminal emulator
software and version. The results of the ucs-detect project create our correction tables.
========
Overview
========
wcwidth()
---------
Use function ``wcwidth()`` to determine the length of a *single unicode
codepoint*.
A brief overview, through examples, for all of the public API functions.
Full API Documentation at https://wcwidth.readthedocs.io/en/latest/api.html
wcwidth()
---------
Measures width of a single codepoint,
.. code-block:: python
>>> # '♀' narrow emoji
>>> wcwidth.wcwidth('\u2640')
1
Use function `wcwidth()`_ to determine the length of a *single unicode character*.
See specification_ of character measurements. Note that ``-1`` is returned for control codes.
wcswidth()
----------
Measures width of a string, returns -1 for control codes.
.. code-block:: python
>>> # '♀️' emoji w/vs-16
>>> wcwidth.wcswidth('\u2640\ufe0f')
2
Use function `wcswidth()`_ to determine the length of many, a *string of unicode characters*.
See specification_ of character measurements. Note that ``-1`` is returned if control codes occurs
anywhere in the string.
wcstwidth()
-----------
Same behavior as `wcswidth()`_ with automatic terminal-specific Corrections_, reading
``TERM_PROGRAM`` or ``TERM`` when ``True`` (default), or caller can provide terminal query
XTVERSION_ or ENQ_ response:
.. code-block:: python
>>> # '♀️' emoji w/vs-16, uncorrected:
>>> wcwidth.wcswidth('\u2640\ufe0f')
2
>>> # corrected,
>>> wcwidth.wcstwidth('\u2640\ufe0f', term_program='vte')
1
width()
-------
Use function `width()`_ to measure a string with improved handling of ``control_codes`` and
measurement Corrections_ through ``term_program``:.
.. code-block:: python
>>> # same support as wcswidth(), eg. regional indicator flag:
>>> wcwidth.width('\U0001F1FF\U0001F1FC')
2
>>> # set term_program=True to use wcstwidth()
>>> wcwidth.width('\U0001F1FF\U0001F1FC', term_program=True)
1
>>> # or set term_program for measurement of a specific terminal
>>> wcwidth.width('\U0001F1FF\U0001F1FC', term_program='contour')
2
>>> # but also supports sequences, like SGR colored text, "WARN", followed by reset
>>> wcwidth.width('\x1b[38;2;255;150;100mWARN\x1b[0m')
4
>>> # tabs are measured as though the string begins at a tabstop,
>>> wcwidth.width('\t', tabsize=4)
4
>>> # or, all control characters can be ignored (including tab)
>>> wcwidth.width('\t\n\a\r', control_codes='ignore')
0
>>> # sequences with "indeterminate" effects like Home + Clear are zero-width
>>> wcwidth.width('\x1b[H\x1b[2J')
0
>>> # horizontal cursor movements are parsed,
>>> wcwidth.width('hello\b\b\b\b\bworld')
5
>>> wcwidth.width('hello\x1b[5Dworld')
5
>>> # or ignored,
>>> wcwidth.width('hello\x1b[5Dworld', control_codes='ignore')
10
>>> # Measure width of text using kitty text sizing protocol (OSC 66),
>>> width('\x1b]66;w=2;XY\x07')
2
>>> # Scaled text sizing: each grapheme occupies 'scale' cells
>>> width('\x1b]66;s=2;ABC\x07')
6
Use ``control_codes='ignore'`` when the input is known not to contain any control characters or
terminal sequences for slightly improved performance. Note that TAB (``'\t'``) is a control
character and is also ignored, you may want to use `str.expandtabs()`_, first.
Use ``control_codes='strict'`` when input is known to contain some control sequences, such as
SGR color, bold, hyperlinks and cursor movement. Any sequence that cannot be accurately parsed
for horizontal measurement, such as clearing the screen, vertical, or absolute cursor movement will
raise ``ValueError``:
.. code-block:: python
>>> # or, raise ValueError for "indeterminate" effects using control_codes='strict'
>>> wcwidth.width('\n', control_codes='strict')
Traceback (most recent call last):
...
ValueError: Vertical movement character 0xa at position 0
>>> wcwidth.width('\x1b[H\x1b[2J', control_codes='strict')
Traceback (most recent call last):
...
ValueError: Indeterminate cursor sequence at position 0, '\x1b[H'
>>> # cursor left movement beyond string start raises in strict mode,
>>> wcwidth.width('a\x1b[5Da', control_codes='strict')
Traceback (most recent call last):
...
ValueError: Cursor left movement at position 1 would move 5 cells left from column 1, exceeding string start
iter_sequences()
----------------
Iterates through text, segmented by terminal sequence,
.. code-block:: python
>>> list(wcwidth.iter_sequences('hello'))
[('hello', False)]
>>> list(wcwidth.iter_sequences('\x1b[31mred\x1b[0m'))
[('\x1b[31m', True), ('red', False), ('\x1b[0m', True)]
Use `iter_sequences()`_ to split text into segments of plain text and escape sequences. Each tuple
contains the segment string and a boolean indicating whether it is an escape sequence (``True``) or
text (``False``).
iter_graphemes()
----------------
Use `iter_graphemes()`_ to iterate over *grapheme clusters* of a string.
.. code-block:: python
>>> from wcwidth import iter_graphemes
>>> # ok + Regional Indicator 'Z', 'W' (Zimbabwe)
>>> list(wcwidth.iter_graphemes('ok\U0001F1FF\U0001F1FC'))
['o', 'k', '🇿🇼']
>>> # cafe + combining acute accent
>>> list(wcwidth.iter_graphemes('cafe\u0301'))
['c', 'a', 'f', 'é']
>>> # ok + Emoji Man + ZWJ + Woman + ZWJ + Girl
>>> list(wcwidth.iter_graphemes('ok\U0001F468\u200D\U0001F469\u200D\U0001F467'))
['o', 'k', '👨\u200d👩\u200d👧']
A grapheme cluster is what a user perceives as a single character, even if it is composed of
multiple Unicode codepoints. This function implements `Unicode Standard Annex #29`_ grapheme cluster
boundary rules.
ljust()
-------
Use `ljust()`_ as replacement of `str.ljust()`_:
.. code-block:: python
>>> 'コンニチハ'.ljust(11, '*') # don't do this
'コンニチハ******'
>>> wcwidth.ljust('コンニチハ', 11, '*') # do this!
'コンニチハ*'
rjust()
-------
Use `rjust()`_ as replacement of `str.rjust()`_:
.. code-block:: python
>>> 'コンニチハ'.rjust(11, '*') # don't do this
'******コンニチハ'
>>> wcwidth.rjust('コンニチハ', 11, '*') # do this!
'*コンニチハ'
center()
--------
Use `center()`_ as replacement of `str.center()`_:
.. code-block:: python
>>> 'cafe\u0301'.center(6, '*') # don't do this
'café*'
>>> wcwidth.center('cafe\u0301', 6, '*')
'*café*' # do this!
wrap()
------
Use function `wrap()`_ to wrap text containing terminal sequences, Unicode grapheme
clusters, and wide characters to a given display width.
.. code-block:: python
>>> from wcwidth import wrap
>>> # Basic wrapping
>>> wrap('hello world', 5)
['hello', 'world']
>>> # Wrapping CJK text (each character is 2 cells wide)
>>> wrap('コンニチハ', 4)
['コン', 'ニチ', 'ハ']
>>> # Text with ANSI color sequences - SGR codes are propagated by default
>>> # Each line ends with reset, next line starts with restored style
>>> wrap('\x1b[1;31mhello world\x1b[0m', 5)
['\x1b[1;31mhello\x1b[0m', '\x1b[1;31mworld\x1b[0m']
clip()
------
Use `clip()`_ to extract a substring by column positions, preserving terminal sequences.
.. code-block:: python
>>> from wcwidth import clip
>>> # Wide characters split to Narrow boundaries using fillchar=' '
>>> clip('中文字', 0, 3)
'中 '
>>> clip('中文字', 1, 5, fillchar='.')
'.文.'
>>> # SGR codes are propagated by default - result begins with active style
>>> # and ends with reset if styles are active
>>> clip('\x1b[1;31mHello world\x1b[0m', 6, 11)
'\x1b[1;31mworld\x1b[0m'
>>> # Disable SGR propagation to preserve sequence order outside of clip boundary
>>> clip('\x1b[31m中文\x1b[32m', 0, 3, propagate_sgr=False)
'\x1b[31m中 \x1b[32m'
>>> # Cursor-left overwrites previous text (painter's algorithm)
>>> clip('hello\x1b[2DXY', 0, 5)
'helXY'
>>> # Carriage return resets to column 0, overwriting earlier cells
>>> clip('abc\rXY', 0, 5)
'XYc'
>>> # even OSC 8 hyperlink text may be clipped, 'Click This link' -> 'is link' !
>>> clip('\x1b]8;;http://example.com\x07Click This link\x1b]8;;\x07', 8, 15)
'\x1b]8;;http://example.com\x07is link\x1b]8;;\x07'
>>> # and OSC 66 kitty text sizing, supporting width and scale, 'Look' -> '...ook'
>>> clip('\x1b]66;w=4:s=4;Look\x07', 1, 16, fillchar='.')
'...\x1b]66;s=4:w=3;ook\x07'
Use ``overtyping=False`` when the input is known not to contain any cursor movement characters
(``\b``, ``\r``, ``CSI C``, ``CSI D``, ``CSI G``) for improved performance. When
``overtyping=None`` (default), a slower "Painter's algorithm" may be used after testing for the
presence of these characters. ``overtyping`` has no effect when ``control_codes='ignore'``.
strip_sequences()
-----------------
Use `strip_sequences()`_ to remove all terminal escape sequences from text.
.. code-block:: python
>>> from wcwidth import strip_sequences
>>> strip_sequences('\x1b[31mred\x1b[0m')
'red'
.. _ambiguous_width:
Ambiguous Width
---------------
Some Unicode characters have "East Asian Ambiguous" (A) width. These characters display as 1 cell by
default, matching Western terminal contexts, but many CJK (Chinese, Japanese, Korean) environments
may have a preference for 2 cells. This is often found as boolean option, "Ambiguous width as wide"
in Terminal Emulator software preferences.
The ``ambiguous_width`` parameter is available on all width-measuring functions: `wcwidth()`_,
`wcswidth()`_, `width()`_, `ljust()`_, `rjust()`_, `center()`_, `wrap()`_, and `clip()`_.
By default, wcwidth treats ambiguous characters as narrow (width 1). For CJK environments where your
terminal is configured to display ambiguous characters as double-width, pass ``ambiguous_width=2``:
.. code-block:: python
>>> # CIRCLED DIGIT ONE - ambiguous width
>>> wcwidth.width('\u2460')
1
>>> wcwidth.width('\u2460', ambiguous_width=2)
2
**Terminal Detection**
The most reliable method to detect whether a terminal profile is set for "Ambiguous width as wide"
mode is to display an ambiguous character surrounded by a pair of Cursor Position Report (CPR)
queries with a terminal in cooked or raw mode, and to parse the responses for their ``(y, x)``
locations and measure the difference ``x``.
This code should also be careful to check whether it is attached to a terminal and be careful of
possible timeout, slow network, or non-response when working with "dumb terminals" like a CI build.
`jquast/blessed`_ library provides such a helping `Terminal.detect_ambiguous_width()`_ method:
.. code-block:: python
>>> import blessed, functools
>>> # Detect terminal ambiguous width as wide (2) or narrow (1)
>>> ambiguous_width = blessed.Terminal().detect_ambiguous_width()
>>> # Define a new 'width' function with this argument
>>> awidth = functools.partial(wcwidth.width, ambiguous_width=ambiguous_width)
>>> # result depends on attached terminal mode
>>> awidth('\u2460')
1
Corrections
-----------
Corrections may be automatically applied depending on the detected or given terminal software name
beginning with wcwidth release 0.8.0. This allows to correct widths for terminal software that
differs from the python wcwidth specification_. These corrections are sourced from the
`jquast/ucs-detect`_ project.
The ``term_program`` parameter is available on all width-measuring functions: `wcstwidth()`_,
`width()`_, `ljust()`_, `rjust()`_, `center()`_, `wrap()`_, and `clip()`_.
`wcstwidth()`_ defaults to ``term_program=True``, auto-detecting the terminal from the
``TERM_PROGRAM`` or ``TERM`` environment variable. All other functions default to
``term_program=False``, disabling corrections. Use ``term_program=True`` for automatic
detection by environment values of ``TERM`` and ``TERM_PROGRAM``.
.. code-block:: python
# VTE terminals (Gnome Terminal Et al.) still render trigrams as narrow (1 cell), but their
# definition was changed to wide in Unicode 16 (September 2024).
>>> wcwidth.wcswidth('\u2630')
2
>>> wcwidth.wcstwidth('\u2630', term_program='vte')
1
# account for Alacritty non-support of emoji ZWJ:
# man + ZWJ + woman + ZWJ + girl + ZWJ + boy
>>> family = '\U0001F468\u200D\U0001F469\u200D\U0001F467\u200D\U0001F466'
>>> wcwidth.wcswidth(family)
2
>>> wcwidth.wcstwidth(family, term_program='alacritty')
8
Only detectable_ terminals are included: those that identify themselves by XTVERSION_, ENQ_, any
``TERM_PROGRAM`` or a unique ``TERM`` environment value. For the most accurate correction tables,
query the terminal's software version via XTVERSION_ (``CSI > q``) using a higher-level interactive
terminal library like `jquast/blessed`_:
.. code-block:: python
>>> import blessed, wcwidth
>>> term = blessed.Terminal()
>>> sw_ver = term.get_software_version()
>>> print(sw_ver)
SoftwareVersion(name='VTE', version='7600')
>>> wcwidth.width('\u2630', term_program=sw_ver.name)
1
This is important because ``TERM_PROGRAM`` is not forwarded for remote hosts, like SSH, and many
terminals may only be identified using XTVERSION_ or ENQ_. Use `list_term_programs()`_ to see all
recognized names:
.. BEGIN_LIST_TERM_PROGRAMS
.. code-block:: python
>>> wcwidth.list_term_programs()
('alacritty', 'apple_terminal', 'bobcat', 'contour', 'extraterm', 'foot',
'ghostty', 'hyper', 'iterm.app', 'iterm2', 'kitty', 'konsole', 'mintty',
'mlterm', 'pterm', 'putty', 'rio', 'rxvt', 'rxvt-unicode-256color', 'st',
'st-256color', 'tabby', 'terminology', 'urxvt', 'vscode', 'vte', 'warp',
'warpterminal', 'wezterm', 'xterm', 'xterm-ghostty', 'xterm-kitty',
'xterm.js')
.. END_LIST_TERM_PROGRAMS
``term_program=False`` (the default for `width()`_, `ljust()`_, `rjust()`_, `center()`_,
`wrap()`_, and `clip()`_) disables terminal corrections. `wcstwidth()`_ defaults to
``term_program=True`` for auto-detection.
For automatic tests and other purposes that require cross-environment consistency, set static values
or unset ``TERM`` and ``TERM_PROGRAM`` environment values, such as in ``conftest.py`` with pytest:
.. code-block:: python
@pytest.fixture(autouse=True)
def _clear_term_program():
"""unset TERM/TERM_PROGRAM before each test."""
saved_term = os.environ.pop('TERM', None)
saved_tprog = os.environ.pop('TERM_PROGRAM', None)
yield
if saved_term is not None:
os.environ['TERM'] = saved_term
if saved_tprog is not None:
os.environ['TERM_PROGRAM'] = saved_tprog
==========
Developing
==========
Install wcwidth in editable mode::
pip install -e .
Execute all code generation, autoformatters, linters and unit tests using tox::
tox
Or execute individual tasks, see ``tox -lv`` for all available targets::
tox -e pylint,py36,py314
To run tests with detailed coverage reporting showing missing lines::
tox -epy314 -- --cov-report=term-missing
Updating Unicode Version
------------------------
Regenerate python code tables from latest Unicode Specification data files::
tox -e update
The script is located at ``bin/update-tables.py``, requires Python 3.9 or
later. It is recommended but not necessary to run this script with the newest
Python, because the newest Python has the latest ``unicodedata`` for generating
comments.
Building Documentation
----------------------
This project is using `sphinx`_ 4.5 to build documentation::
tox -e sphinx
The output will be in ``docs/_build/html/``.
Updating Requirements
---------------------
This project is using `pip-tools`_ to manage requirements.
To upgrade requirements for updating unicode version, run::
tox -e update_requirements_update
To upgrade requirements for testing, run::
tox -e update_requirements38,update_requirements39
To upgrade requirements for building documentation, run::
tox -e update_requirements_docs
Utilities
---------
Supplementary tools for browsing and testing terminals for wide unicode
characters are found in the `bin/`_ of this project's source code. Just ensure
to first ``pip install -r requirements-develop.txt`` from this projects main
folder. For example, an interactive browser for testing::
python ./bin/wcwidth-browser.py
====
Uses
====
This library is used in:
- `jquast/blessed`_: a thin, practical wrapper around terminal capabilities in
Python.
- `jquast/telix`_: A Modern telnet client especially designed for BBSs and MUDs.
- `prompt-toolkit/python-prompt-toolkit`_: a Library for building powerful
interactive command lines in Python.
- `urwid/urwid`_: Console user interface library for Python
- `prettytable/prettytable`_: Display tabular data in a visually appealing ASCII table format
- `leviathan0992/Pylsy`_: Pylsy is a simple python library draw tables in the Terminal. Just two
lines of code.
- `dbcli/pgcli`_: Postgres CLI with autocompletion and syntax highlighting.
- `thomasballinger/curtsies`_: a Curses-like terminal wrapper with a display
based on compositing 2d arrays of text.
- `selectel/pyte`_: Simple VTXXX-compatible linux terminal emulator.
- `astanin/python-tabulate`_: Pretty-print tabular data in Python, a library
and a command-line utility.
- `rspeer/python-ftfy`_: Fixes mojibake and other glitches in Unicode
text.
- `nbedos/termtosvg`_: Terminal recorder that renders sessions as SVG
animations.
- `peterbrittain/asciimatics`_: A cross platform package to do curses-like operations, plus higher
level APIs and widgets to create text UIs and ASCII art animations
- `python-cmd2/cmd2`_: A tool for building interactive command line apps
- `stratis-storage/stratis-cli`_: CLI for the Stratis project
- `ihabunek/toot`_: A Mastodon CLI/TUI client
- `saulpw/visidata`_: Terminal spreadsheet multitool for discovering and
arranging data
- `jquast/ucs-detect`_: Utility for unicode support detection.
===============
Other Languages
===============
The following libraries provide grapheme and emoji support and closely align with our
specification_:
- `jacobsandlund/uucode`_ Zig
- `contour-terminal/libunicode`_ C++20
There are similar implementations of at least the `wcwidth()`_ and `wcswidth()`_ functions in other
languages:
- `ridiculousfish/widecharwidth`_: Python
- `termux/wcwidth`_: C
- `powerman/wcwidth-icons`_: C
- `timoxley/wcwidth`_: JavaScript
- `janlelis/unicode-display_width`_: Ruby
- `alecrabbit/php-wcwidth`_: PHP
- `Text::CharWidth`_: Perl
- `bluebear94/Terminal-WCWidth`_: Perl 6
- `mattn/go-runewidth`_: Go
- `grepsuzette/wcwidth`_: Haxe
- `aperezdc/lua-wcwidth`_: Lua
- `joachimschmidt557/zig-wcwidth`_: Zig
- `mycoboco/wcwidth.js`_: JavaScript
- `ainame/swift-displaywidth`_: Swift
- `pmonks/clj-wcwidth`_: Clojure
- `fumiyas/wcwidth-cjk`_: `LD_PRELOAD` override
- `joshuarubin/wcwidth9`_: Unicode version 9 in C
- `spectreconsole/wcwidth`_: C#
=======
History
=======
0.8.2 *2026-06-29*
* **Bugfix** Do not raise IndexError when given legacy POSIX ``n`` argument to `wcswidth()`_ or
`wcstwidth()`_ exceed string length without raising IndexError
0.8.1 *2026-06-08*
* **Improved** `wcstwidth()`_ with new ``zeroer``, ``narrow_wider``, and ``narrow_zeroer``
Corrections_. `PR #226`_
0.8.0 *2026-06-05*
* **New** support for Variation Selector 15 Emojis as narrow, `Issue #211`_.
* **New** argument, ``term_program`` for `wcstwidth()`_, `width()`_, `clip()`_, `wrap()`_,
`ljust()`_, `rjust()`_, and `center()`_. ``False`` disables Corrections_; ``True``
auto-detects by ``TERM_PROGRAM`` or ``TERM``; string values accept canonical names matching
`list_term_programs()`_. `wcstwidth()`_ defaults to ``True``; all other functions
default to ``False``.
* **Improved** performance on Python 3.15 using standard library iter_graphemes() `PR #206`_.
* **Improved** memory usage and import time for Python 3.15 using lazy imports `PR #221`_.
* **Bugfix** Invisible_Stacker viramas now form conjuncts (Burmese, Khmer, etc.) and
change some Virama width calculations to match `jacobsandlund/uucode`_ (ghostty) `PR #223`_.
* **Updated** graphemes width maximum now 2, matching Ghostty, foot, and Windows Terminal `PR
#224`_.
0.7.0 *2026-05-02*
* **New** support for `kitty text sizing protocol`_ (OSC 66) in `width()`_ and `clip()`_.
* **New** `clip()`_ parameter ``control_codes='parse'``, ``'ignore'``, and ``'strict'``. `clip()`_
is now able to clip OSC 8 hyperlinks and OSC 66 text sizing sequences.
* **Improved** `clip()`_ and `width()`_ to support horizontal cursor sequences (``cub``, ``cuf``,
``hpa``). Cursor-left (``cub``) or backspace (``\b``) now overwrites text. ``column_address``
(``hpa``) and carriage return (``\r``) are now parsed, and more values conditionally raise
``ValueError`` when ``control_codes='strict'``.
0.6.0 *2026-02-06*
* **New** Parameters ``expand_tabs``, ``replace_whitespace``, ``fix_sentence_endings``,
``drop_whitespace``, ``max_lines``, and ``placeholder`` for `wrap()`_, completing stdlib
`textwrap.wrap()`_ compatibility.
0.5.3 *2026-01-30*
* **Bugfix** Brahmic using Virama conjunct formation. `Issue #155`_, `PR #204`_.
0.5.2 *2026-01-29*
* **Bugfix** Measurement of category ``Mc`` (`Spacing Combining Mark`_), approx. 443, has a more
nuanced specification_, and may be categorized as either zero or wide. `PR #200`_.
* **Bugfix** Measurement of "standalone" modifiers and regional indicators, `PR #202`_.
* **Updated** Data files used in some automatic tests are no longer distributed. `PR #199`_
0.5.1 *2026-01-27*
* **Updated** generated zero and wide code tables to length of 1 to complete the previously
announced removal of historical wide and zero tables. `PR #196`_.
0.5.0 *2026-01-26*
* **Drop Support** of many historical versions of wide and zero unicode tables. Only the latest
Unicode version (17.0.0) is now shipped. The related ``unicode_version='auto'`` keyword of the
`wcwidth()`_ family of functions are ignored. `list_versions()`_ always returns a tuple of only
a single element of the only unicode version supported. `PR #195`_.
* **Performance** improvement of most common call without version or ambiguous_width specified by
20%. `PR #195`_.
* **New** Function `propagate_sgr()`_ for applying SGR state propagation to a list of lines.
`PR #194`_.
* **Improved** `wrap()`_ and `clip()`_ with ``propagate_sgr=True``. `PR #194`_.
* **Bugfix** `clip()`_ zero-width characters at clipping boundaries. `PR #194`_.
* **Bugfix** OSC Hyperlinks when broken mid-text by `wrap()`_. `PR #193`_.
0.4.0 *2026-01-25*
* **New** Functions `iter_graphemes_reverse()`_, `grapheme_boundary_before()`_. `PR #192`_.
* **Bugfix** OSC Hyperlinks should not be broken by `wrap()`_. `PR #191`_.
0.3.5 *2026-01-24*
* **Bugfix** packaging of 0.3.4 contains a failing test.
0.3.4 *2026-01-24*
* **Bugfix** `center()`_ should match the eccentric `parity padding`_.
of `str.center()`_. `PR #188`_.
0.3.3 *2026-01-24*
* **Performance** improvement in `width()`_. `PR #185`_.
* **Bugfix** missing ``py.typed``, ``Typing :: Typed``. `PR #184`_.
0.3.2 *2026-01-23*
* **Updated** type hinting for full ``mympy --strict`` compliance. `PR #183`_.
0.3.1 *2026-01-22*
* **Performance** improvement up to 30% in `width()_`. `PR #181`_.
0.3.0 *2026-01-21*
* **Drop Support** for Python 3.6 and 3.7. `PR #156`_.
* **New** Function `iter_graphemes()`_. `PR #165`_.
* **New** Functions `width()`_ and `iter_sequences()`_. `PR #166`_.
* **New** Functions `ljust()`_, `rjust()`_, `center()`_. `PR #168`_.
* **New** Function `wrap()`_. `PR #169`_.
* **Performance** improvement in `wcswidth()`_. `PR #171`_.
* **New** argument ``ambiguous_width`` to all functions. `PR #172`_.
* **New** Functions `clip()`_ and `strip_sequences()`_. `PR #173`_.
* **Bugfix** Characters with ``Default_Ignorable_Code_Point`` property now
return width 0. `PR #174`_.
* **Bugfix** Characters with ``Prepended_Concatenation_Mark`` property now
return width 1. `PR #175`_.
0.2.14 *2025-09-22*
* **Drop Support** for Python 2.7 and 3.5. `PR #117`_.
* **Update** tables to include Unicode Specifications 16.0.0 and 17.0.0.
`PR #146`_.
* **Bugfix** U+00AD SOFT HYPHEN should measure as 1, versions 0.2.9 through
0.2.13 measured as 0. `PR #149`_.
0.2.13 *2024-01-06*
* **Bugfix** zero-width support for Hangul Jamo (Korean)
0.2.12 *2023-11-21*
* **Bugfix** Re-release to remove `.pyi` files misplaced in wheel `Issue #101`_.
0.2.11 *2023-11-20*
* **Updated** Include tests files in the source distribution (`PR #98`_, `PR #100`_).
0.2.10 *2023-11-13*
* **Bugfix** accounting of some kinds of emoji sequences using U+FE0F
Variation Selector 16 (`PR #97`_).
* **Updated** specification_.
0.2.9 *2023-10-30*
* **Bugfix** zero-width characters used in Emoji ZWJ sequences, Balinese,
Jamo, Devanagari, Tamil, Kannada and others (`PR #91`_).
* **Updated** to include specification_ of character measurements.
0.2.8 *2023-09-30*
* Include requirements files in the source distribution (`PR #82`_).
0.2.7 *2023-09-28*
* **Updated** tables to include Unicode Specification 15.1.0.
* Include ``bin``, ``docs``, and ``tox.ini`` in the source distribution
0.2.6 *2023-01-14*
* **Updated** tables to include Unicode Specification 14.0.0 and 15.0.0.
* **Changed** developer tools to use pip-compile, and to use jinja2 templates
for code generation in `bin/update-tables.py` to prepare for possible
compiler optimization release.
0.2.1 .. 0.2.5 *2020-06-23*
* **Repository** changes to update tests and packaging issues, and
begin tagging repository with matching release versions.
0.2.0 *2020-06-01*
* **Enhancement**: Unicode version may be selected by exporting the
Environment variable ``UNICODE_VERSION``, such as ``13.0``, or ``6.3.0``.
See the `jquast/ucs-detect`_ CLI utility for automatic detection.
* **Enhancement**:
API Documentation is published to readthedocs.io.
* **Updated** tables for *all* Unicode Specifications with files
published in a programmatically consumable format, versions 4.1.0
through 13.0
0.1.9 *2020-03-22*
* **Performance** optimization by `Avram Lubkin`_, `PR #35`_.
* **Updated** tables to Unicode Specification 13.0.0.
0.1.8 *2020-01-01*
* **Updated** tables to Unicode Specification 12.0.0. (`PR #30`_).
0.1.7 *2016-07-01*
* **Updated** tables to Unicode Specification 9.0.0. (`PR #18`_).
0.1.6 *2016-01-08 Production/Stable*
* ``LICENSE`` file now included with distribution.
0.1.5 *2015-09-13 Alpha*
* **Bugfix**:
Resolution of "combining_ character width" issue, most especially
those that previously returned -1 now often (correctly) return 0.
resolved by `Philip Craig`_ via `PR #11`_.
* **Deprecated**:
The module path ``wcwidth.table_comb`` is no longer available,
it has been superseded by module path ``wcwidth.table_zero``.
0.1.4 *2014-11-20 Pre-Alpha*
* **Feature**: ``wcswidth()`` now determines printable length
for (most) combining_ characters. The developer's tool
`bin/wcwidth-browser.py`_ is improved to display combining_
characters when provided the ``--combining`` option
(`Thomas Ballinger`_ and `Leta Montopoli`_ `PR #5`_).
* **Feature**: added static analysis (prospector_) to testing
framework.
0.1.3 *2014-10-29 Pre-Alpha*
* **Bugfix**: 2nd parameter of wcswidth was not honored.
(`Thomas Ballinger`_, `PR #4`_).
0.1.2 *2014-10-28 Pre-Alpha*
* **Updated** tables to Unicode Specification 7.0.0.
(`Thomas Ballinger`_, `PR #3`_).
0.1.1 *2014-05-14 Pre-Alpha*
* Initial release to pypi, Based on Unicode Specification 6.3.0
This code was originally derived directly from C code of the same name,
whose latest version is available at
https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c::
* Markus Kuhn -- 2007-05-26 (Unicode 5.0)
*
* Permission to use, copy, modify, and distribute this software
* for any purpose and without fee is hereby granted. The author
* disclaims all warranties with regard to this software.
.. _`Spacing Combining Mark`: https://www.unicode.org/versions/latest/ch04.pdf#G134153
.. _`specification`: https://wcwidth.readthedocs.io/en/latest/specs.html
.. _`tox`: https://tox.wiki/en/latest/
.. _`prospector`: https://github.com/landscapeio/prospector
.. _`combining`: https://en.wikipedia.org/wiki/Combining_character
.. _`bin/`: https://github.com/jquast/wcwidth/tree/master/bin
.. _`bin/wcwidth-browser.py`: https://github.com/jquast/wcwidth/blob/master/bin/wcwidth-browser.py
.. _`Thomas Ballinger`: https://github.com/thomasballinger
.. _`Leta Montopoli`: https://github.com/lmontopo
.. _`Philip Craig`: https://github.com/philipc
.. _`PR #3`: https://github.com/jquast/wcwidth/pull/3
.. _`PR #4`: https://github.com/jquast/wcwidth/pull/4
.. _`PR #5`: https://github.com/jquast/wcwidth/pull/5
.. _`PR #11`: https://github.com/jquast/wcwidth/pull/11
.. _`PR #18`: https://github.com/jquast/wcwidth/pull/18
.. _`PR #30`: https://github.com/jquast/wcwidth/pull/30
.. _`PR #35`: https://github.com/jquast/wcwidth/pull/35
.. _`PR #82`: https://github.com/jquast/wcwidth/pull/82
.. _`PR #91`: https://github.com/jquast/wcwidth/pull/91
.. _`PR #97`: https://github.com/jquast/wcwidth/pull/97
.. _`PR #98`: https://github.com/jquast/wcwidth/pull/98
.. _`PR #100`: https://github.com/jquast/wcwidth/pull/100
.. _`PR #117`: https://github.com/jquast/wcwidth/pull/117
.. _`PR #146`: https://github.com/jquast/wcwidth/pull/146
.. _`PR #149`: https://github.com/jquast/wcwidth/pull/149
.. _`PR #156`: https://github.com/jquast/wcwidth/pull/156
.. _`PR #165`: https://github.com/jquast/wcwidth/pull/165
.. _`PR #166`: https://github.com/jquast/wcwidth/pull/166
.. _`PR #168`: https://github.com/jquast/wcwidth/pull/168
.. _`PR #169`: https://github.com/jquast/wcwidth/pull/169
.. _`PR #171`: https://github.com/jquast/wcwidth/pull/171
.. _`PR #172`: https://github.com/jquast/wcwidth/pull/172
.. _`PR #173`: https://github.com/jquast/wcwidth/pull/173
.. _`PR #174`: https://github.com/jquast/wcwidth/pull/174
.. _`PR #175`: https://github.com/jquast/wcwidth/pull/175
.. _`PR #181`: https://github.com/jquast/wcwidth/pull/181
.. _`PR #183`: https://github.com/jquast/wcwidth/pull/183
.. _`PR #184`: https://github.com/jquast/wcwidth/pull/184
.. _`PR #185`: https://github.com/jquast/wcwidth/pull/185
.. _`PR #188`: https://github.com/jquast/wcwidth/pull/188
.. _`PR #191`: https://github.com/jquast/wcwidth/pull/191
.. _`PR #192`: https://github.com/jquast/wcwidth/pull/192
.. _`PR #193`: https://github.com/jquast/wcwidth/pull/193
.. _`PR #194`: https://github.com/jquast/wcwidth/pull/194
.. _`PR #195`: https://github.com/jquast/wcwidth/pull/195
.. _`PR #196`: https://github.com/jquast/wcwidth/pull/196
.. _`PR #199`: https://github.com/jquast/wcwidth/pull/199
.. _`PR #200`: https://github.com/jquast/wcwidth/pull/200
.. _`PR #202`: https://github.com/jquast/wcwidth/pull/202
.. _`PR #204`: https://github.com/jquast/wcwidth/pull/204
.. _`PR #206`: https://github.com/jquast/wcwidth/pull/206
.. _`PR #221`: https://github.com/jquast/wcwidth/pull/221
.. _`PR #223`: https://github.com/jquast/wcwidth/pull/223
.. _`PR #224`: https://github.com/jquast/wcwidth/pull/224
.. _`PR #226`: https://github.com/jquast/wcwidth/pull/226
.. _`Issue #101`: https://github.com/jquast/wcwidth/issues/101
.. _`Issue #155`: https://github.com/jquast/wcwidth/issues/155
.. _`Issue #190`: https://github.com/jquast/wcwidth/issues/190
.. _`Issue #211`: https://github.com/jquast/wcwidth/issues/211
.. _`jquast/blessed`: https://github.com/jquast/blessed
.. _`jquast/telix`: https://github.com/jquast/telix
.. _`selectel/pyte`: https://github.com/selectel/pyte
.. _`thomasballinger/curtsies`: https://github.com/thomasballinger/curtsies
.. _`dbcli/pgcli`: https://github.com/dbcli/pgcli
.. _`prompt-toolkit/python-prompt-toolkit`: https://github.com/prompt-toolkit/python-prompt-toolkit
.. _`timoxley/wcwidth`: https://github.com/timoxley/wcwidth
.. _`wcwidth(3)`: https://man7.org/linux/man-pages/man3/wcwidth.3.html
.. _`wcswidth(3)`: https://man7.org/linux/man-pages/man3/wcswidth.3.html
.. _`astanin/python-tabulate`: https://github.com/astanin/python-tabulate
.. _`janlelis/unicode-display_width`: https://github.com/janlelis/unicode-display_width
.. _`rspeer/python-ftfy`: https://github.com/rspeer/python-ftfy
.. _`alecrabbit/php-wcwidth`: https://github.com/alecrabbit/php-wcwidth
.. _`Text::CharWidth`: https://metacpan.org/pod/Text::CharWidth
.. _`bluebear94/Terminal-WCWidth`: https://github.com/bluebear94/Terminal-WCWidth
.. _`mattn/go-runewidth`: https://github.com/mattn/go-runewidth
.. _`grepsuzette/wcwidth`: https://github.com/grepsuzette/wcwidth
.. _`jquast/ucs-detect`: https://github.com/jquast/ucs-detect
.. _`Avram Lubkin`: https://github.com/avylove
.. _`nbedos/termtosvg`: https://github.com/nbedos/termtosvg
.. _`peterbrittain/asciimatics`: https://github.com/peterbrittain/asciimatics
.. _`aperezdc/lua-wcwidth`: https://github.com/aperezdc/lua-wcwidth
.. _`joachimschmidt557/zig-wcwidth`: https://github.com/joachimschmidt557/zig-wcwidth
.. _`fumiyas/wcwidth-cjk`: https://github.com/fumiyas/wcwidth-cjk
.. _`joshuarubin/wcwidth9`: https://github.com/joshuarubin/wcwidth9
.. _`spectreconsole/wcwidth`: https://github.com/spectreconsole/wcwidth
.. _`contour-terminal/libunicode`: https://github.com/contour-terminal/libunicode
.. _`jacobsandlund/uucode`: https://github.com/jacobsandlund/uucode
.. _`ridiculousfish/widecharwidth`: https://github.com/ridiculousfish/widecharwidth
.. _`termux/wcwidth`: https://github.com/termux/wcwidth
.. _`powerman/wcwidth-icons`: https://github.com/powerman/wcwidth-icons
.. _`mycoboco/wcwidth.js`: https://github.com/mycoboco/wcwidth.js
.. _`ainame/swift-displaywidth`: https://github.com/ainame/swift-displaywidth
.. _`pmonks/clj-wcwidth`: https://github.com/pmonks/clj-wcwidth
.. _`python-cmd2/cmd2`: https://github.com/python-cmd2/cmd2
.. _`stratis-storage/stratis-cli`: https://github.com/stratis-storage/stratis-cli
.. _`ihabunek/toot`: https://github.com/ihabunek/toot
.. _`saulpw/visidata`: https://github.com/saulpw/visidata
.. _`urwid/urwid`: https://github.com/urwid/urwid
.. _`prettytable/prettytable`: https://github.com/prettytable/prettytable
.. _`leviathan0992/Pylsy`: https://github.com/leviathan0992/Pylsy
.. _`pip-tools`: https://pip-tools.readthedocs.io/
.. _`sphinx`: https://www.sphinx-doc.org/
.. _`textwrap.wrap()`: https://docs.python.org/3/library/textwrap.html#textwrap.wrap
.. _`str.ljust()`: https://docs.python.org/3/library/stdtypes.html#str.ljust
.. _`str.rjust()`: https://docs.python.org/3/library/stdtypes.html#str.rjust
.. _`str.center()`: https://docs.python.org/3/library/stdtypes.html#str.center
.. _`str.expandtabs()`: https://docs.python.org/3/library/stdtypes.html#str.expandtabs
.. _`General Tabulated Summary`: https://ucs-detect.readthedocs.io/results.html#tabulated-results
.. _`wcwidth()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.wcwidth
.. _`wcswidth()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.wcswidth
.. _`wcstwidth()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.wcstwidth
.. _`width()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.width
.. _`iter_graphemes()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.iter_graphemes
.. _`iter_graphemes_reverse()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.iter_graphemes_reverse
.. _`grapheme_boundary_before()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.grapheme_boundary_before
.. _`ljust()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.ljust
.. _`rjust()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.rjust
.. _`center()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.center
.. _`wrap()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.wrap
.. _`clip()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.clip
.. _`strip_sequences()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.strip_sequences
.. _`propagate_sgr()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.propagate_sgr
.. _`TextSizing`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.TextSizing
.. _`TextSizingParams`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.TextSizingParams
.. _`iter_sequences()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.iter_sequences
.. _`list_versions()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.list_versions
.. _`list_term_programs()`: https://wcwidth.readthedocs.io/en/latest/api.html#wcwidth.list_term_programs
.. _`Unicode Standard Annex #29`: https://www.unicode.org/reports/tr29/
.. _`Terminal.detect_ambiguous_width()`: https://blessed.readthedocs.io/en/latest/api/terminal.html#blessed.terminal.Terminal.detect_ambiguous_width
.. _`parity padding`: https://jazcap53.github.io/pythons-eccentric-strcenter.html
.. _`kitty text sizing protocol`: https://sw.kovidgoyal.net/kitty/text-sizing-protocol/
.. _`Grapheme Clusters and Terminal Emulators`: https://mitchellh.com/writing/grapheme-clusters-in-terminals
.. _`terminal-unicode-core.tex`: https://github.com/contour-terminal/terminal-unicode-core/blob/master/spec/terminal-unicode-core.tex
.. _`State of Terminal Emulators in 2025`: https://www.jeffquast.com/post/state-of-terminal-emulation-2025/
.. _`Perfecting Terminal Character Width Using Correction Tables (2026)`: https://www.jeffquast.com/post/perfecting-terminal-character-width-using-correction-tables/
.. _XTVERSION: https://vtdn.dev/docs/dcs/xtversion/
.. _ENQ: https://documentation.help/PuTTY/config-answerback.html
.. _detectable: https://ucs-detect.readthedocs.io/results.html#terminal-identification
.. |pypi_downloads| image:: https://img.shields.io/pypi/dm/wcwidth.svg?logo=pypi
:alt: Downloads
:target: https://pypi.org/project/wcwidth/
.. |codecov| image:: https://codecov.io/gh/jquast/wcwidth/branch/master/graph/badge.svg
:alt: codecov.io Code Coverage
:target: https://app.codecov.io/gh/jquast/wcwidth/
.. |license| image:: https://img.shields.io/pypi/l/wcwidth.svg
:target: https://pypi.org/project/wcwidth/
:alt: MIT License
|