1from typing import cast, List, Optional, Tuple, TYPE_CHECKING, Union 2 3if TYPE_CHECKING: 4 from .console import ( 5 Console, 6 ConsoleOptions, 7 RenderableType, 8 RenderResult, 9 ) 10from .jupyter import JupyterMixin 11from .measure import Measurement 12from .style import Style 13from .segment import Segment 14 15 16PaddingDimensions = Union[int, Tuple[int], Tuple[int, int], Tuple[int, int, int, int]] 17 18 19class Padding(JupyterMixin): 20 """Draw space around content. 21 22 Example: 23 >>> print(Padding("Hello", (2, 4), style="on blue")) 24 25 Args: 26 renderable (RenderableType): String or other renderable. 27 pad (Union[int, Tuple[int]]): Padding for top, right, bottom, and left borders. 28 May be specified with 1, 2, or 4 integers (CSS style). 29 style (Union[str, Style], optional): Style for padding characters. Defaults to "none". 30 expand (bool, optional): Expand padding to fit available width. Defaults to True. 31 """ 32 33 def __init__( 34 self, 35 renderable: "RenderableType", 36 pad: "PaddingDimensions" = (0, 0, 0, 0), 37 *, 38 style: Union[str, Style] = "none", 39 expand: bool = True, 40 ): 41 self.renderable = renderable 42 self.top, self.right, self.bottom, self.left = self.unpack(pad) 43 self.style = style 44 self.expand = expand 45 46 @classmethod 47 def indent(cls, renderable: "RenderableType", level: int) -> "Padding": 48 """Make padding instance to render an indent. 49 50 Args: 51 renderable (RenderableType): String or other renderable. 52 level (int): Number of characters to indent. 53 54 Returns: 55 Padding: A Padding instance. 56 """ 57 58 return Padding(renderable, pad=(0, 0, 0, level), expand=False) 59 60 @staticmethod 61 def unpack(pad: "PaddingDimensions") -> Tuple[int, int, int, int]: 62 """Unpack padding specified in CSS style.""" 63 if isinstance(pad, int): 64 return (pad, pad, pad, pad) 65 if len(pad) == 1: 66 _pad = pad[0] 67 return (_pad, _pad, _pad, _pad) 68 if len(pad) == 2: 69 pad_top, pad_right = cast(Tuple[int, int], pad) 70 return (pad_top, pad_right, pad_top, pad_right) 71 if len(pad) == 4: 72 top, right, bottom, left = cast(Tuple[int, int, int, int], pad) 73 return (top, right, bottom, left) 74 raise ValueError(f"1, 2 or 4 integers required for padding; {len(pad)} given") 75 76 def __repr__(self) -> str: 77 return f"Padding({self.renderable!r}, ({self.top},{self.right},{self.bottom},{self.left}))" 78 79 def __rich_console__( 80 self, console: "Console", options: "ConsoleOptions" 81 ) -> "RenderResult": 82 style = console.get_style(self.style) 83 if self.expand: 84 width = options.max_width 85 else: 86 width = min( 87 Measurement.get(console, options, self.renderable).maximum 88 + self.left 89 + self.right, 90 options.max_width, 91 ) 92 render_options = options.update_width(width - self.left - self.right) 93 if render_options.height is not None: 94 render_options = render_options.update_height( 95 height=render_options.height - self.top - self.bottom 96 ) 97 lines = console.render_lines( 98 self.renderable, render_options, style=style, pad=True 99 ) 100 _Segment = Segment 101 102 left = _Segment(" " * self.left, style) if self.left else None 103 right = ( 104 [_Segment(f'{" " * self.right}', style), _Segment.line()] 105 if self.right 106 else [_Segment.line()] 107 ) 108 blank_line: Optional[List[Segment]] = None 109 if self.top: 110 blank_line = [_Segment(f'{" " * width}\n', style)] 111 yield from blank_line * self.top 112 if left: 113 for line in lines: 114 yield left 115 yield from line 116 yield from right 117 else: 118 for line in lines: 119 yield from line 120 yield from right 121 if self.bottom: 122 blank_line = blank_line or [_Segment(f'{" " * width}\n', style)] 123 yield from blank_line * self.bottom 124 125 def __rich_measure__( 126 self, console: "Console", options: "ConsoleOptions" 127 ) -> "Measurement": 128 max_width = options.max_width 129 extra_width = self.left + self.right 130 if max_width - extra_width < 1: 131 return Measurement(max_width, max_width) 132 measure_min, measure_max = Measurement.get(console, options, self.renderable) 133 measurement = Measurement(measure_min + extra_width, measure_max + extra_width) 134 measurement = measurement.with_maximum(max_width) 135 return measurement 136 137 138if __name__ == "__main__": # pragma: no cover 139 from rich import print 140 141 print(Padding("Hello, World", (2, 4), style="on blue")) 142