def assertDesignspaceRoundtrip(self, designspace): directory = tempfile.mkdtemp() font = to_glyphs(designspace, minimize_ufo_diffs=True) # Check that round-tripping in memory is the same as writing on disk roundtrip_in_mem = to_designspace(font, propagate_anchors=False) tmpfont_path = os.path.join(directory, "font.glyphs") font.save(tmpfont_path) font_rt = classes.GSFont(tmpfont_path) roundtrip = to_designspace(font_rt, propagate_anchors=False) font.save("intermediary.glyphs") write_designspace_and_UFOs(designspace, "expected/test.designspace") for source in designspace.sources: normalize_ufo_lib(source.path) normalizeUFO(source.path, floatPrecision=3, writeModTimes=False) write_designspace_and_UFOs(roundtrip, "actual/test.designspace") for source in roundtrip.sources: normalize_ufo_lib(source.path) normalizeUFO(source.path, floatPrecision=3, writeModTimes=False) self.assertDesignspacesEqual( roundtrip_in_mem, roundtrip, "The round-trip in memory or written to disk should be equivalent", ) self.assertDesignspacesEqual( designspace, roundtrip, "The font should not be modified by the roundtrip" )
def run(args): options = Options(args) # Set the current dir to the design space dir, so that relative paths in # the design space file will work. dsDir, dsFile = os.path.split(os.path.abspath(options.dsPath)) os.chdir(dsDir) options.dsPath = dsFile dsPath, newInstancesList = readDesignSpaceFile(options) if not dsPath: return version = 2 if len(newInstancesList) == 1: logMsg.log("Building 1 instance...") else: logMsg.log("Building %s instances..." % (len(newInstancesList))) mutatorMathBuild(documentPath=dsPath, outputUFOFormatVersion=version, roundGeometry=(not options.allowDecimalCoords)) if (dsPath != options.dsPath) and os.path.exists(dsPath): os.remove(dsPath) logMsg.log("Built %s instances." % (len(newInstancesList))) # Remove glyph.lib and font.lib (except for "public.glyphOrder") for instancePath in newInstancesList: postProcessInstance(instancePath, options) if options.doNormalize: logMsg.log("Applying UFO normalization...") for instancePath in newInstancesList: normalizeUFO(instancePath, outputPath=None, onlyModified=True, writeModTimes=False) if options.doAutoHint or options.doOverlapRemoval: logMsg.log("Applying post-processing...") # Apply autohint and checkoutlines, if requested. for instancePath in newInstancesList: # make new instance font. updateInstance(options, instancePath) # checkoutlinesufo does ufotools.validateLayers() if not options.doOverlapRemoval: for instancePath in newInstancesList: # make sure that that there are no old glyphs left in the # processed glyphs folder validateLayers(instancePath) # The defcon library renames glyphs. Need to fix them again if options.doOverlapRemoval or options.doAutoHint: for instancePath in newInstancesList: if options.doNormalize: normalizeUFO(instancePath, outputPath=None, onlyModified=False, writeModTimes=False)
def build_masters(filename, master_dir, designspace_instance_dir=None, designspace_path=None, family_name=None, propagate_anchors=True, minimize_glyphs_diffs=False, normalize_ufos=False, create_background_layers=False): """Write and return UFOs from the masters and the designspace defined in a .glyphs file. Args: master_dir: Directory where masters are written. designspace_instance_dir: If provided, a designspace document will be written alongside the master UFOs though no instances will be built. family_name: If provided, the master UFOs will be given this name and only instances with this name will be included in the designspace. Returns: A named tuple of master UFOs (`ufos`) and the path to the designspace file (`designspace_path`). """ font = GSFont(filename) if designspace_instance_dir is None: instance_dir = None else: instance_dir = os.path.relpath(designspace_instance_dir, master_dir) designspace = to_designspace(font, family_name=family_name, propagate_anchors=propagate_anchors, instance_dir=instance_dir, minimize_glyphs_diffs=minimize_glyphs_diffs) ufos = [] for source in designspace.sources: ufos.append(source.font) if create_background_layers: ufo_create_background_layer_for_all_glyphs(source.font) ufo_path = os.path.join(master_dir, source.filename) clean_ufo(ufo_path) source.font.save(ufo_path) if normalize_ufos: import ufonormalizer ufonormalizer.normalizeUFO(ufo_path, writeModTimes=False) if not designspace_path: designspace_path = os.path.join(master_dir, designspace.filename) designspace.write(designspace_path) return Masters(ufos, designspace_path)
def run(options): # Set the current dir to the design space dir, so that relative paths in # the design space file will work. dsDir, dsFile = os.path.split(os.path.abspath(options.dsPath)) os.chdir(dsDir) options.dsPath = dsFile dsPath, newInstancesList = readDesignSpaceFile(options) if not dsPath: return if len(newInstancesList) == 1: logger.info("Building 1 instance...") else: logger.info("Building %s instances..." % len(newInstancesList)) ufoProcessorBuild(documentPath=dsPath, outputUFOFormatVersion=options.ufo_version, roundGeometry=(not options.no_round), logger=logger) if (dsPath != options.dsPath) and os.path.exists(dsPath): os.remove(dsPath) logger.info("Built %s instances." % len(newInstancesList)) # Remove glyph.lib and font.lib (except for "public.glyphOrder") for instancePath in newInstancesList: postProcessInstance(instancePath, options) if options.doNormalize: logger.info("Applying UFO normalization...") for instancePath in newInstancesList: normalizeUFO(instancePath, outputPath=None, onlyModified=True, writeModTimes=False) if options.doAutoHint or options.doOverlapRemoval: logger.info("Applying post-processing...") # Apply autohint and checkoutlines, if requested. for instancePath in newInstancesList: # make new instance font. updateInstance(options, instancePath) # checkoutlinesufo does ufotools.validateLayers() if not options.doOverlapRemoval: for instancePath in newInstancesList: # make sure that that there are no old glyphs left in the # processed glyphs folder validateLayers(instancePath) # The defcon library renames glyphs. Need to fix them again if options.doOverlapRemoval or options.doAutoHint: for instancePath in newInstancesList: if options.doNormalize: normalizeUFO(instancePath, outputPath=None, onlyModified=False, writeModTimes=False)
def assertDesignspacesEqual(self, expected, actual, message=""): directory = tempfile.mkdtemp() def git(*args): return subprocess.check_output(["git", "-C", directory] + list(args)) def clean_git_folder(): with os.scandir(directory) as entries: for entry in entries: if entry.is_file() or entry.is_symlink(): os.remove(entry.path) elif entry.is_dir() and entry.name != ".git": shutil.rmtree(entry.path) # Strategy: init a git repo, dump expected, commit, dump actual, diff designspace_filename = os.path.join(directory, "test.designspace") git("init") write_designspace_and_UFOs(expected, designspace_filename) for source in expected.sources: normalize_ufo_lib(source.path) normalizeUFO(source.path, floatPrecision=3, writeModTimes=False) git("add", ".") git("commit", "-m", "expected") clean_git_folder() write_designspace_and_UFOs(actual, designspace_filename) for source in actual.sources: normalize_ufo_lib(source.path) normalizeUFO(source.path, floatPrecision=3, writeModTimes=False) git("add", ".") status = git("status") diff = git( "diff", "--staged", "--src-prefix= original/", "--dst-prefix=roundtrip/" ) if diff: sys.stderr.write(status) sys.stderr.write(diff) self.assertEqual(0, len(diff), message)
def run(options): ds_doc = DesignSpaceDocument.fromfile(options.dsPath) # can still have a successful read but useless DSD (no sources, no # instances, sources non-existent, other conditions validateDesignspaceDoc(ds_doc, options) copy_features = any(src.copyFeatures for src in ds_doc.sources) features_store = {} if not copy_features: # '<features copy="1"/>' is NOT set in any of masters. # Collect the contents of 'features.fea' of any existing # instances so that they can be restored later. features_store = collect_features_content(ds_doc.instances, options.indexList) # Set the current dir to the design space dir, so that relative paths in # the design space file will work. dsDir, dsFile = os.path.split(os.path.abspath(options.dsPath)) os.chdir(dsDir) options.dsPath = dsFile if options.indexList: dsPath = filterDesignspaceInstances(ds_doc, options) else: dsPath = dsFile newInstancesList = [inst.path for inst in ds_doc.instances] newInstancesCount = len(newInstancesList) if newInstancesCount == 1: logger.info("Building 1 instance...") else: logger.info("Building %s instances..." % newInstancesCount) ufoProcessorBuild(documentPath=dsPath, outputUFOFormatVersion=options.ufo_version, roundGeometry=(not options.no_round), logger=logger) if (dsPath != options.dsPath) and os.path.exists(dsPath): os.remove(dsPath) logger.info("Built %s instances." % newInstancesCount) # Remove glyph.lib and font.lib (except for "public.glyphOrder") for instancePath in newInstancesList: postProcessInstance(instancePath, options) if options.doNormalize: logger.info("Applying UFO normalization...") for instancePath in newInstancesList: normalizeUFO(instancePath, outputPath=None, onlyModified=True, writeModTimes=False) if options.doAutoHint or options.doOverlapRemoval: logger.info("Applying post-processing...") # Apply autohint and checkoutlines, if requested. for instancePath in newInstancesList: # make new instance font. updateInstance(instancePath, options) # checkoutlinesufo does ufotools.validateLayers() if not options.doOverlapRemoval: for instancePath in newInstancesList: # make sure that there are no old glyphs left in the # processed glyphs folder validateLayers(instancePath) # The defcon library renames glyphs. Need to fix them again if options.doOverlapRemoval or options.doAutoHint: for instancePath in newInstancesList: if options.doNormalize: normalizeUFO(instancePath, outputPath=None, onlyModified=False, writeModTimes=False) # Restore the contents of the instances' 'features.fea' files for fea_pth, fea_cntnts in features_store.items(): with open(fea_pth, 'w') as fp: fp.write(fea_cntnts)
def build_masters( filename, master_dir, designspace_instance_dir=None, designspace_path=None, family_name=None, propagate_anchors=True, minimize_glyphs_diffs=False, normalize_ufos=False, create_background_layers=False, generate_GDEF=True, store_editor_state=True, write_skipexportglyphs=False, ): """Write and return UFOs from the masters and the designspace defined in a .glyphs file. Args: master_dir: Directory where masters are written. designspace_instance_dir: If provided, a designspace document will be written alongside the master UFOs though no instances will be built. family_name: If provided, the master UFOs will be given this name and only instances with this name will be included in the designspace. Returns: A named tuple of master UFOs (`ufos`) and the path to the designspace file (`designspace_path`). """ font = GSFont(filename) if not os.path.isdir(master_dir): os.mkdir(master_dir) if designspace_instance_dir is None: instance_dir = None else: instance_dir = os.path.relpath(designspace_instance_dir, master_dir) designspace = to_designspace( font, family_name=family_name, propagate_anchors=propagate_anchors, instance_dir=instance_dir, minimize_glyphs_diffs=minimize_glyphs_diffs, generate_GDEF=generate_GDEF, store_editor_state=store_editor_state, write_skipexportglyphs=write_skipexportglyphs, ) # Only write full masters to disk. This assumes that layer sources are always part # of another full master source, which must always be the case in a .glyphs file. ufos = {} for source in designspace.sources: if source.filename in ufos: assert source.font is ufos[source.filename] continue if create_background_layers: ufo_create_background_layer_for_all_glyphs(source.font) ufo_path = os.path.join(master_dir, source.filename) clean_ufo(ufo_path) source.font.save(ufo_path) if normalize_ufos: import ufonormalizer ufonormalizer.normalizeUFO(ufo_path, writeModTimes=False) ufos[source.filename] = source.font if not designspace_path: designspace_path = os.path.join(master_dir, designspace.filename) designspace.write(designspace_path) return Masters(ufos, designspace_path)
import sys import ufonormalizer # The only argument is the base name of the font. No directory or extension. fileName = sys.argv[1] # File paths ttf = os.path.join(os.path.join("..", "fonts", "ttf"), fileName + ".ttf") ufo = os.path.join(os.path.join("..", "sources"), fileName + ".ufo") # Create a new FontForge font object from the TrueType font file font = fontforge.open(ttf) #font.correctReferences() #for glyph in font.glyphs(): # glyph.removeOverlap() # glyph.correctDirection() # glyph.addExtrema() # glyph.simplify() # glyph.round() # Save the font object as a UFO (Unified Font Object) font.generate(ufo) # Close the font object since we are going to normalize it next font.close() # Normalize the UFO without writing modification times ufonormalizer.normalizeUFO(ufo, writeModTimes=False)