ttyengine 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ttyengine/__init__.py +45 -0
- ttyengine/ansi.py +201 -0
- ttyengine/ascii_styler.py +473 -0
- ttyengine/components.py +4718 -0
- ttyengine/compositor.py +780 -0
- ttyengine/errors.py +181 -0
- ttyengine/figlet-LICENSE.txt +27 -0
- ttyengine/figlet.py +430 -0
- ttyengine/fonts.zip +0 -0
- ttyengine/image_reader.py +813 -0
- ttyengine/input_handler.py +814 -0
- ttyengine/launcher.py +822 -0
- ttyengine/region.py +180 -0
- ttyengine/scene.py +614 -0
- ttyengine/sound.py +1439 -0
- ttyengine/terminal.py +283 -0
- ttyengine-0.1.0.dist-info/METADATA +903 -0
- ttyengine-0.1.0.dist-info/RECORD +21 -0
- ttyengine-0.1.0.dist-info/WHEEL +5 -0
- ttyengine-0.1.0.dist-info/licenses/LICENSE +373 -0
- ttyengine-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import re, shutil, colorsys
|
|
2
|
+
from functools import lru_cache
|
|
3
|
+
from . import ansi
|
|
4
|
+
from . import region as _regions
|
|
5
|
+
from .figlet import Figlet, available
|
|
6
|
+
from .image_reader import read as readimage
|
|
7
|
+
|
|
8
|
+
DEFAULT_IMAGE_WIDTH = 80
|
|
9
|
+
|
|
10
|
+
RGB_MAP = {
|
|
11
|
+
"red": (205, 49, 49),
|
|
12
|
+
"orange": (255, 165, 0),
|
|
13
|
+
"brown": (139, 69, 19),
|
|
14
|
+
"gold": (255, 215, 0),
|
|
15
|
+
"yellow": (229, 229, 16),
|
|
16
|
+
"green": (13, 188, 121),
|
|
17
|
+
"turquoise": (64, 224, 208),
|
|
18
|
+
"cyan": (17, 168, 205),
|
|
19
|
+
"blue": (36, 114, 200),
|
|
20
|
+
"purple": (128, 0, 128),
|
|
21
|
+
"violet": (238, 130, 238),
|
|
22
|
+
"magenta": (188, 63, 188),
|
|
23
|
+
"pink": (255, 105, 180), # 13 colors
|
|
24
|
+
"brightred": (241, 76, 76),
|
|
25
|
+
"brightorange": (255, 200, 0),
|
|
26
|
+
"brightbrown": (205, 133, 63),
|
|
27
|
+
"brightgold": (255, 235, 100),
|
|
28
|
+
"brightyellow": (245, 245, 67),
|
|
29
|
+
"brightgreen": (35, 209, 139),
|
|
30
|
+
"brightturquoise": (127, 255, 212),
|
|
31
|
+
"brightcyan": (41, 184, 219),
|
|
32
|
+
"brightblue": (59, 142, 234),
|
|
33
|
+
"brightpurple": (191, 0, 191),
|
|
34
|
+
"brightviolet": (255, 170, 255),
|
|
35
|
+
"brightmagenta": (214, 112, 214),
|
|
36
|
+
"brightpink": (255, 182, 193), # 13 bright colors
|
|
37
|
+
"darkred": (139, 0, 0),
|
|
38
|
+
"darkorange": (255, 140, 0),
|
|
39
|
+
"darkbrown": (60, 30, 10),
|
|
40
|
+
"darkgold": (184, 134, 11),
|
|
41
|
+
"darkyellow": (139, 139, 0),
|
|
42
|
+
"darkgreen": (0, 100, 0),
|
|
43
|
+
"darkturquoise": (0, 134, 139),
|
|
44
|
+
"darkcyan": (0, 139, 139),
|
|
45
|
+
"darkblue": (0, 0, 139),
|
|
46
|
+
"darkpurple": (75, 0, 130),
|
|
47
|
+
"darkviolet": (148, 0, 211),
|
|
48
|
+
"darkmagenta": (139, 0, 139),
|
|
49
|
+
"darkpink": (231, 84, 128), # 13 dark colors
|
|
50
|
+
"black": (0, 0, 0),
|
|
51
|
+
"darkgray": (64, 64, 64),
|
|
52
|
+
"brightblack": (102, 102, 102),
|
|
53
|
+
"gray": (128, 128, 128),
|
|
54
|
+
"brightgray": (192, 192, 192),
|
|
55
|
+
"default": (204, 204, 204),
|
|
56
|
+
"bright": (229, 229, 229),
|
|
57
|
+
"white": (255, 255, 255), # 8 gray scale colors
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
FONT_MAP = {
|
|
61
|
+
1: "standard", 2: "big", 3: "slant", 4: "doom", 5: "bubble",
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
DEFAULT_BG = (0,0,0)
|
|
65
|
+
DEFAULT_FG = RGB_MAP["default"]
|
|
66
|
+
bgrecomp = ansi.Pattern(re.escape(ansi.CSI) + r'48;2;(\d{1,3});(\d{1,3});(\d{1,3})m')
|
|
67
|
+
fgrecomp = ansi.Pattern(re.escape(ansi.CSI) + r'38;2;(\d{1,3});(\d{1,3});(\d{1,3})m')
|
|
68
|
+
recomp = ansi.SGR
|
|
69
|
+
ALPHA_FG = 1000
|
|
70
|
+
ALPHA_FGBACK = 1300
|
|
71
|
+
ALPHA_BG = 1600
|
|
72
|
+
alphamark = ansi.Pattern(re.escape(ansi.CSI) + r'(1[0-8]\d\d)m')
|
|
73
|
+
_figlet_cache = {}
|
|
74
|
+
DEFAULT_SHADES = "█"#" .:-=+*#%@"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def alphacode(base, alpha):
|
|
78
|
+
return ansi.sgr(base + int(round(max(0.0, min(1.0, alpha)) * 255)))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def alphaparse(text):
|
|
82
|
+
fa = ba = None
|
|
83
|
+
back = False
|
|
84
|
+
for match in alphamark.finditer(text):
|
|
85
|
+
code = int(match.group(1))
|
|
86
|
+
if ALPHA_BG <= code <= ALPHA_BG + 255:
|
|
87
|
+
ba = (code - ALPHA_BG) / 255
|
|
88
|
+
elif ALPHA_FGBACK <= code <= ALPHA_FGBACK + 255:
|
|
89
|
+
fa = (code - ALPHA_FGBACK) / 255
|
|
90
|
+
back = True
|
|
91
|
+
elif ALPHA_FG <= code <= ALPHA_FG + 255:
|
|
92
|
+
fa = (code - ALPHA_FG) / 255
|
|
93
|
+
back = False
|
|
94
|
+
return fa, back, ba
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@lru_cache(maxsize=512)
|
|
98
|
+
def _rgb(color):
|
|
99
|
+
return Styler._rgb_uncached(color)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class Styler:
|
|
103
|
+
def __init__(self, source=None, bgcolor=DEFAULT_BG, fgcolor=DEFAULT_FG):
|
|
104
|
+
self.source = source
|
|
105
|
+
self._bgcolor = bgcolor
|
|
106
|
+
self._fgcolor = fgcolor
|
|
107
|
+
self.shapes = Shapes
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def bgcolor(self):
|
|
111
|
+
return self._bgcolor if self.source is None else self.source.bgcolor
|
|
112
|
+
@bgcolor.setter
|
|
113
|
+
def bgcolor(self, value):
|
|
114
|
+
if self.source is None: self._bgcolor = value
|
|
115
|
+
else: self.source.bgcolor = value
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def fgcolor(self):
|
|
119
|
+
return self._fgcolor if self.source is None else self.source.fgcolor
|
|
120
|
+
@fgcolor.setter
|
|
121
|
+
def fgcolor(self, value):
|
|
122
|
+
if self.source is None: self._fgcolor = value
|
|
123
|
+
else: self.source.fgcolor = value
|
|
124
|
+
|
|
125
|
+
@staticmethod
|
|
126
|
+
def getfiglet(font, width):
|
|
127
|
+
key = (font, width)
|
|
128
|
+
if key not in _figlet_cache:
|
|
129
|
+
_figlet_cache[key] = Figlet(font=font, width=width)
|
|
130
|
+
return _figlet_cache[key]
|
|
131
|
+
|
|
132
|
+
@staticmethod
|
|
133
|
+
def fonts():
|
|
134
|
+
return available()
|
|
135
|
+
|
|
136
|
+
@staticmethod
|
|
137
|
+
def rgb(color):
|
|
138
|
+
if isinstance(color, list):
|
|
139
|
+
color = tuple(color)
|
|
140
|
+
try:
|
|
141
|
+
hash(color)
|
|
142
|
+
return _rgb(color)
|
|
143
|
+
except (TypeError, ValueError, OverflowError):
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
@staticmethod
|
|
147
|
+
def _rgb_uncached(color):
|
|
148
|
+
if isinstance(color, tuple):
|
|
149
|
+
if len(color) == 3:
|
|
150
|
+
return tuple(max(0, min(255, int(c))) for c in color)
|
|
151
|
+
elif len(color) == 2:
|
|
152
|
+
rainbow = max(1, min(3, color[1]))
|
|
153
|
+
start = (max(1, min(4, color[1]))-1) * 13
|
|
154
|
+
rainbow *= 13
|
|
155
|
+
if color[1] > 3: rainbow += 8
|
|
156
|
+
for i,c in enumerate(RGB_MAP.values()):
|
|
157
|
+
if i in range(start, rainbow) and i==color[0]:
|
|
158
|
+
return c
|
|
159
|
+
elif len(color) == 1:
|
|
160
|
+
for i,c in enumerate(RGB_MAP.values()):
|
|
161
|
+
if i==color[0]:
|
|
162
|
+
return c
|
|
163
|
+
elif isinstance(color, (float,int)):
|
|
164
|
+
for i,c in enumerate(RGB_MAP.values()):
|
|
165
|
+
if i==round(color):
|
|
166
|
+
return c
|
|
167
|
+
elif isinstance(color, str):
|
|
168
|
+
return RGB_MAP.get(color.lower(), None)
|
|
169
|
+
return None
|
|
170
|
+
|
|
171
|
+
@staticmethod
|
|
172
|
+
def hsv(hue, saturation=1.0, value=1.0):
|
|
173
|
+
s = max(0.0, min(1.0, saturation))
|
|
174
|
+
v = max(0.0, min(1.0, value))
|
|
175
|
+
r, g, b = colorsys.hsv_to_rgb((hue % 360) / 360, s, v)
|
|
176
|
+
return (round(r * 255), round(g * 255), round(b * 255))
|
|
177
|
+
|
|
178
|
+
@staticmethod
|
|
179
|
+
def getcolor(text:str, back=False, default=None):
|
|
180
|
+
fallback = default if default is not None else (DEFAULT_BG if back else DEFAULT_FG)
|
|
181
|
+
target = ansi.CSI + ("48;2;" if back else "38;2;")
|
|
182
|
+
if target not in text:
|
|
183
|
+
return fallback, ""
|
|
184
|
+
matches = list(bgrecomp.finditer(text) if back else fgrecomp.finditer(text))
|
|
185
|
+
tupl = tuple(int(x) for x in matches[-1].groups()) if matches else fallback
|
|
186
|
+
color = matches[-1].group(0) if matches else ""
|
|
187
|
+
return tupl, color
|
|
188
|
+
|
|
189
|
+
def _style(self, text: str, color=None, alpha=None, font=None, width=80, bgcolor=None,
|
|
190
|
+
bgalpha=None, bg=DEFAULT_BG, fg=DEFAULT_FG, onbg=False, reset=True, clean=False,
|
|
191
|
+
bold=False, italic=False, underline=False, size=None):
|
|
192
|
+
rgb = self.rgb
|
|
193
|
+
getcolor = self.getcolor
|
|
194
|
+
if alpha is not None:
|
|
195
|
+
alpha = max(0.0, min(1.0, alpha))
|
|
196
|
+
if bgalpha is not None:
|
|
197
|
+
bgalpha = max(0.0, min(1.0, bgalpha))
|
|
198
|
+
bg_rgb = rgb(bg) or bg
|
|
199
|
+
fg_rgb = rgb(fg) or fg
|
|
200
|
+
match_bgcolor = ""
|
|
201
|
+
match_color = ""
|
|
202
|
+
user_color = color
|
|
203
|
+
user_bgcolor = bgcolor
|
|
204
|
+
bgcolor_rgb = rgb(bgcolor)
|
|
205
|
+
color_rgb = rgb(color)
|
|
206
|
+
old_fa, old_back, old_ba = alphaparse(text) if clean else (None, False, None)
|
|
207
|
+
fg_mark = ""
|
|
208
|
+
bg_mark = ""
|
|
209
|
+
if user_bgcolor is not None or bgalpha is not None:
|
|
210
|
+
_bgalpha = bgalpha if bgalpha is not None else 1.0
|
|
211
|
+
bg_marked = _bgalpha
|
|
212
|
+
transparent_bg = False
|
|
213
|
+
if bgcolor_rgb is None:
|
|
214
|
+
bgcolor_rgb, match_bgcolor = getcolor(text, True, bg_rgb)
|
|
215
|
+
transparent_bg = not match_bgcolor
|
|
216
|
+
if old_ba is not None:
|
|
217
|
+
bg_marked = _bgalpha * old_ba
|
|
218
|
+
if transparent_bg:
|
|
219
|
+
ansi_bgcolor = ""
|
|
220
|
+
else:
|
|
221
|
+
if bg_marked < 1:
|
|
222
|
+
bg_mark = alphacode(ALPHA_BG, bg_marked)
|
|
223
|
+
rb = int(bg_rgb[0] * (1 - _bgalpha) + bgcolor_rgb[0] * _bgalpha)
|
|
224
|
+
gb = int(bg_rgb[1] * (1 - _bgalpha) + bgcolor_rgb[1] * _bgalpha)
|
|
225
|
+
bb = int(bg_rgb[2] * (1 - _bgalpha) + bgcolor_rgb[2] * _bgalpha)
|
|
226
|
+
ansi_bgcolor = ansi.bg(rb, gb, bb)
|
|
227
|
+
else:
|
|
228
|
+
bgcolor_rgb, match_bgcolor = getcolor(text, True, bg_rgb)
|
|
229
|
+
ansi_bgcolor = ""
|
|
230
|
+
if match_bgcolor and old_ba is not None and old_ba < 1:
|
|
231
|
+
bg_mark = alphacode(ALPHA_BG, old_ba)
|
|
232
|
+
if user_color is not None or alpha is not None:
|
|
233
|
+
_alpha = alpha if alpha is not None else 1.0
|
|
234
|
+
fg_marked = _alpha
|
|
235
|
+
fg_back = bool(onbg)
|
|
236
|
+
if color_rgb is None:
|
|
237
|
+
color_rgb, match_color = getcolor(text, False, fg_rgb)
|
|
238
|
+
if old_fa is not None:
|
|
239
|
+
fg_marked = _alpha * old_fa
|
|
240
|
+
if alpha is None:
|
|
241
|
+
fg_back = old_back
|
|
242
|
+
if fg_marked < 1:
|
|
243
|
+
fg_mark = alphacode(ALPHA_FGBACK if fg_back else ALPHA_FG, fg_marked)
|
|
244
|
+
activebg_rgb = bg_rgb if onbg else bgcolor_rgb
|
|
245
|
+
r = int(activebg_rgb[0] * (1 - _alpha) + color_rgb[0] * _alpha)
|
|
246
|
+
g = int(activebg_rgb[1] * (1 - _alpha) + color_rgb[1] * _alpha)
|
|
247
|
+
b = int(activebg_rgb[2] * (1 - _alpha) + color_rgb[2] * _alpha)
|
|
248
|
+
ansi_color = ansi.fg(r, g, b)
|
|
249
|
+
else:
|
|
250
|
+
ansi_color = ""
|
|
251
|
+
if old_fa is not None:
|
|
252
|
+
fg_mark = alphacode(ALPHA_FGBACK if old_back else ALPHA_FG, old_fa)
|
|
253
|
+
if clean:
|
|
254
|
+
bold = bold or ansi.BOLD in text
|
|
255
|
+
italic = italic or ansi.ITALIC in text
|
|
256
|
+
underline = underline or ansi.UNDERLINE in text
|
|
257
|
+
ascii_art = recomp.sub('', text)
|
|
258
|
+
if not ansi_bgcolor:
|
|
259
|
+
ansi_bgcolor = match_bgcolor
|
|
260
|
+
if not ansi_color:
|
|
261
|
+
ansi_color = match_color
|
|
262
|
+
else:
|
|
263
|
+
ascii_art = text
|
|
264
|
+
if font is None:
|
|
265
|
+
font = size
|
|
266
|
+
if font is not None and (not isinstance(font, int) or font >= 1):
|
|
267
|
+
name = FONT_MAP[max(1, min(5, font))] if isinstance(font, int) else font
|
|
268
|
+
ascii_art = self.getfiglet(font=name, width=width).renderText(ascii_art)
|
|
269
|
+
ansi_attrs = (ansi.BOLD if bold else '') + (ansi.ITALIC if italic else '') + (ansi.UNDERLINE if underline else '')
|
|
270
|
+
prefix = ansi_bgcolor + ansi_color + ansi_attrs + bg_mark + fg_mark
|
|
271
|
+
suffix = ansi.RESET if reset else ""
|
|
272
|
+
trailing = '\n' if ascii_art.endswith('\n') else ''
|
|
273
|
+
|
|
274
|
+
lines = ascii_art.split('\n')
|
|
275
|
+
while lines and not lines[-1].strip():
|
|
276
|
+
lines.pop()
|
|
277
|
+
|
|
278
|
+
styled_text = '\n'.join(prefix + line.rstrip() + suffix for line in lines) + trailing
|
|
279
|
+
return styled_text
|
|
280
|
+
|
|
281
|
+
def style(self, *args, **kwargs):
|
|
282
|
+
if len(args) <= 7 and 'bg' not in kwargs:
|
|
283
|
+
kwargs['bg'] = self.bgcolor
|
|
284
|
+
if len(args) <= 8 and 'fg' not in kwargs:
|
|
285
|
+
kwargs['fg'] = self.fgcolor
|
|
286
|
+
return self._style(*args, **kwargs)
|
|
287
|
+
|
|
288
|
+
@staticmethod
|
|
289
|
+
def fill(color=None, width=None, height=None, xoffset=0, yoffset=0, termx=None, termy=None, region=None):
|
|
290
|
+
if termx is None or termy is None:
|
|
291
|
+
termx, termy = shutil.get_terminal_size()
|
|
292
|
+
if region is not None:
|
|
293
|
+
xoffset, yoffset, width, height = _regions.resolve(region, termx, termy)
|
|
294
|
+
width = termx if width is None else int(width)
|
|
295
|
+
height = termy if height is None else int(height)
|
|
296
|
+
xoffset = int(xoffset)
|
|
297
|
+
yoffset = int(yoffset)
|
|
298
|
+
x_start = max(0, xoffset)
|
|
299
|
+
y_start = max(0, yoffset)
|
|
300
|
+
x_end = min(termx, xoffset + width)
|
|
301
|
+
y_end = min(termy, yoffset + height)
|
|
302
|
+
if x_end <= x_start or y_end <= y_start:
|
|
303
|
+
return ""
|
|
304
|
+
draw_width = x_end - x_start
|
|
305
|
+
if color is None:
|
|
306
|
+
row_str = ansi.RESET + " " * draw_width
|
|
307
|
+
return "".join(
|
|
308
|
+
ansi.move(row + 1, x_start + 1) + row_str
|
|
309
|
+
for row in range(y_start, y_end)
|
|
310
|
+
)
|
|
311
|
+
rgb = Styler.rgb(color)
|
|
312
|
+
if rgb is None:
|
|
313
|
+
raise ValueError('unknown color: %r' % (color,))
|
|
314
|
+
r, g, b = rgb
|
|
315
|
+
row_str = ansi.bg(r, g, b) + " " * draw_width + ansi.RESET
|
|
316
|
+
return "".join(
|
|
317
|
+
ansi.move(row + 1, x_start + 1) + row_str
|
|
318
|
+
for row in range(y_start, y_end)
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
@staticmethod
|
|
322
|
+
def image(path, width=None, height=None, shades=DEFAULT_SHADES, aspect=0.5, color=None):
|
|
323
|
+
if not shades:
|
|
324
|
+
raise ValueError("shades must not be empty")
|
|
325
|
+
if aspect <= 0:
|
|
326
|
+
raise ValueError("aspect must be positive")
|
|
327
|
+
if color is None:
|
|
328
|
+
color = len(shades) == 1
|
|
329
|
+
iw, ih, px = readimage(path)
|
|
330
|
+
if width is None and height is None:
|
|
331
|
+
width = min(iw, DEFAULT_IMAGE_WIDTH)
|
|
332
|
+
if width is None:
|
|
333
|
+
width = round(height * iw / (ih * aspect))
|
|
334
|
+
if height is None:
|
|
335
|
+
height = round(width * ih / iw * aspect)
|
|
336
|
+
width = max(1, int(width))
|
|
337
|
+
height = max(1, int(height))
|
|
338
|
+
top = len(shades) - 1
|
|
339
|
+
lines = []
|
|
340
|
+
for cy in range(height):
|
|
341
|
+
y0 = cy * ih // height
|
|
342
|
+
y1 = max(y0 + 1, (cy + 1) * ih // height)
|
|
343
|
+
sr = [0] * iw
|
|
344
|
+
sg = [0] * iw
|
|
345
|
+
sb = [0] * iw
|
|
346
|
+
sa = [0] * iw
|
|
347
|
+
for y in range(y0, y1):
|
|
348
|
+
o = y * iw * 4
|
|
349
|
+
r = px[o:o + iw * 4:4]
|
|
350
|
+
g = px[o + 1:o + iw * 4:4]
|
|
351
|
+
b = px[o + 2:o + iw * 4:4]
|
|
352
|
+
a = px[o + 3:o + iw * 4:4]
|
|
353
|
+
sr = [t + v * w for t, v, w in zip(sr, r, a)]
|
|
354
|
+
sg = [t + v * w for t, v, w in zip(sg, g, a)]
|
|
355
|
+
sb = [t + v * w for t, v, w in zip(sb, b, a)]
|
|
356
|
+
if color:
|
|
357
|
+
sa = [t + w for t, w in zip(sa, a)]
|
|
358
|
+
row = []
|
|
359
|
+
current = None
|
|
360
|
+
for cx in range(width):
|
|
361
|
+
x0 = cx * iw // width
|
|
362
|
+
x1 = max(x0 + 1, (cx + 1) * iw // width)
|
|
363
|
+
norm = (x1 - x0) * (y1 - y0) * 255
|
|
364
|
+
red = sum(sr[x0:x1]) / norm
|
|
365
|
+
green = sum(sg[x0:x1]) / norm
|
|
366
|
+
blue = sum(sb[x0:x1]) / norm
|
|
367
|
+
level = (54 * red + 183 * green + 19 * blue) / 65280
|
|
368
|
+
char = shades[min(top, int(level * len(shades)))]
|
|
369
|
+
if color:
|
|
370
|
+
if not sum(sa[x0:x1]):
|
|
371
|
+
char = ' '
|
|
372
|
+
elif char != ' ':
|
|
373
|
+
rgb = (round(red), round(green), round(blue))
|
|
374
|
+
if rgb != current:
|
|
375
|
+
row.append(ansi.fg(*rgb))
|
|
376
|
+
current = rgb
|
|
377
|
+
row.append(char)
|
|
378
|
+
line = ''.join(row).rstrip(' ')
|
|
379
|
+
lines.append(line + ansi.RESET if current else line)
|
|
380
|
+
return '\n'.join(lines)
|
|
381
|
+
|
|
382
|
+
class Shapes:
|
|
383
|
+
@staticmethod
|
|
384
|
+
def ellipse(rx, ry, char='o', filled=False, thickness=1):
|
|
385
|
+
rx = round(rx)
|
|
386
|
+
ry = round(ry)
|
|
387
|
+
if rx < 1 or ry < 1:
|
|
388
|
+
return char
|
|
389
|
+
thickness = max(1, thickness)
|
|
390
|
+
ox, oy = rx + 0.5, ry + 0.5
|
|
391
|
+
ix, iy = ox - thickness, oy - thickness
|
|
392
|
+
rows = []
|
|
393
|
+
for dy in range(-ry, ry + 1):
|
|
394
|
+
row = ''
|
|
395
|
+
for dx in range(-rx, rx + 1):
|
|
396
|
+
hit = (dx / ox) ** 2 + (dy / oy) ** 2 <= 1
|
|
397
|
+
if hit and not filled and ix > 0 and iy > 0:
|
|
398
|
+
hit = (dx / ix) ** 2 + (dy / iy) ** 2 >= 1
|
|
399
|
+
row += char if hit else ' '
|
|
400
|
+
rows.append(row)
|
|
401
|
+
return '\n'.join(rows)
|
|
402
|
+
|
|
403
|
+
@staticmethod
|
|
404
|
+
def circle(radius, char='o', filled=False, thickness=1, aspect=0.5):
|
|
405
|
+
return Shapes.ellipse(radius * (1 / aspect), radius, char, filled, thickness)
|
|
406
|
+
|
|
407
|
+
@staticmethod
|
|
408
|
+
def block(width, height=None, char='█', filled=False, thickness=1, aspect=0.5):
|
|
409
|
+
width = max(1, round(width))
|
|
410
|
+
height = max(1, round(width * aspect) if height is None else round(height))
|
|
411
|
+
thickness = max(1, round(thickness))
|
|
412
|
+
rows = []
|
|
413
|
+
for i in range(height):
|
|
414
|
+
if filled or i < thickness or i >= height - thickness or width <= 2 * thickness:
|
|
415
|
+
rows.append(char * width)
|
|
416
|
+
else:
|
|
417
|
+
rows.append(char * thickness + ' ' * (width - 2 * thickness) + char * thickness)
|
|
418
|
+
return '\n'.join(rows)
|
|
419
|
+
|
|
420
|
+
@staticmethod
|
|
421
|
+
def triangle(height, base=None, char='█', filled=False, thickness=1, aspect=0.5, direction='up'):
|
|
422
|
+
height = max(1, round(height))
|
|
423
|
+
thickness = max(1, round(thickness))
|
|
424
|
+
if base is None:
|
|
425
|
+
base = height / aspect
|
|
426
|
+
half = max(0, round((base - 1) / 2))
|
|
427
|
+
widths = [2 * round(half * i / (height - 1) if height > 1 else 0) + 1 for i in range(height)]
|
|
428
|
+
full = widths[-1]
|
|
429
|
+
rows = []
|
|
430
|
+
for i, w in enumerate(widths):
|
|
431
|
+
if filled or i >= height - thickness or w <= 2 * thickness:
|
|
432
|
+
body = char * w
|
|
433
|
+
else:
|
|
434
|
+
body = char * thickness + ' ' * (w - 2 * thickness) + char * thickness
|
|
435
|
+
rows.append(' ' * ((full - w) // 2) + body)
|
|
436
|
+
if direction == 'down':
|
|
437
|
+
rows.reverse()
|
|
438
|
+
return '\n'.join(rows)
|
|
439
|
+
|
|
440
|
+
@staticmethod
|
|
441
|
+
def diamond(radius, char='█', filled=False, thickness=1, aspect=0.5):
|
|
442
|
+
ry = max(1, round(radius))
|
|
443
|
+
rx = max(1, round(radius * (1 / aspect)))
|
|
444
|
+
thickness = max(1, round(thickness))
|
|
445
|
+
rows = []
|
|
446
|
+
for dy in range(-ry, ry + 1):
|
|
447
|
+
hw = round(rx * (1 - abs(dy) / ry))
|
|
448
|
+
w = 2 * hw + 1
|
|
449
|
+
if filled or w <= 2 * thickness:
|
|
450
|
+
body = char * w
|
|
451
|
+
else:
|
|
452
|
+
body = char * thickness + ' ' * (w - 2 * thickness) + char * thickness
|
|
453
|
+
rows.append(' ' * (rx - hw) + body)
|
|
454
|
+
return '\n'.join(rows)
|
|
455
|
+
|
|
456
|
+
@staticmethod
|
|
457
|
+
def line(length, char='█', thickness=1, vertical=False):
|
|
458
|
+
length = max(1, round(length))
|
|
459
|
+
thickness = max(1, round(thickness))
|
|
460
|
+
if vertical:
|
|
461
|
+
return '\n'.join([char * thickness] * length)
|
|
462
|
+
return '\n'.join([char * length] * thickness)
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
_default_styler = Styler()
|
|
466
|
+
rgb = Styler.rgb
|
|
467
|
+
hsv = Styler.hsv
|
|
468
|
+
getcolor = Styler.getcolor
|
|
469
|
+
getfiglet = Styler.getfiglet
|
|
470
|
+
fonts = Styler.fonts
|
|
471
|
+
fill = Styler.fill
|
|
472
|
+
image = Styler.image
|
|
473
|
+
style = _default_styler._style
|