from typing import TypedDict from typing import Dict from dataclasses import dataclass from pathlib import Path # 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": " ", } known_misspellings = { # simple misspelling "excent" : "accent", "overdot" : "dotaccent", "diaresis": "dieresis", "diaeresis": "dieresis", # character does not exist.. maybe something else "Odoubleacute": "Ohungarumlaut", "Udoubleacute": "Uhungarumlaut", "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 generate_name_to_glyph_d(): d = {} with open(f"{Path(__file__).parent}/glyphNamesToUnicode.txt") as f: for line in f: if line[0] == '#': continue (name, hexstr) = line.split(' ') val = chr(int(hexstr, base=16)) d[name] = val return 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 # TODO: better class structure? # TODO: get fonts and faces directly 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) == 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 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) == None: fonts[font_name].faces[face_name] = FontFace({}) if fonts[font_name].faces[face_name].glyphs.get(glyph_id) == 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_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` """ if not fonts.keys().__contains__(font_name): print(f"ABC3D::get_glyph: font name({font_name}) not found") print(fonts.keys()) return None face = fonts[font_name].faces.get(face_name) if face == None: print(f"ABC3D::get_glyph: font({font_name}) face({face_name}) not found") print(fonts[font_name].faces.keys()) return None glyphs_for_id = face.glyphs.get(glyph_id) if glyphs_for_id == None or len(glyphs_for_id) <= alternate: print(f"ABC3D::get_glyph: font({font_name}) face({face_name}) glyph({glyph_id})[{alternate}] 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 None return fonts[font_name].faces[face_name].glyphs.get(glyph_id)[alternate] 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) == None: return "", "", text # , , loaded = [] missing = [] maybe = [] 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: maybe.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 ''.join(loaded), ''.join(missing), ''.join(maybe), 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) == None: return MISSING_FACE loaded, missing, maybe, filepaths = test_glyphs_availability(font_name, face_name, text) return { "loaded": loaded, "missing": missing, "maybe": maybe, "filepaths": filepaths, } # holds all fonts fonts = {}