Measures the displayed width of unicode strings in a terminal
npx @tessl/cli install tessl/pypi-wcwidth@0.2.0A Python implementation of the POSIX wcwidth() and wcswidth() C functions for determining the printable width of Unicode strings on terminals. This library addresses the issue that string length doesn't always equal terminal display width due to characters that occupy 0 cells (zero-width/combining), 1 cell (normal), or 2 cells (wide East Asian characters).
The library includes comprehensive Unicode character width tables that can be configured to match specific Unicode versions via environment variables, making it essential for CLI applications, terminal emulators, and any software that needs accurate text formatting and alignment in terminal environments.
pip install wcwidthimport wcwidthSelective imports for commonly used functions:
from wcwidth import wcwidth, wcswidth, list_versionsImport all (includes private functions):
from wcwidth import *from wcwidth import wcwidth, wcswidth
# Get width of a single character
char_width = wcwidth('A') # Returns 1
wide_char_width = wcwidth('コ') # Returns 2 (Japanese character)
zero_width = wcwidth('\u200d') # Returns 0 (zero-width joiner)
# Get width of a string
string_width = wcswidth('Hello') # Returns 5
japanese_width = wcswidth('コンニチハ') # Returns 10
mixed_width = wcswidth('Hello コ') # Returns 7
# Use with specific Unicode version
width_unicode_9 = wcwidth('🎉', unicode_version='9.0.0')The wcwidth library is built around Unicode character width tables and binary search algorithms:
_bisearch() functionUNICODE_VERSION environment variableCore functions for determining the printable width of Unicode characters and strings in terminal environments.
def wcwidth(wc, unicode_version='auto'):
"""
Given one Unicode character, return its printable length on a terminal.
Parameters:
- wc: str, a single Unicode character
- unicode_version: str, Unicode version ('auto', 'latest', or specific version like '9.0.0')
Returns:
int, the width in cells:
- -1: not printable or indeterminate effect (control characters)
- 0: does not advance cursor (NULL, combining characters, zero-width)
- 1: normal width characters
- 2: wide characters (East Asian full-width)
"""
def wcswidth(pwcs, n=None, unicode_version='auto'):
"""
Given a unicode string, return its printable length on a terminal.
Parameters:
- pwcs: str, unicode string to measure
- n: int, optional maximum number of characters to measure (for POSIX compatibility)
- unicode_version: str, Unicode version ('auto', 'latest', or specific version)
Returns:
int, total width in cells, or -1 if any character is not printable
"""Functions for working with supported Unicode versions and version matching.
def list_versions():
"""
Return Unicode version levels supported by this module release.
Returns:
tuple of str, supported Unicode version numbers in ascending sorted order
"""Internal functions exported for advanced usage, but not part of the main public API.
def _bisearch(ucs, table):
"""
Auxiliary function for binary search in interval table.
Parameters:
- ucs: int, ordinal value of unicode character
- table: list, list of starting and ending ranges as [(start, end), ...]
Returns:
int, 1 if ordinal value ucs is found within lookup table, else 0
"""
def _wcmatch_version(given_version):
"""
Return nearest matching supported Unicode version level.
Parameters:
- given_version: str, version for compare, may be 'auto' or 'latest'
Returns:
str, matched unicode version string
"""
def _wcversion_value(ver_string):
"""
Integer-mapped value of given dotted version string.
Parameters:
- ver_string: str, Unicode version string of form 'n.n.n'
Returns:
tuple of int, digit tuples representing version components
"""Character width lookup tables and constants for different character categories.
ZERO_WIDTH: dict
# Unicode character table for zero-width characters by version
# Format: {'version': [(start, end), ...]}
WIDE_EASTASIAN: dict
# Unicode character table for wide East Asian characters by version
# Format: {'version': [(start, end), ...]}
VS16_NARROW_TO_WIDE: dict
# Unicode character table for variation selector 16 width changes
# Format: {'version': [(start, end), ...]}
__version__: str
# Package version string, currently '0.2.13'Controls which Unicode version tables to use when unicode_version='auto' is specified.
import os
os.environ['UNICODE_VERSION'] = '13.0'
# Now wcwidth() will use Unicode 13.0 tables by default
width = wcwidth('🎉') # Uses Unicode 13.0 tablesIf not set, defaults to the latest supported version (15.1.0).
The library supports the following Unicode versions:
from wcwidth import wcswidth
# ZWJ sequences are handled specially
emoji_sequence = '👨👩👧👦' # Family emoji with ZWJ
width = wcswidth(emoji_sequence) # Correctly handles ZWJ sequences# VS16 can change narrow characters to wide
text_with_vs16 = '🎉\uFE0F' # Emoji with VS16
width = wcswidth(text_with_vs16, unicode_version='9.0.0')# Control characters return -1
control_char_width = wcwidth('\x01') # Returns -1
string_with_control = wcswidth('Hello\x01World') # Returns -1The library handles various edge cases:
wcwidth('') returns 0, wcswidth('') returns 0wcswidth() returns -1 if any character is non-printablewcwidth() uses @lru_cache(maxsize=1000) for performance@lru_cache(maxsize=8)@lru_cache(maxsize=128)from wcwidth import wcswidth
def terminal_center(text, width):
"""Center text in terminal with correct width calculation."""
text_width = wcswidth(text)
if text_width is None or text_width < 0:
return text # Handle unprintable characters
padding = max(0, width - text_width)
left_pad = padding // 2
return ' ' * left_pad + text
# Usage
centered = terminal_center('Hello コンニチハ', 40)from wcwidth import wcswidth
def truncate_to_width(text, max_width):
"""Truncate text to fit within specified terminal width."""
for i in range(len(text) + 1):
substring = text[:i]
width = wcswidth(substring)
if width is not None and width > max_width:
return text[:i-1] + '…'
return text
# Usage
truncated = truncate_to_width('Very long text with unicode コンニチハ', 20)from wcwidth import wcswidth
def format_columns(rows, column_widths):
"""Format data in aligned columns considering Unicode width."""
formatted_rows = []
for row in rows:
formatted_row = []
for cell, width in zip(row, column_widths):
cell_width = wcswidth(str(cell)) or 0
padding = max(0, width - cell_width)
formatted_row.append(str(cell) + ' ' * padding)
formatted_rows.append(''.join(formatted_row))
return formatted_rows
# Usage
data = [['Name', 'Age', 'City'], ['Alice', '25', 'Tokyo 東京']]
formatted = format_columns(data, [15, 5, 20])