Skip to content

Instantly share code, notes, and snippets.

@simoncozens
Last active September 13, 2023 13:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save simoncozens/5ddafef074dfb47df8a68f8e2b2191b8 to your computer and use it in GitHub Desktop.
Save simoncozens/5ddafef074dfb47df8a68f8e2b2191b8 to your computer and use it in GitHub Desktop.
#!/opt/homebrew/bin/python3
import sys
sys.path.insert(0, "/Users/simon/others-repos/ufo2ft/Lib")
from glyphsLib.classes import GSFont
from glyphsLib.builder import UFOBuilder
from glyphsLib.builder.font import fill_ufo_metadata
from fontmake.font_project import FontProject
from timeit import default_timer as timer
from pathlib import Path
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class FileModifiedHandler(FileSystemEventHandler):
def __init__(self, file_paths, callback):
self.paths = file_paths
self.callback = callback
# set observer to watch for changes in the directory
self.observer = Observer()
for directory in set([Path(x).parent for x in self.paths]):
self.observer.schedule(self, directory, recursive=False)
self.observer.start()
self.observer.join()
def on_modified(self, event):
# only act on the change that we're looking for
if not event.is_directory and any(
event.src_path.endswith(file_name) for file_name in self.paths
):
self.observer.stop() # stop watching
self.callback(event.src_path) # call callback
def build_file(file):
start = timer()
font = GSFont(file)
gsfont_time = timer()
regular_master = [ m for m in font.masters if m.name == "Regular" ][0]
builder = UFOBuilder(
font,
minimal=True
)
for index, master in enumerate(font.masters):
ufo = builder.ufo_module.Font()
if master.id == regular_master.id:
fill_ufo_metadata(master, ufo)
builder.to_ufo_names(ufo, master, builder.family_name) # .names
builder.to_ufo_family_user_data(ufo) # .user_data
builder.to_ufo_custom_params(ufo, font) # .custom_params
builder.to_ufo_master_attributes(ufo, master) # .masters
source = builder._designspace.newSourceDescriptor()
source.font = ufo
builder._designspace.addSource(source)
builder._sources[master.id] = source
master_layer_ids = {m.id for m in builder.font.masters}
# Generate the main (master) layers first.
for glyph in builder.font.glyphs:
for layer in glyph.layers.values():
if layer.associatedMasterId != layer.layerId:
continue
if layer.associatedMasterId != regular_master.id:
continue
ufo_layer = builder.to_ufo_layer(glyph, layer) # .layers
ufo_glyph = ufo_layer.newGlyph(glyph.name)
builder.to_ufo_glyph(ufo_glyph, layer, glyph) # .glyph
for master_id, source in builder._sources.items():
ufo = source.font
master = builder.font.masters[master_id]
if builder.propagate_anchors:
builder.to_ufo_propagate_font_anchors(ufo) # .anchor_propagation
if not builder.minimal:
for layer in list(ufo.layers):
builder.to_ufo_layer_lib(master, ufo, layer) # .user_data
builder.to_ufo_master_features(ufo, master) # .features
builder.to_ufo_custom_params(ufo, master) # .custom_params
if builder.write_skipexportglyphs:
# Sanitize skip list and write it to both Designspace- and UFO-level lib
# keys. The latter is unnecessary when using e.g. the ufo2ft.compile*FromDS`
# functions, but the data may take a different path. Writing it everywhere
# can save on surprises/logic in other software.
skip_export_glyphs = builder._designspace.lib.get("public.skipExportGlyphs")
if skip_export_glyphs is not None:
skip_export_glyphs = sorted(set(skip_export_glyphs))
builder._designspace.lib["public.skipExportGlyphs"] = skip_export_glyphs
for source in builder._sources.values():
source.font.lib["public.skipExportGlyphs"] = skip_export_glyphs
builder.to_ufo_groups() # .groups
builder.to_ufo_kerning() # .kerning
ufo = builder._sources[regular_master.id].font
ufo_time = timer()
FontProject().save_otfs(
[ufo],
ttf=True,
autohint=False,
remove_overlaps=False,
reverse_direction=False,
output_dir="master_ttf/",
flatten_components=False
)
endtime = timer()
print(f"Done in: {endtime-start}s")
print(f" (Glyphs parsing: {gsfont_time-start}s)")
print(f" (Glyphs to UFO: {ufo_time-gsfont_time}s)")
print(f" (UFO to TTF: {endtime-ufo_time}s)")
import argparse
parser = argparse.ArgumentParser(description='Build fonts really fast.')
parser.add_argument('paths', metavar='N', nargs='+',
help='filenames')
parser.add_argument('--watch', action="store_true",
help='stay on and watch the files')
args = parser.parse_args()
for path in args.paths:
build_file(path)
if args.watch:
while True:
FileModifiedHandler(args.paths, build_file)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment