# -*- coding: utf-8 -*- """Art module.""" from .art_dic import * from .art_param import * import os import random class artError(Exception): # pragma: no cover """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) # pragma: no cover 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]