font3d_blender_addon/common/Font.py
themancalledjakob 7c72dd54dc font creation
improve font creation operator
offer fixing common misspellings
use glyphNamesToUnicode.txt table to generate name_to_glyph_d
fix typos in code
more consistent naming font_face -> face_name
2024-08-12 11:22:44 +02:00

175 lines
5.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
"""
def __init__(self, glyphs = {}):
self.glyphs = glyphs
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 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({})
# print("is it has been added", fonts.keys())
if fonts[font_name].faces.get(face_name) == None:
fonts[font_name].faces[face_name] = FontFace({})
# print("is it has been added faces", fonts[font_name].faces[face_name])
if fonts[font_name].faces[face_name].glyphs.get(glyph_id) == None:
fonts[font_name].faces[face_name].glyphs[glyph_id] = []
# print("is it has been added glyph", fonts[font_name].faces[face_name].glyphs[glyph_id])
fonts[font_name].faces[face_name].glyphs.get(glyph_id).append(glyph_object)
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`
"""
# print(fonts)
if not fonts.keys().__contains__(font_name):
print(f"FONT3D::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"FONT3D::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"FONT3D::get_glyph: font({font_name}) face({face_name}) glyph({glyph_id})[{alternate}] not found")
return None
return fonts[font_name].faces[face_name].glyphs.get(glyph_id)[alternate]
def get_loaded_fonts():
return fonts.keys()
# holds all fonts
fonts = {}