def __init__(self, path): self.doc_ = ast.VoltFile() self.glyphs_ = OrderedSymbolTable() self.groups_ = SymbolTable() self.anchors_ = {} # dictionary of SymbolTable() keyed by glyph self.scripts_ = SymbolTable() self.langs_ = SymbolTable() self.lookups_ = SymbolTable() self.next_token_type_, self.next_token_ = (None, None) self.next_token_location_ = None self.make_lexer_(path) self.advance_lexer_()
def exportVoltAnchors(font): glyphOrder = [g.name for g in font] # Save groups and lookups files for each master. for master in font.masters: # Collect anchors that need mkmk lookups (base glyph is a mark). mkmk = set() for glyph in font: layer = glyph.layers[master.name] for anchor in layer.anchors: name = anchor.name if not name.startswith('_') and glyph.openTypeGlyphClass == 3: mkmk.add(name.split('_')[0]) # Collect anchors that need ligature lookups (anchor with index). ligs = set() for glyph in font: layer = glyph.layers[master.name] for anchor in layer.anchors: name = anchor.name if name.startswith('_'): name = name[1:] if '_' in name: ligs.add(name.split('_')[0]) lookups = {} groups = {} anchors = [] for glyph in font: layer = glyph.layers[master.name] for anchor in layer.anchors: x = otRound(anchor.x) y = otRound(anchor.y) if anchor.name.startswith('_'): # Mark anchor. if glyph.openTypeGlyphClass != 3: # Not a mark glyph? Ignore the anchor or VOLT will error. continue name = anchor.name[1:] # Add to groups. We build groups for mark glyphs by anchor. group = f'MARK_{name}' if group not in groups: groups[group] = set() groups[group].add(glyph.name) # mkmk anchors are added to both mark and mkmk lookups # because they might be used with non-mark bases after # anchor propagation. lookupnames = [f'mark_{name}'] if name in mkmk: lookupnames.append(f'mkmk_{name}') if name in ligs: lookupnames.append(f'mark_{name}_ligs') # Add the glyph to respective lookup(s). for lookupname in lookupnames: if lookupname not in lookups: lookups[lookupname] = _attachment_lookup( lookupname) # For mkmk lookups we use individual glyphs, for mark # lookups we use groups. There is no technical reason # for this, just how JH likes it. if lookupname.startswith('mkmk'): to = ([ast.GlyphName(glyph.name)], name) lookups[lookupname].pos.coverage_to.append(to) elif not lookups[lookupname].pos.coverage_to: to = ([ast.GroupName(group, None)], name) lookups[lookupname].pos.coverage_to.append(to) # Add the anchor. name, comp = f'MARK_{name}', 1 pos = ast.Pos(None, x, y, {}, {}, {}) gid = glyphOrder.index(glyph.name) anchors.append( ast.AnchorDefinition(name, gid, glyph.name, comp, False, pos)) else: # Base anchor. name, comp = anchor.name, 1 lookupname = f'mark_{name}' if '_' in name: # Split ligature anchor (e.g. “top_1” and use the # number for ligature component. name, comp = name.split('_') lookupname = f'mark_{name}_ligs' if glyph.openTypeGlyphClass == 3: # If this is a mark glyph, then add to mkmk lookup. lookupname = f'mkmk_{name}' # Add the glyph to respective lookup. if lookupname not in lookups: lookups[lookupname] = _attachment_lookup(lookupname) lookups[lookupname].pos.coverage.add(glyph.name) # Add the anchor. pos = ast.Pos(None, x, y, {}, {}, {}) gid = glyphOrder.index(glyph.name) anchors.append( ast.AnchorDefinition(name, gid, glyph.name, comp, False, pos)) # Save groups file. with open(master.psn + '-anchors.vtg', 'w') as fp: doc = ast.VoltFile() for group in groups: glyphs = tuple(ast.GlyphName(g) for g in sorted(groups[group])) enum = ast.Enum(glyphs) doc.statements.append(ast.GroupDefinition(group, enum)) fp.write(str(doc)) # Save lookups file. with open(master.psn + '-anchors.vtl', 'w') as fp: doc = ast.VoltFile() for lookup in lookups.values(): # Sort coverage by glyph ID to be stable. lookup.pos.coverage = sorted( [ast.GlyphName(g) for g in lookup.pos.coverage], key=lambda g: glyphOrder.index(g.glyph), ) doc.statements.append(lookup) doc.statements += anchors fp.write(str(doc))
def exportVoltKerning(font): # Save groups and lookups files for each master. for master in font.masters: classes = {k.name: k.names for k in master.kerning.classes} pairs = master.kerning.pairs.copy() format1 = _pair_lookup(r'kern\1_PPF1') format2 = _pair_lookup(r'kern\2_PPF2') nullpos = ast.Pos(None, None, None, {}, {}, {}) # Left exceptions filtered = {} for left in pairs: if left.startswith('@'): names = [] for name in classes[left[1:]]: if name in pairs: pairs[name] = {**pairs[left], **pairs[name]} else: names.append(name) filtered[left[1:]] = names # Right exceptions rights = set() for left in pairs: rights.update(pairs[left]) for right in sorted(rights): if right.startswith('@'): names = [] for name in classes[right[1:]]: if name in rights: for left in pairs: if right in pairs[left]: pairs[left][name] = pairs[left][right] else: names.append(name) filtered[right[1:]] = names groups = set() values = {} for left in pairs: for right, value in pairs[left].items(): lookup = format1 if left.startswith('@') and right.startswith('@'): lookup = format2 groups.update([left[1:], right[1:]]) else: if left.startswith('@') and not filtered[left[1:]]: continue if right.startswith('@') and not filtered[right[1:]]: continue if left not in lookup.pos.coverages_1: lookup.pos.coverages_1.append(left) id1 = lookup.pos.coverages_1.index(left) + 1 if right not in lookup.pos.coverages_2: lookup.pos.coverages_2.append(right) id2 = lookup.pos.coverages_2.index(right) + 1 pos = ast.Pos(otRound(float(value)), None, None, {}, {}, {}) lookup.pos.adjust_pair[(id1, id2)] = (pos, nullpos) _warn_overlapping_classes(master, groups) # Save groups file. with open(master.psn + '-kerning.vtg', 'w') as fp: doc = ast.VoltFile() for name in sorted(groups): glyphs = tuple(ast.GlyphName(g) for g in sorted(classes[name])) enum = ast.Enum(glyphs) doc.statements.append(ast.GroupDefinition(f'KERN{name}', enum)) fp.write(str(doc)) # Save lookups file. with open(master.psn + '-kerning.vtl', 'w') as fp: format1.pos.coverages_1 = _kern_coverage(format1.pos.coverages_1, filtered) format1.pos.coverages_2 = _kern_coverage(format1.pos.coverages_2, filtered) format2.pos.coverages_1 = _kern_coverage(format2.pos.coverages_1) format2.pos.coverages_2 = _kern_coverage(format2.pos.coverages_2) doc = ast.VoltFile() doc.statements = [format1, format2] fp.write(str(doc))