[feaLib] Implement GPOS type 2, format 1
This commit is contained in:
parent
e54fced0b9
commit
3433f363e7
@ -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)
|
||||
|
@ -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()
|
||||
|
20
Lib/fontTools/feaLib/testdata/GPOS_2.fea
vendored
Normal file
20
Lib/fontTools/feaLib/testdata/GPOS_2.fea
vendored
Normal 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
151
Lib/fontTools/feaLib/testdata/GPOS_2.ttx
vendored
Normal 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>
|
Loading…
x
Reference in New Issue
Block a user