diff options
Diffstat (limited to 'Lib/fontTools/feaLib/builder.py')
-rw-r--r-- | Lib/fontTools/feaLib/builder.py | 150 |
1 files changed, 146 insertions, 4 deletions
diff --git a/Lib/fontTools/feaLib/builder.py b/Lib/fontTools/feaLib/builder.py index 30046bda..4a7d9575 100644 --- a/Lib/fontTools/feaLib/builder.py +++ b/Lib/fontTools/feaLib/builder.py @@ -1,4 +1,4 @@ -from fontTools.misc.py23 import * +from fontTools.misc.py23 import Tag, tostr from fontTools.misc import sstruct from fontTools.misc.textTools import binary2num, safeEval from fontTools.feaLib.error import FeatureLibError @@ -33,6 +33,7 @@ from fontTools.otlLib.builder import ( from fontTools.otlLib.error import OpenTypeLibError from collections import defaultdict import itertools +from io import StringIO import logging import warnings import os @@ -78,7 +79,7 @@ def addOpenTypeFeaturesFromString( """ - featurefile = UnicodeIO(tounicode(features)) + featurefile = StringIO(tostr(features)) if filename: featurefile.name = filename addOpenTypeFeatures(font, featurefile, tables=tables, debug=debug) @@ -98,6 +99,7 @@ class Builder(object): "hhea", "name", "vhea", + "STAT", ] ) @@ -159,6 +161,8 @@ class Builder(object): self.hhea_ = {} # for table 'vhea' self.vhea_ = {} + # for table 'STAT' + self.stat_ = {} def build(self, tables=None, debug=False): if self.parseTree is None: @@ -188,6 +192,8 @@ class Builder(object): self.build_name() if "OS/2" in tables: self.build_OS_2() + if "STAT" in tables: + self.build_STAT() for tag in ("GPOS", "GSUB"): if tag not in tables: continue @@ -510,6 +516,140 @@ class Builder(object): if version >= 5: checkattr(table, ("usLowerOpticalPointSize", "usUpperOpticalPointSize")) + def setElidedFallbackName(self, value, location): + # ElidedFallbackName is a convenience method for setting + # ElidedFallbackNameID so only one can be allowed + for token in ("ElidedFallbackName", "ElidedFallbackNameID"): + if token in self.stat_: + raise FeatureLibError( + f"{token} is already set.", + location, + ) + if isinstance(value, int): + self.stat_["ElidedFallbackNameID"] = value + elif isinstance(value, list): + self.stat_["ElidedFallbackName"] = value + else: + raise AssertionError(value) + + def addDesignAxis(self, designAxis, location): + if "DesignAxes" not in self.stat_: + self.stat_["DesignAxes"] = [] + if designAxis.tag in (r.tag for r in self.stat_["DesignAxes"]): + raise FeatureLibError( + f'DesignAxis already defined for tag "{designAxis.tag}".', + location, + ) + if designAxis.axisOrder in (r.axisOrder for r in self.stat_["DesignAxes"]): + raise FeatureLibError( + f"DesignAxis already defined for axis number {designAxis.axisOrder}.", + location, + ) + self.stat_["DesignAxes"].append(designAxis) + + def addAxisValueRecord(self, axisValueRecord, location): + if "AxisValueRecords" not in self.stat_: + self.stat_["AxisValueRecords"] = [] + # Check for duplicate AxisValueRecords + for record_ in self.stat_["AxisValueRecords"]: + if ( + {n.asFea() for n in record_.names} + == {n.asFea() for n in axisValueRecord.names} + and {n.asFea() for n in record_.locations} + == {n.asFea() for n in axisValueRecord.locations} + and record_.flags == axisValueRecord.flags + ): + raise FeatureLibError( + "An AxisValueRecord with these values is already defined.", + location, + ) + self.stat_["AxisValueRecords"].append(axisValueRecord) + + def build_STAT(self): + if not self.stat_: + return + + axes = self.stat_.get("DesignAxes") + if not axes: + raise FeatureLibError("DesignAxes not defined", None) + axisValueRecords = self.stat_.get("AxisValueRecords") + axisValues = {} + format4_locations = [] + for tag in axes: + axisValues[tag.tag] = [] + if axisValueRecords is not None: + for avr in axisValueRecords: + valuesDict = {} + if avr.flags > 0: + valuesDict["flags"] = avr.flags + if len(avr.locations) == 1: + location = avr.locations[0] + values = location.values + if len(values) == 1: # format1 + valuesDict.update({"value": values[0], "name": avr.names}) + if len(values) == 2: # format3 + valuesDict.update( + { + "value": values[0], + "linkedValue": values[1], + "name": avr.names, + } + ) + if len(values) == 3: # format2 + nominal, minVal, maxVal = values + valuesDict.update( + { + "nominalValue": nominal, + "rangeMinValue": minVal, + "rangeMaxValue": maxVal, + "name": avr.names, + } + ) + axisValues[location.tag].append(valuesDict) + else: + valuesDict.update( + { + "location": {i.tag: i.values[0] for i in avr.locations}, + "name": avr.names, + } + ) + format4_locations.append(valuesDict) + + designAxes = [ + { + "ordering": a.axisOrder, + "tag": a.tag, + "name": a.names, + "values": axisValues[a.tag], + } + for a in axes + ] + + nameTable = self.font.get("name") + if not nameTable: # this only happens for unit tests + nameTable = self.font["name"] = newTable("name") + nameTable.names = [] + + if "ElidedFallbackNameID" in self.stat_: + nameID = self.stat_["ElidedFallbackNameID"] + name = nameTable.getDebugName(nameID) + if not name: + raise FeatureLibError( + f"ElidedFallbackNameID {nameID} points " + "to a nameID that does not exist in the " + '"name" table', + None, + ) + elif "ElidedFallbackName" in self.stat_: + nameID = self.stat_["ElidedFallbackName"] + + otl.buildStatTable( + self.font, + designAxes, + locations=format4_locations, + elidedFallbackName=nameID, + ) + def build_codepages_(self, pages): pages2bits = { 1252: 0, @@ -718,8 +858,10 @@ class Builder(object): str(ix) ]._replace(feature=key) except KeyError: - warnings.warn("feaLib.Builder subclass needs upgrading to " - "stash debug information. See fonttools#2065.") + warnings.warn( + "feaLib.Builder subclass needs upgrading to " + "stash debug information. See fonttools#2065." + ) feature_key = (feature_tag, lookup_indices) feature_index = feature_indices.get(feature_key) |