def open(self, use_hash_map): font_path = self.font_path if self.font_format == 'UFO': self.font_type = UFO_FONT_TYPE ufotools.validateLayers(font_path) self.defcon_font = defcon.Font(font_path) self.ufo_format = self.defcon_font.ufoFormatVersionTuple self.use_hash_map = use_hash_map self.ufo_font_hash_data = ufotools.UFOFontData( font_path, self.use_hash_map, programName=ufotools.kCheckOutlineName) self.ufo_font_hash_data.readHashMap() else: print("Converting to temp UFO font...") self.temp_ufo_path = temp_path = get_temp_dir_path('font.ufo') if not run_shell_command(['tx', '-ufo', font_path, temp_path]): raise FocusFontError('Failed to convert input font to UFO.') try: self.defcon_font = defcon.Font(temp_path) except UFOLibError: raise if self.font_format == 'OTF': self.font_type = OTF_FONT_TYPE elif self.font_format == 'CFF': self.font_type = CFF_FONT_TYPE else: self.font_type = TYPE1_FONT_TYPE return self.defcon_font
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 open(self, use_hash_map): font_path = self.font_path try: ufotools.validateLayers(font_path) self.defcon_font = defcon.Font(font_path) self.ufo_format = self.defcon_font.ufoFormatVersion if self.ufo_format < 2: self.ufo_format = 2 self.font_type = UFO_FONT_TYPE self.use_hash_map = use_hash_map self.ufo_font_hash_data = ufotools.UFOFontData( font_path, self.use_hash_map, programName=ufotools.kCheckOutlineName) self.ufo_font_hash_data.readHashMap() except ufoLib.UFOLibError as e: if (not os.path.isdir(font_path)) \ and "metainfo.plist is missing" in e.message: # It was a file, but not a UFO font. # Try converting to UFO font, and try again. print("converting to temp UFO font...") self.temp_ufo_path = temp_path = font_path + ".temp.ufo" if os.path.exists(temp_path): shutil.rmtree(temp_path) cmd = "tx -ufo \"%s\" \"%s\"" % (font_path, temp_path) subprocess.call(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if os.path.exists(temp_path): try: self.defcon_font = defcon.Font(temp_path) except ufoLib.UFOLibError: return # It must be a font file! self.temp_ufo_path = temp_path # figure out font type. try: ff = open(font_path, "rb") data = ff.read(10) ff.close() except (IOError, OSError): return if data[:4] == "OTTO": # it is an OTF font. self.font_type = OPENTYPE_CFF_FONT_TYPE elif (data[0] == '\1') and (data[1] == '\0'): # CFF file self.font_type = CFF_FONT_TYPE elif "%" in data: self.font_type = TYPE1_FONT_TYPE else: print('Font type is unknown: ' 'will not be able to save changes') else: raise e return self.defcon_font
def open(self, use_hash_map): font_path = self.font_path try: ufotools.validateLayers(font_path) self.defcon_font = defcon.Font(font_path) self.ufo_format = self.defcon_font.ufoFormatVersion if self.ufo_format < 2: self.ufo_format = 2 self.font_type = UFO_FONT_TYPE self.use_hash_map = use_hash_map self.ufo_font_hash_data = ufotools.UFOFontData( font_path, self.use_hash_map, programName=ufotools.kCheckOutlineName) self.ufo_font_hash_data.readHashMap() except ufoLib.UFOLibError as e: if (not os.path.isdir(font_path)) \ and "metainfo.plist is missing" in e.message: # It was a file, but not a UFO font. # Try converting to UFO font, and try again. print("converting to temp UFO font...") self.temp_ufo_path = temp_path = font_path + ".temp.ufo" if os.path.exists(temp_path): shutil.rmtree(temp_path) cmd = "tx -ufo \"%s\" \"%s\"" % (font_path, temp_path) subprocess.call( cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if os.path.exists(temp_path): try: self.defcon_font = defcon.Font(temp_path) except ufoLib.UFOLibError: return # It must be a font file! self.temp_ufo_path = temp_path # figure out font type. try: ff = open(font_path, "rb") data = ff.read(10) ff.close() except (IOError, OSError): return if data[:4] == "OTTO": # it is an OTF font. self.font_type = OPENTYPE_CFF_FONT_TYPE elif (data[0] == '\1') and (data[1] == '\0'): # CFF file self.font_type = CFF_FONT_TYPE elif "%" in data: self.font_type = TYPE1_FONT_TYPE else: print('Font type is unknown: ' 'will not be able to save changes') else: raise e return self.defcon_font
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 run(args=None): options = get_options(args) font_path = os.path.abspath(options.file_path) font_file = FontFile(font_path) defcon_font = font_file.open(options.allow_changes) # We allow use of a hash map to skip glyphs only if fixing glyphs if options.clear_hash_map: font_file.clear_hash_map() return if defcon_font is None: print("Could not open file: %s." % font_path) return if not options.glyph_list: glyph_list = list(defcon_font.keys()) else: if not defcon_font.glyphOrder: raise FocusFontError( "Error: public.glyphOrder is empty or missing " "from lib.plist file of %s" % os.path.abspath(options.file_path)) else: glyph_list = filter_glyph_list(options.glyph_list, defcon_font.glyphOrder, options.file_path) if not glyph_list: raise FocusFontError( "Error: selected glyph list is empty for font <%s>." % options.file_path) if not options.write_to_default_layer: try: processed_layer = defcon_font.layers[PROCD_GLYPHS_LAYER_NAME] except KeyError: processed_layer = defcon_font.newLayer(PROCD_GLYPHS_LAYER_NAME) else: processed_layer = None font_file.save_to_default_layer = True font_changed = False last_had_msg = False seen_glyph_count = 0 processed_glyph_count = 0 for glyph_name in sorted(glyph_list): changed = False seen_glyph_count += 1 msg = [] if glyph_name not in defcon_font: continue # font_file.check_skip_glyph updates the hash map for the glyph, # so we call it even when the '-all' option is used. skip = font_file.check_skip_glyph(glyph_name, options.check_all) # Note: this will delete glyphs from the processed layer, # if the glyph hash has changed. if skip: continue processed_glyph_count += 1 defcon_glyph = defcon_font[glyph_name] if defcon_glyph.components: defcon_glyph.decomposeAllComponents() new_glyph = booleanOperations.booleanGlyph.BooleanGlyph(defcon_glyph) if len(new_glyph) == 0: # Complain about empty glyph only if it is not a space glyph. if not RE_SPACE_PATTERN.search(glyph_name): msg = ["has no contours"] else: msg = [] else: for test in options.test_list: if test is not None: new_glyph, changed, msg = \ test(new_glyph, changed, msg, options) if not options.quiet_mode: if len(msg) == 0: if last_had_msg: print() print('.', end='') last_had_msg = False else: print(os.linesep + glyph_name, ' '.join(msg), end='') last_had_msg = True if changed and options.allow_changes: font_changed = True original_contours = list(defcon_glyph) font_file.update_hash_entry(glyph_name, changed) if options.write_to_default_layer: fixed_glyph = defcon_glyph fixed_glyph.clearContours() else: # this will replace any pre-existing glyph: processed_layer.newGlyph(glyph_name) fixed_glyph = processed_layer[glyph_name] fixed_glyph.width = defcon_glyph.width fixed_glyph.height = defcon_glyph.height fixed_glyph.unicodes = defcon_glyph.unicodes point_pen = fixed_glyph.getPointPen() new_glyph.drawPoints(point_pen) if options.allow_decimal_coords: for contour in fixed_glyph: for point in contour: point.x = round(point.x, 3) point.y = round(point.y, 3) else: for contour in fixed_glyph: for point in contour: point.x = int(round(point.x)) point.y = int(round(point.y)) restore_contour_order(fixed_glyph, original_contours) # The following is needed when the script is called from another # script with Popen(): sys.stdout.flush() # update layer plist: the hash check call may have deleted processed layer # glyphs because the default layer glyph is newer. # At this point, we may have deleted glyphs in the # processed layer.writer.getGlyphSet() # will fail unless we update the contents.plist file to match. if options.allow_changes: ufotools.validateLayers(font_path, False) if not font_changed: # Even if the program didn't change any glyphs, # we should still save updates to the src glyph hash file. font_file.close() else: font_file.save() if processed_glyph_count != seen_glyph_count: print("Skipped %s of %s glyphs." % (seen_glyph_count - processed_glyph_count, seen_glyph_count)) if not options.quiet_mode: print("Done with font")
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 run(args=None): options = get_options(args) font_path = options.font_path font_format = options.font_format font_file = FontFile(font_path, font_format) use_hash_map = True if font_format == 'UFO' else False defcon_font = font_file.open(use_hash_map) if font_format == 'PFC': font_format = 'UFO' # We allow use of a hash map to skip glyphs only if fixing glyphs if options.clear_hash_map: font_file.clear_hash_map() return if defcon_font is None: print("Could not open file: %s." % font_path) return if not options.glyph_list: glyph_list = list(defcon_font.keys()) else: if not defcon_font.glyphOrder: raise FocusFontError( "Error: public.glyphOrder is empty or missing " "from lib.plist file of %s" % font_path) else: glyph_list = filter_glyph_list(options.glyph_list, defcon_font.glyphOrder, font_path) if not glyph_list: raise FocusFontError( "Error: selected glyph list is empty for font <%s>." % font_path) if (font_format == 'UFO') and (not options.write_to_default_layer): try: processed_layer = defcon_font.layers[PROCD_GLYPHS_LAYER_NAME] except KeyError: processed_layer = defcon_font.newLayer(PROCD_GLYPHS_LAYER_NAME) else: processed_layer = None font_file.save_to_default_layer = True font_changed = False seen_glyph_count = 0 processed_glyph_count = 0 max_length = max([len(g) for g in glyph_list]) fmt = '{:<%d}' % max_length glyph_list = sorted(glyph_list) list_length = len(glyph_list) with trange(list_length) as t: for i in t: glyph_name = glyph_list[i] t.set_description('Checking outlines for %s' % fmt.format(glyph_name)) changed = False seen_glyph_count += 1 msg = [] if glyph_name not in defcon_font: continue # font_file.check_skip_glyph updates the hash map for the glyph, # so we call it even when the '-all' option is used. skip = font_file.check_skip_glyph(glyph_name, options.check_all) # Note: this will delete glyphs from the processed layer, # if the glyph hash has changed. if skip: continue processed_glyph_count += 1 defcon_glyph = defcon_font[glyph_name] if defcon_glyph.components: defcon_glyph.decomposeAllComponents() new_glyph = booleanOperations.booleanGlyph.BooleanGlyph( defcon_glyph) if len(new_glyph) == 0: # Complain about empty glyph only if it is not a space glyph. if not RE_SPACE_PATTERN.search(glyph_name): msg = ["has no contours"] else: msg = [] else: for test in options.test_list: if test is not None: new_glyph, changed, msg = \ test(new_glyph, changed, msg, options) if not options.quiet_mode: if msg: tqdm.write('%s %s' % (glyph_name, ' '.join(msg))) if changed and options.allow_changes: font_changed = True original_contours = list(defcon_glyph) if font_file.save_to_default_layer: fixed_glyph = defcon_glyph fixed_glyph.clearContours() else: # this will replace any pre-existing glyph: processed_layer.newGlyph(glyph_name) fixed_glyph = processed_layer[glyph_name] fixed_glyph.width = defcon_glyph.width fixed_glyph.height = defcon_glyph.height fixed_glyph.unicodes = defcon_glyph.unicodes point_pen = fixed_glyph.getPointPen() new_glyph.drawPoints(point_pen) if options.allow_decimal_coords: for contour in fixed_glyph: for point in contour: point.x = round(point.x, 3) point.y = round(point.y, 3) else: for contour in fixed_glyph: for point in contour: point.x = int(round(point.x)) point.y = int(round(point.y)) # JH May 2020: remove overlap can leave some coincident points # we use thresholdAttrGlyph (modified thresholdPen) to remove # them prior to restore_contour_order. thresholdAttrGlyph(fixed_glyph, 1) if not options.ignore_contour_order: restore_contour_order(fixed_glyph, original_contours) # The following is needed when the script is called from another # script with Popen(): sys.stdout.flush() if i == list_length - 1: t.set_description("Finished checkoutlinesufo") # update layer plist: the hash check call may have deleted processed layer # glyphs because the default layer glyph is newer. # At this point, we may have deleted glyphs in the # processed layer.writer.getGlyphSet() # will fail unless we update the contents.plist file to match. if options.allow_changes: ufotools.validateLayers(font_path, False) if not font_changed: # Even if the program didn't change any glyphs, # we should still save updates to the src glyph hash file. font_file.close() else: font_file.save() if processed_glyph_count != seen_glyph_count: print("Skipped %s of %s glyphs." % (seen_glyph_count - processed_glyph_count, seen_glyph_count)) if not options.quiet_mode: print("Done with font")
def run(args=None): options = get_options(args) font_path = os.path.abspath(options.file_path) font_file = FontFile(font_path) defcon_font = font_file.open(options.allow_changes) # We allow use of a hash map to skip glyphs only if fixing glyphs if options.clear_hash_map: font_file.clear_hash_map() return if defcon_font is None: print("Could not open file: %s." % font_path) return if not options.glyph_list: glyph_list = list(defcon_font.keys()) else: if not defcon_font.glyphOrder: raise FocusFontError( "Error: public.glyphOrder is empty or missing " "from lib.plist file of %s" % os.path.abspath(options.file_path)) else: glyph_list = filter_glyph_list( options.glyph_list, defcon_font.glyphOrder, options.file_path) if not glyph_list: raise FocusFontError( "Error: selected glyph list is empty for font <%s>." % options.file_path) if not options.write_to_default_layer: try: processed_layer = defcon_font.layers[PROCD_GLYPHS_LAYER_NAME] except KeyError: processed_layer = defcon_font.newLayer(PROCD_GLYPHS_LAYER_NAME) else: processed_layer = None font_file.save_to_default_layer = True font_changed = False last_had_msg = False seen_glyph_count = 0 processed_glyph_count = 0 for glyph_name in sorted(glyph_list): changed = False seen_glyph_count += 1 msg = [] if glyph_name not in defcon_font: continue # font_file.check_skip_glyph updates the hash map for the glyph, # so we call it even when the '-all' option is used. skip = font_file.check_skip_glyph(glyph_name, options.check_all) # Note: this will delete glyphs from the processed layer, # if the glyph hash has changed. if skip: continue processed_glyph_count += 1 defcon_glyph = defcon_font[glyph_name] if defcon_glyph.components: defcon_glyph.decomposeAllComponents() new_glyph = booleanOperations.booleanGlyph.BooleanGlyph(defcon_glyph) if len(new_glyph) == 0: # Complain about empty glyph only if it is not a space glyph. if not RE_SPACE_PATTERN.search(glyph_name): msg = ["has no contours"] else: msg = [] else: for test in options.test_list: if test is not None: new_glyph, changed, msg = \ test(new_glyph, changed, msg, options) if not options.quiet_mode: if len(msg) == 0: if last_had_msg: print() print('.', end='') last_had_msg = False else: print(os.linesep + glyph_name, ' '.join(msg), end='') last_had_msg = True if changed and options.allow_changes: font_changed = True original_contours = list(defcon_glyph) if options.write_to_default_layer: fixed_glyph = defcon_glyph fixed_glyph.clearContours() else: # this will replace any pre-existing glyph: processed_layer.newGlyph(glyph_name) fixed_glyph = processed_layer[glyph_name] fixed_glyph.width = defcon_glyph.width fixed_glyph.height = defcon_glyph.height fixed_glyph.unicodes = defcon_glyph.unicodes point_pen = fixed_glyph.getPointPen() new_glyph.drawPoints(point_pen) if options.allow_decimal_coords: for contour in fixed_glyph: for point in contour: point.x = round(point.x, 3) point.y = round(point.y, 3) else: for contour in fixed_glyph: for point in contour: point.x = int(round(point.x)) point.y = int(round(point.y)) restore_contour_order(fixed_glyph, original_contours) # The following is needed when the script is called from another # script with Popen(): sys.stdout.flush() # update layer plist: the hash check call may have deleted processed layer # glyphs because the default layer glyph is newer. # At this point, we may have deleted glyphs in the # processed layer.writer.getGlyphSet() # will fail unless we update the contents.plist file to match. if options.allow_changes: ufotools.validateLayers(font_path, False) if not font_changed: # Even if the program didn't change any glyphs, # we should still save updates to the src glyph hash file. font_file.close() else: font_file.save() if processed_glyph_count != seen_glyph_count: print("Skipped %s of %s glyphs." % (seen_glyph_count - processed_glyph_count, seen_glyph_count)) if not options.quiet_mode: print("Done with font")