wijjit.rendering.paint_context.PaintContext

class wijjit.rendering.paint_context.PaintContext(buffer, style_resolver, bounds, clip_region=None)[source]

Rendering context for cell-based element painting.

This class provides the environment and helper methods for elements to render themselves to a cell-based screen buffer using theme styles.

Parameters:
  • buffer (ScreenBuffer) – Target screen buffer for rendering

  • style_resolver (StyleResolver) – Style resolver for applying theme styles

  • bounds (Bounds) – Rendering bounds for the element (position and size)

  • clip_region (Bounds, optional) – Clip region to restrict rendering. Content outside this region is not drawn. If None, defaults to bounds. Used for scrollable frames to prevent children from drawing outside the frame’s visible content area.

Variables:
  • buffer (ScreenBuffer) – Target screen buffer

  • style_resolver (StyleResolver) – Style resolver

  • bounds (Bounds) – Element bounds

  • clip_region (Bounds) – Effective clip region for rendering

Examples

Create a paint context and write styled text:

>>> from wijjit.terminal.screen_buffer import ScreenBuffer
>>> from wijjit.styling.resolver import StyleResolver
>>> from wijjit.styling.theme import DefaultTheme
>>> from wijjit.layout.bounds import Bounds
>>> buffer = ScreenBuffer(80, 24)
>>> resolver = StyleResolver(DefaultTheme())
>>> bounds = Bounds(x=0, y=0, width=10, height=1)
>>> ctx = PaintContext(buffer, resolver, bounds)
>>> style = resolver.resolve_style_by_class('button')
>>> ctx.write_text(0, 0, 'Click', style)

Fill a rectangular region:

>>> ctx.fill_rect(0, 0, 10, 5, ' ', style)
__init__(buffer, style_resolver, bounds, clip_region=None)[source]
Parameters:
Return type:

None

Methods

__init__(buffer, style_resolver, bounds[, ...])

clear([style])

Clear the element's bounds region.

cursor_anchor(x, y)

Absolute screen coordinates for a caret at a relative position.

draw_border(x, y, width, height, style[, ...])

Draw a border around a rectangular region.

fill_rect(x, y, width, height, char, style)

Fill a rectangular region with a styled character.

sub_context(x, y, width, height)

Create a sub-context with relative bounds.

write_cell(x, y, cell)

Write a single cell to the buffer with clipping.

write_cells(x, y, cells)

Write a horizontal run of pre-built cells with clipping.

write_cells_vertical(x, y, cells)

Write a vertical run of pre-built cells with clipping.

write_text(x, y, text, style[, clip])

Write styled text to the buffer at specified position.

write_text_wrapped(x, y, text, style, max_width)

Write text with word wrapping to buffer.

write_text(x, y, text, style, clip=True)[source]

Write styled text to the buffer at specified position.

Parameters:
  • x (int) – X coordinate (column) relative to element bounds

  • y (int) – Y coordinate (row) relative to element bounds

  • text (str) – Text to write (plain text, no ANSI codes)

  • style (Style) – Style to apply to the text

  • clip (bool, optional) – Whether to clip text to element bounds (default: True)

Return type:

None

Notes

Coordinates are relative to the element’s bounds. The method automatically translates to absolute screen coordinates.

Text is written cluster by cluster and the width budget is spent in terminal columns, not Python characters. A width-2 glyph (most CJK, many emoji) is written as a head Cell holding the glyph plus a continuation Cell (char == CONTINUATION_CHAR) in the next column carrying the same style attributes; see wijjit.terminal.cell.is_continuation(). Zero-width combining marks are folded onto the preceding base glyph (one Cell), and control characters are dropped.

If clip=True, text extending beyond the element bounds is truncated in columns and rendering is also clipped to clip_region. When only one column of a width-2 glyph is inside the budget or clip region, a styled space is written in the visible column so a half glyph never appears.

ANSI escape sequences embedded in text are stripped (whole sequences, via wijjit.terminal.ansi.strip_ansi()) rather than honored: this method takes plain text, and the supported path for pre-styled ANSI content is content_type="ansi". Stripping keeps unsupported input degrading to clean uncolored text instead of leaving visible sequence remnants after the ESC byte is dropped.

Examples

Write text at element’s origin:

>>> ctx.write_text(0, 0, 'Hello', style)

Write text at offset within element:

>>> ctx.write_text(5, 2, 'World', style)

Write without clipping (may overflow bounds):

>>> ctx.write_text(0, 0, 'Very long text', style, clip=False)
write_text_wrapped(x, y, text, style, max_width)[source]

Write text with word wrapping to buffer.

Parameters:
  • x (int) – X coordinate (column) relative to element bounds

  • y (int) – Y coordinate (row) relative to element bounds

  • text (str) – Text to write (may contain newlines)

  • style (Style) – Style to apply to the text

  • max_width (int) – Maximum width for wrapping

Returns:

Number of lines written

Return type:

int

Notes

This method wraps text at word boundaries where possible, falling back to character wrapping for long words.

Existing newlines in the text are preserved as line breaks. Rendering is clipped to the clip_region if set.

Examples

Write wrapped text:

>>> lines = ctx.write_text_wrapped(0, 0, 'Long text here', style, 10)
>>> lines  # Number of lines used
2
fill_rect(x, y, width, height, char, style)[source]

Fill a rectangular region with a styled character.

Parameters:
  • x (int) – X coordinate (column) relative to element bounds

  • y (int) – Y coordinate (row) relative to element bounds

  • width (int) – Rectangle width

  • height (int) – Rectangle height

  • char (str) – Character to fill with (should be single character)

  • style (Style) – Style to apply

