Shofel2_T124_python/venv/lib/python3.10/site-packages/urwid/font.py

691 lines
31 KiB
Python
Raw Normal View History

2024-05-25 16:45:07 +00:00
#!/usr/bin/python
#
# Urwid BigText fonts
# Copyright (C) 2004-2006 Ian Ward
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Urwid web site: https://urwid.org/
from __future__ import annotations
import typing
import warnings
from pprint import pformat
from urwid.canvas import CanvasError, TextCanvas
from urwid.escape import SAFE_ASCII_DEC_SPECIAL_RE
from urwid.util import apply_target_encoding, str_util
if typing.TYPE_CHECKING:
from collections.abc import Iterator, Sequence
from typing_extensions import Literal
def separate_glyphs(gdata: str, height: int) -> tuple[dict[str, tuple[int, list[str]]], bool]:
"""return (dictionary of glyphs, utf8 required)"""
gl: list[str] = gdata.split("\n")[1:-1]
if any("\t" in elem for elem in gl):
raise ValueError(f"Incorrect glyphs data:\n{gdata!r}")
if len(gl) != height + 1:
raise ValueError(f"Incorrect glyphs height (expected: {height}):\n{gdata}")
key_line: str = gl[0]
character: str | None = None # current character
key_index = 0 # index into character key line
end_col = 0 # column position at end of glyph
start_col = 0 # column position at start of glyph
jl: list[int] = [0] * height # indexes into lines of gdata (gl)
result: dict[str, tuple[int, list[str]]] = {}
utf8_required = False
while True:
if character is None:
if key_index >= len(key_line):
break
character = key_line[key_index]
if key_index < len(key_line) and key_line[key_index] == character:
end_col += str_util.get_width(ord(character))
key_index += 1
continue
out: list[str] = []
y = 0
fill = 0
for k, line in enumerate(gl[1:]):
j: int = jl[k]
y = 0
fill = 0
while y < end_col - start_col:
if j >= len(line):
fill = end_col - start_col - y
break
y += str_util.get_width(ord(line[j]))
j += 1
if y + fill != end_col - start_col:
raise ValueError(repr((y, fill, end_col)))
segment = line[jl[k] : j]
if not SAFE_ASCII_DEC_SPECIAL_RE.match(segment):
utf8_required = True
out.append(segment + " " * fill)
jl[k] = j
start_col = end_col
result[character] = (y + fill, out)
character = None
return result, utf8_required
def add_font(name: str, cls: FontRegistry) -> None:
warnings.warn(
"`add_font` is deprecated, please set 'name' attribute to the font class,"
" use metaclass keyword argument 'font_name'"
" or use `Font.register(<name>)`",
DeprecationWarning,
stacklevel=2,
)
cls.register(name)
class FontRegistryWarning(UserWarning):
"""FontRegistry warning."""
class FontRegistry(type):
"""Font registry.
Store all registered fonts, register during class creation if possible.
"""
__slots__ = ()
__registered: typing.ClassVar[dict[str, FontRegistry]] = {}
def __iter__(self) -> Iterator[str]:
"""Iterate over registered font names."""
return iter(self.__registered)
def __getitem__(self, item: str) -> FontRegistry | None:
"""Get font by name if registered."""
return self.__registered.get(item)
def __class_getitem__(cls, item: str) -> FontRegistry | None:
"""Get font by name if registered.
This method is needed to get access to font from registry class.
>>> repr(FontRegistry["a"])
'None'
>>> font = FontRegistry["Thin 3x3"]()
>>> font.height
3
>>> canvas: TextCanvas = font.render("+")
>>> b'\\n'.join(canvas.text).decode('utf-8') == " \\n ┼\\n "
True
"""
return cls.__registered.get(item)
@property
def registered(cls) -> Sequence[str]:
"""Registered font names in alphabetical order."""
return tuple(sorted(cls.__registered))
@classmethod
def as_list(mcs) -> list[tuple[str, FontRegistry]]:
"""List of (font name, font class) tuples."""
return list(mcs.__registered.items())
def __new__( # noqa: PYI034 # new can not return Self
cls: type[FontRegistry],
name: str,
bases: tuple[type, ...],
namespace: dict[str, typing.Any],
**kwds: typing.Any,
) -> FontRegistry:
font_name: str = namespace.setdefault("name", kwds.get("font_name", ""))
font_class = super().__new__(cls, name, bases, namespace)
if font_name:
if font_name not in cls.__registered:
cls.__registered[font_name] = font_class
if cls.__registered[font_name] != font_class:
warnings.warn(
f"{font_name!r} is already registered, please override explicit if required or change name",
FontRegistryWarning,
stacklevel=2,
)
return font_class
def register(cls, font_name: str) -> None:
"""Register font explicit.
:param font_name: Font name to use in registration.
"""
if not font_name:
raise ValueError('"font_name" is not set.')
cls.__registered[font_name] = cls
get_all_fonts = FontRegistry.as_list
class Font(metaclass=FontRegistry):
"""Font base class."""
__slots__ = ("char", "canvas", "utf8_required")
height: int
data: Sequence[str]
name: str
def __init__(self) -> None:
if not self.height:
raise ValueError(f'"height" is invalid: {self.height!r}')
if not self.data:
raise ValueError(f'"data" is empty: {self.data!r}')
self.char: dict[str, tuple[int, list[str]]] = {}
self.canvas: dict[str, TextCanvas] = {}
self.utf8_required = False
data: list[str] = [self._to_text(block) for block in self.data]
for gdata in data:
self.add_glyphs(gdata)
def __repr__(self) -> str:
return f"{self.__class__.__name__}()"
def __str__(self) -> str:
"""Font description."""
return f"{self.__class__.__name__}():\n {self.height!r}\n {pformat(self.data, indent=4)}"
@staticmethod
def _to_text(
obj: str | bytes,
encoding: str = "utf-8",
errors: Literal["strict", "ignore", "replace"] = "strict",
) -> str:
if isinstance(obj, str):
return obj
if isinstance(obj, bytes):
warnings.warn(
"Bytes based fonts are deprecated, please switch to the text one",
DeprecationWarning,
stacklevel=3,
)
return obj.decode(encoding, errors)
raise TypeError(f"{obj!r} is not str|bytes")
def add_glyphs(self, gdata: str) -> None:
d, utf8_required = separate_glyphs(gdata, self.height)
self.char.update(d)
self.utf8_required |= utf8_required
def characters(self) -> str:
return "".join(sorted(self.char))
def char_width(self, character: str) -> int:
if character in self.char:
return self.char[character][0]
return 0
def char_data(self, character: str) -> list[str]:
return self.char[character][1]
def render(self, character: str) -> TextCanvas:
if character in self.canvas:
return self.canvas[character]
width, line = self.char[character]
byte_lines = []
character_set_lines = []
for d in line:
t, cs = apply_target_encoding(d)
byte_lines.append(t)
character_set_lines.append(cs)
try:
canv = TextCanvas(byte_lines, None, character_set_lines, maxcol=width, check_width=False)
except CanvasError as exc:
raise CanvasError(f"Failed render of {character!r} from line {line!r}:\n{self}\n:{exc}").with_traceback(
exc.__traceback__
) from exc
self.canvas[character] = canv
return canv
# safe_palette = u"┘┐┌└┼─├┤┴┬│"
# more_palette = u"═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟╠╡╢╣╤╥╦╧╨╩╪╫╬○"
# block_palette = u"▄#█#▀#▌#▐#▖#▗#▘#▙#▚#▛#▜#▝#▞#▟"
class Thin3x3Font(Font):
name = "Thin 3x3"
height = 3
data = (
"""
000111222333444555666777888999 !
.
""",
r"""
"###$$$%%%'*++,--.///:;==???[[\\\]]^__`
" ┼┼┌┼┐O /' /.. _┌─┐┌ \ ┐^ `
/ * / ., _ \
/ O , ./ . \
""",
)
class Thin4x3Font(Font):
name = "Thin 4x3"
height = 3
data = (
*Thin3x3Font.data,
"""
0000111122223333444455556666777788889999 ####$$$$
""",
)
class Sextant3x3Font(Font):
name = "Sextant 3x3"
height = 3
data = (
"""
!!!###$$$%%%&&&'''((()))***+++,,,---...///
🬞🬲🬲🬞🬍🬋🬉🬄🬖🬦🬧 🬉 🬞🬅 🬁🬢 🬞🬦🬞 🬦 🬖
🬉 🬇🬛🬛🬞🬰🬗🬞🬅🬭🬦🬈🬖 🬉🬏 🬘 🬇🬨🬈🬁🬨🬂 🬭 🬁🬂🬂 🬭 🬞🬅
🬁 🬀🬀 🬁 🬂 🬂🬁 🬁 🬁 🬅 🬂
""",
"""
000111222333444555666777888999
🬦🬂🬧🬞🬫 🬇🬂🬧🬁🬂🬧 🬞🬫🬂🬂🬞🬅🬀🬁🬂🬙🬦🬂🬧🬦🬂🬧
🬁 🬞🬅🬀 🬂🬧🬇🬌🬫🬁🬂🬧🬂🬧 🬔 🬦🬂🬧 🬂🬙
🬂🬀 🬁 🬁🬂🬂🬁🬂🬀 🬁🬁🬂🬀 🬂🬀 🬀 🬂🬀 🬂
""",
"""
\"\"\"
🬄🬄
""",
"""
:::;;;<<<===>>>???@@@
🬭 🬭 🬖🬀 🬁🬢 🬇🬂🬧🬦🬂🬧
🬰 🬰 🬁🬢 🬠🬰🬰 🬖🬀 🬇🬀🬉🬅
🬂 🬅 🬀 🬁 🬁 🬂🬀
""",
"""
AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQ
🬞🬅🬢🬂🬧🬦🬂🬈🬂🬧🬂🬂🬂🬂🬦🬂🬈 🬨🬀 🬞🬅 🬢🬫🬢🬦🬂🬧🬂🬧🬦🬂🬧
🬋🬫🬂🬧 🬞 🬂 🬂 🬨🬂🬨 🬞 🬈🬏 🬁 🬨 🬂🬀🬇🬘
🬁 🬁🬁🬂🬀 🬂🬀🬁🬂🬀🬁🬂🬂🬁 🬂🬂🬁 🬁 🬂🬀 🬂🬀🬁 🬁🬁🬂🬂🬁 🬁🬁 🬁 🬂🬀🬁 🬂🬁
""",
"""
RRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[]]]^^^___```
🬂🬧🬦🬂🬈🬁🬨🬂 🬉🬏🬘 🬁🬂🬙 🬕🬀 🬂🬞🬅🬢 🬈🬏
🬊🬐🬞🬂🬧 🬉🬏🬘🬖🬷🬞🬅🬢 🬧🬀🬞🬅
🬁 🬁 🬂🬀 🬁 🬂🬀 🬁 🬁 🬁🬁 🬁 🬁 🬁🬂🬂 🬂🬀 🬂🬀 🬂🬂🬂
""",
"""
\\\\\\
🬇🬏
🬁🬢
""",
)
class Sextant2x2Font(Font):
name = "Sextant 2x2"
height = 2
data = """
..,,%%00112233445566778899
🬁🬖🬨🬇🬁🬗🬠🬸🬦🬒🬭🬁🬙🬸🬸
🬇 🬇🬀🬁🬇🬉🬍 🬄🬉🬋🬇🬍🬁🬊🬇🬅🬉🬍 🬄🬉🬍 🬉
"""
class HalfBlock5x4Font(Font):
name = "Half Block 5x4"
height = 4
data = (
"""
00000111112222233333444445555566666777778888899999 !!
""",
'''
"""######$$$$$$%%%%%&&&&&((()))******++++++,,,-----..////::;;;
''',
r"""
<<<<<=====>>>>>?????@@@@@@[[[[\\\\]]]]^^^^____```{{{{||}}}}~~~~''```
""",
"""
AAAAABBBBBCCCCCDDDDDEEEEEFFFFFGGGGGHHHHHIIJJJJJKKKKK
""",
"""
LLLLLMMMMMMNNNNNOOOOOPPPPPQQQQQRRRRRSSSSSTTTTT
""",
"""
UUUUUVVVVVVWWWWWWXXXXXXYYYYYYZZZZZ
""",
"""
aaaaabbbbbcccccdddddeeeeeffffggggghhhhhiijjjjkkkkk
""",
"""
llmmmmmmnnnnnooooopppppqqqqqrrrrssssstttt
""",
"""
uuuuuvvvvvwwwwwwxxxxxxyyyyyzzzzz
""",
)
class HalfBlock6x5Font(Font):
name = "Half Block 6x5"
height = 5
data = """
000000111111222222333333444444555555666666777777888888999999 ..::////
"""
class HalfBlockHeavy6x5Font(Font):
name = "Half Block Heavy 6x5"
height = 5
data = """
000000111111222222333333444444555555666666777777888888999999 ..::////
"""
class Thin6x6Font(Font):
name = "Thin 6x6"
height = 6
data = (
"""
000000111111222222333333444444555555666666777777888888999999''
/
""",
r'''
!! """######$$$$$$%%%%%%&&&&&&((()))******++++++
/ / \
/ \ /
/ \ X
/ \, / \
. / \ \ /
''',
r"""
,,-----..//////::;;<<<<=====>>>>??????@@@@@@
/
/ . . / \
/ / \
/ . , \ /
, . / \ / .
""",
r"""
[[\\\\\\]]^^^____``{{||}}~~~~~~
\ /\ \
\
\
\
\
""",
"""
AAAAAABBBBBBCCCCCCDDDDDDEEEEEEFFFFFFGGGGGGHHHHHHIIJJJJJJ
""",
"""
KKKKKKLLLLLLMMMMMMNNNNNNOOOOOOPPPPPPQQQQQQRRRRRRSSSSSS
""",
"""
TTTTTTUUUUUUVVVVVVWWWWWWXXXXXXYYYYYYZZZZZZ
""",
"""
aaaaaabbbbbbccccccddddddeeeeeefffgggggghhhhhhiijjj
. .
""",
"""
kkkkkkllmmmmmmnnnnnnooooooppppppqqqqqqrrrrrssssss
""",
"""
ttttuuuuuuvvvvvvwwwwwwxxxxxxyyyyyyzzzzzz
""",
)
class HalfBlock7x7Font(Font):
name = "Half Block 7x7"
height = 7
data = (
"""
0000000111111122222223333333444444455555556666666777777788888889999999'''
""",
'''
!!! """""#######$$$$$$$%%%%%%%&&&&&&&(((())))*******++++++
''',
"""
,,,------.../////:::;;;<<<<<<<======>>>>>>>???????@@@@@@@
""",
r"""
[[[[\\\\\]]]]^^^^^^^_____```{{{{{|||}}}}}~~~~~~~```
""",
"""
AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIJJJJJJJ
""",
"""
KKKKKKKLLLLLLLMMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSS
""",
"""
TTTTTTTUUUUUUUVVVVVVVWWWWWWWWXXXXXXXYYYYYYYZZZZZZZ
""",
"""
aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffggggggghhhhhhhiiijjjj
""",
"""
kkkkkkkllllmmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrsssssss
""",
"""
tttttuuuuuuuvvvvvvvwwwwwwwwxxxxxxxyyyyyyyzzzzzzz
""",
)
if __name__ == "__main__":
all_ascii = frozenset(chr(x) for x in range(32, 127))
print("Available Fonts: (U) = UTF-8 required")
print("----------------")
for n, font_cls in get_all_fonts():
f = font_cls()
u = ""
if f.utf8_required:
u = "(U)"
print(f"{n:<20} {u:>3} ", end=" ")
chars = f.characters()
font_chars = frozenset(chars)
if font_chars == all_ascii:
print("Full ASCII")
elif font_chars & all_ascii == all_ascii:
print(f"Full ASCII + {''.join(font_chars^all_ascii)!r}")
else:
print(f"Characters: {chars!r}")