[feaLib] Basic test framework for building OpenType features
This commit is contained in:
parent
b54c04f1d4
commit
df740092d9
@ -6,6 +6,9 @@ class FeatureFile(object):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.statements = []
|
self.statements = []
|
||||||
|
|
||||||
|
def build(self, builder):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class FeatureBlock(object):
|
class FeatureBlock(object):
|
||||||
def __init__(self, location, name, use_extension):
|
def __init__(self, location, name, use_extension):
|
||||||
|
45
Lib/fontTools/feaLib/builder.py
Normal file
45
Lib/fontTools/feaLib/builder.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
from __future__ import print_function, division, absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from fontTools.feaLib.error import FeatureLibError
|
||||||
|
from fontTools.feaLib.parser import Parser
|
||||||
|
from fontTools.ttLib.tables import otTables
|
||||||
|
|
||||||
|
|
||||||
|
def addOpenTypeFeatures(featurefile_path, font):
|
||||||
|
builder = Builder(featurefile_path, font)
|
||||||
|
builder.build()
|
||||||
|
|
||||||
|
|
||||||
|
class Builder(object):
|
||||||
|
def __init__(self, featurefile_path, font):
|
||||||
|
self.featurefile_path = featurefile_path
|
||||||
|
self.font = font
|
||||||
|
self.language_systems_ = []
|
||||||
|
|
||||||
|
def build(self):
|
||||||
|
self.gpos = self.font['GPOS'] = self.makeTable('GPOS')
|
||||||
|
self.gsub = self.font['GSUB'] = self.makeTable('GSUB')
|
||||||
|
parsetree = Parser(self.featurefile_path).parse()
|
||||||
|
parsetree.build(self)
|
||||||
|
|
||||||
|
def makeTable(self, tag):
|
||||||
|
table = getattr(otTables, tag, None)()
|
||||||
|
table.Version = 1.0
|
||||||
|
table.ScriptList = otTables.ScriptList()
|
||||||
|
table.ScriptList.ScriptCount = 0
|
||||||
|
table.ScriptList.ScriptRecord = []
|
||||||
|
table.FeatureList = otTables.FeatureList()
|
||||||
|
table.FeatureList.FeatureCount = 0
|
||||||
|
table.FeatureList.FeatureRecord = []
|
||||||
|
table.LookupList = otTables.LookupList()
|
||||||
|
table.LookupList.LookupCount = 0
|
||||||
|
table.LookupList.Lookup = []
|
||||||
|
return table
|
||||||
|
|
||||||
|
def add_language_system(self, location, script, language):
|
||||||
|
if script == "DFLT" and language == "dflt" and self.language_systems_:
|
||||||
|
# OpenType Feature File Specification, section 4.b.i
|
||||||
|
raise FeatureLibError(
|
||||||
|
'If "languagesystem DFLT dflt" is present, it must be '
|
||||||
|
'the first of the languagesystem statements', location)
|
||||||
|
self.language_systems_.append((script, language))
|
73
Lib/fontTools/feaLib/builder_test.py
Normal file
73
Lib/fontTools/feaLib/builder_test.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
from __future__ import print_function, division, absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from fontTools.feaLib.builder import addOpenTypeFeatures
|
||||||
|
from fontTools.ttLib import TTFont
|
||||||
|
import codecs
|
||||||
|
import difflib
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class BuilderTest(unittest.TestCase):
|
||||||
|
def __init__(self, methodName):
|
||||||
|
unittest.TestCase.__init__(self, methodName)
|
||||||
|
# Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
|
||||||
|
# and fires deprecation warnings if a program uses the old name.
|
||||||
|
if not hasattr(self, "assertRaisesRegex"):
|
||||||
|
self.assertRaisesRegex = self.assertRaisesRegexp
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.tempdir = None
|
||||||
|
self.num_tempfiles = 0
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
if self.tempdir:
|
||||||
|
shutil.rmtree(self.tempdir)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def getpath(testfile):
|
||||||
|
path, _ = os.path.split(__file__)
|
||||||
|
return os.path.join(path, "testdata", testfile)
|
||||||
|
|
||||||
|
def temp_path(self, suffix):
|
||||||
|
if not self.tempdir:
|
||||||
|
self.tempdir = tempfile.mkdtemp()
|
||||||
|
self.num_tempfiles += 1
|
||||||
|
return os.path.join(self.tempdir,
|
||||||
|
"tmp%d%s" % (self.num_tempfiles, suffix))
|
||||||
|
|
||||||
|
def read_ttx(self, path):
|
||||||
|
lines = []
|
||||||
|
with codecs.open(path, "r", "utf-8") as ttx:
|
||||||
|
for line in ttx.readlines():
|
||||||
|
# Elide ttFont attributes because ttLibVersion may change,
|
||||||
|
# and use os-native line separators so we can run difflib.
|
||||||
|
if line.startswith("<ttFont "):
|
||||||
|
lines.append("<ttFont>" + os.linesep)
|
||||||
|
else:
|
||||||
|
lines.append(line.rstrip() + os.linesep)
|
||||||
|
return lines
|
||||||
|
|
||||||
|
def expect_ttx(self, font, expected_ttx):
|
||||||
|
path = self.temp_path(suffix=".ttx")
|
||||||
|
font.saveXML(path, quiet=True, tables=['GSUB', 'GPOS'])
|
||||||
|
actual = self.read_ttx(path)
|
||||||
|
expected = self.read_ttx(expected_ttx)
|
||||||
|
if actual != expected:
|
||||||
|
for line in difflib.unified_diff(
|
||||||
|
expected, actual, fromfile=path, tofile=expected_ttx):
|
||||||
|
sys.stdout.write(line)
|
||||||
|
self.fail("TTX output is different from expected")
|
||||||
|
|
||||||
|
def test_spec4h1(self):
|
||||||
|
# OpenType Feature File specification, section 4.h, example 1.
|
||||||
|
font = TTFont()
|
||||||
|
addOpenTypeFeatures(self.getpath("spec4h1.fea"), font)
|
||||||
|
self.expect_ttx(font, self.getpath("spec4h1_expected.ttx"))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
64
Lib/fontTools/feaLib/testdata/spec4h1.fea
vendored
Normal file
64
Lib/fontTools/feaLib/testdata/spec4h1.fea
vendored
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# OpenType Feature File specification, section 4.h, example 1.
|
||||||
|
# http://www.adobe.com/devnet/opentype/afdko/topic_feature_file_syntax.html
|
||||||
|
|
||||||
|
languagesystem DFLT dflt;
|
||||||
|
languagesystem latn dflt;
|
||||||
|
languagesystem latn DEU;
|
||||||
|
languagesystem latn TRK;
|
||||||
|
languagesystem cyrl dflt;
|
||||||
|
|
||||||
|
feature smcp {
|
||||||
|
sub [a-z] by [A.sc-Z.sc];
|
||||||
|
|
||||||
|
# Since all the rules in this feature are of the same type, they
|
||||||
|
# will be grouped in a single lookup. Since no script or language
|
||||||
|
# keyword has been specified yet, the lookup will be registered
|
||||||
|
# for this feature under all the language systems.
|
||||||
|
} smcp;
|
||||||
|
|
||||||
|
feature liga {
|
||||||
|
sub f f by f_f;
|
||||||
|
sub f i by f_i;
|
||||||
|
sub f l by f_l;
|
||||||
|
|
||||||
|
# Since all the rules in this feature are of the same type, they
|
||||||
|
# will be grouped in a single lookup. Since no script or language
|
||||||
|
# keyword has been specified yet, the lookup will be registered
|
||||||
|
# for this feature under all the language systems.
|
||||||
|
|
||||||
|
script latn;
|
||||||
|
language dflt;
|
||||||
|
# lookupflag 0; (implicit)
|
||||||
|
sub c t by c_t;
|
||||||
|
sub c s by c_s;
|
||||||
|
|
||||||
|
# The rules above will be placed in a lookup that is registered
|
||||||
|
# for all the specified languages for the script latn, but not any
|
||||||
|
# other scripts.
|
||||||
|
|
||||||
|
language DEU;
|
||||||
|
# script latn; (stays the same)
|
||||||
|
# lookupflag 0; (stays the same)
|
||||||
|
sub c h by c_h;
|
||||||
|
sub c k by c_k;
|
||||||
|
|
||||||
|
# The rules above will be placed in a lookup that is registered
|
||||||
|
# only under the script latn, language DEU.
|
||||||
|
|
||||||
|
language TRK;
|
||||||
|
|
||||||
|
# This will inherit both the top level default rules - the rules
|
||||||
|
# defined before the first 'script' statement, and the
|
||||||
|
# script-level default rules for 'latn': all the lookups of this
|
||||||
|
# feature defined after the 'script latn' statement, and before
|
||||||
|
# the language DEU statement. If TRK were not named here, it
|
||||||
|
# would not inherit the default rules for the script latn.
|
||||||
|
} liga;
|
||||||
|
|
||||||
|
# TODO(sascha): Uncomment once we support 'pos' statements.
|
||||||
|
# feature kern {
|
||||||
|
# pos a y -150;
|
||||||
|
# # [more pos statements]
|
||||||
|
# # All the rules in this feature will be grouped in a single lookup
|
||||||
|
# # that is is registered under all the language systems.
|
||||||
|
# } kern;
|
34
Lib/fontTools/feaLib/testdata/spec4h1_expected.ttx
vendored
Normal file
34
Lib/fontTools/feaLib/testdata/spec4h1_expected.ttx
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ttFont>
|
||||||
|
|
||||||
|
<GSUB>
|
||||||
|
<GSUB>
|
||||||
|
<Version value="1.0"/>
|
||||||
|
<ScriptList>
|
||||||
|
<!-- ScriptCount=0 -->
|
||||||
|
</ScriptList>
|
||||||
|
<FeatureList>
|
||||||
|
<!-- FeatureCount=0 -->
|
||||||
|
</FeatureList>
|
||||||
|
<LookupList>
|
||||||
|
<!-- LookupCount=0 -->
|
||||||
|
</LookupList>
|
||||||
|
</GSUB>
|
||||||
|
</GSUB>
|
||||||
|
|
||||||
|
<GPOS>
|
||||||
|
<GPOS>
|
||||||
|
<Version value="1.0"/>
|
||||||
|
<ScriptList>
|
||||||
|
<!-- ScriptCount=0 -->
|
||||||
|
</ScriptList>
|
||||||
|
<FeatureList>
|
||||||
|
<!-- FeatureCount=0 -->
|
||||||
|
</FeatureList>
|
||||||
|
<LookupList>
|
||||||
|
<!-- LookupCount=0 -->
|
||||||
|
</LookupList>
|
||||||
|
</GPOS>
|
||||||
|
</GPOS>
|
||||||
|
|
||||||
|
</ttFont>
|
Loading…
x
Reference in New Issue
Block a user