diff options
author | Rasmus Andersson <rasmus@notion.se> | 2017-08-22 10:05:20 +0300 |
---|---|---|
committer | Rasmus Andersson <rasmus@notion.se> | 2017-08-22 12:23:08 +0300 |
commit | 3b1fffade1473f20f2558733fbd218f4580fc7c3 (patch) | |
tree | ea4f80b43b08744d493bb86ab646444ec04ddc7f /misc/pylib/fontbuild/features.py | |
download | inter-3b1fffade1473f20f2558733fbd218f4580fc7c3.tar.xz |
Initial public commitv1.0
Diffstat (limited to 'misc/pylib/fontbuild/features.py')
-rwxr-xr-x | misc/pylib/fontbuild/features.py | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/misc/pylib/fontbuild/features.py b/misc/pylib/fontbuild/features.py new file mode 100755 index 000000000..fe6eca012 --- /dev/null +++ b/misc/pylib/fontbuild/features.py @@ -0,0 +1,189 @@ +# Copyright 2015 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import re + +from feaTools import parser +from feaTools.writers.fdkSyntaxWriter import FDKSyntaxFeatureWriter + + +class FilterFeatureWriter(FDKSyntaxFeatureWriter): + """Feature writer to detect invalid references and duplicate definitions.""" + + def __init__(self, refs=set(), name=None, isFeature=False): + """Initializes the set of known references, empty by default.""" + self.refs = refs + self.featureNames = set() + self.lookupNames = set() + self.tableNames = set() + self.languageSystems = set() + super(FilterFeatureWriter, self).__init__( + name=name, isFeature=isFeature) + + # error to print when undefined reference is found in glyph class + self.classErr = ('Undefined reference "%s" removed from glyph class ' + 'definition %s.') + + # error to print when undefined reference is found in sub or pos rule + subErr = ['Substitution rule with undefined reference "%s" removed'] + if self._name: + subErr.append(" from ") + subErr.append("feature" if self._isFeature else "lookup") + subErr.append(' "%s"' % self._name) + subErr.append(".") + self.subErr = "".join(subErr) + self.posErr = self.subErr.replace("Substitution", "Positioning") + + def _subwriter(self, name, isFeature): + """Use this class for nested expressions e.g. in feature definitions.""" + return FilterFeatureWriter(self.refs, name, isFeature) + + def _flattenRefs(self, refs, flatRefs): + """Flatten a list of references.""" + for ref in refs: + if type(ref) == list: + self._flattenRefs(ref, flatRefs) + elif ref != "'": # ignore contextual class markings + flatRefs.append(ref) + + def _checkRefs(self, refs, errorMsg): + """Check a list of references found in a sub or pos rule.""" + flatRefs = [] + self._flattenRefs(refs, flatRefs) + for ref in flatRefs: + # trailing apostrophes should be ignored + if ref[-1] == "'": + ref = ref[:-1] + if ref not in self.refs: + print errorMsg % ref + # insert an empty instruction so that we can't end up with an + # empty block, which is illegal syntax + super(FilterFeatureWriter, self).rawText(";") + return False + return True + + def classDefinition(self, name, contents): + """Check that contents are valid, then add name to known references.""" + if name in self.refs: + return + newContents = [] + for ref in contents: + if ref not in self.refs and ref != "-": + print self.classErr % (ref, name) + else: + newContents.append(ref) + self.refs.add(name) + super(FilterFeatureWriter, self).classDefinition(name, newContents) + + def gsubType1(self, target, replacement): + """Check a sub rule with one-to-one replacement.""" + if self._checkRefs([target, replacement], self.subErr): + super(FilterFeatureWriter, self).gsubType1(target, replacement) + + def gsubType4(self, target, replacement): + """Check a sub rule with many-to-one replacement.""" + if self._checkRefs([target, replacement], self.subErr): + super(FilterFeatureWriter, self).gsubType4(target, replacement) + + def gsubType6(self, precedingContext, target, trailingContext, replacement): + """Check a sub rule with contextual replacement.""" + refs = [precedingContext, target, trailingContext, replacement] + if self._checkRefs(refs, self.subErr): + super(FilterFeatureWriter, self).gsubType6( + precedingContext, target, trailingContext, replacement) + + def gposType1(self, target, value): + """Check a single positioning rule.""" + if self._checkRefs([target], self.posErr): + super(FilterFeatureWriter, self).gposType1(target, value) + + def gposType2(self, target, value, needEnum=False): + """Check a pair positioning rule.""" + if self._checkRefs(target, self.posErr): + super(FilterFeatureWriter, self).gposType2(target, value, needEnum) + + # these rules may contain references, but they aren't present in Roboto + def gsubType3(self, target, replacement): + raise NotImplementedError + + def feature(self, name): + """Adds a feature definition only once.""" + if name not in self.featureNames: + self.featureNames.add(name) + return super(FilterFeatureWriter, self).feature(name) + # we must return a new writer even if we don't add it to this one + return FDKSyntaxFeatureWriter(name, True) + + def lookup(self, name): + """Adds a lookup block only once.""" + if name not in self.lookupNames: + self.lookupNames.add(name) + return super(FilterFeatureWriter, self).lookup(name) + # we must return a new writer even if we don't add it to this one + return FDKSyntaxFeatureWriter(name, False) + + def languageSystem(self, langTag, scriptTag): + """Adds a language system instruction only once.""" + system = (langTag, scriptTag) + if system not in self.languageSystems: + self.languageSystems.add(system) + super(FilterFeatureWriter, self).languageSystem(langTag, scriptTag) + + def table(self, name, data): + """Adds a table only once.""" + if name in self.tableNames: + return + self.tableNames.add(name) + self._instructions.append("table %s {" % name) + self._instructions.extend([" %s %s;" % line for line in data]) + self._instructions.append("} %s;" % name) + + +def compileFeatureRE(name): + """Compiles a feature-matching regex.""" + + # this is the pattern used internally by feaTools: + # https://github.com/typesupply/feaTools/blob/master/Lib/feaTools/parser.py + featureRE = list(parser.featureContentRE) + featureRE.insert(2, name) + featureRE.insert(6, name) + return re.compile("".join(featureRE)) + + +def updateFeature(font, name, value): + """Add a feature definition, or replace existing one.""" + featureRE = compileFeatureRE(name) + if featureRE.search(font.features.text): + font.features.text = featureRE.sub(value, font.features.text) + else: + font.features.text += "\n" + value + + +def readFeatureFile(font, text, prepend=True): + """Incorporate valid definitions from feature text into font.""" + writer = FilterFeatureWriter(set(font.keys())) + if prepend: + text += font.features.text + else: + text = font.features.text + text + parser.parseFeatures(writer, text) + font.features.text = writer.write() + + +def writeFeatureFile(font, path): + """Write the font's features to an external file.""" + fout = open(path, "w") + fout.write(font.features.text) + fout.close() |