def test_substitution_single_in_contexts(self): parser = self.parser( 'DEF_GROUP "Hebrew" ENUM GLYPH "uni05D0" GLYPH "uni05D1" ' 'END_ENUM END_GROUP\n' 'DEF_LOOKUP "HebrewCurrency" PROCESS_BASE PROCESS_MARKS ALL ' 'DIRECTION LTR\n' 'IN_CONTEXT\n' 'RIGHT GROUP "Hebrew"\n' 'RIGHT GLYPH "one.Hebr"\n' 'END_CONTEXT\n' 'IN_CONTEXT\n' 'LEFT GROUP "Hebrew"\n' 'LEFT GLYPH "one.Hebr"\n' 'END_CONTEXT\n' 'AS_SUBSTITUTION\n' 'SUB GLYPH "dollar"\n' 'WITH GLYPH "dollar.Hebr"\n' 'END_SUB\n' 'END_SUBSTITUTION') [group, lookup] = parser.parse().statements context1 = lookup.context[0] context2 = lookup.context[1] self.assertEqual( (lookup.name, context1.ex_or_in, context1.left, context1.right, context2.ex_or_in, context2.left, context2.right), ("HebrewCurrency", "IN_CONTEXT", [], [ ast.Enum([ast.GroupName("Hebrew", parser)]), self.enum(["one.Hebr"]) ], "IN_CONTEXT", [ ast.Enum([ast.GroupName("Hebrew", parser)]), self.enum(["one.Hebr"]) ], []))
def test_def_group_groups_not_yet_defined(self): parser = self.parser( 'DEF_GROUP "Group1"\n' 'ENUM GLYPH "a" GLYPH "b" GLYPH "c" GLYPH "d" END_ENUM\n' 'END_GROUP\n' 'DEF_GROUP "TestGroup1"\n' 'ENUM GROUP "Group1" GROUP "Group2" END_ENUM\n' 'END_GROUP\n' 'DEF_GROUP "TestGroup2"\n' 'ENUM GROUP "Group2" END_ENUM\n' 'END_GROUP\n' 'DEF_GROUP "TestGroup3"\n' 'ENUM GROUP "Group2" GROUP "Group1" END_ENUM\n' 'END_GROUP\n' 'DEF_GROUP "Group2"\n' 'ENUM GLYPH "e" GLYPH "f" GLYPH "g" GLYPH "h" END_ENUM\n' 'END_GROUP\n') [group1, test_group1, test_group2, test_group3, group2] = \ parser.parse().statements self.assertEqual((test_group1.name, test_group1.enum), ("TestGroup1", ast.Enum([ ast.GroupName("Group1", parser), ast.GroupName("Group2", parser) ]))) self.assertEqual( (test_group2.name, test_group2.enum), ("TestGroup2", ast.Enum([ast.GroupName("Group2", parser)]))) self.assertEqual((test_group3.name, test_group3.enum), ("TestGroup3", ast.Enum([ ast.GroupName("Group2", parser), ast.GroupName("Group1", parser) ])))
def test_substitution_single_in_context(self): parser = self.parser( 'DEF_GROUP "Denominators" ENUM GLYPH "one.dnom" GLYPH "two.dnom" ' 'END_ENUM END_GROUP\n' 'DEF_LOOKUP "fracdnom" PROCESS_BASE PROCESS_MARKS ALL ' 'DIRECTION LTR\n' 'IN_CONTEXT LEFT ENUM GROUP "Denominators" GLYPH "fraction" ' 'END_ENUM\n' 'END_CONTEXT\n' 'AS_SUBSTITUTION\n' 'SUB GLYPH "one"\n' 'WITH GLYPH "one.dnom"\n' 'END_SUB\n' 'SUB GLYPH "two"\n' 'WITH GLYPH "two.dnom"\n' 'END_SUB\n' 'END_SUBSTITUTION') [group, lookup] = parser.parse().statements context = lookup.context[0] self.assertEqual((lookup.name, list(lookup.sub.mapping.items()), context.ex_or_in, context.left, context.right), ("fracdnom", [ (self.enum(["one"]), self.enum(["one.dnom"])), (self.enum(["two"]), self.enum(["two.dnom"])) ], "IN_CONTEXT", [ ast.Enum([ ast.GroupName("Denominators", parser=parser), ast.GlyphName("fraction") ]) ], []))
def test_def_group_groups(self): parser = self.parser( 'DEF_GROUP "Group1"\n' 'ENUM GLYPH "a" GLYPH "b" GLYPH "c" GLYPH "d" END_ENUM\n' 'END_GROUP\n' 'DEF_GROUP "Group2"\n' 'ENUM GLYPH "e" GLYPH "f" GLYPH "g" GLYPH "h" END_ENUM\n' 'END_GROUP\n' 'DEF_GROUP "TestGroup"\n' 'ENUM GROUP "Group1" GROUP "Group2" END_ENUM\n' 'END_GROUP\n') [group1, group2, test_group] = parser.parse().statements self.assertEqual((test_group.name, test_group.enum), ("TestGroup", ast.Enum([ ast.GroupName("Group1", parser), ast.GroupName("Group2", parser) ])))
def _kern_coverage(names, classes=None): ret = [] for name in names: if name.startswith('@'): name = name[1:] if classes is not None: glyphs = tuple(ast.GlyphName(g) for g in sorted(classes[name])) ret.append([ast.Enum(glyphs)]) else: ret.append([ast.GroupName(f'KERN{name}', None)]) else: ret.append([ast.GlyphName(name)]) return ret
def parse_coverage_(self): coverage = [] location = self.cur_token_location_ while self.next_token_ in ("GLYPH", "GROUP", "RANGE", "ENUM"): if self.next_token_ == "ENUM": enum = self.parse_enum_() coverage.append(enum) elif self.next_token_ == "GLYPH": self.expect_keyword_("GLYPH") name = self.expect_string_() coverage.append(ast.GlyphName(name, location=location)) elif self.next_token_ == "GROUP": self.expect_keyword_("GROUP") name = self.expect_string_() coverage.append(ast.GroupName(name, self, location=location)) elif self.next_token_ == "RANGE": self.expect_keyword_("RANGE") start = self.expect_string_() self.expect_keyword_("TO") end = self.expect_string_() coverage.append(ast.Range(start, end, self, location=location)) return tuple(coverage)
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))