[feaLib] Implement GPOS type 2, format 1

This commit is contained in:
Sascha Brawer 2015-12-07 21:26:58 +01:00
parent e54fced0b9
commit 3433f363e7
4 changed files with 261 additions and 2 deletions

View File

@ -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)

View File

@ -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()

View File

@ -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;

151
Lib/fontTools/feaLib/testdata/GPOS_2.ttx vendored Normal file
View File

@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont>
<GSUB>
<Version value="1.0"/>
<ScriptList>
<!-- ScriptCount=0 -->
</ScriptList>
<FeatureList>
<!-- FeatureCount=0 -->
</FeatureList>
<LookupList>
<!-- LookupCount=0 -->
</LookupList>
</GSUB>
<GPOS>
<Version value="1.0"/>
<ScriptList>
<!-- ScriptCount=1 -->
<ScriptRecord index="0">
<ScriptTag value="DFLT"/>
<Script>
<DefaultLangSys>
<ReqFeatureIndex value="65535"/>
<!-- FeatureCount=2 -->
<FeatureIndex index="0" value="0"/>
<FeatureIndex index="1" value="1"/>
</DefaultLangSys>
<!-- LangSysCount=0 -->
</Script>
</ScriptRecord>
</ScriptList>
<FeatureList>
<!-- FeatureCount=2 -->
<FeatureRecord index="0">
<FeatureTag value="kern"/>
<Feature>
<!-- LookupCount=1 -->
<LookupListIndex index="0" value="0"/>
</Feature>
</FeatureRecord>
<FeatureRecord index="1">
<FeatureTag value="vkrn"/>
<Feature>
<!-- LookupCount=1 -->
<LookupListIndex index="0" value="1"/>
</Feature>
</FeatureRecord>
</FeatureList>
<LookupList>
<!-- LookupCount=2 -->
<Lookup index="0">
<!-- LookupType=2 -->
<LookupFlag value="0"/>
<!-- SubTableCount=2 -->
<PairPos index="0" Format="1">
<Coverage>
<Glyph value="T"/>
<Glyph value="X"/>
<Glyph value="Y"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
<!-- PairSetCount=3 -->
<PairSet index="0">
<!-- PairValueCount=5 -->
<PairValueRecord index="0">
<SecondGlyph value="one"/>
<Value1 XAdvance="100"/>
</PairValueRecord>
<PairValueRecord index="1">
<SecondGlyph value="two"/>
<Value1 XAdvance="200"/>
</PairValueRecord>
<PairValueRecord index="2">
<SecondGlyph value="three"/>
<Value1 XAdvance="300"/>
</PairValueRecord>
<PairValueRecord index="3">
<SecondGlyph value="four"/>
<Value1 XAdvance="400"/>
</PairValueRecord>
<PairValueRecord index="4">
<SecondGlyph value="two.oldstyle"/>
<Value1 XAdvance="200"/>
</PairValueRecord>
</PairSet>
<PairSet index="1">
<!-- PairValueCount=2 -->
<PairValueRecord index="0">
<SecondGlyph value="a"/>
<Value1 XAdvance="100"/>
</PairValueRecord>
<PairValueRecord index="1">
<SecondGlyph value="b"/>
<Value1 XAdvance="200"/>
</PairValueRecord>
</PairSet>
<PairSet index="2">
<!-- PairValueCount=2 -->
<PairValueRecord index="0">
<SecondGlyph value="a"/>
<Value1 XAdvance="100"/>
</PairValueRecord>
<PairValueRecord index="1">
<SecondGlyph value="b"/>
<Value1 XAdvance="200"/>
</PairValueRecord>
</PairSet>
</PairPos>
<PairPos index="1" Format="1">
<Coverage>
<Glyph value="Y"/>
</Coverage>
<ValueFormat1 value="15"/>
<ValueFormat2 value="0"/>
<!-- PairSetCount=1 -->
<PairSet index="0">
<!-- PairValueCount=1 -->
<PairValueRecord index="0">
<SecondGlyph value="c"/>
<Value1 XPlacement="3" YPlacement="3" XAdvance="3" YAdvance="3"/>
</PairValueRecord>
</PairSet>
</PairPos>
</Lookup>
<Lookup index="1">
<!-- LookupType=2 -->
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
<Coverage>
<Glyph value="T"/>
</Coverage>
<ValueFormat1 value="8"/>
<ValueFormat2 value="0"/>
<!-- PairSetCount=1 -->
<PairSet index="0">
<!-- PairValueCount=1 -->
<PairValueRecord index="0">
<SecondGlyph value="one"/>
<Value1 YAdvance="100"/>
</PairValueRecord>
</PairSet>
</PairPos>
</Lookup>
</LookupList>
</GPOS>
</ttFont>