Return type:

None

Notes

Coordinates are relative to element bounds. The rectangle is automatically clipped to element bounds and clip_region.

Useful for backgrounds, borders, or clearing regions.

Examples

Fill background with spaces:

>>> ctx.fill_rect(0, 0, 10, 5, ' ', style)

Draw a filled box with a character:

>>> ctx.fill_rect(2, 2, 5, 3, '#', style)
write_cell(x, y, cell)[source]

Write a single cell to the buffer with clipping.

Parameters:
  • x (int) – X coordinate relative to element bounds

  • y (int) – Y coordinate relative to element bounds

  • cell (Cell) – Cell to write

Returns:

Number of terminal columns consumed by the cell (1 or 2), regardless of whether anything was actually written. Per-cluster render loops advance their column counter by this value.

Return type:

int

Notes

Coordinates are relative to element bounds. The cell is only written where it falls within the clip_region.

A cell whose char has display width 2 (most CJK, many emoji) is written as a head cell plus a continuation cell (char == CONTINUATION_CHAR) in the next column carrying the same style attributes, matching write_text(). When only one of the two columns is inside the clip region, a styled space is written in the visible column so a half glyph never appears. A cell that is itself a continuation cell, or whose char is width <= 1, is written as a plain single clipped cell.

write_cells(x, y, cells)[source]

Write a horizontal run of pre-built cells with clipping.

Fast path for callers that assemble whole rows of Cell objects: the run is sliced to the intersection of the row with the clip region and written with a single set_cells_horizontal() call instead of per-cell writes.

Parameters:
  • x (int) – Starting X coordinate relative to element bounds

  • y (int) – Y coordinate relative to element bounds

  • cells (list of Cell) – Cells to write left to right. The list is not mutated.

Return type:

None

Notes

Wide glyphs severed at the slice edges are guarded: if the first visible cell is a continuation cell (its head fell outside the clip region), or the last visible cell is a width-2 head (its continuation fell outside), a styled space is written in that column instead so a half glyph never appears.

write_cells_vertical(x, y, cells)[source]

Write a vertical run of pre-built cells with clipping.

Fast path for callers that assemble whole columns of Cell objects (borders, scrollbars): the run is sliced to the intersection of the column with the clip region and written with a single set_cells_vertical() call.

Parameters:
  • x (int) – X coordinate relative to element bounds

  • y (int) – Starting Y coordinate relative to element bounds

  • cells (list of Cell) – Cells to write top to bottom. The list is not mutated.

Return type:

None

Notes

Cells in a vertical run should be single-column glyphs; width-2 glyphs are not expanded into continuation cells here (a vertical run cannot place the continuation column).

cursor_anchor(x, y)[source]

Absolute screen coordinates for a caret at a relative position.

Parameters:
  • x (int) – X coordinate relative to element bounds

  • y (int) – Y coordinate relative to element bounds

Returns:

Absolute (x, y) screen coordinates, or None if the position falls outside the clip region (e.g. a caret scrolled out of a frame’s visible interior).

Return type:

tuple of (int, int) or None

Notes

Elements that show a text caret call this where they paint it so the application can park the hardware terminal cursor on the same cell. Clip awareness comes for free: a caret outside the visible region yields None and the hardware cursor stays hidden.

draw_border(x, y, width, height, style, border_chars=None)[source]

Draw a border around a rectangular region.

Parameters:
  • x (int) – X coordinate (column) relative to element bounds

  • y (int) – Y coordinate (row) relative to element bounds

  • width (int) – Border width (including border characters)

  • height (int) – Border height (including border characters)

  • style (Style) – Style to apply to border

  • border_chars (dict, optional) – Border characters dict with keys: ‘tl’, ‘tr’, ‘bl’, ‘br’, ‘h’, ‘v’ (top-left, top-right, etc.). If None, uses single line box drawing characters.

Return type:

None

Notes

Draws box-drawing characters around the specified rectangle. The rectangle dimensions include the border itself. Rendering is clipped to the clip_region if set.

Minimum size is 2x2 (for corners only).

Examples

Draw default single-line border:

>>> ctx.draw_border(0, 0, 20, 10, style)

Draw with custom characters:

>>> custom = {'tl': '+', 'tr': '+', 'bl': '+', 'br': '+',
...           'h': '-', 'v': '|'}
>>> ctx.draw_border(0, 0, 20, 10, style, custom)
clear(style=None)[source]

Clear the element’s bounds region.

Parameters:

style (Style, optional) – Style to apply to cleared cells (for background color). If None, uses default empty style.

Return type:

None

Notes

Fills the entire element bounds with space characters. Useful for clearing before rendering new content.

Examples

Clear with default style:

>>> ctx.clear()

Clear with background color:

>>> bg_style = Style(bg_color=(40, 40, 40))
>>> ctx.clear(bg_style)
sub_context(x, y, width, height)[source]

Create a sub-context with relative bounds.

Parameters:
  • x (int) – X offset relative to current bounds

  • y (int) – Y offset relative to current bounds

  • width (int) – Width of sub-context

  • height (int) – Height of sub-context

Returns:

New context with adjusted bounds

Return type:

PaintContext

Notes

This is useful for nested rendering where a child element needs its own coordinate system within a parent’s bounds.

The new context shares the same buffer, style resolver, and clip_region (inherited from parent).

Examples

Create sub-context for nested element:

>>> sub_ctx = ctx.sub_context(10, 5, 20, 10)
>>> # Now (0, 0) in sub_ctx is (10, 5) in parent context