| from operator import itemgetter |
| from typing import TYPE_CHECKING, Callable, NamedTuple, Optional, Sequence |
| |
| from . import errors |
| from .protocol import is_renderable, rich_cast |
| |
| if TYPE_CHECKING: |
| from .console import Console, ConsoleOptions, RenderableType |
| |
| |
| class Measurement(NamedTuple): |
| """Stores the minimum and maximum widths (in characters) required to render an object.""" |
| |
| minimum: int |
| """Minimum number of cells required to render.""" |
| maximum: int |
| """Maximum number of cells required to render.""" |
| |
| @property |
| def span(self) -> int: |
| """Get difference between maximum and minimum.""" |
| return self.maximum - self.minimum |
| |
| def normalize(self) -> "Measurement": |
| """Get measurement that ensures that minimum <= maximum and minimum >= 0 |
| |
| Returns: |
| Measurement: A normalized measurement. |
| """ |
| minimum, maximum = self |
| minimum = min(max(0, minimum), maximum) |
| return Measurement(max(0, minimum), max(0, max(minimum, maximum))) |
| |
| def with_maximum(self, width: int) -> "Measurement": |
| """Get a RenderableWith where the widths are <= width. |
| |
| Args: |
| width (int): Maximum desired width. |
| |
| Returns: |
| Measurement: New Measurement object. |
| """ |
| minimum, maximum = self |
| return Measurement(min(minimum, width), min(maximum, width)) |
| |
| def with_minimum(self, width: int) -> "Measurement": |
| """Get a RenderableWith where the widths are >= width. |
| |
| Args: |
| width (int): Minimum desired width. |
| |
| Returns: |
| Measurement: New Measurement object. |
| """ |
| minimum, maximum = self |
| width = max(0, width) |
| return Measurement(max(minimum, width), max(maximum, width)) |
| |
| def clamp( |
| self, min_width: Optional[int] = None, max_width: Optional[int] = None |
| ) -> "Measurement": |
| """Clamp a measurement within the specified range. |
| |
| Args: |
| min_width (int): Minimum desired width, or ``None`` for no minimum. Defaults to None. |
| max_width (int): Maximum desired width, or ``None`` for no maximum. Defaults to None. |
| |
| Returns: |
| Measurement: New Measurement object. |
| """ |
| measurement = self |
| if min_width is not None: |
| measurement = measurement.with_minimum(min_width) |
| if max_width is not None: |
| measurement = measurement.with_maximum(max_width) |
| return measurement |
| |
| @classmethod |
| def get( |
| cls, console: "Console", options: "ConsoleOptions", renderable: "RenderableType" |
| ) -> "Measurement": |
| """Get a measurement for a renderable. |
| |
| Args: |
| console (~rich.console.Console): Console instance. |
| options (~rich.console.ConsoleOptions): Console options. |
| renderable (RenderableType): An object that may be rendered with Rich. |
| |
| Raises: |
| errors.NotRenderableError: If the object is not renderable. |
| |
| Returns: |
| Measurement: Measurement object containing range of character widths required to render the object. |
| """ |
| _max_width = options.max_width |
| if _max_width < 1: |
| return Measurement(0, 0) |
| if isinstance(renderable, str): |
| renderable = console.render_str( |
| renderable, markup=options.markup, highlight=False |
| ) |
| renderable = rich_cast(renderable) |
| if is_renderable(renderable): |
| get_console_width: Optional[ |
| Callable[["Console", "ConsoleOptions"], "Measurement"] |
| ] = getattr(renderable, "__rich_measure__", None) |
| if get_console_width is not None: |
| render_width = ( |
| get_console_width(console, options) |
| .normalize() |
| .with_maximum(_max_width) |
| ) |
| if render_width.maximum < 1: |
| return Measurement(0, 0) |
| return render_width.normalize() |
| else: |
| return Measurement(0, _max_width) |
| else: |
| raise errors.NotRenderableError( |
| f"Unable to get render width for {renderable!r}; " |
| "a str, Segment, or object with __rich_console__ method is required" |
| ) |
| |
| |
| def measure_renderables( |
| console: "Console", |
| options: "ConsoleOptions", |
| renderables: Sequence["RenderableType"], |
| ) -> "Measurement": |
| """Get a measurement that would fit a number of renderables. |
| |
| Args: |
| console (~rich.console.Console): Console instance. |
| options (~rich.console.ConsoleOptions): Console options. |
| renderables (Iterable[RenderableType]): One or more renderable objects. |
| |
| Returns: |
| Measurement: Measurement object containing range of character widths required to |
| contain all given renderables. |
| """ |
| if not renderables: |
| return Measurement(0, 0) |
| get_measurement = Measurement.get |
| measurements = [ |
| get_measurement(console, options, renderable) for renderable in renderables |
| ] |
| measured_width = Measurement( |
| max(measurements, key=itemgetter(0)).minimum, |
| max(measurements, key=itemgetter(1)).maximum, |
| ) |
| return measured_width |