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
ttyengine/errors.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import os, re, sys, traceback
|
|
2
|
+
|
|
3
|
+
from . import ansi
|
|
4
|
+
|
|
5
|
+
HOLD_ENV = 'TTYENGINE_HOLD'
|
|
6
|
+
HOLD_PROMPT = 'Press Enter to close...'
|
|
7
|
+
EXIT_CODE = 1
|
|
8
|
+
|
|
9
|
+
def highlight_traceback(tb_str: str) -> str:
|
|
10
|
+
if not ansi.color_enabled():
|
|
11
|
+
return tb_str
|
|
12
|
+
RESET, BOLD, RED, GREEN, YELLOW, CYAN, MAGENTA = (
|
|
13
|
+
ansi.RESET, ansi.BOLD, ansi.sgr(91), ansi.sgr(92), ansi.sgr(93), ansi.sgr(96), ansi.sgr(95)
|
|
14
|
+
)
|
|
15
|
+
lines = []
|
|
16
|
+
for line in tb_str.splitlines(True):
|
|
17
|
+
if line.startswith("Traceback"):
|
|
18
|
+
lines.append(f"{BOLD}{RED}{line}{RESET}")
|
|
19
|
+
elif line.strip().startswith("File "):
|
|
20
|
+
m = re.match(r'^(\s*File )("[^"]+")(, line )(\d+)(, in )(.*)$', line)
|
|
21
|
+
if m:
|
|
22
|
+
lines.append(
|
|
23
|
+
f"{m.group(1)}{GREEN}{m.group(2)}{RESET}"
|
|
24
|
+
f"{m.group(3)}{YELLOW}{m.group(4)}{RESET}"
|
|
25
|
+
f"{m.group(5)}{CYAN}{m.group(6)}{RESET}\n"
|
|
26
|
+
)
|
|
27
|
+
else:
|
|
28
|
+
lines.append(line)
|
|
29
|
+
elif ":" in line and not line.startswith(" "):
|
|
30
|
+
parts = line.split(":", 1)
|
|
31
|
+
lines.append(f"{BOLD}{RED}{parts[0]}:{RESET}{BOLD}{parts[1]}{RESET}")
|
|
32
|
+
else:
|
|
33
|
+
line_hl = re.sub(r'("[^"]*"|\'[^\']*\')', rf'{GREEN}\1{RESET}', line)
|
|
34
|
+
kw = r'\b(def|class|return|if|elif|else|while|for|in|import|from|as|try|except|finally|raise|with|pass|break|continue|and|or|not|is|None|True|False)\b'
|
|
35
|
+
line_hl = re.sub(kw, rf'{MAGENTA}\1{RESET}', line_hl)
|
|
36
|
+
lines.append(line_hl)
|
|
37
|
+
return "".join(lines)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _stream(stream=None):
|
|
41
|
+
if stream is not None:
|
|
42
|
+
return stream
|
|
43
|
+
for candidate in (sys.stderr, sys.stdout, sys.__stderr__, sys.__stdout__):
|
|
44
|
+
if candidate is not None:
|
|
45
|
+
return candidate
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _is_tty(stream):
|
|
50
|
+
try:
|
|
51
|
+
return bool(stream and stream.isatty())
|
|
52
|
+
except Exception:
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def owns_console():
|
|
57
|
+
if sys.platform != 'win32':
|
|
58
|
+
return False
|
|
59
|
+
try:
|
|
60
|
+
import ctypes
|
|
61
|
+
pids = (ctypes.c_uint * 4)()
|
|
62
|
+
count = ctypes.windll.kernel32.GetConsoleProcessList(pids, 4)
|
|
63
|
+
return 0 < count <= 2
|
|
64
|
+
except Exception:
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def should_hold(hold=None):
|
|
69
|
+
if hold is None:
|
|
70
|
+
hold = os.environ.get(HOLD_ENV, 'auto')
|
|
71
|
+
if isinstance(hold, str):
|
|
72
|
+
mode = hold.strip().lower()
|
|
73
|
+
if mode in ('1', 'true', 'yes', 'on', 'always'):
|
|
74
|
+
return True
|
|
75
|
+
if mode in ('0', 'false', 'no', 'off', 'never'):
|
|
76
|
+
return False
|
|
77
|
+
return owns_console()
|
|
78
|
+
return bool(hold)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def hold_console(prompt=HOLD_PROMPT, stream=None):
|
|
82
|
+
stream = _stream(stream)
|
|
83
|
+
try:
|
|
84
|
+
if stream is not None and prompt:
|
|
85
|
+
stream.write(prompt)
|
|
86
|
+
stream.flush()
|
|
87
|
+
input()
|
|
88
|
+
except (Exception, KeyboardInterrupt):
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def format_error(exc=None, color=None, skip=0):
|
|
93
|
+
if exc is None:
|
|
94
|
+
exc = sys.exc_info()[1]
|
|
95
|
+
if exc is None:
|
|
96
|
+
return ''
|
|
97
|
+
tb = exc.__traceback__
|
|
98
|
+
for _ in range(skip):
|
|
99
|
+
if tb is not None and tb.tb_next is not None:
|
|
100
|
+
tb = tb.tb_next
|
|
101
|
+
text = ''.join(traceback.format_exception(type(exc), exc, tb))
|
|
102
|
+
if color is None:
|
|
103
|
+
color = ansi.color_enabled()
|
|
104
|
+
return highlight_traceback(text) if color else text
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def report(exc=None, clear=True, stream=None, out=None, flush=None, skip=0):
|
|
108
|
+
stream = _stream(stream)
|
|
109
|
+
if out is None:
|
|
110
|
+
if stream is None:
|
|
111
|
+
return False
|
|
112
|
+
out, flush = stream.write, stream.flush
|
|
113
|
+
tty = _is_tty(stream)
|
|
114
|
+
try:
|
|
115
|
+
if clear and tty:
|
|
116
|
+
out(ansi.wrap_all(ansi.CLEAR_ALL))
|
|
117
|
+
text = format_error(exc, color=tty and ansi.color_enabled(), skip=skip) + '\n'
|
|
118
|
+
out(ansi.wrap_all(text) if tty else text)
|
|
119
|
+
if flush:
|
|
120
|
+
flush()
|
|
121
|
+
return True
|
|
122
|
+
except Exception:
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def trace(exception, lexer=None, clear=True, out=None, flush=None):
|
|
127
|
+
if not report(exception, clear=clear, out=out, flush=flush):
|
|
128
|
+
raise exception
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def attempt(func, catch=None, lexer=None, fatal=True, clear=True, reset=True, out=None, flush=None,
|
|
132
|
+
hold=None, code=EXIT_CODE, stream=None):
|
|
133
|
+
try:
|
|
134
|
+
return func()
|
|
135
|
+
except Exception as e:
|
|
136
|
+
target = _stream(stream)
|
|
137
|
+
tty = _is_tty(target)
|
|
138
|
+
if out is None and target is not None:
|
|
139
|
+
out, flush = target.write, target.flush
|
|
140
|
+
if reset and fatal and tty and out:
|
|
141
|
+
try:
|
|
142
|
+
out(ansi.wrap(ansi.FULL_RESET))
|
|
143
|
+
except Exception:
|
|
144
|
+
pass
|
|
145
|
+
catch_exc = None
|
|
146
|
+
if callable(catch):
|
|
147
|
+
try:
|
|
148
|
+
catch()
|
|
149
|
+
except Exception as ce:
|
|
150
|
+
catch_exc = ce
|
|
151
|
+
if not report(e, clear=clear, stream=target, out=out, flush=flush, skip=1):
|
|
152
|
+
raise
|
|
153
|
+
if catch_exc is not None:
|
|
154
|
+
report(catch_exc, clear=False, stream=target, out=out, flush=flush)
|
|
155
|
+
if not fatal:
|
|
156
|
+
return None
|
|
157
|
+
if should_hold(hold):
|
|
158
|
+
hold_console(stream=target)
|
|
159
|
+
sys.exit(code)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def install(hold=None, code=EXIT_CODE, stream=None):
|
|
163
|
+
previous = sys.excepthook
|
|
164
|
+
|
|
165
|
+
def hook(exc_type, exc, tb):
|
|
166
|
+
if issubclass(exc_type, KeyboardInterrupt):
|
|
167
|
+
return previous(exc_type, exc, tb)
|
|
168
|
+
target = _stream(stream)
|
|
169
|
+
text = ''.join(traceback.format_exception(exc_type, exc, tb))
|
|
170
|
+
tty = _is_tty(target)
|
|
171
|
+
try:
|
|
172
|
+
if target is not None:
|
|
173
|
+
target.write(highlight_traceback(text) if tty and ansi.color_enabled() else text)
|
|
174
|
+
target.flush()
|
|
175
|
+
except Exception:
|
|
176
|
+
return previous(exc_type, exc, tb)
|
|
177
|
+
if should_hold(hold):
|
|
178
|
+
hold_console(stream=target)
|
|
179
|
+
|
|
180
|
+
sys.excepthook = hook
|
|
181
|
+
return previous
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
PyFiglet: An implementation of figlet written in Python
|
|
2
|
+
|
|
3
|
+
The MIT License (MIT)
|
|
4
|
+
|
|
5
|
+
Copyright © 2007-2018
|
|
6
|
+
Christopher Jones <cjones@insub.org>
|
|
7
|
+
Stefano Rivera <stefano@rivera.za.net>
|
|
8
|
+
Peter Waller <p@pwaller.net>
|
|
9
|
+
And various contributors (see git history).
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
12
|
+
this software and associated documentation files (the “Software”), to deal in
|
|
13
|
+
the Software without restriction, including without limitation the rights to
|
|
14
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
15
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
|
16
|
+
so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
27
|
+
DEALINGS IN THE SOFTWARE.
|
ttyengine/figlet.py
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import io, os, re, zipfile
|
|
2
|
+
|
|
3
|
+
DEFAULT_FONT = 'standard'
|
|
4
|
+
|
|
5
|
+
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
6
|
+
_BUNDLE = os.path.join(_HERE, 'fonts.zip')
|
|
7
|
+
_LOCATIONS = ('./', os.path.join(_HERE, 'fonts'))
|
|
8
|
+
_MAGIC = re.compile(r'^[tf]lf2.')
|
|
9
|
+
_END = re.compile(r'(.)\s*$')
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FigletError(Exception):
|
|
13
|
+
def __init__(self, error):
|
|
14
|
+
self.error = error
|
|
15
|
+
|
|
16
|
+
def __str__(self):
|
|
17
|
+
return str(self.error)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CharNotPrinted(FigletError):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class FontNotFound(FigletError):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class FontError(FigletError):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _bundled(filename):
|
|
33
|
+
try:
|
|
34
|
+
with zipfile.ZipFile(_BUNDLE) as bundle:
|
|
35
|
+
return bundle.read(filename)
|
|
36
|
+
except (KeyError, OSError, zipfile.BadZipFile):
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _locate(font):
|
|
41
|
+
for extension in ('tlf', 'flf'):
|
|
42
|
+
filename = '%s.%s' % (font, extension)
|
|
43
|
+
data = _bundled(filename)
|
|
44
|
+
if data is not None:
|
|
45
|
+
return data
|
|
46
|
+
for location in _LOCATIONS:
|
|
47
|
+
path = os.path.join(location, filename)
|
|
48
|
+
if os.path.isfile(path):
|
|
49
|
+
with open(path, 'rb') as handle:
|
|
50
|
+
return handle.read()
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _unpack(data):
|
|
55
|
+
if data and zipfile.is_zipfile(io.BytesIO(data)):
|
|
56
|
+
with zipfile.ZipFile(io.BytesIO(data)) as inner:
|
|
57
|
+
return inner.read(inner.namelist()[0])
|
|
58
|
+
return data
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _valid(data):
|
|
62
|
+
header = _unpack(data).split(b'\n', 1)[0].decode('utf-8', 'replace')
|
|
63
|
+
return bool(_MAGIC.search(header))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def available():
|
|
67
|
+
found = set()
|
|
68
|
+
try:
|
|
69
|
+
with zipfile.ZipFile(_BUNDLE) as bundle:
|
|
70
|
+
for name in bundle.namelist():
|
|
71
|
+
if name.endswith(('.flf', '.tlf')) and _valid(bundle.read(name)):
|
|
72
|
+
found.add(name.split('.', 2)[0])
|
|
73
|
+
except (OSError, zipfile.BadZipFile):
|
|
74
|
+
pass
|
|
75
|
+
for location in _LOCATIONS:
|
|
76
|
+
if os.path.isdir(location):
|
|
77
|
+
for name in os.listdir(location):
|
|
78
|
+
path = os.path.join(location, name)
|
|
79
|
+
if name.endswith(('.flf', '.tlf')) and os.path.isfile(path):
|
|
80
|
+
with open(path, 'rb') as handle:
|
|
81
|
+
if _valid(handle.read()):
|
|
82
|
+
found.add(name.split('.', 2)[0])
|
|
83
|
+
return sorted(found)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class FigletFont:
|
|
87
|
+
def __init__(self, font=DEFAULT_FONT):
|
|
88
|
+
self.font = font
|
|
89
|
+
self.comment = ''
|
|
90
|
+
self.chars = {}
|
|
91
|
+
self.width = {}
|
|
92
|
+
data = _unpack(_locate(font))
|
|
93
|
+
if not data:
|
|
94
|
+
raise FontNotFound(font)
|
|
95
|
+
self.data = data.decode('utf-8', 'replace')
|
|
96
|
+
self.loadFont()
|
|
97
|
+
|
|
98
|
+
def loadFont(self):
|
|
99
|
+
try:
|
|
100
|
+
data = re.sub(r'[\u0085
]', ' ', self.data)
|
|
101
|
+
data = data.splitlines()
|
|
102
|
+
header = data.pop(0)
|
|
103
|
+
if _MAGIC.search(header) is None:
|
|
104
|
+
raise FontError('%s is not a valid figlet font' % self.font)
|
|
105
|
+
header = _MAGIC.sub('', header).split()
|
|
106
|
+
if len(header) < 6:
|
|
107
|
+
raise FontError('malformed header for %s' % self.font)
|
|
108
|
+
hardBlank = header[0]
|
|
109
|
+
height, baseLine, maxLength, oldLayout, commentLines = map(int, header[1:6])
|
|
110
|
+
printDirection = fullLayout = None
|
|
111
|
+
if len(header) > 6:
|
|
112
|
+
printDirection = int(header[6])
|
|
113
|
+
if len(header) > 7:
|
|
114
|
+
fullLayout = int(header[7])
|
|
115
|
+
if fullLayout is None:
|
|
116
|
+
if oldLayout == 0:
|
|
117
|
+
fullLayout = 64
|
|
118
|
+
elif oldLayout < 0:
|
|
119
|
+
fullLayout = 0
|
|
120
|
+
else:
|
|
121
|
+
fullLayout = (oldLayout & 31) | 128
|
|
122
|
+
self.height = height
|
|
123
|
+
self.hardBlank = hardBlank
|
|
124
|
+
self.printDirection = printDirection
|
|
125
|
+
self.smushMode = fullLayout
|
|
126
|
+
for _ in range(commentLines):
|
|
127
|
+
self.comment += data.pop(0)
|
|
128
|
+
|
|
129
|
+
def character(data):
|
|
130
|
+
end = None
|
|
131
|
+
width = 0
|
|
132
|
+
rows = []
|
|
133
|
+
for _ in range(height):
|
|
134
|
+
line = data.pop(0)
|
|
135
|
+
if end is None:
|
|
136
|
+
end = _END.search(line).group(1)
|
|
137
|
+
end = re.compile(re.escape(end) + r'{1,2}\s*$')
|
|
138
|
+
line = end.sub('', line)
|
|
139
|
+
if len(line) > width:
|
|
140
|
+
width = len(line)
|
|
141
|
+
rows.append(line)
|
|
142
|
+
return width, rows
|
|
143
|
+
|
|
144
|
+
for i in range(32, 127):
|
|
145
|
+
width, letter = character(data)
|
|
146
|
+
if i == 32 or ''.join(letter) != '':
|
|
147
|
+
self.chars[i] = letter
|
|
148
|
+
self.width[i] = width
|
|
149
|
+
if data:
|
|
150
|
+
for i in 'ÄÖÜäöüß':
|
|
151
|
+
width, letter = character(data)
|
|
152
|
+
if ''.join(letter) != '':
|
|
153
|
+
self.chars[ord(i)] = letter
|
|
154
|
+
self.width[ord(i)] = width
|
|
155
|
+
while data:
|
|
156
|
+
line = data.pop(0).strip()
|
|
157
|
+
i = line.split(' ', 1)[0]
|
|
158
|
+
if i == '':
|
|
159
|
+
continue
|
|
160
|
+
if re.search('^0x', i, re.IGNORECASE) is not None:
|
|
161
|
+
i = int(i, 16)
|
|
162
|
+
width, letter = character(data)
|
|
163
|
+
if ''.join(letter) != '':
|
|
164
|
+
self.chars[i] = letter
|
|
165
|
+
self.width[i] = width
|
|
166
|
+
except Exception as e:
|
|
167
|
+
raise FontError('problem parsing %s font: %s' % (self.font, e))
|
|
168
|
+
|
|
169
|
+
def __str__(self):
|
|
170
|
+
return '<FigletFont object: %s>' % self.font
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
SM_EQUAL = 1
|
|
174
|
+
SM_LOWLINE = 2
|
|
175
|
+
SM_HIERARCHY = 4
|
|
176
|
+
SM_PAIR = 8
|
|
177
|
+
SM_BIGX = 16
|
|
178
|
+
SM_HARDBLANK = 32
|
|
179
|
+
SM_KERN = 64
|
|
180
|
+
SM_SMUSH = 128
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class _Builder:
|
|
184
|
+
def __init__(self, text, font, direction, width, justify):
|
|
185
|
+
self.text = [ord(c) for c in text]
|
|
186
|
+
self.direction = direction
|
|
187
|
+
self.width = width
|
|
188
|
+
self.font = font
|
|
189
|
+
self.justify = justify
|
|
190
|
+
self.iterator = 0
|
|
191
|
+
self.maxSmush = 0
|
|
192
|
+
self.curCharWidth = 0
|
|
193
|
+
self.prevCharWidth = 0
|
|
194
|
+
self.currentTotalWidth = 0
|
|
195
|
+
self.blankMarkers = []
|
|
196
|
+
self.queue = []
|
|
197
|
+
self.buffer = ['' for _ in range(font.height)]
|
|
198
|
+
|
|
199
|
+
def run(self):
|
|
200
|
+
while self.iterator < len(self.text):
|
|
201
|
+
self.addCharToProduct()
|
|
202
|
+
self.iterator += 1
|
|
203
|
+
if self.buffer[0] != '':
|
|
204
|
+
self.queue.append(self.buffer)
|
|
205
|
+
out = ''
|
|
206
|
+
for buffer in self.queue:
|
|
207
|
+
buffer = self.justifyString(buffer)
|
|
208
|
+
out += self.replaceHardblanks(buffer)
|
|
209
|
+
return out
|
|
210
|
+
|
|
211
|
+
def getCharAt(self, i):
|
|
212
|
+
if i < 0 or i >= len(self.text):
|
|
213
|
+
return None
|
|
214
|
+
c = self.text[i]
|
|
215
|
+
if c not in self.font.chars:
|
|
216
|
+
return None
|
|
217
|
+
return self.font.chars[c]
|
|
218
|
+
|
|
219
|
+
def getCurChar(self):
|
|
220
|
+
return self.getCharAt(self.iterator)
|
|
221
|
+
|
|
222
|
+
def getCurWidth(self):
|
|
223
|
+
i = self.iterator
|
|
224
|
+
if i < 0 or i >= len(self.text):
|
|
225
|
+
return None
|
|
226
|
+
c = self.text[i]
|
|
227
|
+
if c not in self.font.chars:
|
|
228
|
+
return None
|
|
229
|
+
return self.font.width[c]
|
|
230
|
+
|
|
231
|
+
def addCharToProduct(self):
|
|
232
|
+
curChar = self.getCurChar()
|
|
233
|
+
if self.text[self.iterator] == ord('\n'):
|
|
234
|
+
self.blankMarkers.append(([row for row in self.buffer], self.iterator))
|
|
235
|
+
self.handleNewLine()
|
|
236
|
+
return
|
|
237
|
+
if curChar is None:
|
|
238
|
+
return
|
|
239
|
+
if self.width < self.getCurWidth():
|
|
240
|
+
raise CharNotPrinted('Width is not enough to print this character')
|
|
241
|
+
self.curCharWidth = self.getCurWidth()
|
|
242
|
+
self.maxSmush = self.smushAmount(self.buffer, curChar)
|
|
243
|
+
self.currentTotalWidth = len(self.buffer[0]) + self.curCharWidth - self.maxSmush
|
|
244
|
+
if self.text[self.iterator] == ord(' '):
|
|
245
|
+
self.blankMarkers.append(([row for row in self.buffer], self.iterator))
|
|
246
|
+
if self.currentTotalWidth >= self.width and self.buffer[0]:
|
|
247
|
+
self.handleNewLine()
|
|
248
|
+
else:
|
|
249
|
+
for row in range(self.font.height):
|
|
250
|
+
self.addCurCharRowToBufferRow(curChar, row)
|
|
251
|
+
self.prevCharWidth = self.curCharWidth
|
|
252
|
+
|
|
253
|
+
def getLeftSmushedChar(self, i, addLeft):
|
|
254
|
+
idx = len(addLeft) - self.maxSmush + i
|
|
255
|
+
if idx >= 0 and idx < len(addLeft):
|
|
256
|
+
left = addLeft[idx]
|
|
257
|
+
else:
|
|
258
|
+
left = ''
|
|
259
|
+
return left, idx
|
|
260
|
+
|
|
261
|
+
def smushRow(self, curChar, row):
|
|
262
|
+
addLeft = self.buffer[row]
|
|
263
|
+
addRight = curChar[row]
|
|
264
|
+
if self.direction == 'right-to-left':
|
|
265
|
+
addLeft, addRight = addRight, addLeft
|
|
266
|
+
for i in range(self.maxSmush):
|
|
267
|
+
left, idx = self.getLeftSmushedChar(i, addLeft)
|
|
268
|
+
right = addRight[i] if i < len(addRight) else ''
|
|
269
|
+
smushed = self.smushChars(left=left, right=right)
|
|
270
|
+
characters = list(addLeft)
|
|
271
|
+
if 0 <= idx <= len(characters):
|
|
272
|
+
characters[idx] = smushed
|
|
273
|
+
addLeft = ''.join(characters)
|
|
274
|
+
return addLeft, addRight
|
|
275
|
+
|
|
276
|
+
def addCurCharRowToBufferRow(self, curChar, row):
|
|
277
|
+
addLeft, addRight = self.smushRow(curChar, row)
|
|
278
|
+
self.buffer[row] = addLeft + addRight[self.maxSmush:]
|
|
279
|
+
|
|
280
|
+
def cutBufferCommon(self):
|
|
281
|
+
self.currentTotalWidth = 0
|
|
282
|
+
self.buffer = ['' for _ in range(self.font.height)]
|
|
283
|
+
self.blankMarkers = []
|
|
284
|
+
self.prevCharWidth = 0
|
|
285
|
+
curChar = self.getCurChar()
|
|
286
|
+
if curChar is None:
|
|
287
|
+
return
|
|
288
|
+
self.maxSmush = self.smushAmount(self.buffer, curChar)
|
|
289
|
+
|
|
290
|
+
def handleNewLine(self):
|
|
291
|
+
if self.blankMarkers:
|
|
292
|
+
saved_buffer, saved_iterator = self.blankMarkers.pop()
|
|
293
|
+
self.queue.append(saved_buffer)
|
|
294
|
+
self.iterator = saved_iterator
|
|
295
|
+
else:
|
|
296
|
+
self.queue.append(self.buffer)
|
|
297
|
+
self.iterator -= 1
|
|
298
|
+
self.cutBufferCommon()
|
|
299
|
+
|
|
300
|
+
def justifyString(self, buffer):
|
|
301
|
+
if self.justify == 'right':
|
|
302
|
+
for row in range(self.font.height):
|
|
303
|
+
buffer[row] = (' ' * (self.width - len(buffer[row]) - 1)) + buffer[row]
|
|
304
|
+
elif self.justify == 'center':
|
|
305
|
+
for row in range(self.font.height):
|
|
306
|
+
buffer[row] = (' ' * int((self.width - len(buffer[row])) / 2)) + buffer[row]
|
|
307
|
+
return buffer
|
|
308
|
+
|
|
309
|
+
def replaceHardblanks(self, buffer):
|
|
310
|
+
return ('\n'.join(buffer) + '\n').replace(self.font.hardBlank, ' ')
|
|
311
|
+
|
|
312
|
+
def smushAmount(self, buffer, curChar):
|
|
313
|
+
if (self.font.smushMode & (SM_SMUSH | SM_KERN)) == 0:
|
|
314
|
+
return 0
|
|
315
|
+
maxSmush = self.curCharWidth
|
|
316
|
+
for row in range(self.font.height):
|
|
317
|
+
lineLeft = buffer[row]
|
|
318
|
+
lineRight = curChar[row]
|
|
319
|
+
if self.direction == 'right-to-left':
|
|
320
|
+
lineLeft, lineRight = lineRight, lineLeft
|
|
321
|
+
linebd = len(lineLeft.rstrip(' ')) - 1
|
|
322
|
+
if linebd < 0:
|
|
323
|
+
linebd = 0
|
|
324
|
+
if linebd < len(lineLeft):
|
|
325
|
+
ch1 = lineLeft[linebd]
|
|
326
|
+
else:
|
|
327
|
+
linebd = 0
|
|
328
|
+
ch1 = ''
|
|
329
|
+
charbd = len(lineRight) - len(lineRight.lstrip(' '))
|
|
330
|
+
if charbd < len(lineRight):
|
|
331
|
+
ch2 = lineRight[charbd]
|
|
332
|
+
else:
|
|
333
|
+
charbd = len(lineRight)
|
|
334
|
+
ch2 = ''
|
|
335
|
+
amt = charbd + len(lineLeft) - 1 - linebd
|
|
336
|
+
if ch1 == '' or ch1 == ' ':
|
|
337
|
+
amt += 1
|
|
338
|
+
elif ch2 != '' and self.smushChars(left=ch1, right=ch2) is not None:
|
|
339
|
+
amt += 1
|
|
340
|
+
if amt < maxSmush:
|
|
341
|
+
maxSmush = amt
|
|
342
|
+
return maxSmush
|
|
343
|
+
|
|
344
|
+
def smushChars(self, left='', right=''):
|
|
345
|
+
if left == ' ':
|
|
346
|
+
return right
|
|
347
|
+
if right == ' ':
|
|
348
|
+
return left
|
|
349
|
+
if (self.prevCharWidth < 2) or (self.curCharWidth < 2):
|
|
350
|
+
return
|
|
351
|
+
mode = self.font.smushMode
|
|
352
|
+
if (mode & SM_SMUSH) == 0:
|
|
353
|
+
return
|
|
354
|
+
if (mode & 63) == 0:
|
|
355
|
+
if left == self.font.hardBlank:
|
|
356
|
+
return right
|
|
357
|
+
if right == self.font.hardBlank:
|
|
358
|
+
return left
|
|
359
|
+
if self.direction == 'right-to-left':
|
|
360
|
+
return left
|
|
361
|
+
return right
|
|
362
|
+
if mode & SM_HARDBLANK:
|
|
363
|
+
if left == self.font.hardBlank and right == self.font.hardBlank:
|
|
364
|
+
return left
|
|
365
|
+
if left == self.font.hardBlank or right == self.font.hardBlank:
|
|
366
|
+
return
|
|
367
|
+
if mode & SM_EQUAL:
|
|
368
|
+
if left == right:
|
|
369
|
+
return left
|
|
370
|
+
smushes = ()
|
|
371
|
+
if mode & SM_LOWLINE:
|
|
372
|
+
smushes += (('_', r'|/\[]{}()<>'),)
|
|
373
|
+
if mode & SM_HIERARCHY:
|
|
374
|
+
smushes += (
|
|
375
|
+
('|', r'/\[]{}()<>'),
|
|
376
|
+
(r'\/', '[]{}()<>'),
|
|
377
|
+
('[]', '{}()<>'),
|
|
378
|
+
('{}', '()<>'),
|
|
379
|
+
('()', '<>'),
|
|
380
|
+
)
|
|
381
|
+
for a, b in smushes:
|
|
382
|
+
if left in a and right in b:
|
|
383
|
+
return right
|
|
384
|
+
if right in a and left in b:
|
|
385
|
+
return left
|
|
386
|
+
if mode & SM_PAIR:
|
|
387
|
+
for pair in [left + right, right + left]:
|
|
388
|
+
if pair in ['[]', '{}', '()']:
|
|
389
|
+
return '|'
|
|
390
|
+
if mode & SM_BIGX:
|
|
391
|
+
if left == '/' and right == '\\':
|
|
392
|
+
return '|'
|
|
393
|
+
if right == '/' and left == '\\':
|
|
394
|
+
return 'Y'
|
|
395
|
+
if left == '>' and right == '<':
|
|
396
|
+
return 'X'
|
|
397
|
+
return
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
class Figlet:
|
|
401
|
+
def __init__(self, font=DEFAULT_FONT, direction='auto', justify='auto', width=80):
|
|
402
|
+
self.font = font
|
|
403
|
+
self._direction = direction
|
|
404
|
+
self._justify = justify
|
|
405
|
+
self.width = width
|
|
406
|
+
self.Font = FigletFont(font)
|
|
407
|
+
|
|
408
|
+
@property
|
|
409
|
+
def direction(self):
|
|
410
|
+
if self._direction == 'auto':
|
|
411
|
+
if self.Font.printDirection == 1:
|
|
412
|
+
return 'right-to-left'
|
|
413
|
+
return 'left-to-right'
|
|
414
|
+
return self._direction
|
|
415
|
+
|
|
416
|
+
@property
|
|
417
|
+
def justify(self):
|
|
418
|
+
if self._justify == 'auto':
|
|
419
|
+
return 'left' if self.direction == 'left-to-right' else 'right'
|
|
420
|
+
return self._justify
|
|
421
|
+
|
|
422
|
+
def renderText(self, text):
|
|
423
|
+
return _Builder(text, self.Font, self.direction, self.width, self.justify).run()
|
|
424
|
+
|
|
425
|
+
def getFonts(self):
|
|
426
|
+
return available()
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def figlet_format(text, font=DEFAULT_FONT, **kwargs):
|
|
430
|
+
return Figlet(font, **kwargs).renderText(text)
|
ttyengine/fonts.zip
ADDED
|
Binary file
|