diff --git a/README.md b/README.md index 7316d62..bb598e9 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ / ___ \| |_) | |___ ___) | |_| | /_/ \_\____/ \____|____/|____/ ``` -v0.0.10 +v0.0.9 Convenience tool to work with 3D typography in Blender and Cinema4D. diff --git a/__init__.py b/__init__.py index 55ef467..3d7f6bc 100644 --- a/__init__.py +++ b/__init__.py @@ -16,7 +16,7 @@ from .common import Font, utils bl_info = { "name": "ABC3D", "author": "Jakob Schlötter, Studio Pointer*", - "version": (0, 0, 10), + "version": (0, 0, 9), "blender": (4, 1, 0), "location": "VIEW3D", "description": "Convenience addon for 3D fonts", @@ -35,6 +35,7 @@ if "Font" in locals(): importlib.reload(bimport) importlib.reload(addon_updater_ops) + def getPreferences(context): preferences = context.preferences return preferences.addons[__name__].preferences @@ -397,51 +398,51 @@ class ABC3D_PT_FontList(bpy.types.Panel): available_font = abc3d_data.available_fonts[abc3d_data.active_font_index] font_name = available_font.font_name face_name = available_font.face_name - face : Font.FontFace = Font.get_font_face(font_name, face_name) - if face is not None: - available_glyphs = face.glyphs_in_fontfile - loaded_glyphs = sorted(face.loaded_glyphs) - box = layout.box() - box.row().label(text=f"Font Name: {font_name}") - box.row().label(text=f"Face Name: {face_name}") - n = 16 - n_rows = int(len(available_glyphs) / n) - box.row().label(text="Glyphs:") - subbox = box.box() - for i in range(0, n_rows + 1): - text = "".join( - [ - f"{u}" - for ui, u in enumerate(available_glyphs) - if ui < (i + 1) * n and ui >= i * n - ] - ) - scale_y = 0.5 - row = subbox.row() - row.scale_y = scale_y - row.alignment = "CENTER" - row.label(text=text) - n_rows = int(len(loaded_glyphs) / n) - box.row().label(text="Loaded/Used Glyphs:") - subbox = box.box() - for i in range(0, n_rows + 1): - text = "".join( - [ - f"{u}" - for ui, u in enumerate(loaded_glyphs) - if ui < (i + 1) * n and ui >= i * n - ] - ) - scale_y = 0.5 - row = subbox.row() - row.scale_y = scale_y - row.label(text=text) - row = layout.row() - oper_lf = row.operator( - f"{__name__}.load_font", text="Load all glyphs in memory" + available_glyphs = sorted( + Font.fonts[font_name].faces[face_name].glyphs_in_fontfile + ) + loaded_glyphs = sorted(Font.fonts[font_name].faces[face_name].loaded_glyphs) + box = layout.box() + box.row().label(text=f"Font Name: {font_name}") + box.row().label(text=f"Face Name: {face_name}") + n = 16 + n_rows = int(len(available_glyphs) / n) + box.row().label(text="Glyphs:") + subbox = box.box() + for i in range(0, n_rows + 1): + text = "".join( + [ + f"{u}" + for ui, u in enumerate(available_glyphs) + if ui < (i + 1) * n and ui >= i * n + ] ) - oper_lf.font_name = font_name - oper_lf.face_name = face_name + scale_y = 0.5 + row = subbox.row() + row.scale_y = scale_y + row.alignment = "CENTER" + row.label(text=text) + n_rows = int(len(loaded_glyphs) / n) + box.row().label(text="Loaded/Used Glyphs:") + subbox = box.box() + for i in range(0, n_rows + 1): + text = "".join( + [ + f"{u}" + for ui, u in enumerate(loaded_glyphs) + if ui < (i + 1) * n and ui >= i * n + ] + ) + scale_y = 0.5 + row = subbox.row() + row.scale_y = scale_y + row.label(text=text) + row = layout.row() + oper_lf = row.operator( + f"{__name__}.load_font", text="Load all glyphs in memory" + ) + oper_lf.font_name = font_name + oper_lf.face_name = face_name class ABC3D_PT_TextPlacement(bpy.types.Panel): @@ -753,6 +754,7 @@ class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel): return t return None + # NOTE: HERE def get_active_glyph_properties(self): a_o = bpy.context.active_object if a_o is not None: @@ -1024,11 +1026,7 @@ class ABC3D_OT_LoadFont(bpy.types.Operator): face_name: bpy.props.StringProperty() def execute(self, context): - face : Font.FontFace = Font.get_font_face(self.font_name, self.face_name) - if face is None: - butils.ShowMessageBox(f"{utils.prefix()} Load Font", icon="ERROR", message=["Could not load font, sorry!", f"{self.font_name=} {self.face_name=}"]) - return {"CANCELLED"} - filepaths = face.filepaths + filepaths = Font.fonts[self.font_name].faces[self.face_name].filepaths for f in filepaths: butils.load_font_from_filepath(f) return {"FINISHED"} @@ -1404,9 +1402,6 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator): bl_label = "Save Font" bl_options = {"REGISTER", "UNDO"} - can_execute : bpy.props.BoolProperty(default=True) - create_output_directory : bpy.props.BoolProperty(default=False) - def invoke(self, context, event): wm = context.window_manager preferences = getPreferences(context) @@ -1430,95 +1425,30 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator): available_font = abc3d_data.available_fonts[abc3d_data.active_font_index] font_name = available_font.font_name face_name = available_font.face_name - face : Font.FontFace = Font.get_font_face(font_name, face_name) - if face is not None: - loaded_glyphs = sorted(face.loaded_glyphs) - n = 16 - n_rows = int(len(loaded_glyphs) / n) - box = layout.box() - box.row().label(text="Glyphs to be exported:") - subbox = box.box() - for i in range(0, n_rows + 1): - text = "".join( - [ - f"{u}" - for ui, u in enumerate(loaded_glyphs) - if ui < (i + 1) * n and ui >= i * n - ] - ) - scale_y = 0.5 - row = subbox.row() - row.scale_y = scale_y - row.label(text=text) - row = layout.row() - export_dir = butils.bpy_to_abspath(abc3d_data.export_dir) - if os.access(export_dir, os.W_OK): - self.can_execute = True - elif os.path.exists(export_dir): - self.can_execute = False - row.alert = True - row.label(text="Export directory exists but is not writable") - row = layout.row() - row.alert = True - row.label(text="Please select another directory") - row = layout.row() - row.alert = True - elif not utils.can_create_path(export_dir): # does not exist and cannot be created - self.can_execute = False - row.alert = True - row.label(text="Directory does not exist and cannot be created") - row = layout.row() - row.alert = True - row.label(text="Please select another directory") - row = layout.row() - row.alert = True - elif utils.can_create_path(export_dir): # does not exist and can be created - self.can_execute = True - row.label(text="Directory does not exist") - row = layout.row() - row.label(text="But can and will be created on export") - row = layout.row() - else: - self.can_execute = False - row.alert = True - row.label(text="Please select another directory") - row = layout.row() - row.alert = True - - row.prop(abc3d_data, "export_dir") - else: - print(f"{utils.prefix()}::save_font_to_file ERROR {face=} {font_name=} {face_name=}") - print(f"{utils.prefix()} {Font.fonts=}") + loaded_glyphs = sorted(Font.fonts[font_name].faces[face_name].loaded_glyphs) + n = 16 + n_rows = int(len(loaded_glyphs) / n) + box = layout.box() + box.row().label(text="Glyphs to be exported:") + subbox = box.box() + for i in range(0, n_rows + 1): + text = "".join( + [ + f"{u}" + for ui, u in enumerate(loaded_glyphs) + if ui < (i + 1) * n and ui >= i * n + ] + ) + scale_y = 0.5 + row = subbox.row() + row.scale_y = scale_y + row.label(text=text) + layout.prop(abc3d_data, "export_dir") def execute(self, context): global shared scene = bpy.context.scene abc3d_data = scene.abc3d_data - if not self.can_execute: - butils.ShowMessageBox( - "Cannot export font", - "ERROR", - [ - f"export directory '{abc3d_data.export_dir}' does not exist or is not writable", - "try setting another path" - ] - ) - return {'CANCELLED'} - - if not os.path.exists(butils.bpy_to_abspath(abc3d_data.export_dir)): - path = butils.bpy_to_abspath(abc3d_data.export_dir) - if utils.can_create_path(path): - os.makedirs(path, exist_ok=True) - else: - butils.ShowMessageBox( - "Cannot export font", - "ERROR", - [ - f"export directory '{abc3d_data.export_dir}' does not exist and cannot be created", - "try setting another path" - ] - ) - return {'CANCELLED'} fontcollection = bpy.data.collections.get("ABC3D") @@ -1598,10 +1528,7 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator): use_selection=True, use_active_scene=True, ) - def delete_scene(): - bpy.ops.scene.delete() - return None - bpy.app.timers.register(lambda: delete_scene(), first_interval=1) + bpy.app.timers.register(lambda: bpy.ops.scene.delete(), first_interval=1) # bpy.ops.scene.delete() # restore() @@ -1614,7 +1541,7 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator): return None bpy.app.timers.register(lambda: remove_faces(), first_interval=2) - self.report({"INFO"}, f"{utils.prefix()}::save_font_to_file done") + self.report({"INFO"}, "did it") return {"FINISHED"} @@ -1742,6 +1669,9 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator): font_name = self.font_name face_name = self.face_name + # TODO: do not clear + # abc3d_data.available_fonts.clear() + # Font.fonts = {} currentObjects = [] for o in context.selected_objects: if o.name not in currentObjects: @@ -1912,7 +1842,7 @@ def load_used_glyphs(): abc3d_data = scene.abc3d_data for t in abc3d_data.available_texts: a = Font.test_availability(t.font_name, t.face_name, t.text) - if isinstance(a, int): + if type(a) == type(int()): if a == Font.MISSING_FONT: butils.ShowMessageBox( "Missing Font", @@ -1929,9 +1859,9 @@ def load_used_glyphs(): "Do you have it installed?", ], ) - elif len(a.unloaded) > 0: - for fp in a.filepaths: - butils.load_font_from_filepath(fp, a.unloaded) + elif len(a["maybe"]) > 0: + for fp in a["filepaths"]: + butils.load_font_from_filepath(fp, a["maybe"]) @persistent diff --git a/butils.py b/butils.py index 557054a..138ed9f 100644 --- a/butils.py +++ b/butils.py @@ -94,8 +94,6 @@ def calc_point_on_bezier(bezier_point_1, bezier_point_2, t): h1 = bezier_point_1.handle_right p2 = bezier_point_2.co h2 = bezier_point_2.handle_left - if p1 == h1 and p2 == h2: - return p1 + t * (p2 - p1) return ( ((1 - t) ** 3) * p1 + (3 * t * (1 - t) ** 2) * h1 @@ -128,8 +126,6 @@ def calc_tangent_on_bezier(bezier_point_1, bezier_point_2, t): h1 = bezier_point_1.handle_right p2 = bezier_point_2.co h2 = bezier_point_2.handle_left - if p1 == h1 and p2 == h2: - return (p2 - p1).normalized() return ( (-3 * (1 - t) ** 2) * p1 + (-6 * t * (1 - t) + 3 * (1 - t) ** 2) * h1 @@ -138,24 +134,6 @@ def calc_tangent_on_bezier(bezier_point_1, bezier_point_2, t): ).normalized() -# class TestCalcPoint(): - # co: mathutils.Vector - # handle_left: mathutils.Vector - # handle_right: mathutils.Vector - # def __init__(self, co, handle_left=None, handle_right=None): - # self.co = co - # if handle_left is not None: - # self.handle_left = handle_left - # if handle_right is not None: - # self.handle_right = handle_right - - -# a = TestCalcPoint(mathutils.Vector((0,0,0)), handle_right=mathutils.Vector((0,1,0))) -# b = TestCalcPoint(mathutils.Vector((1,0,0)), handle_left=mathutils.Vector((1,1,0))) -# c = TestCalcPoint(mathutils.Vector((0,0,0)), handle_right=mathutils.Vector((0,0,0))) -# d = TestCalcPoint(mathutils.Vector((1,0,0)), handle_left=mathutils.Vector((1,0,0))) -# calc_point_on_bezier(a,b,0.5) - def align_rotations_auto_pivot( @@ -215,34 +193,6 @@ def calc_bezier_length(bezier_point_1, bezier_point_2, resolution=20): return length -def get_real_beziers_and_lengths(bezier_spline_obj, resolution_factor): - beziers = [] - lengths = [] - total_length = 0 - n_bezier_points = len(bezier_spline_obj.bezier_points) - real_n_bezier_points = len(bezier_spline_obj.bezier_points) - if bezier_spline_obj.use_cyclic_u: - n_bezier_points += 1 - for i in range(0, n_bezier_points - 1): - i_a = i % (n_bezier_points - 1) - i_b = (i_a + 1) % real_n_bezier_points - bezier = [ - bezier_spline_obj.bezier_points[i_a], - bezier_spline_obj.bezier_points[i_b], - ] - length = calc_bezier_length( - bezier[0], - bezier[1], - int(bezier_spline_obj.resolution_u * resolution_factor), - ) - total_length += length - beziers.append(bezier) - lengths.append(length) - # if total_length > distance: - # break - return beziers, lengths, total_length - - def calc_point_on_bezier_spline( bezier_spline_obj, distance, output_tangent=False, resolution_factor=1.0 ): @@ -269,17 +219,6 @@ def calc_point_on_bezier_spline( if distance <= 0: p = bezier_spline_obj.bezier_points[0] travel = (p.co - p.handle_left).normalized() * distance - - # in case the handles sit on the points - # we interpolate the travel from points of the bezier - # if the bezier points sit on each other we have same issue - # but that is then to be fixed in the bezier - if p.handle_left == p.co and len(bezier_spline_obj.bezier_points) > 1: - beziers, lengths, total_length = get_real_beziers_and_lengths(bezier_spline_obj, - resolution_factor) - travel_point = calc_point_on_bezier(beziers[0][1], beziers[0][0], 0.001) - travel = travel_point.normalized() * distance - location = p.co + travel if output_tangent: p2 = bezier_spline_obj.bezier_points[1] @@ -288,8 +227,30 @@ def calc_point_on_bezier_spline( else: return location - beziers, lengths, total_length = get_real_beziers_and_lengths(bezier_spline_obj, - resolution_factor) + beziers = [] + lengths = [] + total_length = 0 + n_bezier_points = len(bezier_spline_obj.bezier_points) + real_n_bezier_points = len(bezier_spline_obj.bezier_points) + if bezier_spline_obj.use_cyclic_u: + n_bezier_points += 1 + for i in range(0, n_bezier_points - 1): + i_a = i % (n_bezier_points - 1) + i_b = (i_a + 1) % real_n_bezier_points + bezier = [ + bezier_spline_obj.bezier_points[i_a], + bezier_spline_obj.bezier_points[i_b], + ] + length = calc_bezier_length( + bezier[0], + bezier[1], + int(bezier_spline_obj.resolution_u * resolution_factor), + ) + total_length += length + beziers.append(bezier) + lengths.append(length) + # if total_length > distance: + # break iterated_distance = 0 for i in range(0, len(beziers)): @@ -307,16 +268,7 @@ def calc_point_on_bezier_spline( # if we are here, the point is outside the spline last_i = len(beziers) - 1 p = beziers[last_i][1] - travel = (p.handle_right - p.co).normalized() * (distance - total_length) - - # in case the handles sit on the points - # we interpolate the travel from points of the bezier - # if the bezier points sit on each other we have same issue - # but that is then to be fixed in the bezier - if p.handle_right == p.co and len(beziers) > 0: - travel_point = calc_point_on_bezier(beziers[-1][1], beziers[-1][0], 0.001) - travel = travel_point.normalized() * (distance - total_length) location = p.co + travel if output_tangent: tangent = calc_tangent_on_bezier(beziers[last_i][0], p, 1) @@ -563,11 +515,7 @@ def load_font_from_filepath(filepath, glyphs="", font_name="", face_name=""): for mff in modified_font_faces: mff_glyphs = [] - face : Font.FontFace = Font.get_font_face(mff["font_name"], mff["face_name"]) - if face is None: - print(f"{utils.prefix()}::load_font_from_path({filepath=}, {glyphs=}, {font_name=}, {face_name=}) failed") - print(f"modified font face {mff=} could not be accessed.") - continue + face = Font.fonts[mff["font_name"]].faces[mff["face_name"]] # iterate glyphs for g in face.glyphs: # iterate alternates @@ -595,18 +543,17 @@ def load_font_from_filepath(filepath, glyphs="", font_name="", face_name=""): def update_available_fonts(): abc3d_data = bpy.context.scene.abc3d_data - for font_and_face in Font.get_loaded_fonts_and_faces(): - found = False - font_name = font_and_face[0] - face_name = font_and_face[1] - for f in abc3d_data.available_fonts.values(): - if font_name == f.font_name and face_name == f.face_name: - found = True - if not found: - f = abc3d_data.available_fonts.add() - f.font_name = font_name - f.face_name = face_name - print(f"{utils.prefix()}::update_available_fonts: {__name__} added {font_name} {face_name}") + for font_name in Font.fonts.keys(): + for face_name in Font.fonts[font_name].faces.keys(): + found = False + for f in abc3d_data.available_fonts.values(): + if font_name == f.font_name and face_name == f.face_name: + found = True + if not found: + f = abc3d_data.available_fonts.add() + f.font_name = font_name + f.face_name = face_name + print(f"{__name__} added {font_name} {face_name}") # def update_available_texts(): @@ -807,28 +754,21 @@ def get_glyph_height(glyph_obj): def prepare_text(font_name, face_name, text, allow_replacement=True): - availability = Font.test_glyphs_availability( + loaded, missing, loadable, files = Font.test_glyphs_availability( font_name, face_name, text ) - if isinstance(availability, int): - if availability == Font.MISSING_FONT: - print(f"{utils.prefix()}::prepare_text({font_name=}, {face_name=}, {text=}) failed with MISSING_FONT") - if availability is Font.MISSING_FACE: - print(f"{utils.prefix()}::prepare_text({font_name=}, {face_name=}, {text=}) failed with MISSING_FACE") - return False - loadable = availability.unloaded # possibly replace upper and lower case letters with each other - if len(availability.missing) > 0 and allow_replacement: + if len(missing) > 0 and allow_replacement: replacement_search = "" - for m in availability.missing: + for m in missing: if m.isalpha(): replacement_search += m.swapcase() r = Font.test_availability(font_name, face_name, replacement_search) - loadable += r.unloaded + loadable += r["maybe"] # not update (loaded, missing, files), we only use loadable/maybe later if len(loadable) > 0: - for filepath in availability.filepaths: + for filepath in files: load_font_from_filepath(filepath, loadable, font_name, face_name) return True @@ -836,9 +776,9 @@ def predict_actual_text(text_properties): availability = Font.test_availability(text_properties.font_name, text_properties.face_name, text_properties.text) AVAILABILITY = Font.test_availability(text_properties.font_name, text_properties.face_name, text_properties.text.swapcase()) t_text = text_properties.text - for c in availability.missing: + for c in availability["missing"]: t_text = t_text.replace(c, "") - for c in AVAILABILITY.missing: + for c in AVAILABILITY["missing"]: t_text = t_text.replace(c, "") return t_text @@ -1076,8 +1016,14 @@ def transfer_text_object_to_text_properties(text_object, text_properties, id_fro if text == text_properties.text: is_good_text = True else: - t_text = predict_actual_text(text_properties) - if t_text == text: + availability = Font.test_availability(text_properties.font_name, text_properties.face_name, text_properties.text) + AVAILABILITY = Font.test_availability(text_properties.font_name, text_properties.face_name, text_properties.text.swapcase()) + t_text = text_properties.text + for c in availability["missing"]: + t_text = t_text.replace(c, "") + for c in AVAILABILITY["missing"]: + t_text = t_text.replace(c, "") + if len(t_text) == len(text): is_good_text = True if is_good_text: text_properties.actual_text = text @@ -1306,7 +1252,7 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4, actual_text = "" for i, c in enumerate(text_properties.text): - face = Font.get_font_face(text_properties.font_name, text_properties.face_name) + face = Font.fonts[text_properties.font_name].faces[text_properties.face_name] scalor = face.unit_factor * text_properties.font_size if c == "\\": is_command = True @@ -1500,39 +1446,26 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4, mom, advance + glyph_advance, False, True ) if psi == si: - n_max = 100 - n = 0 while ( previous_location - new_location - ).length > glyph_advance and psi == si and n < n_max: + ).length > glyph_advance and psi == si: curve_compensation = curve_compensation - glyph_advance * 0.01 - tmp_new_location, si = calc_point_on_bezier_curve( + new_location, si = calc_point_on_bezier_curve( mom, advance + glyph_advance + curve_compensation, output_tangent=False, output_spline_index=True, ) - if tmp_new_location == new_location: - print(f"{utils.prefix()}::set_text_on_curve::compensate_curvature while loop overstaying welcome") - break - new_location = tmp_new_location - n += 1 - n = 0 while ( previous_location - new_location - ).length < glyph_advance and psi == si and n < n_max: + ).length < glyph_advance and psi == si: curve_compensation = curve_compensation + glyph_advance * 0.01 - tmp_new_location, si = calc_point_on_bezier_curve( + new_location, si = calc_point_on_bezier_curve( mom, advance + glyph_advance + curve_compensation, output_tangent=False, output_spline_index=True, ) - if tmp_new_location == new_location: - print(f"{utils.prefix()}::set_text_on_curve::compensate_curvature while loop overstaying welcome") - break - new_location = tmp_new_location - n += 1 advance = advance + glyph_advance + curve_compensation glyph_index += 1 diff --git a/common/Font.py b/common/Font.py index 4d0c6a7..e7a7ac0 100644 --- a/common/Font.py +++ b/common/Font.py @@ -1,6 +1,5 @@ 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" @@ -161,7 +160,6 @@ class Font: 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({}) @@ -179,34 +177,6 @@ def register_font(font_name, face_name, glyphs_in_fontfile, filepath): 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 @@ -233,38 +203,6 @@ def add_glyph(font_name, face_name, glyph_id, glyph_object): 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 @@ -280,22 +218,25 @@ def get_glyph(font_name, face_name, glyph_id, alternate=0): :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" - ) + if not fonts.keys().__contains__(font_name): + # print(f"ABC3D::get_glyph: font name({font_name}) not found") + # print(fonts.keys()) return None - return glyphs[alternate] + face = fonts[font_name].faces.get(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 None + glyphs_for_id = face.glyphs.get(glyph_id) + if glyphs_for_id is 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 -class GlyphsAvailability(NamedTuple): - loaded: str - missing: str - unloaded: str - filepaths: list[str] + return fonts[font_name].faces[face_name].glyphs.get(glyph_id)[alternate] def test_glyphs_availability(font_name, face_name, text): @@ -304,24 +245,24 @@ def test_glyphs_availability(font_name, face_name, text): not fonts.keys().__contains__(font_name) or fonts[font_name].faces.get(face_name) is None ): - return GlyphsAvailability("", "", "", []) + return "", "", text # , , loaded = [] missing = [] - unloaded = [] + 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: - unloaded.append(c) + 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 GlyphsAvailability( + return ( "".join(loaded), "".join(missing), - "".join(unloaded), + "".join(maybe), fonts[font_name].faces[face_name].filepaths, ) @@ -347,10 +288,15 @@ def test_availability(font_name, face_name, text): return MISSING_FONT if fonts[font_name].faces.get(face_name) is None: return MISSING_FACE - availability: GlyphsAvailability = test_glyphs_availability( + loaded, missing, maybe, filepaths = test_glyphs_availability( font_name, face_name, text ) - return availability + return { + "loaded": loaded, + "missing": missing, + "maybe": maybe, + "filepaths": filepaths, + } # holds all fonts diff --git a/common/utils.py b/common/utils.py index 3ad1e34..796d3e6 100644 --- a/common/utils.py +++ b/common/utils.py @@ -8,7 +8,7 @@ def get_version_minor(): def get_version_patch(): - return 10 + return 9 def get_version_string(): @@ -82,10 +82,6 @@ def open_file_browser(directory): # xdg-open *should* be supported by recent Gnome, KDE, Xfce -def LINE(): - return sys._getframe(1).f_lineno - - def printerr(*args, **kwargs): print(*args, file=sys.stderr, **kwargs) @@ -93,26 +89,6 @@ def printerr(*args, **kwargs): def removeNonAlphabetic(s): return "".join([i for i in s if i.isalpha()]) -import pathlib -import os - -def can_create_path(path_str : str): - path = pathlib.Path(path_str).absolute().resolve() - - while True: # this looks dangerours, but it actually is not - if path.exists(): - if os.access(path, os.W_OK): - return True - else: - return False - elif path == path.parent: - # should never be reached, because root exists - # but if it doesn't.. well then we can't - return False - - path = path.parent - - # # Evaluate a bezier curve for the parameter 0<=t<=1 along its length # def evaluateBezierPoint(p1, h1, h2, p2, t):