[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
|
lookup.mapping[from_glyph] = to_glyph
|
||||||
|
|
||||||
def add_pair_pos(self, location, enumerated,
|
def add_pair_pos(self, location, enumerated,
|
||||||
glyph1, value1, glyph2, value2):
|
glyphclass1, value1, glyphclass2, value2):
|
||||||
pass # TODO: Implement.
|
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):
|
def add_single_pos(self, location, glyph, valuerecord):
|
||||||
lookup = self.get_lookup_(location, SinglePosBuilder)
|
lookup = self.get_lookup_(location, SinglePosBuilder)
|
||||||
@ -341,6 +346,8 @@ def _makeOpenTypeDeviceTable(deviceTable, device):
|
|||||||
|
|
||||||
def makeOpenTypeValueRecord(v):
|
def makeOpenTypeValueRecord(v):
|
||||||
"""ast.ValueRecord --> (otBase.ValueRecord, int ValueFormat)"""
|
"""ast.ValueRecord --> (otBase.ValueRecord, int ValueFormat)"""
|
||||||
|
if v is None:
|
||||||
|
return None, 0
|
||||||
vr = otBase.ValueRecord()
|
vr = otBase.ValueRecord()
|
||||||
if v.xPlacement:
|
if v.xPlacement:
|
||||||
vr.XPlacement = v.xPlacement
|
vr.XPlacement = v.xPlacement
|
||||||
@ -533,6 +540,71 @@ class MultipleSubstBuilder(LookupBuilder):
|
|||||||
return lookup
|
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):
|
class ReverseChainSingleSubstBuilder(LookupBuilder):
|
||||||
def __init__(self, font, location, lookup_flag):
|
def __init__(self, font, location, lookup_flag):
|
||||||
LookupBuilder.__init__(self, font, location, 'GSUB', 8, 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;"
|
" sub f_f_i by f f i;"
|
||||||
"} test;")
|
"} 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):
|
def test_reverseChainingSingleSubst(self):
|
||||||
font = makeTTFont()
|
font = makeTTFont()
|
||||||
addOpenTypeFeatures(self.getpath("GSUB_8.fea"), font)
|
addOpenTypeFeatures(self.getpath("GSUB_8.fea"), font)
|
||||||
@ -150,6 +161,11 @@ class BuilderTest(unittest.TestCase):
|
|||||||
addOpenTypeFeatures(self.getpath("GPOS_1.fea"), font)
|
addOpenTypeFeatures(self.getpath("GPOS_1.fea"), font)
|
||||||
self.expect_ttx(font, self.getpath("GPOS_1.ttx"))
|
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):
|
def test_spec4h1(self):
|
||||||
# OpenType Feature File specification, section 4.h, example 1.
|
# OpenType Feature File specification, section 4.h, example 1.
|
||||||
font = makeTTFont()
|
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