diff --git a/Lib/fontTools/feaLib/builder.py b/Lib/fontTools/feaLib/builder.py index 0e2c2aeb4..555f44f88 100644 --- a/Lib/fontTools/feaLib/builder.py +++ b/Lib/fontTools/feaLib/builder.py @@ -305,8 +305,13 @@ class Builder(object): lookup.mapping[from_glyph] = to_glyph def add_pair_pos(self, location, enumerated, - glyph1, value1, glyph2, value2): - pass # TODO: Implement. + glyphclass1, value1, glyphclass2, value2): + lookup = self.get_lookup_(location, PairPosBuilder) + if enumerated: + for glyph in glyphclass1: + lookup.add_pair(location, {glyph}, value1, glyphclass2, value2) + else: + lookup.add_pair(location, glyphclass1, value1, glyphclass2, value2) def add_single_pos(self, location, glyph, valuerecord): lookup = self.get_lookup_(location, SinglePosBuilder) @@ -341,6 +346,8 @@ def _makeOpenTypeDeviceTable(deviceTable, device): def makeOpenTypeValueRecord(v): """ast.ValueRecord --> (otBase.ValueRecord, int ValueFormat)""" + if v is None: + return None, 0 vr = otBase.ValueRecord() if v.xPlacement: vr.XPlacement = v.xPlacement @@ -533,6 +540,71 @@ class MultipleSubstBuilder(LookupBuilder): return lookup +class PairPosBuilder(LookupBuilder): + def __init__(self, font, location, lookup_flag): + LookupBuilder.__init__(self, font, location, 'GPOS', 2, lookup_flag) + self.pairs = {} # (gc1, gc2) -> (location, value1, value2) + + def add_pair(self, location, glyphclass1, value1, glyphclass2, value2): + gc1 = tuple(sorted(glyphclass1, key=self.font.getGlyphID)) + gc2 = tuple(sorted(glyphclass2, key=self.font.getGlyphID)) + oldValue = self.pairs.get((gc1, gc2), None) + if oldValue is not None: + otherLoc, _, _ = oldValue + raise FeatureLibError( + 'Already defined position for pair [%s] [%s] at %s:%d:%d' + % (' '.join(gc1), ' '.join(gc2), + otherLoc[0], otherLoc[1], otherLoc[2]), + location) + self.pairs[(gc1, gc2)] = (location, value1, value2) + + def equals(self, other): + return (LookupBuilder.equals(self, other) and + self.pairs == other.pairs) + + def build(self): + subtables = [] + + # (valueFormat1, valueFormat2) --> [(glyph1, glyph2, value1, value2)*] + format1 = {} + for (gc1, gc2), (location, value1, value2) in self.pairs.items(): + if len(gc1) == 1 and len(gc2) == 1: + val1, valFormat1 = makeOpenTypeValueRecord(value1) + val2, valFormat2 = makeOpenTypeValueRecord(value2) + format1.setdefault(((valFormat1, valFormat2)), []).append( + (gc1[0], gc2[0], val1, val2)) + for (vf1, vf2), pairs in sorted(format1.items()): + p = {} + for glyph1, glyph2, val1, val2 in pairs: + p.setdefault(glyph1, []).append((glyph2, val1, val2)) + st = otTables.PairPos() + subtables.append(st) + st.Format = 1 + st.ValueFormat1, st.ValueFormat2 = vf1, vf2 + st.Coverage = otTables.Coverage() + st.Coverage.glyphs = sorted(p.keys(), key=self.font.getGlyphID) + st.PairSet = [] + for glyph in st.Coverage.glyphs: + ps = otTables.PairSet() + ps.PairValueRecord = [] + st.PairSet.append(ps) + for glyph2, val1, val2 in sorted( + p[glyph], key=lambda x: self.font.getGlyphID(x[0])): + pvr = otTables.PairValueRecord() + pvr.SecondGlyph = glyph2 + pvr.Value1, pvr.Value2 = val1, val2 + ps.PairValueRecord.append(pvr) + ps.PairValueCount = len(ps.PairValueRecord) + st.PairSetCount = len(st.PairSet) + + lookup = otTables.Lookup() + lookup.SubTable = subtables + lookup.LookupFlag = self.lookup_flag + lookup.LookupType = self.lookup_type + lookup.SubTableCount = len(lookup.SubTable) + return lookup + + class ReverseChainSingleSubstBuilder(LookupBuilder): def __init__(self, font, location, lookup_flag): LookupBuilder.__init__(self, font, location, 'GSUB', 8, lookup_flag) diff --git a/Lib/fontTools/feaLib/builder_test.py b/Lib/fontTools/feaLib/builder_test.py index 2234fbc70..70282cee0 100644 --- a/Lib/fontTools/feaLib/builder_test.py +++ b/Lib/fontTools/feaLib/builder_test.py @@ -124,6 +124,17 @@ class BuilderTest(unittest.TestCase): " sub f_f_i by f f i;" "} test;") + def test_pairPos_redefinition(self): + self.assertRaisesRegex( + FeatureLibError, + r"Already defined position for " + "pair \[A B\] \[zero one two\] at .*:2:[0-9]+", # :2: = line 2 + self.build, + "feature test {\n" + " pos [A B] [zero one two] 123;\n" # line 2 + " pos [A B] [zero one two] 456;\n" + "} test;\n") + def test_reverseChainingSingleSubst(self): font = makeTTFont() addOpenTypeFeatures(self.getpath("GSUB_8.fea"), font) @@ -150,6 +161,11 @@ class BuilderTest(unittest.TestCase): addOpenTypeFeatures(self.getpath("GPOS_1.fea"), font) self.expect_ttx(font, self.getpath("GPOS_1.ttx")) + def test_GPOS_type2(self): + font = makeTTFont() + addOpenTypeFeatures(self.getpath("GPOS_2.fea"), font) + self.expect_ttx(font, self.getpath("GPOS_2.ttx")) + def test_spec4h1(self): # OpenType Feature File specification, section 4.h, example 1. font = makeTTFont() diff --git a/Lib/fontTools/feaLib/testdata/GPOS_2.fea b/Lib/fontTools/feaLib/testdata/GPOS_2.fea new file mode 100644 index 000000000..e30209e65 --- /dev/null +++ b/Lib/fontTools/feaLib/testdata/GPOS_2.fea @@ -0,0 +1,20 @@ +languagesystem DFLT dflt; + +feature kern { + pos T one 100; + pos T two 200; + pos T two.oldstyle 200; + pos T three 300; + pos T four 400; + + pos X a 100; + pos X b 200; + + pos Y a 100; + pos Y b 200; + pos Y c <3 3 3 3>; +} kern; + +feature vkrn { + pos T one 100; +} vkrn; diff --git a/Lib/fontTools/feaLib/testdata/GPOS_2.ttx b/Lib/fontTools/feaLib/testdata/GPOS_2.ttx new file mode 100644 index 000000000..d2dbba40d --- /dev/null +++ b/Lib/fontTools/feaLib/testdata/GPOS_2.ttx @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +