from typing import Dict from pathlib import Path from typing import NamedTuple # convenience dictionary for translating names to glyph ids # note: overwritten/extended by the content of "glypNamesToUnicode.txt" # when addon is registered in __init__.py name_to_glyph_d = { "zero": "0", "one": "1", "two": "2", "three": "3", "four": "4", "five": "5", "six": "6", "seven": "7", "eight": "8", "nine": "9", "ampersand": "&", "backslash": "\\", "colon": ":", "comma": ",", "equal": "=", "exclam": "!", "hyphen": "-", "minus": "−", "parenleft": "(", "parenright": "(", "period": ".", "plus": "+", "question": "?", "quotedblleft": "“", "quotedblright": "”", "semicolon": ";", "slash": "/", "space": " ", } space_d = {} known_misspellings = { # simple misspelling "excent": "accent", "overdot": "dotaccent", "diaresis": "dieresis", "diaeresis": "dieresis", # different conventions "doubleacute": "hungarumlaut", # character does not exist.. maybe something else "Wcaron": "Wcircumflex", "Neng": "Nlongrightleg", "Lgrave": "Lacute", # currency stuff "doller": "dollar", "euro": "Euro", "yuan": "yen", # https://en.wikipedia.org/wiki/Yen_and_yuan_sign "pound": "sterling", # whoopsie "__": "_", } def fix_glyph_name_misspellings(name): for misspelling in known_misspellings: if misspelling in name: return name.replace(misspelling, known_misspellings[misspelling]) return name def name_to_glyph(name): if len(name) == 1: return name if name in name_to_glyph_d: return name_to_glyph_d[name] else: return None def glyph_to_name(glyph_id): for k in name_to_glyph_d: if glyph_id == name_to_glyph_d[k]: return k return glyph_id def is_space(character): for name in space_d: if character == space_d[name][0]: return space_d[name][1] return False def generate_from_file_d(filepath): d = {} with open(filepath) as f: for line in f: if line[0] == "#": continue split = line.split(" ") if len(split) == 2: (name, hexstr) = line.split(" ") val = chr(int(hexstr, base=16)) d[name] = val if len(split) == 3: # we might have a parameter, like for the spaces (name, hexstr, parameter) = line.split(" ") parameter_value = float(parameter) val = chr(int(hexstr, base=16)) d[name] = [val, parameter_value] return d def generate_name_to_glyph_d(): return generate_from_file_d(f"{Path(__file__).parent}/glyphNamesToUnicode.txt") def generate_space_d(): return generate_from_file_d(f"{Path(__file__).parent}/spacesUnicode.txt") def init(): global name_to_glyph_d global space_d name_to_glyph_d = generate_name_to_glyph_d() space_d = generate_space_d() class FontFace: """FontFace is a class holding glyphs :param glyphs: dictionary of glyphs, defaults to ``{}`` :type glyphs: dict, optional :param loaded_glyphs: glyphs currently loaded :type loaded_glyphs: List[str], optional :param missing_glyphs: glyphs not present in the fontfile :type missing_glyphs: List[str], optional :param filenames: from which file is this face :type filenames: List[str] """ def __init__(self, glyphs={}): self.glyphs = glyphs # lists have to be initialized in __init__ # to be attributes per instance. # otherwise they are static class attributes self.loaded_glyphs = [] self.missing_glyphs = [] self.glyphs_in_fontfile = [] self.filepaths = [] self.unit_factor = 1.0 class Font: """Font holds the faces and various metadata for a font :param faces: dictionary of faces, defaults to ``Dict[str, FontFace]`` :type faces: Dict[str, FontFace] """ def __init__(self, faces=Dict[str, FontFace]): self.faces = faces def register_font(font_name, face_name, glyphs_in_fontfile, filepath): if not fonts.keys().__contains__(font_name): fonts[font_name] = Font({}) if fonts[font_name].faces.get(face_name) is None: fonts[font_name].faces[face_name] = FontFace({}) fonts[font_name].faces[face_name].glyphs_in_fontfile = glyphs_in_fontfile else: fonts[font_name].faces[face_name].glyphs_in_fontfile = list( set( fonts[font_name].faces[face_name].glyphs_in_fontfile + glyphs_in_fontfile ) ) if filepath not in fonts[font_name].faces[face_name].filepaths: fonts[font_name].faces[face_name].filepaths.append(filepath) def get_font(font_name): if not fonts.keys().__contains__(font_name): print(f"ABC3D::get_font: font name({font_name}) not found") print(fonts.keys()) return None return fonts[font_name] def get_font_face(font_name, face_name): font = get_font(font_name) if font is None: return None if not font.faces.keys().__contains__(face_name): print( f"ABC3D::get_font_face (font: {font_name}): face name({face_name}) not found" ) print(font.faces.keys()) return None return font.faces[face_name] def get_font_face_filepaths(font_name, face_name): face = get_font_face(font_name, face_name) if not face: return None return face.filepaths def add_glyph(font_name, face_name, glyph_id, glyph_object): """add_glyph adds a glyph to a FontFace it creates the :class:`Font` and :class:`FontFace` if it does not exist yet :param font_name: The Font you want to add the glyph to :type font_name: str :param face_name: The FontFace you want to add the glyph to :type face_name: str :param glyph_id: The glyph_id you want this glyph to be stored under :type glyph_id: str :param glyph_object: The object containing the glyph :type glyph_object: `Object` """ if not fonts.keys().__contains__(font_name): fonts[font_name] = Font({}) if fonts[font_name].faces.get(face_name) is None: fonts[font_name].faces[face_name] = FontFace({}) if fonts[font_name].faces[face_name].glyphs.get(glyph_id) is None: fonts[font_name].faces[face_name].glyphs[glyph_id] = [] fonts[font_name].faces[face_name].glyphs.get(glyph_id).append(glyph_object) if glyph_id not in fonts[font_name].faces[face_name].loaded_glyphs: fonts[font_name].faces[face_name].loaded_glyphs.append(glyph_id) def get_glyphs(font_name, face_name, glyph_id): """get_glyphs returns an array of glyphs of a FontFace :param font_name: The :class:`Font` you want to get the glyph from :type font_name: str :param face_name: The :class:`FontFace` you want to get the glyph from :type face_name: str :param glyph_id: The ``glyph_id`` from the glyph you want :type glyph_id: str ... :return: returns a list of the glyph objects, or an empty list if none exists :rtype: `List` """ face = get_font_face(font_name, face_name) if face is None: print(f"ABC3D::get_glyph: font({font_name}) face({face_name}) not found") print(fonts[font_name].faces.keys()) return [] glyphs_for_id = face.glyphs.get(glyph_id) if glyphs_for_id is None: print( f"ABC3D::get_glyph: font({font_name}) face({face_name}) glyph({glyph_id}) not found" ) if glyph_id not in fonts[font_name].faces[face_name].missing_glyphs: fonts[font_name].faces[face_name].missing_glyphs.append(glyph_id) return [] return glyphs_for_id def get_glyph(font_name, face_name, glyph_id, alternate=0): """add_glyph adds a glyph to a FontFace it creates the :class:`Font` and :class:`FontFace` if it does not exist yet :param font_name: The :class:`Font` you want to get the glyph from :type font_name: str :param face_name: The :class:`FontFace` you want to get the glyph from :type face_name: str :param glyph_id: The ``glyph_id`` from the glyph you want :type glyph_id: str ... :return: returns the glyph object, or ``None`` if it does not exist :rtype: `Object` """ glyphs = get_glyphs(font_name, face_name, glyph_id) if len(glyphs) <= alternate or len(glyphs) == 0: print( f"ABC3D::get_glyph: font({font_name}) face({face_name}) glyph({glyph_id})[{alternate}] not found" ) return None return glyphs[alternate] class GlyphsAvailability(NamedTuple): loaded: str missing: str unloaded: str filepaths: list[str] def test_glyphs_availability(font_name, face_name, text): # maybe there is NOTHING yet if ( not fonts.keys().__contains__(font_name) or fonts[font_name].faces.get(face_name) is None ): return GlyphsAvailability("", "", "", []) loaded = [] missing = [] unloaded = [] for c in text: if c in fonts[font_name].faces[face_name].loaded_glyphs: loaded.append(c) elif c in fonts[font_name].faces[face_name].glyphs_in_fontfile: unloaded.append(c) else: if c not in fonts[font_name].faces[face_name].missing_glyphs: fonts[font_name].faces[face_name].missing_glyphs.append(c) missing.append(c) return GlyphsAvailability( "".join(loaded), "".join(missing), "".join(unloaded), fonts[font_name].faces[face_name].filepaths, ) def get_loaded_fonts(): return fonts.keys() def get_loaded_fonts_and_faces(): out = [] for f in fonts.keys(): for ff in fonts[f].faces.keys(): out.append([f, ff]) return out MISSING_FONT = 0 MISSING_FACE = 1 def test_availability(font_name, face_name, text): if not fonts.keys().__contains__(font_name): return MISSING_FONT if fonts[font_name].faces.get(face_name) is None: return MISSING_FACE availability: GlyphsAvailability = test_glyphs_availability( font_name, face_name, text ) return availability # holds all fonts fonts = {}