use class for glyph availability use isinstance instead of type better user experience when export directory does not exist
357 lines
10 KiB
Python
357 lines
10 KiB
Python
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 = {}
|