def to_ufos(data, include_instances=False, family_name=None, debug=False): """Take .glyphs file data and load it into UFOs. Takes in data as a dictionary structured according to https://github.com/schriftgestalt/GlyphsSDK/blob/master/GlyphsFileFormat.md and returns a list of UFOs, one per master. If debug is True, returns unused input data instead of the resulting UFOs. """ # check that source was generated with at least stable version 2.3 # https://github.com/googlei18n/glyphsLib/pull/65#issuecomment-237158140 if data.pop('.appVersion', 0) < 895: warn('This Glyphs source was generated with an outdated version of ' 'Glyphs. The resulting UFOs may be incorrect.') source_family_name = data.pop('familyName') if family_name is None: family_name = source_family_name feature_prefixes, classes, features = [], [], [] for f in data.get('featurePrefixes', []): feature_prefixes.append( (f.pop('name'), f.pop('code'), f.pop('automatic', None))) for c in data.get('classes', []): classes.append((c.pop('name'), c.pop('code'), c.pop('automatic', None))) for f in data.get('features', []): features.append( (f.pop('name'), f.pop('code'), f.pop('automatic', None), f.pop('disabled', None), f.pop('notes', None))) kerning_groups = {} # stores background data from "associated layers" supplementary_bg_data = [] #TODO(jamesgk) maybe create one font at a time to reduce memory usage ufos, master_id_order = generate_base_fonts(data, family_name) glyph_order = [] for glyph in data['glyphs']: add_glyph_to_groups(kerning_groups, glyph) glyph_name = glyph.pop('glyphname') glyph_order.append(glyph_name) if not re.match(r'^([A-Za-z_][\w.]*|\.notdef)$', glyph_name): warn('Illegal glyph name "%s". If this is used in the font\'s ' 'feature syntax, it could cause errors.' % glyph_name) # pop glyph metadata only once, i.e. not when looping through layers metadata_keys = [ 'unicode', 'color', 'export', 'lastChange', 'leftMetricsKey', 'note', 'production', 'rightMetricsKey', 'widthMetricsKey' ] glyph_data = {k: glyph.pop(k) for k in metadata_keys if k in glyph} for layer in glyph['layers']: layer_id = layer.pop('layerId') layer_name = layer.pop('name', None) assoc_id = layer.pop('associatedMasterId', None) if assoc_id is not None: if layer_name is not None: supplementary_bg_data.append( (assoc_id, glyph_name, layer_name, layer)) continue ufo = ufos[layer_id] glyph = ufo.newGlyph(glyph_name) load_glyph(glyph, layer, glyph_data) for layer_id, glyph_name, bg_name, bg_data in supplementary_bg_data: glyph = ufos[layer_id][glyph_name] set_robofont_glyph_background(glyph, bg_name, bg_data) for ufo in ufos.values(): propagate_font_anchors(ufo) add_features_to_ufo(ufo, feature_prefixes, classes, features) add_groups_to_ufo(ufo, kerning_groups) ufo.lib[PUBLIC_PREFIX + 'glyphOrder'] = glyph_order for master_id, kerning in data.pop('kerning', {}).items(): load_kerning(ufos[master_id], kerning) result = [ufos[master_id] for master_id in master_id_order] instances = { 'defaultFamilyName': source_family_name, 'data': data.pop('instances', []) } if debug: return clear_data(data) elif include_instances: return result, instances return result
def to_ufos(data, include_instances=False, family_name=None, debug=False): """Take .glyphs file data and load it into UFOs. Takes in data as a dictionary structured according to https://github.com/schriftgestalt/GlyphsSDK/blob/master/GlyphsFileFormat.md and returns a list of UFOs, one per master. If debug is True, returns unused input data instead of the resulting UFOs. """ # check that source was generated with at least stable version 2.3 # https://github.com/googlei18n/glyphsLib/pull/65#issuecomment-237158140 if data.pop('.appVersion', 0) < 895: warn('This Glyphs source was generated with an outdated version of ' 'Glyphs. The resulting UFOs may be incorrect.') source_family_name = data.pop('familyName') if family_name is None: family_name = source_family_name feature_prefixes, classes, features = [], [], [] for f in data.get('featurePrefixes', []): feature_prefixes.append((f.pop('name'), f.pop('code'), f.pop('automatic', None))) for c in data.get('classes', []): classes.append((c.pop('name'), c.pop('code'), c.pop('automatic', None))) for f in data.get('features', []): features.append((f.pop('name'), f.pop('code'), f.pop('automatic', None), f.pop('disabled', None), f.pop('notes', None))) kerning_groups = {} # stores background data from "associated layers" supplementary_bg_data = [] #TODO(jamesgk) maybe create one font at a time to reduce memory usage ufos, master_id_order = generate_base_fonts(data, family_name) glyph_order = [] for glyph in data['glyphs']: add_glyph_to_groups(kerning_groups, glyph) glyph_name = glyph.pop('glyphname') glyph_order.append(glyph_name) if not re.match(r'^([A-Za-z_][\w.]*|\.notdef)$', glyph_name): warn('Illegal glyph name "%s". If this is used in the font\'s ' 'feature syntax, it could cause errors.' % glyph_name) # pop glyph metadata only once, i.e. not when looping through layers metadata_keys = ['unicode', 'color', 'export', 'lastChange', 'leftMetricsKey', 'note', 'production', 'rightMetricsKey', 'widthMetricsKey'] glyph_data = {k: glyph.pop(k) for k in metadata_keys if k in glyph} for layer in glyph['layers']: layer_id = layer.pop('layerId') layer_name = layer.pop('name', None) assoc_id = layer.pop('associatedMasterId', None) if assoc_id is not None: if layer_name is not None: supplementary_bg_data.append( (assoc_id, glyph_name, layer_name, layer)) continue ufo = ufos[layer_id] glyph = ufo.newGlyph(glyph_name) load_glyph(glyph, layer, glyph_data) for layer_id, glyph_name, bg_name, bg_data in supplementary_bg_data: glyph = ufos[layer_id][glyph_name] set_glyph_background(glyph, bg_name, bg_data) for ufo in ufos.values(): propagate_font_anchors(ufo) add_features_to_ufo(ufo, feature_prefixes, classes, features) add_groups_to_ufo(ufo, kerning_groups) ufo.lib[PUBLIC_PREFIX + 'glyphOrder'] = glyph_order for master_id, kerning in data.pop('kerning', {}).items(): load_kerning(ufos[master_id], kerning) result = [ufos[master_id] for master_id in master_id_order] instances = {'defaultFamilyName': source_family_name, 'data': data.pop('instances', [])} if debug: return clear_data(data) elif include_instances: return result, instances return result
def to_ufos(data, include_instances=False, family_name=None, debug=False): """Take .glyphs file data and load it into UFOs. Takes in data as a dictionary structured according to https://github.com/schriftgestalt/GlyphsSDK/blob/master/GlyphsFileFormat.md and returns a list of UFOs, one per master. If debug is True, returns unused input data instead of the resulting UFOs. """ # check that source was generated with at least stable version 2.3 # https://github.com/googlei18n/glyphsLib/pull/65#issuecomment-237158140 if data.pop('.appVersion', 0) < 895: logger.warn( 'This Glyphs source was generated with an outdated version ' 'of Glyphs. The resulting UFOs may be incorrect.') source_family_name = data.pop('familyName') if family_name is None: family_name = source_family_name feature_prefixes, classes, features = [], [], [] for f in data.get('featurePrefixes', []): feature_prefixes.append( (f.pop('name'), f.pop('code'), f.pop('automatic', None))) for c in data.get('classes', []): classes.append((c.pop('name'), c.pop('code'), c.pop('automatic', None))) for f in data.get('features', []): features.append( (f.pop('name'), f.pop('code'), f.pop('automatic', None), f.pop('disabled', None), f.pop('notes', None))) kerning_groups = {} # stores background data from "associated layers" supplementary_bg_data = [] #TODO(jamesgk) maybe create one font at a time to reduce memory usage ufos, master_id_order = generate_base_fonts(data, family_name) # get the 'glyphOrder' custom parameter as stored in the lib.plist. # We assume it's the same for all ufos. first_ufo = ufos[master_id_order[0]] glyphOrder_key = PUBLIC_PREFIX + 'glyphOrder' if glyphOrder_key in first_ufo.lib: glyph_order = first_ufo.lib[glyphOrder_key] else: glyph_order = [] sorted_glyphset = set(glyph_order) for glyph in data['glyphs']: add_glyph_to_groups(kerning_groups, glyph) glyph_name = glyph.pop('glyphname') if glyph_name not in sorted_glyphset: # glyphs not listed in the 'glyphOrder' custom parameter but still # in the font are appended after the listed glyphs, in the order # in which they appear in the source file glyph_order.append(glyph_name) # pop glyph metadata only once, i.e. not when looping through layers metadata_keys = [ 'unicode', 'color', 'export', 'lastChange', 'leftMetricsKey', 'note', 'production', 'rightMetricsKey', 'widthMetricsKey', 'category', 'subCategory' ] glyph_data = {k: glyph.pop(k) for k in metadata_keys if k in glyph} for layer in glyph['layers']: layer_id = layer.pop('layerId') layer_name = layer.pop('name', None) assoc_id = layer.pop('associatedMasterId', None) if assoc_id is not None: if layer_name is not None: supplementary_bg_data.append( (assoc_id, glyph_name, layer_name, layer)) continue ufo = ufos[layer_id] glyph = ufo.newGlyph(glyph_name) load_glyph(glyph, layer, glyph_data) for layer_id, glyph_name, bg_name, bg_data in supplementary_bg_data: glyph = ufos[layer_id][glyph_name] set_robofont_glyph_background(glyph, bg_name, bg_data) for ufo in ufos.values(): ufo.lib[glyphOrder_key] = glyph_order propagate_font_anchors(ufo) add_features_to_ufo(ufo, feature_prefixes, classes, features) add_groups_to_ufo(ufo, kerning_groups) for master_id, kerning in data.pop('kerning', {}).items(): load_kerning(ufos[master_id], kerning) result = [ufos[master_id] for master_id in master_id_order] instances = { 'defaultFamilyName': source_family_name, 'data': data.pop('instances', []) } # the 'Variation Font Origin' is a font-wide custom parameter, thus it is # shared by all the master ufos; here we just get it from the first one varfont_origin_key = "Variation Font Origin" varfont_origin = first_ufo.lib.get(GLYPHS_PREFIX + varfont_origin_key) if varfont_origin: instances[varfont_origin_key] = varfont_origin if debug: return clear_data(data) elif include_instances: return result, instances return result
def merge(args): """Merges Arabic and Latin fonts together, and messages the combined font a bit. Returns the combined font.""" ufo = Font(args.arabicfile) propagate_font_anchors(ufo) latin = Font(args.latinfile) # Parse the GlyphOrderAndAliasDB file for Unicode values and production # glyph names of the Latin glyphs. goadb = GOADBParser( os.path.dirname(args.latinfile) + "/../GlyphOrderAndAliasDB") ufo.lib[POSTSCRIPT_NAMES] = {} # Save original glyph order, used below. glyphOrder = ufo.glyphOrder + latin.glyphOrder # Generate production glyph names for Arabic glyphs, in case it differs # from working names. This will be used by ufo2ft to set the final glyph # names in the font file. for glyph in ufo: if glyph.unicode is not None: if glyph.unicode < 0xffff: postName = "uni%04X" % glyph.unicode else: postName = "u%06X" % glyph.unicode if postName != glyph.name: ufo.lib[POSTSCRIPT_NAMES][glyph.name] = postName # Populate the font’s feature text, we keep our main feature file out of # the UFO to share it between the fonts. features = ufo.features with open(args.feature_file) as feafile: fea = feafile.read() # Set Latin language system, ufo2ft will use it when generating kern # feature. features.text += fea.replace("#{languagesystems}", "languagesystem latn dflt;") features.text += generateStyleSets(ufo) for glyph in latin: if glyph.name in goadb.encodings: glyph.unicode = goadb.encodings[glyph.name] # Source Sans Pro has different advance widths for space and NBSP # glyphs, fix it. latin["nbspace"].width = latin["space"].width glyphs, components = collectGlyphs(latin, args.latin_subset) counter = Counter(components) uniqueComponents = set() for name in counter: if name not in glyphs: latin[name].unicode = None if counter[name] == 1: uniqueComponents.add(name) else: glyphs.add(name) # Set Latin production names ufo.lib[POSTSCRIPT_NAMES].update(goadb.names) # Copy Latin glyphs. for name in glyphs: glyph = latin[name] for component in glyph.components: if component.baseGlyph in uniqueComponents: glyph.decomposeComponent(component) # Remove anchors from spacing marks, otherwise ufo2ft will give them # mark glyph class which will cause HarfBuzz to zero their width. if glyph.unicode and unicodedata.category(unichr( glyph.unicode)) in ("Sk", "Lm"): for anchor in glyph.anchors: glyph.removeAnchor(anchor) # Add Arabic anchors to the dotted circle, we use an offset of 100 # units because the Latin anchors are too close to the glyph. offset = 100 if glyph.unicode == 0x25CC: for anchor in glyph.anchors: if anchor.name == "aboveLC": glyph.appendAnchor( dict(name="markAbove", x=anchor.x, y=anchor.y + offset)) glyph.appendAnchor( dict(name="hamzaAbove", x=anchor.x, y=anchor.y + offset)) if anchor.name == "belowLC": glyph.appendAnchor( dict(name="markBelow", x=anchor.x, y=anchor.y - offset)) glyph.appendAnchor( dict(name="hamzaBelow", x=anchor.x, y=anchor.y - offset)) # Break loudly if we have duplicated glyph in Latin and Arabic. # TODO should check duplicated Unicode values as well assert glyph.name not in ufo, glyph.name ufo.insertGlyph(glyph) # Copy kerning and groups. ufo.groups.update(latin.groups) ufo.kerning.update(latin.kerning) # We don’t set these in the Arabic font, so we just copy the Latin’s. for attr in ("xHeight", "capHeight"): value = getattr(latin.info, attr) if value is not None: setattr(ufo.info, attr, getattr(latin.info, attr)) # MutatorMath does not like multiple unicodes and will drop it entirely, # turning the glyph unencoded: # https://github.com/LettError/MutatorMath/issues/85 for glyph in ufo: assert not " " in glyph.unicodes # Make sure we don’t have glyphs with the same unicode value unicodes = [] for glyph in ufo: unicodes.extend(glyph.unicodes) duplicates = set([u for u in unicodes if unicodes.count(u) > 1]) assert len(duplicates) == 0, "Duplicate unicodes: %s " % ( ["%04X" % d for d in duplicates]) # Make sure we have a fixed glyph order by using the original Arabic and # Latin glyph order, not whatever we end up with after adding glyphs. ufo.glyphOrder = sorted(ufo.glyphOrder, key=glyphOrder.index) return ufo