|
|
|
|
|
"""Art module.""" |
|
|
from .art_dic import * |
|
|
from .art_param import * |
|
|
import os |
|
|
import random |
|
|
|
|
|
|
|
|
class artError(Exception): |
|
|
"""Art error class.""" |
|
|
|
|
|
pass |
|
|
|
|
|
|
|
|
def font_size_splitter(font_map): |
|
|
""" |
|
|
Split fonts to 4 category (small,medium,large,xlarge) by maximum length of letter in each font. |
|
|
|
|
|
:param font_map: input fontmap |
|
|
:type font_map : dict |
|
|
:return: splitted fonts as dict |
|
|
""" |
|
|
small_font = [] |
|
|
medium_font = [] |
|
|
large_font = [] |
|
|
xlarge_font = [] |
|
|
fonts = set(font_map.keys()) - set(RANDOM_FILTERED_FONTS) |
|
|
for font in fonts: |
|
|
length = max(map(len, font_map[font][0].values())) |
|
|
if length <= FONT_SMALL_THRESHOLD: |
|
|
small_font.append(font) |
|
|
elif length > FONT_SMALL_THRESHOLD and length <= FONT_MEDIUM_THRESHOLD: |
|
|
medium_font.append(font) |
|
|
elif length > FONT_MEDIUM_THRESHOLD and length <= FONT_LARGE_THRESHOLD: |
|
|
large_font.append(font) |
|
|
else: |
|
|
xlarge_font.append(font) |
|
|
return { |
|
|
"small_list": small_font, |
|
|
"medium_list": medium_font, |
|
|
"large_list": large_font, |
|
|
"xlarge_list": xlarge_font} |
|
|
|
|
|
|
|
|
RND_SIZE_DICT = font_size_splitter(FONT_MAP) |
|
|
|
|
|
|
|
|
def line(char="*", number=30): |
|
|
""" |
|
|
Print line of chars. |
|
|
|
|
|
:param char: input character |
|
|
:type char:str |
|
|
:param number: number of characters |
|
|
:return: None |
|
|
""" |
|
|
print(char * number) |
|
|
|
|
|
|
|
|
def font_list(text="test", mode="all"): |
|
|
""" |
|
|
Print all fonts. |
|
|
|
|
|
:param text : input text |
|
|
:type text : str |
|
|
:param mode: fonts mode (all,ascii,non-ascii) |
|
|
:type mode: str |
|
|
:return: None |
|
|
""" |
|
|
fonts = set(FONT_NAMES) |
|
|
if mode.lower() == "ascii": |
|
|
fonts = fonts - set(NON_ASCII_FONTS) |
|
|
if mode.lower() == "non-ascii": |
|
|
fonts = set(NON_ASCII_FONTS) |
|
|
for item in sorted(list(fonts)): |
|
|
print(str(item) + " : ") |
|
|
text_temp = text + "\n" |
|
|
tprint(text_temp, str(item)) |
|
|
|
|
|
|
|
|
def art_list(mode="all"): |
|
|
""" |
|
|
Print all 1-Line arts. |
|
|
|
|
|
:param mode: fonts mode (all,ascii,non-ascii) |
|
|
:type mode: str |
|
|
:return: None |
|
|
""" |
|
|
arts = set(ART_NAMES) |
|
|
if mode.lower() == "ascii": |
|
|
arts = arts - set(NON_ASCII_ARTS) |
|
|
if mode.lower() == "non-ascii": |
|
|
arts = set(NON_ASCII_ARTS) |
|
|
for i in sorted(list(arts)): |
|
|
print(i) |
|
|
aprint(i) |
|
|
line() |
|
|
|
|
|
|
|
|
def decor_list(text="test", font="fancy6"): |
|
|
""" |
|
|
Print all decorations. |
|
|
|
|
|
:param text : input text |
|
|
:type text : str |
|
|
:param font: input font |
|
|
:type font:str |
|
|
:return: None |
|
|
""" |
|
|
for decor in DECORATION_NAMES: |
|
|
print(decor) |
|
|
tprint(text, font=font, decoration=decor) |
|
|
line() |
|
|
|
|
|
|
|
|
def help_func(): |
|
|
""" |
|
|
Print help page. |
|
|
|
|
|
:return: None |
|
|
""" |
|
|
tprint("art") |
|
|
tprint("v" + ART_VERSION) |
|
|
print(DESCRIPTION) |
|
|
print(CLI_HELP) |
|
|
|
|
|
|
|
|
def aprint(artname, number=1): |
|
|
""" |
|
|
Print 1-line art. |
|
|
|
|
|
:param artname: artname |
|
|
:type artname : str |
|
|
:param number: number of repeats |
|
|
:type number: int |
|
|
:return: None |
|
|
""" |
|
|
try: |
|
|
if artname == "UnicodeEncodeError": |
|
|
raise UnicodeEncodeError( |
|
|
'test', u"", 42, 43, 'test unicode-encode-error') |
|
|
print(art(artname=artname, number=number)) |
|
|
except UnicodeEncodeError: |
|
|
print(ART_ENVIRONMENT_WARNING.format(artname)) |
|
|
|
|
|
|
|
|
def art(artname, number=1): |
|
|
""" |
|
|
Return 1-line art. |
|
|
|
|
|
:param artname: artname |
|
|
:type artname : str |
|
|
:param number: number of repeats |
|
|
:type number: int |
|
|
:return: ascii art as str |
|
|
""" |
|
|
if isinstance(artname, str) is False: |
|
|
raise artError(ART_TYPE_ERROR) |
|
|
artname = artname.lower() |
|
|
arts = ART_NAMES |
|
|
if artname in ["random", "rand", "rnd"]: |
|
|
filtered_arts = list(set(arts) - set(RANDOM_FILTERED_ARTS)) |
|
|
artname = random.choice(filtered_arts) |
|
|
elif artname not in art_dic.keys(): |
|
|
distance_list = list(map(lambda x: distance_calc(artname, x), |
|
|
arts)) |
|
|
min_distance = min(distance_list) |
|
|
selected_art = arts[distance_list.index(min_distance)] |
|
|
threshold = max(len(artname), len(selected_art)) / 2 |
|
|
if min_distance < threshold: |
|
|
artname = selected_art |
|
|
else: |
|
|
raise artError(ART_NAME_ERROR) |
|
|
art_value = art_dic[artname] |
|
|
if isinstance(number, int) is False: |
|
|
raise artError(NUMBER_TYPE_ERROR) |
|
|
return (art_value + " ") * number |
|
|
|
|
|
|
|
|
def randart(): |
|
|
""" |
|
|
Return random 1-line art. |
|
|
|
|
|
:return: ascii art as str |
|
|
""" |
|
|
return art("random") |
|
|
|
|
|
|
|
|
def tprint( |
|
|
text, |
|
|
font=DEFAULT_FONT, |
|
|
chr_ignore=True, |
|
|
decoration=None, |
|
|
sep="\n", |
|
|
space=0): |
|
|
r""" |
|
|
Print art text (support \n). |
|
|
|
|
|
:param text: input text |
|
|
:type text:str |
|
|
:param font: input font |
|
|
:type font:str |
|
|
:param chr_ignore: ignore not supported character |
|
|
:type chr_ignore:bool |
|
|
:param decoration: text decoration |
|
|
:type decoration:str |
|
|
:param sep: line separator char |
|
|
:type sep: str |
|
|
:param space: space between characters |
|
|
:type space: int |
|
|
:return: None |
|
|
""" |
|
|
try: |
|
|
if font == "UnicodeEncodeError": |
|
|
raise UnicodeEncodeError( |
|
|
'test', u"", 42, 43, 'test unicode-encode-error') |
|
|
result = text2art( |
|
|
text, |
|
|
font=font, |
|
|
decoration=decoration, |
|
|
chr_ignore=chr_ignore, |
|
|
sep=sep, |
|
|
space=space) |
|
|
print(result) |
|
|
except UnicodeEncodeError: |
|
|
print(FONT_ENVIRONMENT_WARNING.format(font)) |
|
|
|
|
|
|
|
|
def tsave( |
|
|
text, |
|
|
font=DEFAULT_FONT, |
|
|
filename="art", |
|
|
chr_ignore=True, |
|
|
print_status=True, |
|
|
overwrite=False, |
|
|
decoration=None, |
|
|
sep="\n", |
|
|
space=0): |
|
|
r""" |
|
|
Save ascii art (support \n). |
|
|
|
|
|
:param text: input text |
|
|
:param font: input font |
|
|
:type font:str |
|
|
:type text:str |
|
|
:param filename: output file name |
|
|
:type filename:str |
|
|
:param chr_ignore: ignore not supported character |
|
|
:type chr_ignore:bool |
|
|
:param print_status : save message print flag |
|
|
:type print_status:bool |
|
|
:param overwrite : overwrite the saved file if true |
|
|
:type overwrite:bool |
|
|
:param decoration: text decoration |
|
|
:type decoration:str |
|
|
:param sep: line separator char |
|
|
:type sep: str |
|
|
:param space: space between characters |
|
|
:type space: int |
|
|
:return: None |
|
|
""" |
|
|
try: |
|
|
if isinstance(text, str) is False: |
|
|
raise Exception(TEXT_TYPE_ERROR) |
|
|
files_list = os.listdir(os.getcwd()) |
|
|
splitted_filename = filename.split(".") |
|
|
if len(splitted_filename) > 1: |
|
|
name = filename[:-1 * filename[::-1].find('.') - 1] |
|
|
extension = "." + splitted_filename[-1] |
|
|
else: |
|
|
name = filename |
|
|
extension = ".txt" |
|
|
index = 2 |
|
|
test_name = name |
|
|
while overwrite is False: |
|
|
if test_name + extension in files_list: |
|
|
test_name = name + str(index) |
|
|
index = index + 1 |
|
|
else: |
|
|
break |
|
|
file = open(test_name + extension, "w", encoding='utf-8') |
|
|
result = text2art( |
|
|
text, |
|
|
font=font, |
|
|
decoration=decoration, |
|
|
chr_ignore=chr_ignore, |
|
|
sep=sep, |
|
|
space=space) |
|
|
file.write(result) |
|
|
file.close() |
|
|
if print_status: |
|
|
print("Saved! \nFilename: " + test_name + extension) |
|
|
return {"Status": True, "Message": "OK"} |
|
|
except Exception as e: |
|
|
return {"Status": False, "Message": str(e)} |
|
|
|
|
|
|
|
|
def distance_calc(s1, s2): |
|
|
""" |
|
|
Calculate Levenshtein distance between two words. |
|
|
|
|
|
:param s1: first word |
|
|
:type s1 : str |
|
|
:param s2: second word |
|
|
:type s2 : str |
|
|
:return: distance between two word |
|
|
|
|
|
References : |
|
|
1- https://stackoverflow.com/questions/2460177/edit-distance-in-python |
|
|
2- https://en.wikipedia.org/wiki/Levenshtein_distance |
|
|
""" |
|
|
if len(s1) > len(s2): |
|
|
s1, s2 = s2, s1 |
|
|
|
|
|
distances = range(len(s1) + 1) |
|
|
for i2, c2 in enumerate(s2): |
|
|
distances_ = [i2 + 1] |
|
|
for i1, c1 in enumerate(s1): |
|
|
if c1 == c2: |
|
|
distances_.append(distances[i1]) |
|
|
else: |
|
|
distances_.append( |
|
|
1 + min((distances[i1], distances[i1 + 1], distances_[-1]))) |
|
|
distances = distances_ |
|
|
return distances[-1] |
|
|
|
|
|
|
|
|
def wizard_font(text): |
|
|
""" |
|
|
Check input text length for wizard mode. |
|
|
|
|
|
:param text: input text |
|
|
:type text:str |
|
|
:return: font as str |
|
|
""" |
|
|
text_length = len(text) |
|
|
if text_length <= TEXT_XLARGE_THRESHOLD: |
|
|
font = random.choice(XLARGE_WIZARD_FONT) |
|
|
elif text_length > TEXT_XLARGE_THRESHOLD and text_length <= TEXT_LARGE_THRESHOLD: |
|
|
font = random.choice(LARGE_WIZARD_FONT) |
|
|
elif text_length > TEXT_LARGE_THRESHOLD and text_length <= TEXT_MEDIUM_THRESHOLD: |
|
|
font = random.choice(MEDIUM_WIZARD_FONT) |
|
|
else: |
|
|
font = random.choice(SMALL_WIZARD_FONT) |
|
|
return font |
|
|
|
|
|
|
|
|
def indirect_font(font, text): |
|
|
""" |
|
|
Check input font for indirect modes. |
|
|
|
|
|
:param font: input font |
|
|
:type font : str |
|
|
:param text: input text |
|
|
:type text:str |
|
|
:return: font as str |
|
|
""" |
|
|
fonts = FONT_NAMES |
|
|
if font in ["rnd-small", "random-small", "rand-small"]: |
|
|
font = random.choice(RND_SIZE_DICT["small_list"]) |
|
|
return font |
|
|
if font in ["rnd-medium", "random-medium", "rand-medium"]: |
|
|
font = random.choice(RND_SIZE_DICT["medium_list"]) |
|
|
return font |
|
|
if font in ["rnd-large", "random-large", "rand-large"]: |
|
|
font = random.choice(RND_SIZE_DICT["large_list"]) |
|
|
return font |
|
|
if font in ["rnd-xlarge", "random-xlarge", "rand-xlarge"]: |
|
|
font = random.choice(RND_SIZE_DICT["xlarge_list"]) |
|
|
return font |
|
|
if font in ["random", "rand", "rnd"]: |
|
|
filtered_fonts = list(set(fonts) - set(RANDOM_FILTERED_FONTS)) |
|
|
font = random.choice(filtered_fonts) |
|
|
return font |
|
|
if font in ["wizard", "wiz", "magic"]: |
|
|
font = wizard_font(text) |
|
|
return font |
|
|
if font in ["rnd-na", "random-na", "rand-na"]: |
|
|
font = random.choice(NON_ASCII_FONTS) |
|
|
return font |
|
|
if font not in fonts: |
|
|
distance_list = list(map(lambda x: distance_calc(font, x), fonts)) |
|
|
font = fonts[distance_list.index(min(distance_list))] |
|
|
return font |
|
|
|
|
|
|
|
|
def indirect_decoration(decoration): |
|
|
""" |
|
|
Check input decoration for indirect modes. |
|
|
|
|
|
:param decoration: input decoration |
|
|
:type decoration : str |
|
|
:return: decoration as str |
|
|
""" |
|
|
decorations = DECORATION_NAMES |
|
|
if decoration in ["random", "rand", "rnd"]: |
|
|
decoration = random.choice(decorations) |
|
|
return decoration |
|
|
if decoration not in decorations: |
|
|
distance_list = list( |
|
|
map(lambda x: distance_calc(decoration, x), decorations)) |
|
|
decoration = decorations[distance_list.index(min(distance_list))] |
|
|
return decoration |
|
|
|
|
|
|
|
|
def mix_letters(): |
|
|
""" |
|
|
Return letters list in mix mode. |
|
|
|
|
|
:return: letters as list |
|
|
""" |
|
|
letters = fancy1_dic.copy() |
|
|
fonts = list(set(NON_ASCII_FONTS) - set(MIX_FILTERED_FONTS)) |
|
|
for i in letters.keys(): |
|
|
random_font = random.choice(fonts) |
|
|
letters[i] = get_font_dic(random_font)[i] |
|
|
return letters |
|
|
|
|
|
|
|
|
def __word2art(word, font, chr_ignore, letters, next_word, sep="\n"): |
|
|
""" |
|
|
Return art word. |
|
|
|
|
|
:param word: input word |
|
|
:type word: str |
|
|
:param font: input font |
|
|
:type font: str |
|
|
:param chr_ignore: ignore not supported character |
|
|
:type chr_ignore: bool |
|
|
:param letters: font letters table |
|
|
:type letters: dict |
|
|
:param next_word: next word flag |
|
|
:type next_word: bool |
|
|
:param sep: line separator char |
|
|
:type sep: str |
|
|
:return: ascii art as str |
|
|
""" |
|
|
split_list = [] |
|
|
result_list = [] |
|
|
splitter = "\n" |
|
|
if isinstance(sep, str): |
|
|
splitter = sep |
|
|
if len(word) == 0 and next_word: |
|
|
return splitter |
|
|
for i in word: |
|
|
if (ord(i) == 9) or (ord(i) == 32 and font == "block"): |
|
|
continue |
|
|
if (i not in letters.keys()): |
|
|
if (chr_ignore): |
|
|
continue |
|
|
else: |
|
|
raise artError(str(i) + " is invalid.") |
|
|
if len(letters[i]) == 0: |
|
|
continue |
|
|
split_list.append(letters[i].split("\n")) |
|
|
if font in ["mirror", "mirror_flip"]: |
|
|
split_list.reverse() |
|
|
if len(split_list) == 0: |
|
|
return "" |
|
|
for i in range(len(split_list[0])): |
|
|
temp = "" |
|
|
for item in split_list: |
|
|
temp = temp + item[i] |
|
|
result_list.append(temp) |
|
|
result = (splitter).join(result_list) |
|
|
if result[-1] != "\n" and next_word: |
|
|
result += splitter |
|
|
return result |
|
|
|
|
|
|
|
|
def text2art( |
|
|
text, |
|
|
font=DEFAULT_FONT, |
|
|
chr_ignore=True, |
|
|
decoration=None, |
|
|
sep="\n", |
|
|
space=0): |
|
|
r""" |
|
|
Return art text (support \n). |
|
|
|
|
|
:param text: input text |
|
|
:type text:str |
|
|
:param font: input font |
|
|
:type font:str |
|
|
:param chr_ignore: ignore not supported character |
|
|
:type chr_ignore:bool |
|
|
:param decoration: text decoration |
|
|
:type decoration:str |
|
|
:param sep: line separator char |
|
|
:type sep: str |
|
|
:param space: space between characters |
|
|
:type space: int |
|
|
:return: ascii art text as str |
|
|
""" |
|
|
letters = standard_dic |
|
|
if isinstance(text, str) is False: |
|
|
raise artError(TEXT_TYPE_ERROR) |
|
|
if isinstance(font, str) is False: |
|
|
raise artError(FONT_TYPE_ERROR) |
|
|
text = (' ' * space).join(text) |
|
|
text_temp = text |
|
|
font = font.lower() |
|
|
if font != "mix": |
|
|
font = indirect_font(font, text) |
|
|
letters = get_font_dic(font) |
|
|
if FONT_MAP[font][1]: |
|
|
text_temp = text.lower() |
|
|
if font in UPPERCASE_FONTS: |
|
|
text_temp = text.upper() |
|
|
else: |
|
|
letters = mix_letters() |
|
|
word_list = text_temp.split("\n") |
|
|
result = "" |
|
|
next_word_flag = True |
|
|
for index, word in enumerate(word_list): |
|
|
if index == len(word_list) - 1: |
|
|
next_word_flag = False |
|
|
result = result + __word2art(word=word, |
|
|
font=font, |
|
|
chr_ignore=chr_ignore, |
|
|
letters=letters, |
|
|
next_word=next_word_flag, |
|
|
sep=sep) |
|
|
if decoration is not None: |
|
|
[decor1, decor2] = decor(decoration, both=True) |
|
|
result = decor1 + result + decor2 |
|
|
return result |
|
|
|
|
|
|
|
|
def set_default( |
|
|
font=DEFAULT_FONT, |
|
|
chr_ignore=True, |
|
|
filename="art", |
|
|
print_status=True, |
|
|
overwrite=False, |
|
|
decoration=None, |
|
|
sep="\n", |
|
|
space=0): |
|
|
""" |
|
|
Change text2art, tprint and tsave default values. |
|
|
|
|
|
:param font: input font |
|
|
:type font:str |
|
|
:param chr_ignore: ignore not supported character |
|
|
:type chr_ignore:bool |
|
|
:param filename: output file name (only tsave) |
|
|
:type filename:str |
|
|
:param print_status : save message print flag (only tsave) |
|
|
:type print_status:bool |
|
|
:param overwrite : overwrite the saved file if true (only tsave) |
|
|
:type overwrite:bool |
|
|
:param decoration: input decoration |
|
|
:type decoration:str |
|
|
:param sep: line separator char |
|
|
:type sep: str |
|
|
:param space: space between characters |
|
|
:type space: int |
|
|
:return: None |
|
|
""" |
|
|
if isinstance(font, str) is False: |
|
|
raise artError(FONT_TYPE_ERROR) |
|
|
if isinstance(decoration, str) is False and decoration is not None: |
|
|
raise artError(DECORATION_TYPE_ERROR) |
|
|
if isinstance(chr_ignore, bool) is False: |
|
|
raise artError(CHR_IGNORE_TYPE_ERROR) |
|
|
if isinstance(filename, str) is False: |
|
|
raise artError(FILE_TYPE_ERROR) |
|
|
if isinstance(print_status, bool) is False: |
|
|
raise artError(PRINT_STATUS_TYPE_ERROR) |
|
|
if isinstance(overwrite, bool) is False: |
|
|
raise artError(OVERWRITE_TYPE_ERROR) |
|
|
if isinstance(sep, str) is False: |
|
|
raise artError(SEP_TYPE_ERROR) |
|
|
if isinstance(space, int) is False: |
|
|
raise artError(SPACE_TYPE_ERROR) |
|
|
tprint.__defaults__ = (font, chr_ignore, decoration, sep, space) |
|
|
tsave.__defaults__ = ( |
|
|
font, |
|
|
filename, |
|
|
chr_ignore, |
|
|
print_status, |
|
|
overwrite, |
|
|
decoration, |
|
|
sep, |
|
|
space) |
|
|
text2art.__defaults__ = (font, chr_ignore, decoration, sep, space) |
|
|
|
|
|
|
|
|
def get_font_dic(font_name): |
|
|
""" |
|
|
Return given font's dictionary. |
|
|
|
|
|
:param font_name: font's name |
|
|
:type font_name:str |
|
|
:return: font's dictionary |
|
|
""" |
|
|
return FONT_MAP[font_name][0] |
|
|
|
|
|
|
|
|
def decor(decoration, reverse=False, both=False): |
|
|
""" |
|
|
Return given decoration part. |
|
|
|
|
|
:param decoration: decoration's name |
|
|
:type decoration:str |
|
|
:param reverse: true if second tail of decoration wanted |
|
|
:type reverse:bool |
|
|
:param both: both tails returning flag |
|
|
:type bool: bool |
|
|
:return: decor's tail as str or tails as list of str |
|
|
""" |
|
|
if isinstance(decoration, str) is False: |
|
|
raise artError(DECORATION_TYPE_ERROR) |
|
|
decoration = decoration.lower() |
|
|
decoration = indirect_decoration(decoration) |
|
|
if both is True: |
|
|
return DECORATIONS_MAP[decoration] |
|
|
if reverse is True: |
|
|
return DECORATIONS_MAP[decoration][-1] |
|
|
return DECORATIONS_MAP[decoration][0] |
|
|
|