From c6ee46f299a6f8c355e1c1b5f4132e578949b10e Mon Sep 17 00:00:00 2001 From: Sascha Brawer Date: Mon, 7 Dec 2015 23:56:08 +0100 Subject: [PATCH] [feaLib] Implement GPOS type 3: Cursive Attachment Positioning --- Lib/fontTools/feaLib/ast.py | 4 + Lib/fontTools/feaLib/builder.py | 65 +++++++++++++ Lib/fontTools/feaLib/builder_test.py | 14 +-- Lib/fontTools/feaLib/testdata/GPOS_3.fea | 14 +++ Lib/fontTools/feaLib/testdata/GPOS_3.ttx | 114 +++++++++++++++++++++++ 5 files changed, 202 insertions(+), 9 deletions(-) create mode 100644 Lib/fontTools/feaLib/testdata/GPOS_3.fea create mode 100644 Lib/fontTools/feaLib/testdata/GPOS_3.ttx diff --git a/Lib/fontTools/feaLib/ast.py b/Lib/fontTools/feaLib/ast.py index 65eea9ba1..a383e99a3 100644 --- a/Lib/fontTools/feaLib/ast.py +++ b/Lib/fontTools/feaLib/ast.py @@ -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): diff --git a/Lib/fontTools/feaLib/builder.py b/Lib/fontTools/feaLib/builder.py index 5a050baf9..a8366b796 100644 --- a/Lib/fontTools/feaLib/builder.py +++ b/Lib/fontTools/feaLib/builder.py @@ -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) diff --git a/Lib/fontTools/feaLib/builder_test.py b/Lib/fontTools/feaLib/builder_test.py index 70282cee0..43279fc56 100644 --- a/Lib/fontTools/feaLib/builder_test.py +++ b/Lib/fontTools/feaLib/builder_test.py @@ -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): - 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")) + def test_GPOS(self): + for name in "1 2 3".split(): + font = makeTTFont() + 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. diff --git a/Lib/fontTools/feaLib/testdata/GPOS_3.fea b/Lib/fontTools/feaLib/testdata/GPOS_3.fea new file mode 100644 index 000000000..d460432b0 --- /dev/null +++ b/Lib/fontTools/feaLib/testdata/GPOS_3.fea @@ -0,0 +1,14 @@ +languagesystem DFLT dflt; + +anchorDef 3 4 contourpoint 2 ANCH342; + +feature kern { + pos cursive zero ; + pos cursive one ; + pos cursive two ; + pos cursive three ; + pos cursive four ; + pos cursive five + > + ; +} kern; diff --git a/Lib/fontTools/feaLib/testdata/GPOS_3.ttx b/Lib/fontTools/feaLib/testdata/GPOS_3.ttx new file mode 100644 index 000000000..8ef04c705 --- /dev/null +++ b/Lib/fontTools/feaLib/testdata/GPOS_3.ttx @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +