[feaLib] Implement GPOS type 3: Cursive Attachment Positioning

This commit is contained in:
Sascha Brawer 2015-12-07 23:56:08 +01:00
parent 2fb1cc138c
commit c6ee46f299
5 changed files with 202 additions and 9 deletions

View File

@ -102,6 +102,10 @@ class CursiveAttachmentPositioning(Statement):
self.glyphclass = glyphclass
self.entryAnchor, self.exitAnchor = entryAnchor, exitAnchor
def build(self, builder):
builder.add_cursive_attachment_pos(
self.location, self.glyphclass, self.entryAnchor, self.exitAnchor)
class LanguageStatement(Statement):
def __init__(self, location, language, include_default, required):

View File

@ -310,6 +310,14 @@ class Builder(object):
location)
lookup.mapping[from_glyph] = to_glyph
def add_cursive_attachment_pos(self, location, glyphclass,
entryAnchor, exitAnchor):
lookup = self.get_lookup_(location, CursiveAttachmentPosBuilder)
lookup.add_attachment(
location, glyphclass,
makeOpenTypeAnchor(entryAnchor, otTables.EntryAnchor),
makeOpenTypeAnchor(exitAnchor, otTables.ExitAnchor))
def add_pair_pos(self, location, enumerated,
glyphclass1, value1, glyphclass2, value2):
lookup = self.get_lookup_(location, PairPosBuilder)
@ -350,6 +358,27 @@ def _makeOpenTypeDeviceTable(deviceTable, device):
deviceTable.DeltaFormat = 3
def makeOpenTypeAnchor(anchor, anchorClass):
"""ast.Anchor --> otTables.Anchor"""
if anchor is None:
return None
anch = anchorClass()
anch.Format = 1
anch.XCoordinate, anch.YCoordinate = anchor.x, anchor.y
if anchor.contourpoint is not None:
anch.AnchorPoint = anchor.contourpoint
anch.Format = 2
if anchor.xDeviceTable is not None:
anch.XDeviceTable = otTables.XDeviceTable()
_makeOpenTypeDeviceTable(anch.XDeviceTable, anchor.xDeviceTable)
anch.Format = 3
if anchor.yDeviceTable is not None:
anch.YDeviceTable = otTables.YDeviceTable()
_makeOpenTypeDeviceTable(anch.YDeviceTable, anchor.yDeviceTable)
anch.Format = 3
return anch
def makeOpenTypeValueRecord(v):
"""ast.ValueRecord --> (otBase.ValueRecord, int ValueFormat)"""
if v is None:
@ -611,6 +640,42 @@ class PairPosBuilder(LookupBuilder):
return lookup
class CursiveAttachmentPosBuilder(LookupBuilder):
def __init__(self, font, location, lookup_flag):
LookupBuilder.__init__(self, font, location, 'GPOS', 3, lookup_flag)
self.attachments = {}
def equals(self, other):
return (LookupBuilder.equals(self, other) and
self.attachments == other.attachments)
def add_attachment(self, location, glyphs, entryAnchor, exitAnchor):
for glyph in glyphs:
self.attachments[glyph] = (location, entryAnchor, exitAnchor)
def build(self):
st = otTables.CursivePos()
st.Format = 1
st.Coverage = otTables.Coverage()
st.Coverage.glyphs = \
sorted(self.attachments.keys(), key=self.font.getGlyphID)
st.EntryExitCount = len(self.attachments)
st.EntryExitRecord = []
for glyph in st.Coverage.glyphs:
location, entryAnchor, exitAnchor = self.attachments[glyph]
rec = otTables.EntryExitRecord()
st.EntryExitRecord.append(rec)
rec.EntryAnchor = entryAnchor
rec.ExitAnchor = exitAnchor
subtables = [st]
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

@ -156,15 +156,11 @@ class BuilderTest(unittest.TestCase):
"Already defined different position for glyph \"A\"",
self.build, "feature test { pos A 123; pos A 456; } test;")
def test_GPOS_type1(self):
def test_GPOS(self):
for name in "1 2 3".split():
font = makeTTFont()
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"))
addOpenTypeFeatures(self.getpath("GPOS_%s.fea" % name), font)
self.expect_ttx(font, self.getpath("GPOS_%s.ttx" % name))
def test_spec4h1(self):
# OpenType Feature File specification, section 4.h, example 1.

View File

@ -0,0 +1,14 @@
languagesystem DFLT dflt;
anchorDef 3 4 contourpoint 2 ANCH342;
feature kern {
pos cursive zero <anchor NULL> <anchor NULL>;
pos cursive one <anchor 121 -1> <anchor ANCH342>;
pos cursive two <anchor 122 -2> <anchor 3 4>;
pos cursive three <anchor 123 -3> <anchor NULL>;
pos cursive four <anchor 124 -4 contourpoint 7> <anchor 3 4>;
pos cursive five
<anchor 124 -4 <device 8 1, 9 2> <device 7 3>>
<anchor ANCH342>;
} kern;

114
Lib/fontTools/feaLib/testdata/GPOS_3.ttx vendored Normal file
View File

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont>
<GPOS>
<Version value="1.0"/>
<ScriptList>
<!-- ScriptCount=1 -->
<ScriptRecord index="0">
<ScriptTag value="DFLT"/>
<Script>
<DefaultLangSys>
<ReqFeatureIndex value="65535"/>
<!-- FeatureCount=1 -->
<FeatureIndex index="0" value="0"/>
</DefaultLangSys>
<!-- LangSysCount=0 -->
</Script>
</ScriptRecord>
</ScriptList>
<FeatureList>
<!-- FeatureCount=1 -->
<FeatureRecord index="0">
<FeatureTag value="kern"/>
<Feature>
<!-- LookupCount=1 -->
<LookupListIndex index="0" value="0"/>
</Feature>
</FeatureRecord>
</FeatureList>
<LookupList>
<!-- LookupCount=1 -->
<Lookup index="0">
<!-- LookupType=3 -->
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<CursivePos index="0" Format="1">
<Coverage>
<Glyph value="zero"/>
<Glyph value="one"/>
<Glyph value="two"/>
<Glyph value="three"/>
<Glyph value="four"/>
<Glyph value="five"/>
</Coverage>
<!-- EntryExitCount=6 -->
<EntryExitRecord index="0">
</EntryExitRecord>
<EntryExitRecord index="1">
<EntryAnchor Format="1">
<XCoordinate value="121"/>
<YCoordinate value="-1"/>
</EntryAnchor>
<ExitAnchor Format="2">
<XCoordinate value="3"/>
<YCoordinate value="4"/>
<AnchorPoint value="2"/>
</ExitAnchor>
</EntryExitRecord>
<EntryExitRecord index="2">
<EntryAnchor Format="1">
<XCoordinate value="122"/>
<YCoordinate value="-2"/>
</EntryAnchor>
<ExitAnchor Format="1">
<XCoordinate value="3"/>
<YCoordinate value="4"/>
</ExitAnchor>
</EntryExitRecord>
<EntryExitRecord index="3">
<EntryAnchor Format="1">
<XCoordinate value="123"/>
<YCoordinate value="-3"/>
</EntryAnchor>
</EntryExitRecord>
<EntryExitRecord index="4">
<EntryAnchor Format="2">
<XCoordinate value="124"/>
<YCoordinate value="-4"/>
<AnchorPoint value="7"/>
</EntryAnchor>
<ExitAnchor Format="1">
<XCoordinate value="3"/>
<YCoordinate value="4"/>
</ExitAnchor>
</EntryExitRecord>
<EntryExitRecord index="5">
<EntryAnchor Format="3">
<XCoordinate value="124"/>
<YCoordinate value="-4"/>
<XDeviceTable>
<StartSize value="8"/>
<EndSize value="9"/>
<DeltaFormat value="2"/>
<DeltaValue value="[1, 2]"/>
</XDeviceTable>
<YDeviceTable>
<StartSize value="7"/>
<EndSize value="7"/>
<DeltaFormat value="2"/>
<DeltaValue value="[3]"/>
</YDeviceTable>
</EntryAnchor>
<ExitAnchor Format="2">
<XCoordinate value="3"/>
<YCoordinate value="4"/>
<AnchorPoint value="2"/>
</ExitAnchor>
</EntryExitRecord>
</CursivePos>
</Lookup>
</LookupList>
</GPOS>
</ttFont>