2024-05-21 18:00:49 +02:00
|
|
|
|
from typing import TypedDict
|
|
|
|
|
from typing import Dict
|
|
|
|
|
from dataclasses import dataclass
|
2024-08-12 11:22:44 +02:00
|
|
|
|
from pathlib import Path
|
2024-05-21 18:00:49 +02:00
|
|
|
|
|
|
|
|
|
# convenience dictionary for translating names to glyph ids
|
2024-08-12 11:22:44 +02:00
|
|
|
|
# note: overwritten/extended by the content of "glypNamesToUnicode.txt"
|
|
|
|
|
# when addon is registered in __init__.py
|
2024-05-21 18:00:49 +02:00
|
|
|
|
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": "/",
|
2024-07-10 16:08:03 +02:00
|
|
|
|
"space": " ",
|
2024-05-21 18:00:49 +02:00
|
|
|
|
}
|
|
|
|
|
|
2024-08-12 11:22:44 +02:00
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
2024-05-21 18:00:49 +02:00
|
|
|
|
class FontFace:
|
|
|
|
|
"""FontFace is a class holding glyphs
|
|
|
|
|
|
|
|
|
|
:param glyphs: dictionary of glyphs, defaults to ``{}``
|
|
|
|
|
:type glyphs: dict, optional
|
2024-08-21 14:42:53 +02:00
|
|
|
|
: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]
|
2024-05-21 18:00:49 +02:00
|
|
|
|
"""
|
2024-08-21 14:42:53 +02:00
|
|
|
|
def __init__(self,
|
|
|
|
|
glyphs = {}):
|
2024-05-21 18:00:49 +02:00
|
|
|
|
self.glyphs = glyphs
|
2024-08-21 14:42:53 +02:00
|
|
|
|
# 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
|
2024-05-21 18:00:49 +02:00
|
|
|
|
|
|
|
|
|
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
|
2024-08-21 14:42:53 +02:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
2024-05-21 18:00:49 +02:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
2024-08-21 14:42:53 +02:00
|
|
|
|
if glyph_id not in fonts[font_name].faces[face_name].loaded_glyphs:
|
|
|
|
|
fonts[font_name].faces[face_name].loaded_glyphs.append(glyph_id)
|
2024-05-21 18:00:49 +02:00
|
|
|
|
|
2024-08-07 11:48:40 +02:00
|
|
|
|
def get_glyph(font_name, face_name, glyph_id, alternate=0):
|
2024-05-21 18:00:49 +02:00
|
|
|
|
""" 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`
|
|
|
|
|
"""
|
2024-08-21 14:42:53 +02:00
|
|
|
|
|
2024-05-21 18:00:49 +02:00
|
|
|
|
if not fonts.keys().__contains__(font_name):
|
2024-08-14 11:26:19 +02:00
|
|
|
|
print(f"ABC3D::get_glyph: font name({font_name}) not found")
|
2024-05-21 18:00:49 +02:00
|
|
|
|
print(fonts.keys())
|
|
|
|
|
return None
|
|
|
|
|
|
2024-08-07 11:48:40 +02:00
|
|
|
|
face = fonts[font_name].faces.get(face_name)
|
|
|
|
|
if face == None:
|
2024-08-14 11:26:19 +02:00
|
|
|
|
print(f"ABC3D::get_glyph: font({font_name}) face({face_name}) not found")
|
2024-05-21 18:00:49 +02:00
|
|
|
|
print(fonts[font_name].faces.keys())
|
|
|
|
|
return None
|
|
|
|
|
|
2024-08-07 11:48:40 +02:00
|
|
|
|
glyphs_for_id = face.glyphs.get(glyph_id)
|
|
|
|
|
if glyphs_for_id == None or len(glyphs_for_id) <= alternate:
|
2024-08-14 11:26:19 +02:00
|
|
|
|
print(f"ABC3D::get_glyph: font({font_name}) face({face_name}) glyph({glyph_id})[{alternate}] not found")
|
2024-08-21 14:42:53 +02:00
|
|
|
|
if glyph_id not in fonts[font_name].faces[face_name].missing_glyphs:
|
|
|
|
|
fonts[font_name].faces[face_name].missing_glyphs.append(glyph_id)
|
2024-05-21 18:00:49 +02:00
|
|
|
|
return None
|
2024-08-07 11:48:40 +02:00
|
|
|
|
|
|
|
|
|
return fonts[font_name].faces[face_name].glyphs.get(glyph_id)[alternate]
|
2024-05-21 18:00:49 +02:00
|
|
|
|
|
2024-08-21 14:42:53 +02:00
|
|
|
|
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>
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2024-05-28 14:11:32 +02:00
|
|
|
|
def get_loaded_fonts():
|
|
|
|
|
return fonts.keys()
|
|
|
|
|
|
2024-08-14 16:15:25 +02:00
|
|
|
|
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
|
|
|
|
|
|
2024-05-21 18:00:49 +02:00
|
|
|
|
# holds all fonts
|
|
|
|
|
fonts = {}
|