Sizing & Alignment

The height and width properties always provide a current readout of the size of the window in character cells:

from blessed import Terminal
term = Terminal()

>>> term.height, term.width
(34, 102)

The pixel_height and pixel_width properties provide the window size in pixels when available:

from blessed import Terminal
term = Terminal()

>>> term.pixel_height, term.pixel_width
(1080, 1920)

Note

For sixel graphics, use get_sixel_height_and_width() instead, which returns the actual drawable area by accounting for margins. See Sixel Graphics for details.

Length

Any string containing sequences can be measured using length().

This means that blessed can measure, right-align, center, truncate, or word-wrap its own output! Text containing colors and styling, hyperlinks, or text sizing protocol are measured for their visual width:

from blessed import Terminal
term = Terminal()

>>> print(term.length(term.bold_blue('123')))
3
>>> print(term.length('abc\b\b\bxyz'))
3
>>> print(term.length(term.text_sized('BIG', scale=3)))
9
>>> print(term.length(term.link('http://example.com', 'link')))
4
>> phrase = (
    "\u26F9"        # PERSON WITH BALL
    "\U0001F3FB"    # EMOJI MODIFIER FITZPATRICK TYPE-1-2
    "\u200D"        # ZERO WIDTH JOINER
    "\u2640"        # FEMALE SIGN
    "\uFE0F")       # VARIATION SELECTOR-16
>> print(term.length(phrase))
2

The length measurements of blessed very accurately matches most popular terminals, but not all. The python wcwidth library provides support of complex emojis, CJK, flags, and complex languages, and support for unicode may vary by terminal, as discovered by the results published by ucs-detect.

length() makes a best effort without returning error, even when the cursor position is ambiguous, such as text containing term.move_x(0), or containing vertical movement like '\n' or term.move_up(99), the text is assumed for display on the first column and vertical sequences are ignored. Carriage return, '\r' is treated like term.move_x(0):

from blessed import Terminal
term = Terminal()

>>> print(term.length('abc' + term.move_x(0) + 'xyz'))
3
>>> print(term.length('abc' + term.move_up(99) + 'xyz'))
6
>>> print(term.length('abc' + '\r' + 'xyz'))
3
>>> print(term.length('abc' + '\n' + 'xyz'))
6

Alignment

By combining the measure of the printable width of strings containing sequences with the terminal width, the center(), ljust(), rjust(), truncate(), and wrap() methods “just work” for strings that contain sequences.

from blessed import Terminal
term = Terminal()

with term.location(y=term.height // 2):
    print(term.center(term.bold('press return to begin!')))
    term.inkey()

In the following example, wrap() word-wraps a short poem containing sequences:

from blessed import Terminal
term = Terminal()

poem = (term.bold_cyan('Plan difficult tasks'),
        term.cyan('through the simplest tasks'),
        term.bold_cyan('Achieve large tasks'),
        term.cyan('through the smallest tasks'))

for line in poem:
    print('\n'.join(term.wrap(line, width=25, subsequent_indent=' ' * 4)))

Text Sizing

The text_sized() method wraps text in an OSC 66 escape sequence for terminals that support the kitty text sizing protocol. Call does_text_sizing() to determine whether it is supported. This enables per-character control over cell footprint, fractional font scaling, and alignment:

from blessed import Terminal
term = Terminal()

# determine if text sizing protocol is supported
if not term.does_text_sizing():
    exit("No support for text sizing protocl")

# 'scale' is great for printing large headings
_scale = 3
heading = term.text_sized('Big text', scale=_scale)
print(heading + ('\n' * (_scale)) + '=' * term.length(heading))

# fractional scaling is great for superscript and subscript
print('2' + term.text_sized('128', width=1, numerator=1, denominator=3))

# text sizing sequences can be bookended or wrapped by any other sequence,
footnote_txt = term.text_sized('[2]', width=2, numerator=2, denominator=3, vertical_align='top')
footnote_hlink = term.link('https://sw.kovidgoyal.net/kitty/text-sizing-protocol/', footnote_txt)
print('Text sizing protocol' + footnote_hlink)

# and it may also be aligned
print(term.center(term.reverse_yellow(term.text_sized('Big Yellow', scale=2, vertical_align='bottom'))))
https://dxtz6bzwq9sxx.cloudfront.net/blessed_text_sizing_repl_output.png

Note

Because the first call to text_sized() will cause the side effect of writing destructive spaces to the columns and row following the current cursor position as required for detection of kitty text sizing protocol, it is suggested to first call does_text_sizing() during program initialization.

See also

text_sizing_protocol.py and scroller.py demonstration programs.

Detecting Resize

The terminal can notify your application when the window size changes. Blessed provides a modern cross-platform method using in-band resize notifications, with SIGWINCH as a fallback for older terminals. On Windows, native console WINDOW_BUFFER_SIZE_EVENT records are automatically converted to in-band resize sequences.

Using SIGWINCH

For terminals that don’t support in-band resize notifications (DEC mode 2048), you can use SIGWINCH signals on Unix systems.

import signal
import threading
from blessed import Terminal

term = Terminal()
_resize_pending = threading.Event()

def on_resize(*args):
    _resize_pending.set()

signal.signal(signal.SIGWINCH, on_resize)

with term.cbreak():
    while True:
        if _resize_pending.is_set():
            _resize_pending.clear()
            print(f'New size: {term.height}x{term.width}')

        inp = term.inkey(timeout=0.1)
        if inp == 'q':
            break
A visual animated example of the on_resize() function callback

Warning

SIGWINCH has limitations:

  • Not available on Windows

  • Signal handlers should avoid blocking operations

  • Race conditions can occur between signal delivery and terminal state

  • Requires careful synchronization with application state

Using notify_on_resize

Use does_inband_resize() to check if the terminal supports in-band resize notifications (DEC mode 2048):

from blessed import Terminal

term = Terminal()

if term.does_inband_resize():
    print('In-band resize notifications are supported!')
else:
    print('Falling back to SIGWINCH or polling')

Note

In-band resize notification support (DEC mode 2048) is currently very limited among terminal emulators on Unix. Most terminals do not support this feature yet. Always check with does_inband_resize() and provide a fallback using SIGWINCH on Unix systems.

On Windows, does_inband_resize() always returns True because the native console API provides WINDOW_BUFFER_SIZE_EVENT on all versions – no DEC mode support is required.

The notify_on_resize() context manager enables automatic resize event reporting. When the window is resized, inkey() will return a keystroke with name equal to 'RESIZE_EVENT'. The new dimensions are immediately available through height, width, pixel_height, and pixel_width.

This method is preferred because it:

  • Works cross-platform (Linux, Mac, BSD, Windows)

  • Avoids race conditions inherent in signal handlers

  • Delivers resize events in-band with other input

  • Automatically caches dimensions for fast access

Using both

The following example demonstrates checking for support, using in-band resize notifications when available, and falling back to SIGWINCH on Unix systems:

#!/usr/bin/env python
import threading
from blessed import Terminal

term = Terminal()

_resize_pending = threading.Event()


def on_resize(*args):
    _resize_pending.set()


def display_size(term):
    # conditionally refresh sixel size when enabled
    sixel_height, sixel_width = 0, 0
    if term.does_sixel():
        sixel_height, sixel_width = term.get_sixel_height_and_width(force=True)
    print()
    print(f'height={term.height}, width={term.width}, ' +
          f'pixel_height={term.pixel_height}, pixel_width={term.pixel_width}, ' +
          f'sixel_height={sixel_height}, sixel_width={sixel_width}',
          end='', flush=True)


if not term.does_inband_resize():
    print('In-band Window Resize not supported on this terminal')
    import sys
    if sys.platform != 'win32':
        import signal
        signal.signal(signal.SIGWINCH, on_resize)

with term.cbreak(), term.notify_on_resize():
    print("press 'q' to quit.")
    display_size(term)

    while True:
        inp = term.inkey(timeout=0.1)

        if inp == 'q':
            break

        if inp.name == 'RESIZE_EVENT':
            _resize_pending.set()
        elif _resize_pending.is_set():
            _resize_pending.clear()
            display_size(term)

Make note of these designs:

  1. Signal handlers only set a flag: The on_resize() signal handler only calls _resize_pending.set() and does nothing else. Signal handlers should never perform I/O operations, terminal queries, or other complex work, because these callbacks can occur at any time in your code, even part-way through drawing, for example.

  2. Main loop processes events: The main event loop checks _resize_pending periodically using inkey(timeout=0.1). This 100ms delay naturally debounces rapid resize events from both signals and in-band notifications, displaying only one size update regardless of how many events arrive within that window.

Note the use of force=True when calling get_sixel_height_and_width(), the return value is cached, so force=True ensures updated dimensions are retrieved after a resize event. See Sixel Graphics for more on caching behavior.