1*e1fe3e4aSElliott Hughesfrom fontTools.misc import sstruct 2*e1fe3e4aSElliott Hughesfrom fontTools.misc.textTools import Tag, tostr, binary2num, safeEval 3*e1fe3e4aSElliott Hughesfrom fontTools.feaLib.error import FeatureLibError 4*e1fe3e4aSElliott Hughesfrom fontTools.feaLib.lookupDebugInfo import ( 5*e1fe3e4aSElliott Hughes LookupDebugInfo, 6*e1fe3e4aSElliott Hughes LOOKUP_DEBUG_INFO_KEY, 7*e1fe3e4aSElliott Hughes LOOKUP_DEBUG_ENV_VAR, 8*e1fe3e4aSElliott Hughes) 9*e1fe3e4aSElliott Hughesfrom fontTools.feaLib.parser import Parser 10*e1fe3e4aSElliott Hughesfrom fontTools.feaLib.ast import FeatureFile 11*e1fe3e4aSElliott Hughesfrom fontTools.feaLib.variableScalar import VariableScalar 12*e1fe3e4aSElliott Hughesfrom fontTools.otlLib import builder as otl 13*e1fe3e4aSElliott Hughesfrom fontTools.otlLib.maxContextCalc import maxCtxFont 14*e1fe3e4aSElliott Hughesfrom fontTools.ttLib import newTable, getTableModule 15*e1fe3e4aSElliott Hughesfrom fontTools.ttLib.tables import otBase, otTables 16*e1fe3e4aSElliott Hughesfrom fontTools.otlLib.builder import ( 17*e1fe3e4aSElliott Hughes AlternateSubstBuilder, 18*e1fe3e4aSElliott Hughes ChainContextPosBuilder, 19*e1fe3e4aSElliott Hughes ChainContextSubstBuilder, 20*e1fe3e4aSElliott Hughes LigatureSubstBuilder, 21*e1fe3e4aSElliott Hughes MultipleSubstBuilder, 22*e1fe3e4aSElliott Hughes CursivePosBuilder, 23*e1fe3e4aSElliott Hughes MarkBasePosBuilder, 24*e1fe3e4aSElliott Hughes MarkLigPosBuilder, 25*e1fe3e4aSElliott Hughes MarkMarkPosBuilder, 26*e1fe3e4aSElliott Hughes ReverseChainSingleSubstBuilder, 27*e1fe3e4aSElliott Hughes SingleSubstBuilder, 28*e1fe3e4aSElliott Hughes ClassPairPosSubtableBuilder, 29*e1fe3e4aSElliott Hughes PairPosBuilder, 30*e1fe3e4aSElliott Hughes SinglePosBuilder, 31*e1fe3e4aSElliott Hughes ChainContextualRule, 32*e1fe3e4aSElliott Hughes) 33*e1fe3e4aSElliott Hughesfrom fontTools.otlLib.error import OpenTypeLibError 34*e1fe3e4aSElliott Hughesfrom fontTools.varLib.varStore import OnlineVarStoreBuilder 35*e1fe3e4aSElliott Hughesfrom fontTools.varLib.builder import buildVarDevTable 36*e1fe3e4aSElliott Hughesfrom fontTools.varLib.featureVars import addFeatureVariationsRaw 37*e1fe3e4aSElliott Hughesfrom fontTools.varLib.models import normalizeValue, piecewiseLinearMap 38*e1fe3e4aSElliott Hughesfrom collections import defaultdict 39*e1fe3e4aSElliott Hughesimport copy 40*e1fe3e4aSElliott Hughesimport itertools 41*e1fe3e4aSElliott Hughesfrom io import StringIO 42*e1fe3e4aSElliott Hughesimport logging 43*e1fe3e4aSElliott Hughesimport warnings 44*e1fe3e4aSElliott Hughesimport os 45*e1fe3e4aSElliott Hughes 46*e1fe3e4aSElliott Hughes 47*e1fe3e4aSElliott Hugheslog = logging.getLogger(__name__) 48*e1fe3e4aSElliott Hughes 49*e1fe3e4aSElliott Hughes 50*e1fe3e4aSElliott Hughesdef addOpenTypeFeatures(font, featurefile, tables=None, debug=False): 51*e1fe3e4aSElliott Hughes """Add features from a file to a font. Note that this replaces any features 52*e1fe3e4aSElliott Hughes currently present. 53*e1fe3e4aSElliott Hughes 54*e1fe3e4aSElliott Hughes Args: 55*e1fe3e4aSElliott Hughes font (feaLib.ttLib.TTFont): The font object. 56*e1fe3e4aSElliott Hughes featurefile: Either a path or file object (in which case we 57*e1fe3e4aSElliott Hughes parse it into an AST), or a pre-parsed AST instance. 58*e1fe3e4aSElliott Hughes tables: If passed, restrict the set of affected tables to those in the 59*e1fe3e4aSElliott Hughes list. 60*e1fe3e4aSElliott Hughes debug: Whether to add source debugging information to the font in the 61*e1fe3e4aSElliott Hughes ``Debg`` table 62*e1fe3e4aSElliott Hughes 63*e1fe3e4aSElliott Hughes """ 64*e1fe3e4aSElliott Hughes builder = Builder(font, featurefile) 65*e1fe3e4aSElliott Hughes builder.build(tables=tables, debug=debug) 66*e1fe3e4aSElliott Hughes 67*e1fe3e4aSElliott Hughes 68*e1fe3e4aSElliott Hughesdef addOpenTypeFeaturesFromString( 69*e1fe3e4aSElliott Hughes font, features, filename=None, tables=None, debug=False 70*e1fe3e4aSElliott Hughes): 71*e1fe3e4aSElliott Hughes """Add features from a string to a font. Note that this replaces any 72*e1fe3e4aSElliott Hughes features currently present. 73*e1fe3e4aSElliott Hughes 74*e1fe3e4aSElliott Hughes Args: 75*e1fe3e4aSElliott Hughes font (feaLib.ttLib.TTFont): The font object. 76*e1fe3e4aSElliott Hughes features: A string containing feature code. 77*e1fe3e4aSElliott Hughes filename: The directory containing ``filename`` is used as the root of 78*e1fe3e4aSElliott Hughes relative ``include()`` paths; if ``None`` is provided, the current 79*e1fe3e4aSElliott Hughes directory is assumed. 80*e1fe3e4aSElliott Hughes tables: If passed, restrict the set of affected tables to those in the 81*e1fe3e4aSElliott Hughes list. 82*e1fe3e4aSElliott Hughes debug: Whether to add source debugging information to the font in the 83*e1fe3e4aSElliott Hughes ``Debg`` table 84*e1fe3e4aSElliott Hughes 85*e1fe3e4aSElliott Hughes """ 86*e1fe3e4aSElliott Hughes 87*e1fe3e4aSElliott Hughes featurefile = StringIO(tostr(features)) 88*e1fe3e4aSElliott Hughes if filename: 89*e1fe3e4aSElliott Hughes featurefile.name = filename 90*e1fe3e4aSElliott Hughes addOpenTypeFeatures(font, featurefile, tables=tables, debug=debug) 91*e1fe3e4aSElliott Hughes 92*e1fe3e4aSElliott Hughes 93*e1fe3e4aSElliott Hughesclass Builder(object): 94*e1fe3e4aSElliott Hughes supportedTables = frozenset( 95*e1fe3e4aSElliott Hughes Tag(tag) 96*e1fe3e4aSElliott Hughes for tag in [ 97*e1fe3e4aSElliott Hughes "BASE", 98*e1fe3e4aSElliott Hughes "GDEF", 99*e1fe3e4aSElliott Hughes "GPOS", 100*e1fe3e4aSElliott Hughes "GSUB", 101*e1fe3e4aSElliott Hughes "OS/2", 102*e1fe3e4aSElliott Hughes "head", 103*e1fe3e4aSElliott Hughes "hhea", 104*e1fe3e4aSElliott Hughes "name", 105*e1fe3e4aSElliott Hughes "vhea", 106*e1fe3e4aSElliott Hughes "STAT", 107*e1fe3e4aSElliott Hughes ] 108*e1fe3e4aSElliott Hughes ) 109*e1fe3e4aSElliott Hughes 110*e1fe3e4aSElliott Hughes def __init__(self, font, featurefile): 111*e1fe3e4aSElliott Hughes self.font = font 112*e1fe3e4aSElliott Hughes # 'featurefile' can be either a path or file object (in which case we 113*e1fe3e4aSElliott Hughes # parse it into an AST), or a pre-parsed AST instance 114*e1fe3e4aSElliott Hughes if isinstance(featurefile, FeatureFile): 115*e1fe3e4aSElliott Hughes self.parseTree, self.file = featurefile, None 116*e1fe3e4aSElliott Hughes else: 117*e1fe3e4aSElliott Hughes self.parseTree, self.file = None, featurefile 118*e1fe3e4aSElliott Hughes self.glyphMap = font.getReverseGlyphMap() 119*e1fe3e4aSElliott Hughes self.varstorebuilder = None 120*e1fe3e4aSElliott Hughes if "fvar" in font: 121*e1fe3e4aSElliott Hughes self.axes = font["fvar"].axes 122*e1fe3e4aSElliott Hughes self.varstorebuilder = OnlineVarStoreBuilder( 123*e1fe3e4aSElliott Hughes [ax.axisTag for ax in self.axes] 124*e1fe3e4aSElliott Hughes ) 125*e1fe3e4aSElliott Hughes self.default_language_systems_ = set() 126*e1fe3e4aSElliott Hughes self.script_ = None 127*e1fe3e4aSElliott Hughes self.lookupflag_ = 0 128*e1fe3e4aSElliott Hughes self.lookupflag_markFilterSet_ = None 129*e1fe3e4aSElliott Hughes self.language_systems = set() 130*e1fe3e4aSElliott Hughes self.seen_non_DFLT_script_ = False 131*e1fe3e4aSElliott Hughes self.named_lookups_ = {} 132*e1fe3e4aSElliott Hughes self.cur_lookup_ = None 133*e1fe3e4aSElliott Hughes self.cur_lookup_name_ = None 134*e1fe3e4aSElliott Hughes self.cur_feature_name_ = None 135*e1fe3e4aSElliott Hughes self.lookups_ = [] 136*e1fe3e4aSElliott Hughes self.lookup_locations = {"GSUB": {}, "GPOS": {}} 137*e1fe3e4aSElliott Hughes self.features_ = {} # ('latn', 'DEU ', 'smcp') --> [LookupBuilder*] 138*e1fe3e4aSElliott Hughes self.required_features_ = {} # ('latn', 'DEU ') --> 'scmp' 139*e1fe3e4aSElliott Hughes self.feature_variations_ = {} 140*e1fe3e4aSElliott Hughes # for feature 'aalt' 141*e1fe3e4aSElliott Hughes self.aalt_features_ = [] # [(location, featureName)*], for 'aalt' 142*e1fe3e4aSElliott Hughes self.aalt_location_ = None 143*e1fe3e4aSElliott Hughes self.aalt_alternates_ = {} 144*e1fe3e4aSElliott Hughes # for 'featureNames' 145*e1fe3e4aSElliott Hughes self.featureNames_ = set() 146*e1fe3e4aSElliott Hughes self.featureNames_ids_ = {} 147*e1fe3e4aSElliott Hughes # for 'cvParameters' 148*e1fe3e4aSElliott Hughes self.cv_parameters_ = set() 149*e1fe3e4aSElliott Hughes self.cv_parameters_ids_ = {} 150*e1fe3e4aSElliott Hughes self.cv_num_named_params_ = {} 151*e1fe3e4aSElliott Hughes self.cv_characters_ = defaultdict(list) 152*e1fe3e4aSElliott Hughes # for feature 'size' 153*e1fe3e4aSElliott Hughes self.size_parameters_ = None 154*e1fe3e4aSElliott Hughes # for table 'head' 155*e1fe3e4aSElliott Hughes self.fontRevision_ = None # 2.71 156*e1fe3e4aSElliott Hughes # for table 'name' 157*e1fe3e4aSElliott Hughes self.names_ = [] 158*e1fe3e4aSElliott Hughes # for table 'BASE' 159*e1fe3e4aSElliott Hughes self.base_horiz_axis_ = None 160*e1fe3e4aSElliott Hughes self.base_vert_axis_ = None 161*e1fe3e4aSElliott Hughes # for table 'GDEF' 162*e1fe3e4aSElliott Hughes self.attachPoints_ = {} # "a" --> {3, 7} 163*e1fe3e4aSElliott Hughes self.ligCaretCoords_ = {} # "f_f_i" --> {300, 600} 164*e1fe3e4aSElliott Hughes self.ligCaretPoints_ = {} # "f_f_i" --> {3, 7} 165*e1fe3e4aSElliott Hughes self.glyphClassDefs_ = {} # "fi" --> (2, (file, line, column)) 166*e1fe3e4aSElliott Hughes self.markAttach_ = {} # "acute" --> (4, (file, line, column)) 167*e1fe3e4aSElliott Hughes self.markAttachClassID_ = {} # frozenset({"acute", "grave"}) --> 4 168*e1fe3e4aSElliott Hughes self.markFilterSets_ = {} # frozenset({"acute", "grave"}) --> 4 169*e1fe3e4aSElliott Hughes # for table 'OS/2' 170*e1fe3e4aSElliott Hughes self.os2_ = {} 171*e1fe3e4aSElliott Hughes # for table 'hhea' 172*e1fe3e4aSElliott Hughes self.hhea_ = {} 173*e1fe3e4aSElliott Hughes # for table 'vhea' 174*e1fe3e4aSElliott Hughes self.vhea_ = {} 175*e1fe3e4aSElliott Hughes # for table 'STAT' 176*e1fe3e4aSElliott Hughes self.stat_ = {} 177*e1fe3e4aSElliott Hughes # for conditionsets 178*e1fe3e4aSElliott Hughes self.conditionsets_ = {} 179*e1fe3e4aSElliott Hughes # We will often use exactly the same locations (i.e. the font's masters) 180*e1fe3e4aSElliott Hughes # for a large number of variable scalars. Instead of creating a model 181*e1fe3e4aSElliott Hughes # for each, let's share the models. 182*e1fe3e4aSElliott Hughes self.model_cache = {} 183*e1fe3e4aSElliott Hughes 184*e1fe3e4aSElliott Hughes def build(self, tables=None, debug=False): 185*e1fe3e4aSElliott Hughes if self.parseTree is None: 186*e1fe3e4aSElliott Hughes self.parseTree = Parser(self.file, self.glyphMap).parse() 187*e1fe3e4aSElliott Hughes self.parseTree.build(self) 188*e1fe3e4aSElliott Hughes # by default, build all the supported tables 189*e1fe3e4aSElliott Hughes if tables is None: 190*e1fe3e4aSElliott Hughes tables = self.supportedTables 191*e1fe3e4aSElliott Hughes else: 192*e1fe3e4aSElliott Hughes tables = frozenset(tables) 193*e1fe3e4aSElliott Hughes unsupported = tables - self.supportedTables 194*e1fe3e4aSElliott Hughes if unsupported: 195*e1fe3e4aSElliott Hughes unsupported_string = ", ".join(sorted(unsupported)) 196*e1fe3e4aSElliott Hughes raise NotImplementedError( 197*e1fe3e4aSElliott Hughes "The following tables were requested but are unsupported: " 198*e1fe3e4aSElliott Hughes f"{unsupported_string}." 199*e1fe3e4aSElliott Hughes ) 200*e1fe3e4aSElliott Hughes if "GSUB" in tables: 201*e1fe3e4aSElliott Hughes self.build_feature_aalt_() 202*e1fe3e4aSElliott Hughes if "head" in tables: 203*e1fe3e4aSElliott Hughes self.build_head() 204*e1fe3e4aSElliott Hughes if "hhea" in tables: 205*e1fe3e4aSElliott Hughes self.build_hhea() 206*e1fe3e4aSElliott Hughes if "vhea" in tables: 207*e1fe3e4aSElliott Hughes self.build_vhea() 208*e1fe3e4aSElliott Hughes if "name" in tables: 209*e1fe3e4aSElliott Hughes self.build_name() 210*e1fe3e4aSElliott Hughes if "OS/2" in tables: 211*e1fe3e4aSElliott Hughes self.build_OS_2() 212*e1fe3e4aSElliott Hughes if "STAT" in tables: 213*e1fe3e4aSElliott Hughes self.build_STAT() 214*e1fe3e4aSElliott Hughes for tag in ("GPOS", "GSUB"): 215*e1fe3e4aSElliott Hughes if tag not in tables: 216*e1fe3e4aSElliott Hughes continue 217*e1fe3e4aSElliott Hughes table = self.makeTable(tag) 218*e1fe3e4aSElliott Hughes if self.feature_variations_: 219*e1fe3e4aSElliott Hughes self.makeFeatureVariations(table, tag) 220*e1fe3e4aSElliott Hughes if ( 221*e1fe3e4aSElliott Hughes table.ScriptList.ScriptCount > 0 222*e1fe3e4aSElliott Hughes or table.FeatureList.FeatureCount > 0 223*e1fe3e4aSElliott Hughes or table.LookupList.LookupCount > 0 224*e1fe3e4aSElliott Hughes ): 225*e1fe3e4aSElliott Hughes fontTable = self.font[tag] = newTable(tag) 226*e1fe3e4aSElliott Hughes fontTable.table = table 227*e1fe3e4aSElliott Hughes elif tag in self.font: 228*e1fe3e4aSElliott Hughes del self.font[tag] 229*e1fe3e4aSElliott Hughes if any(tag in self.font for tag in ("GPOS", "GSUB")) and "OS/2" in self.font: 230*e1fe3e4aSElliott Hughes self.font["OS/2"].usMaxContext = maxCtxFont(self.font) 231*e1fe3e4aSElliott Hughes if "GDEF" in tables: 232*e1fe3e4aSElliott Hughes gdef = self.buildGDEF() 233*e1fe3e4aSElliott Hughes if gdef: 234*e1fe3e4aSElliott Hughes self.font["GDEF"] = gdef 235*e1fe3e4aSElliott Hughes elif "GDEF" in self.font: 236*e1fe3e4aSElliott Hughes del self.font["GDEF"] 237*e1fe3e4aSElliott Hughes if "BASE" in tables: 238*e1fe3e4aSElliott Hughes base = self.buildBASE() 239*e1fe3e4aSElliott Hughes if base: 240*e1fe3e4aSElliott Hughes self.font["BASE"] = base 241*e1fe3e4aSElliott Hughes elif "BASE" in self.font: 242*e1fe3e4aSElliott Hughes del self.font["BASE"] 243*e1fe3e4aSElliott Hughes if debug or os.environ.get(LOOKUP_DEBUG_ENV_VAR): 244*e1fe3e4aSElliott Hughes self.buildDebg() 245*e1fe3e4aSElliott Hughes 246*e1fe3e4aSElliott Hughes def get_chained_lookup_(self, location, builder_class): 247*e1fe3e4aSElliott Hughes result = builder_class(self.font, location) 248*e1fe3e4aSElliott Hughes result.lookupflag = self.lookupflag_ 249*e1fe3e4aSElliott Hughes result.markFilterSet = self.lookupflag_markFilterSet_ 250*e1fe3e4aSElliott Hughes self.lookups_.append(result) 251*e1fe3e4aSElliott Hughes return result 252*e1fe3e4aSElliott Hughes 253*e1fe3e4aSElliott Hughes def add_lookup_to_feature_(self, lookup, feature_name): 254*e1fe3e4aSElliott Hughes for script, lang in self.language_systems: 255*e1fe3e4aSElliott Hughes key = (script, lang, feature_name) 256*e1fe3e4aSElliott Hughes self.features_.setdefault(key, []).append(lookup) 257*e1fe3e4aSElliott Hughes 258*e1fe3e4aSElliott Hughes def get_lookup_(self, location, builder_class): 259*e1fe3e4aSElliott Hughes if ( 260*e1fe3e4aSElliott Hughes self.cur_lookup_ 261*e1fe3e4aSElliott Hughes and type(self.cur_lookup_) == builder_class 262*e1fe3e4aSElliott Hughes and self.cur_lookup_.lookupflag == self.lookupflag_ 263*e1fe3e4aSElliott Hughes and self.cur_lookup_.markFilterSet == self.lookupflag_markFilterSet_ 264*e1fe3e4aSElliott Hughes ): 265*e1fe3e4aSElliott Hughes return self.cur_lookup_ 266*e1fe3e4aSElliott Hughes if self.cur_lookup_name_ and self.cur_lookup_: 267*e1fe3e4aSElliott Hughes raise FeatureLibError( 268*e1fe3e4aSElliott Hughes "Within a named lookup block, all rules must be of " 269*e1fe3e4aSElliott Hughes "the same lookup type and flag", 270*e1fe3e4aSElliott Hughes location, 271*e1fe3e4aSElliott Hughes ) 272*e1fe3e4aSElliott Hughes self.cur_lookup_ = builder_class(self.font, location) 273*e1fe3e4aSElliott Hughes self.cur_lookup_.lookupflag = self.lookupflag_ 274*e1fe3e4aSElliott Hughes self.cur_lookup_.markFilterSet = self.lookupflag_markFilterSet_ 275*e1fe3e4aSElliott Hughes self.lookups_.append(self.cur_lookup_) 276*e1fe3e4aSElliott Hughes if self.cur_lookup_name_: 277*e1fe3e4aSElliott Hughes # We are starting a lookup rule inside a named lookup block. 278*e1fe3e4aSElliott Hughes self.named_lookups_[self.cur_lookup_name_] = self.cur_lookup_ 279*e1fe3e4aSElliott Hughes if self.cur_feature_name_: 280*e1fe3e4aSElliott Hughes # We are starting a lookup rule inside a feature. This includes 281*e1fe3e4aSElliott Hughes # lookup rules inside named lookups inside features. 282*e1fe3e4aSElliott Hughes self.add_lookup_to_feature_(self.cur_lookup_, self.cur_feature_name_) 283*e1fe3e4aSElliott Hughes return self.cur_lookup_ 284*e1fe3e4aSElliott Hughes 285*e1fe3e4aSElliott Hughes def build_feature_aalt_(self): 286*e1fe3e4aSElliott Hughes if not self.aalt_features_ and not self.aalt_alternates_: 287*e1fe3e4aSElliott Hughes return 288*e1fe3e4aSElliott Hughes # > alternate glyphs will be sorted in the order that the source features 289*e1fe3e4aSElliott Hughes # > are named in the aalt definition, not the order of the feature definitions 290*e1fe3e4aSElliott Hughes # > in the file. Alternates defined explicitly ... will precede all others. 291*e1fe3e4aSElliott Hughes # https://github.com/fonttools/fonttools/issues/836 292*e1fe3e4aSElliott Hughes alternates = {g: list(a) for g, a in self.aalt_alternates_.items()} 293*e1fe3e4aSElliott Hughes for location, name in self.aalt_features_ + [(None, "aalt")]: 294*e1fe3e4aSElliott Hughes feature = [ 295*e1fe3e4aSElliott Hughes (script, lang, feature, lookups) 296*e1fe3e4aSElliott Hughes for (script, lang, feature), lookups in self.features_.items() 297*e1fe3e4aSElliott Hughes if feature == name 298*e1fe3e4aSElliott Hughes ] 299*e1fe3e4aSElliott Hughes # "aalt" does not have to specify its own lookups, but it might. 300*e1fe3e4aSElliott Hughes if not feature and name != "aalt": 301*e1fe3e4aSElliott Hughes warnings.warn("%s: Feature %s has not been defined" % (location, name)) 302*e1fe3e4aSElliott Hughes continue 303*e1fe3e4aSElliott Hughes for script, lang, feature, lookups in feature: 304*e1fe3e4aSElliott Hughes for lookuplist in lookups: 305*e1fe3e4aSElliott Hughes if not isinstance(lookuplist, list): 306*e1fe3e4aSElliott Hughes lookuplist = [lookuplist] 307*e1fe3e4aSElliott Hughes for lookup in lookuplist: 308*e1fe3e4aSElliott Hughes for glyph, alts in lookup.getAlternateGlyphs().items(): 309*e1fe3e4aSElliott Hughes alts_for_glyph = alternates.setdefault(glyph, []) 310*e1fe3e4aSElliott Hughes alts_for_glyph.extend( 311*e1fe3e4aSElliott Hughes g for g in alts if g not in alts_for_glyph 312*e1fe3e4aSElliott Hughes ) 313*e1fe3e4aSElliott Hughes single = { 314*e1fe3e4aSElliott Hughes glyph: repl[0] for glyph, repl in alternates.items() if len(repl) == 1 315*e1fe3e4aSElliott Hughes } 316*e1fe3e4aSElliott Hughes multi = {glyph: repl for glyph, repl in alternates.items() if len(repl) > 1} 317*e1fe3e4aSElliott Hughes if not single and not multi: 318*e1fe3e4aSElliott Hughes return 319*e1fe3e4aSElliott Hughes self.features_ = { 320*e1fe3e4aSElliott Hughes (script, lang, feature): lookups 321*e1fe3e4aSElliott Hughes for (script, lang, feature), lookups in self.features_.items() 322*e1fe3e4aSElliott Hughes if feature != "aalt" 323*e1fe3e4aSElliott Hughes } 324*e1fe3e4aSElliott Hughes old_lookups = self.lookups_ 325*e1fe3e4aSElliott Hughes self.lookups_ = [] 326*e1fe3e4aSElliott Hughes self.start_feature(self.aalt_location_, "aalt") 327*e1fe3e4aSElliott Hughes if single: 328*e1fe3e4aSElliott Hughes single_lookup = self.get_lookup_(location, SingleSubstBuilder) 329*e1fe3e4aSElliott Hughes single_lookup.mapping = single 330*e1fe3e4aSElliott Hughes if multi: 331*e1fe3e4aSElliott Hughes multi_lookup = self.get_lookup_(location, AlternateSubstBuilder) 332*e1fe3e4aSElliott Hughes multi_lookup.alternates = multi 333*e1fe3e4aSElliott Hughes self.end_feature() 334*e1fe3e4aSElliott Hughes self.lookups_.extend(old_lookups) 335*e1fe3e4aSElliott Hughes 336*e1fe3e4aSElliott Hughes def build_head(self): 337*e1fe3e4aSElliott Hughes if not self.fontRevision_: 338*e1fe3e4aSElliott Hughes return 339*e1fe3e4aSElliott Hughes table = self.font.get("head") 340*e1fe3e4aSElliott Hughes if not table: # this only happens for unit tests 341*e1fe3e4aSElliott Hughes table = self.font["head"] = newTable("head") 342*e1fe3e4aSElliott Hughes table.decompile(b"\0" * 54, self.font) 343*e1fe3e4aSElliott Hughes table.tableVersion = 1.0 344*e1fe3e4aSElliott Hughes table.created = table.modified = 3406620153 # 2011-12-13 11:22:33 345*e1fe3e4aSElliott Hughes table.fontRevision = self.fontRevision_ 346*e1fe3e4aSElliott Hughes 347*e1fe3e4aSElliott Hughes def build_hhea(self): 348*e1fe3e4aSElliott Hughes if not self.hhea_: 349*e1fe3e4aSElliott Hughes return 350*e1fe3e4aSElliott Hughes table = self.font.get("hhea") 351*e1fe3e4aSElliott Hughes if not table: # this only happens for unit tests 352*e1fe3e4aSElliott Hughes table = self.font["hhea"] = newTable("hhea") 353*e1fe3e4aSElliott Hughes table.decompile(b"\0" * 36, self.font) 354*e1fe3e4aSElliott Hughes table.tableVersion = 0x00010000 355*e1fe3e4aSElliott Hughes if "caretoffset" in self.hhea_: 356*e1fe3e4aSElliott Hughes table.caretOffset = self.hhea_["caretoffset"] 357*e1fe3e4aSElliott Hughes if "ascender" in self.hhea_: 358*e1fe3e4aSElliott Hughes table.ascent = self.hhea_["ascender"] 359*e1fe3e4aSElliott Hughes if "descender" in self.hhea_: 360*e1fe3e4aSElliott Hughes table.descent = self.hhea_["descender"] 361*e1fe3e4aSElliott Hughes if "linegap" in self.hhea_: 362*e1fe3e4aSElliott Hughes table.lineGap = self.hhea_["linegap"] 363*e1fe3e4aSElliott Hughes 364*e1fe3e4aSElliott Hughes def build_vhea(self): 365*e1fe3e4aSElliott Hughes if not self.vhea_: 366*e1fe3e4aSElliott Hughes return 367*e1fe3e4aSElliott Hughes table = self.font.get("vhea") 368*e1fe3e4aSElliott Hughes if not table: # this only happens for unit tests 369*e1fe3e4aSElliott Hughes table = self.font["vhea"] = newTable("vhea") 370*e1fe3e4aSElliott Hughes table.decompile(b"\0" * 36, self.font) 371*e1fe3e4aSElliott Hughes table.tableVersion = 0x00011000 372*e1fe3e4aSElliott Hughes if "verttypoascender" in self.vhea_: 373*e1fe3e4aSElliott Hughes table.ascent = self.vhea_["verttypoascender"] 374*e1fe3e4aSElliott Hughes if "verttypodescender" in self.vhea_: 375*e1fe3e4aSElliott Hughes table.descent = self.vhea_["verttypodescender"] 376*e1fe3e4aSElliott Hughes if "verttypolinegap" in self.vhea_: 377*e1fe3e4aSElliott Hughes table.lineGap = self.vhea_["verttypolinegap"] 378*e1fe3e4aSElliott Hughes 379*e1fe3e4aSElliott Hughes def get_user_name_id(self, table): 380*e1fe3e4aSElliott Hughes # Try to find first unused font-specific name id 381*e1fe3e4aSElliott Hughes nameIDs = [name.nameID for name in table.names] 382*e1fe3e4aSElliott Hughes for user_name_id in range(256, 32767): 383*e1fe3e4aSElliott Hughes if user_name_id not in nameIDs: 384*e1fe3e4aSElliott Hughes return user_name_id 385*e1fe3e4aSElliott Hughes 386*e1fe3e4aSElliott Hughes def buildFeatureParams(self, tag): 387*e1fe3e4aSElliott Hughes params = None 388*e1fe3e4aSElliott Hughes if tag == "size": 389*e1fe3e4aSElliott Hughes params = otTables.FeatureParamsSize() 390*e1fe3e4aSElliott Hughes ( 391*e1fe3e4aSElliott Hughes params.DesignSize, 392*e1fe3e4aSElliott Hughes params.SubfamilyID, 393*e1fe3e4aSElliott Hughes params.RangeStart, 394*e1fe3e4aSElliott Hughes params.RangeEnd, 395*e1fe3e4aSElliott Hughes ) = self.size_parameters_ 396*e1fe3e4aSElliott Hughes if tag in self.featureNames_ids_: 397*e1fe3e4aSElliott Hughes params.SubfamilyNameID = self.featureNames_ids_[tag] 398*e1fe3e4aSElliott Hughes else: 399*e1fe3e4aSElliott Hughes params.SubfamilyNameID = 0 400*e1fe3e4aSElliott Hughes elif tag in self.featureNames_: 401*e1fe3e4aSElliott Hughes if not self.featureNames_ids_: 402*e1fe3e4aSElliott Hughes # name table wasn't selected among the tables to build; skip 403*e1fe3e4aSElliott Hughes pass 404*e1fe3e4aSElliott Hughes else: 405*e1fe3e4aSElliott Hughes assert tag in self.featureNames_ids_ 406*e1fe3e4aSElliott Hughes params = otTables.FeatureParamsStylisticSet() 407*e1fe3e4aSElliott Hughes params.Version = 0 408*e1fe3e4aSElliott Hughes params.UINameID = self.featureNames_ids_[tag] 409*e1fe3e4aSElliott Hughes elif tag in self.cv_parameters_: 410*e1fe3e4aSElliott Hughes params = otTables.FeatureParamsCharacterVariants() 411*e1fe3e4aSElliott Hughes params.Format = 0 412*e1fe3e4aSElliott Hughes params.FeatUILabelNameID = self.cv_parameters_ids_.get( 413*e1fe3e4aSElliott Hughes (tag, "FeatUILabelNameID"), 0 414*e1fe3e4aSElliott Hughes ) 415*e1fe3e4aSElliott Hughes params.FeatUITooltipTextNameID = self.cv_parameters_ids_.get( 416*e1fe3e4aSElliott Hughes (tag, "FeatUITooltipTextNameID"), 0 417*e1fe3e4aSElliott Hughes ) 418*e1fe3e4aSElliott Hughes params.SampleTextNameID = self.cv_parameters_ids_.get( 419*e1fe3e4aSElliott Hughes (tag, "SampleTextNameID"), 0 420*e1fe3e4aSElliott Hughes ) 421*e1fe3e4aSElliott Hughes params.NumNamedParameters = self.cv_num_named_params_.get(tag, 0) 422*e1fe3e4aSElliott Hughes params.FirstParamUILabelNameID = self.cv_parameters_ids_.get( 423*e1fe3e4aSElliott Hughes (tag, "ParamUILabelNameID_0"), 0 424*e1fe3e4aSElliott Hughes ) 425*e1fe3e4aSElliott Hughes params.CharCount = len(self.cv_characters_[tag]) 426*e1fe3e4aSElliott Hughes params.Character = self.cv_characters_[tag] 427*e1fe3e4aSElliott Hughes return params 428*e1fe3e4aSElliott Hughes 429*e1fe3e4aSElliott Hughes def build_name(self): 430*e1fe3e4aSElliott Hughes if not self.names_: 431*e1fe3e4aSElliott Hughes return 432*e1fe3e4aSElliott Hughes table = self.font.get("name") 433*e1fe3e4aSElliott Hughes if not table: # this only happens for unit tests 434*e1fe3e4aSElliott Hughes table = self.font["name"] = newTable("name") 435*e1fe3e4aSElliott Hughes table.names = [] 436*e1fe3e4aSElliott Hughes for name in self.names_: 437*e1fe3e4aSElliott Hughes nameID, platformID, platEncID, langID, string = name 438*e1fe3e4aSElliott Hughes # For featureNames block, nameID is 'feature tag' 439*e1fe3e4aSElliott Hughes # For cvParameters blocks, nameID is ('feature tag', 'block name') 440*e1fe3e4aSElliott Hughes if not isinstance(nameID, int): 441*e1fe3e4aSElliott Hughes tag = nameID 442*e1fe3e4aSElliott Hughes if tag in self.featureNames_: 443*e1fe3e4aSElliott Hughes if tag not in self.featureNames_ids_: 444*e1fe3e4aSElliott Hughes self.featureNames_ids_[tag] = self.get_user_name_id(table) 445*e1fe3e4aSElliott Hughes assert self.featureNames_ids_[tag] is not None 446*e1fe3e4aSElliott Hughes nameID = self.featureNames_ids_[tag] 447*e1fe3e4aSElliott Hughes elif tag[0] in self.cv_parameters_: 448*e1fe3e4aSElliott Hughes if tag not in self.cv_parameters_ids_: 449*e1fe3e4aSElliott Hughes self.cv_parameters_ids_[tag] = self.get_user_name_id(table) 450*e1fe3e4aSElliott Hughes assert self.cv_parameters_ids_[tag] is not None 451*e1fe3e4aSElliott Hughes nameID = self.cv_parameters_ids_[tag] 452*e1fe3e4aSElliott Hughes table.setName(string, nameID, platformID, platEncID, langID) 453*e1fe3e4aSElliott Hughes table.names.sort() 454*e1fe3e4aSElliott Hughes 455*e1fe3e4aSElliott Hughes def build_OS_2(self): 456*e1fe3e4aSElliott Hughes if not self.os2_: 457*e1fe3e4aSElliott Hughes return 458*e1fe3e4aSElliott Hughes table = self.font.get("OS/2") 459*e1fe3e4aSElliott Hughes if not table: # this only happens for unit tests 460*e1fe3e4aSElliott Hughes table = self.font["OS/2"] = newTable("OS/2") 461*e1fe3e4aSElliott Hughes data = b"\0" * sstruct.calcsize(getTableModule("OS/2").OS2_format_0) 462*e1fe3e4aSElliott Hughes table.decompile(data, self.font) 463*e1fe3e4aSElliott Hughes version = 0 464*e1fe3e4aSElliott Hughes if "fstype" in self.os2_: 465*e1fe3e4aSElliott Hughes table.fsType = self.os2_["fstype"] 466*e1fe3e4aSElliott Hughes if "panose" in self.os2_: 467*e1fe3e4aSElliott Hughes panose = getTableModule("OS/2").Panose() 468*e1fe3e4aSElliott Hughes ( 469*e1fe3e4aSElliott Hughes panose.bFamilyType, 470*e1fe3e4aSElliott Hughes panose.bSerifStyle, 471*e1fe3e4aSElliott Hughes panose.bWeight, 472*e1fe3e4aSElliott Hughes panose.bProportion, 473*e1fe3e4aSElliott Hughes panose.bContrast, 474*e1fe3e4aSElliott Hughes panose.bStrokeVariation, 475*e1fe3e4aSElliott Hughes panose.bArmStyle, 476*e1fe3e4aSElliott Hughes panose.bLetterForm, 477*e1fe3e4aSElliott Hughes panose.bMidline, 478*e1fe3e4aSElliott Hughes panose.bXHeight, 479*e1fe3e4aSElliott Hughes ) = self.os2_["panose"] 480*e1fe3e4aSElliott Hughes table.panose = panose 481*e1fe3e4aSElliott Hughes if "typoascender" in self.os2_: 482*e1fe3e4aSElliott Hughes table.sTypoAscender = self.os2_["typoascender"] 483*e1fe3e4aSElliott Hughes if "typodescender" in self.os2_: 484*e1fe3e4aSElliott Hughes table.sTypoDescender = self.os2_["typodescender"] 485*e1fe3e4aSElliott Hughes if "typolinegap" in self.os2_: 486*e1fe3e4aSElliott Hughes table.sTypoLineGap = self.os2_["typolinegap"] 487*e1fe3e4aSElliott Hughes if "winascent" in self.os2_: 488*e1fe3e4aSElliott Hughes table.usWinAscent = self.os2_["winascent"] 489*e1fe3e4aSElliott Hughes if "windescent" in self.os2_: 490*e1fe3e4aSElliott Hughes table.usWinDescent = self.os2_["windescent"] 491*e1fe3e4aSElliott Hughes if "vendor" in self.os2_: 492*e1fe3e4aSElliott Hughes table.achVendID = safeEval("'''" + self.os2_["vendor"] + "'''") 493*e1fe3e4aSElliott Hughes if "weightclass" in self.os2_: 494*e1fe3e4aSElliott Hughes table.usWeightClass = self.os2_["weightclass"] 495*e1fe3e4aSElliott Hughes if "widthclass" in self.os2_: 496*e1fe3e4aSElliott Hughes table.usWidthClass = self.os2_["widthclass"] 497*e1fe3e4aSElliott Hughes if "unicoderange" in self.os2_: 498*e1fe3e4aSElliott Hughes table.setUnicodeRanges(self.os2_["unicoderange"]) 499*e1fe3e4aSElliott Hughes if "codepagerange" in self.os2_: 500*e1fe3e4aSElliott Hughes pages = self.build_codepages_(self.os2_["codepagerange"]) 501*e1fe3e4aSElliott Hughes table.ulCodePageRange1, table.ulCodePageRange2 = pages 502*e1fe3e4aSElliott Hughes version = 1 503*e1fe3e4aSElliott Hughes if "xheight" in self.os2_: 504*e1fe3e4aSElliott Hughes table.sxHeight = self.os2_["xheight"] 505*e1fe3e4aSElliott Hughes version = 2 506*e1fe3e4aSElliott Hughes if "capheight" in self.os2_: 507*e1fe3e4aSElliott Hughes table.sCapHeight = self.os2_["capheight"] 508*e1fe3e4aSElliott Hughes version = 2 509*e1fe3e4aSElliott Hughes if "loweropsize" in self.os2_: 510*e1fe3e4aSElliott Hughes table.usLowerOpticalPointSize = self.os2_["loweropsize"] 511*e1fe3e4aSElliott Hughes version = 5 512*e1fe3e4aSElliott Hughes if "upperopsize" in self.os2_: 513*e1fe3e4aSElliott Hughes table.usUpperOpticalPointSize = self.os2_["upperopsize"] 514*e1fe3e4aSElliott Hughes version = 5 515*e1fe3e4aSElliott Hughes 516*e1fe3e4aSElliott Hughes def checkattr(table, attrs): 517*e1fe3e4aSElliott Hughes for attr in attrs: 518*e1fe3e4aSElliott Hughes if not hasattr(table, attr): 519*e1fe3e4aSElliott Hughes setattr(table, attr, 0) 520*e1fe3e4aSElliott Hughes 521*e1fe3e4aSElliott Hughes table.version = max(version, table.version) 522*e1fe3e4aSElliott Hughes # this only happens for unit tests 523*e1fe3e4aSElliott Hughes if version >= 1: 524*e1fe3e4aSElliott Hughes checkattr(table, ("ulCodePageRange1", "ulCodePageRange2")) 525*e1fe3e4aSElliott Hughes if version >= 2: 526*e1fe3e4aSElliott Hughes checkattr( 527*e1fe3e4aSElliott Hughes table, 528*e1fe3e4aSElliott Hughes ( 529*e1fe3e4aSElliott Hughes "sxHeight", 530*e1fe3e4aSElliott Hughes "sCapHeight", 531*e1fe3e4aSElliott Hughes "usDefaultChar", 532*e1fe3e4aSElliott Hughes "usBreakChar", 533*e1fe3e4aSElliott Hughes "usMaxContext", 534*e1fe3e4aSElliott Hughes ), 535*e1fe3e4aSElliott Hughes ) 536*e1fe3e4aSElliott Hughes if version >= 5: 537*e1fe3e4aSElliott Hughes checkattr(table, ("usLowerOpticalPointSize", "usUpperOpticalPointSize")) 538*e1fe3e4aSElliott Hughes 539*e1fe3e4aSElliott Hughes def setElidedFallbackName(self, value, location): 540*e1fe3e4aSElliott Hughes # ElidedFallbackName is a convenience method for setting 541*e1fe3e4aSElliott Hughes # ElidedFallbackNameID so only one can be allowed 542*e1fe3e4aSElliott Hughes for token in ("ElidedFallbackName", "ElidedFallbackNameID"): 543*e1fe3e4aSElliott Hughes if token in self.stat_: 544*e1fe3e4aSElliott Hughes raise FeatureLibError( 545*e1fe3e4aSElliott Hughes f"{token} is already set.", 546*e1fe3e4aSElliott Hughes location, 547*e1fe3e4aSElliott Hughes ) 548*e1fe3e4aSElliott Hughes if isinstance(value, int): 549*e1fe3e4aSElliott Hughes self.stat_["ElidedFallbackNameID"] = value 550*e1fe3e4aSElliott Hughes elif isinstance(value, list): 551*e1fe3e4aSElliott Hughes self.stat_["ElidedFallbackName"] = value 552*e1fe3e4aSElliott Hughes else: 553*e1fe3e4aSElliott Hughes raise AssertionError(value) 554*e1fe3e4aSElliott Hughes 555*e1fe3e4aSElliott Hughes def addDesignAxis(self, designAxis, location): 556*e1fe3e4aSElliott Hughes if "DesignAxes" not in self.stat_: 557*e1fe3e4aSElliott Hughes self.stat_["DesignAxes"] = [] 558*e1fe3e4aSElliott Hughes if designAxis.tag in (r.tag for r in self.stat_["DesignAxes"]): 559*e1fe3e4aSElliott Hughes raise FeatureLibError( 560*e1fe3e4aSElliott Hughes f'DesignAxis already defined for tag "{designAxis.tag}".', 561*e1fe3e4aSElliott Hughes location, 562*e1fe3e4aSElliott Hughes ) 563*e1fe3e4aSElliott Hughes if designAxis.axisOrder in (r.axisOrder for r in self.stat_["DesignAxes"]): 564*e1fe3e4aSElliott Hughes raise FeatureLibError( 565*e1fe3e4aSElliott Hughes f"DesignAxis already defined for axis number {designAxis.axisOrder}.", 566*e1fe3e4aSElliott Hughes location, 567*e1fe3e4aSElliott Hughes ) 568*e1fe3e4aSElliott Hughes self.stat_["DesignAxes"].append(designAxis) 569*e1fe3e4aSElliott Hughes 570*e1fe3e4aSElliott Hughes def addAxisValueRecord(self, axisValueRecord, location): 571*e1fe3e4aSElliott Hughes if "AxisValueRecords" not in self.stat_: 572*e1fe3e4aSElliott Hughes self.stat_["AxisValueRecords"] = [] 573*e1fe3e4aSElliott Hughes # Check for duplicate AxisValueRecords 574*e1fe3e4aSElliott Hughes for record_ in self.stat_["AxisValueRecords"]: 575*e1fe3e4aSElliott Hughes if ( 576*e1fe3e4aSElliott Hughes {n.asFea() for n in record_.names} 577*e1fe3e4aSElliott Hughes == {n.asFea() for n in axisValueRecord.names} 578*e1fe3e4aSElliott Hughes and {n.asFea() for n in record_.locations} 579*e1fe3e4aSElliott Hughes == {n.asFea() for n in axisValueRecord.locations} 580*e1fe3e4aSElliott Hughes and record_.flags == axisValueRecord.flags 581*e1fe3e4aSElliott Hughes ): 582*e1fe3e4aSElliott Hughes raise FeatureLibError( 583*e1fe3e4aSElliott Hughes "An AxisValueRecord with these values is already defined.", 584*e1fe3e4aSElliott Hughes location, 585*e1fe3e4aSElliott Hughes ) 586*e1fe3e4aSElliott Hughes self.stat_["AxisValueRecords"].append(axisValueRecord) 587*e1fe3e4aSElliott Hughes 588*e1fe3e4aSElliott Hughes def build_STAT(self): 589*e1fe3e4aSElliott Hughes if not self.stat_: 590*e1fe3e4aSElliott Hughes return 591*e1fe3e4aSElliott Hughes 592*e1fe3e4aSElliott Hughes axes = self.stat_.get("DesignAxes") 593*e1fe3e4aSElliott Hughes if not axes: 594*e1fe3e4aSElliott Hughes raise FeatureLibError("DesignAxes not defined", None) 595*e1fe3e4aSElliott Hughes axisValueRecords = self.stat_.get("AxisValueRecords") 596*e1fe3e4aSElliott Hughes axisValues = {} 597*e1fe3e4aSElliott Hughes format4_locations = [] 598*e1fe3e4aSElliott Hughes for tag in axes: 599*e1fe3e4aSElliott Hughes axisValues[tag.tag] = [] 600*e1fe3e4aSElliott Hughes if axisValueRecords is not None: 601*e1fe3e4aSElliott Hughes for avr in axisValueRecords: 602*e1fe3e4aSElliott Hughes valuesDict = {} 603*e1fe3e4aSElliott Hughes if avr.flags > 0: 604*e1fe3e4aSElliott Hughes valuesDict["flags"] = avr.flags 605*e1fe3e4aSElliott Hughes if len(avr.locations) == 1: 606*e1fe3e4aSElliott Hughes location = avr.locations[0] 607*e1fe3e4aSElliott Hughes values = location.values 608*e1fe3e4aSElliott Hughes if len(values) == 1: # format1 609*e1fe3e4aSElliott Hughes valuesDict.update({"value": values[0], "name": avr.names}) 610*e1fe3e4aSElliott Hughes if len(values) == 2: # format3 611*e1fe3e4aSElliott Hughes valuesDict.update( 612*e1fe3e4aSElliott Hughes { 613*e1fe3e4aSElliott Hughes "value": values[0], 614*e1fe3e4aSElliott Hughes "linkedValue": values[1], 615*e1fe3e4aSElliott Hughes "name": avr.names, 616*e1fe3e4aSElliott Hughes } 617*e1fe3e4aSElliott Hughes ) 618*e1fe3e4aSElliott Hughes if len(values) == 3: # format2 619*e1fe3e4aSElliott Hughes nominal, minVal, maxVal = values 620*e1fe3e4aSElliott Hughes valuesDict.update( 621*e1fe3e4aSElliott Hughes { 622*e1fe3e4aSElliott Hughes "nominalValue": nominal, 623*e1fe3e4aSElliott Hughes "rangeMinValue": minVal, 624*e1fe3e4aSElliott Hughes "rangeMaxValue": maxVal, 625*e1fe3e4aSElliott Hughes "name": avr.names, 626*e1fe3e4aSElliott Hughes } 627*e1fe3e4aSElliott Hughes ) 628*e1fe3e4aSElliott Hughes axisValues[location.tag].append(valuesDict) 629*e1fe3e4aSElliott Hughes else: 630*e1fe3e4aSElliott Hughes valuesDict.update( 631*e1fe3e4aSElliott Hughes { 632*e1fe3e4aSElliott Hughes "location": {i.tag: i.values[0] for i in avr.locations}, 633*e1fe3e4aSElliott Hughes "name": avr.names, 634*e1fe3e4aSElliott Hughes } 635*e1fe3e4aSElliott Hughes ) 636*e1fe3e4aSElliott Hughes format4_locations.append(valuesDict) 637*e1fe3e4aSElliott Hughes 638*e1fe3e4aSElliott Hughes designAxes = [ 639*e1fe3e4aSElliott Hughes { 640*e1fe3e4aSElliott Hughes "ordering": a.axisOrder, 641*e1fe3e4aSElliott Hughes "tag": a.tag, 642*e1fe3e4aSElliott Hughes "name": a.names, 643*e1fe3e4aSElliott Hughes "values": axisValues[a.tag], 644*e1fe3e4aSElliott Hughes } 645*e1fe3e4aSElliott Hughes for a in axes 646*e1fe3e4aSElliott Hughes ] 647*e1fe3e4aSElliott Hughes 648*e1fe3e4aSElliott Hughes nameTable = self.font.get("name") 649*e1fe3e4aSElliott Hughes if not nameTable: # this only happens for unit tests 650*e1fe3e4aSElliott Hughes nameTable = self.font["name"] = newTable("name") 651*e1fe3e4aSElliott Hughes nameTable.names = [] 652*e1fe3e4aSElliott Hughes 653*e1fe3e4aSElliott Hughes if "ElidedFallbackNameID" in self.stat_: 654*e1fe3e4aSElliott Hughes nameID = self.stat_["ElidedFallbackNameID"] 655*e1fe3e4aSElliott Hughes name = nameTable.getDebugName(nameID) 656*e1fe3e4aSElliott Hughes if not name: 657*e1fe3e4aSElliott Hughes raise FeatureLibError( 658*e1fe3e4aSElliott Hughes f"ElidedFallbackNameID {nameID} points " 659*e1fe3e4aSElliott Hughes "to a nameID that does not exist in the " 660*e1fe3e4aSElliott Hughes '"name" table', 661*e1fe3e4aSElliott Hughes None, 662*e1fe3e4aSElliott Hughes ) 663*e1fe3e4aSElliott Hughes elif "ElidedFallbackName" in self.stat_: 664*e1fe3e4aSElliott Hughes nameID = self.stat_["ElidedFallbackName"] 665*e1fe3e4aSElliott Hughes 666*e1fe3e4aSElliott Hughes otl.buildStatTable( 667*e1fe3e4aSElliott Hughes self.font, 668*e1fe3e4aSElliott Hughes designAxes, 669*e1fe3e4aSElliott Hughes locations=format4_locations, 670*e1fe3e4aSElliott Hughes elidedFallbackName=nameID, 671*e1fe3e4aSElliott Hughes ) 672*e1fe3e4aSElliott Hughes 673*e1fe3e4aSElliott Hughes def build_codepages_(self, pages): 674*e1fe3e4aSElliott Hughes pages2bits = { 675*e1fe3e4aSElliott Hughes 1252: 0, 676*e1fe3e4aSElliott Hughes 1250: 1, 677*e1fe3e4aSElliott Hughes 1251: 2, 678*e1fe3e4aSElliott Hughes 1253: 3, 679*e1fe3e4aSElliott Hughes 1254: 4, 680*e1fe3e4aSElliott Hughes 1255: 5, 681*e1fe3e4aSElliott Hughes 1256: 6, 682*e1fe3e4aSElliott Hughes 1257: 7, 683*e1fe3e4aSElliott Hughes 1258: 8, 684*e1fe3e4aSElliott Hughes 874: 16, 685*e1fe3e4aSElliott Hughes 932: 17, 686*e1fe3e4aSElliott Hughes 936: 18, 687*e1fe3e4aSElliott Hughes 949: 19, 688*e1fe3e4aSElliott Hughes 950: 20, 689*e1fe3e4aSElliott Hughes 1361: 21, 690*e1fe3e4aSElliott Hughes 869: 48, 691*e1fe3e4aSElliott Hughes 866: 49, 692*e1fe3e4aSElliott Hughes 865: 50, 693*e1fe3e4aSElliott Hughes 864: 51, 694*e1fe3e4aSElliott Hughes 863: 52, 695*e1fe3e4aSElliott Hughes 862: 53, 696*e1fe3e4aSElliott Hughes 861: 54, 697*e1fe3e4aSElliott Hughes 860: 55, 698*e1fe3e4aSElliott Hughes 857: 56, 699*e1fe3e4aSElliott Hughes 855: 57, 700*e1fe3e4aSElliott Hughes 852: 58, 701*e1fe3e4aSElliott Hughes 775: 59, 702*e1fe3e4aSElliott Hughes 737: 60, 703*e1fe3e4aSElliott Hughes 708: 61, 704*e1fe3e4aSElliott Hughes 850: 62, 705*e1fe3e4aSElliott Hughes 437: 63, 706*e1fe3e4aSElliott Hughes } 707*e1fe3e4aSElliott Hughes bits = [pages2bits[p] for p in pages if p in pages2bits] 708*e1fe3e4aSElliott Hughes pages = [] 709*e1fe3e4aSElliott Hughes for i in range(2): 710*e1fe3e4aSElliott Hughes pages.append("") 711*e1fe3e4aSElliott Hughes for j in range(i * 32, (i + 1) * 32): 712*e1fe3e4aSElliott Hughes if j in bits: 713*e1fe3e4aSElliott Hughes pages[i] += "1" 714*e1fe3e4aSElliott Hughes else: 715*e1fe3e4aSElliott Hughes pages[i] += "0" 716*e1fe3e4aSElliott Hughes return [binary2num(p[::-1]) for p in pages] 717*e1fe3e4aSElliott Hughes 718*e1fe3e4aSElliott Hughes def buildBASE(self): 719*e1fe3e4aSElliott Hughes if not self.base_horiz_axis_ and not self.base_vert_axis_: 720*e1fe3e4aSElliott Hughes return None 721*e1fe3e4aSElliott Hughes base = otTables.BASE() 722*e1fe3e4aSElliott Hughes base.Version = 0x00010000 723*e1fe3e4aSElliott Hughes base.HorizAxis = self.buildBASEAxis(self.base_horiz_axis_) 724*e1fe3e4aSElliott Hughes base.VertAxis = self.buildBASEAxis(self.base_vert_axis_) 725*e1fe3e4aSElliott Hughes 726*e1fe3e4aSElliott Hughes result = newTable("BASE") 727*e1fe3e4aSElliott Hughes result.table = base 728*e1fe3e4aSElliott Hughes return result 729*e1fe3e4aSElliott Hughes 730*e1fe3e4aSElliott Hughes def buildBASEAxis(self, axis): 731*e1fe3e4aSElliott Hughes if not axis: 732*e1fe3e4aSElliott Hughes return 733*e1fe3e4aSElliott Hughes bases, scripts = axis 734*e1fe3e4aSElliott Hughes axis = otTables.Axis() 735*e1fe3e4aSElliott Hughes axis.BaseTagList = otTables.BaseTagList() 736*e1fe3e4aSElliott Hughes axis.BaseTagList.BaselineTag = bases 737*e1fe3e4aSElliott Hughes axis.BaseTagList.BaseTagCount = len(bases) 738*e1fe3e4aSElliott Hughes axis.BaseScriptList = otTables.BaseScriptList() 739*e1fe3e4aSElliott Hughes axis.BaseScriptList.BaseScriptRecord = [] 740*e1fe3e4aSElliott Hughes axis.BaseScriptList.BaseScriptCount = len(scripts) 741*e1fe3e4aSElliott Hughes for script in sorted(scripts): 742*e1fe3e4aSElliott Hughes record = otTables.BaseScriptRecord() 743*e1fe3e4aSElliott Hughes record.BaseScriptTag = script[0] 744*e1fe3e4aSElliott Hughes record.BaseScript = otTables.BaseScript() 745*e1fe3e4aSElliott Hughes record.BaseScript.BaseLangSysCount = 0 746*e1fe3e4aSElliott Hughes record.BaseScript.BaseValues = otTables.BaseValues() 747*e1fe3e4aSElliott Hughes record.BaseScript.BaseValues.DefaultIndex = bases.index(script[1]) 748*e1fe3e4aSElliott Hughes record.BaseScript.BaseValues.BaseCoord = [] 749*e1fe3e4aSElliott Hughes record.BaseScript.BaseValues.BaseCoordCount = len(script[2]) 750*e1fe3e4aSElliott Hughes for c in script[2]: 751*e1fe3e4aSElliott Hughes coord = otTables.BaseCoord() 752*e1fe3e4aSElliott Hughes coord.Format = 1 753*e1fe3e4aSElliott Hughes coord.Coordinate = c 754*e1fe3e4aSElliott Hughes record.BaseScript.BaseValues.BaseCoord.append(coord) 755*e1fe3e4aSElliott Hughes axis.BaseScriptList.BaseScriptRecord.append(record) 756*e1fe3e4aSElliott Hughes return axis 757*e1fe3e4aSElliott Hughes 758*e1fe3e4aSElliott Hughes def buildGDEF(self): 759*e1fe3e4aSElliott Hughes gdef = otTables.GDEF() 760*e1fe3e4aSElliott Hughes gdef.GlyphClassDef = self.buildGDEFGlyphClassDef_() 761*e1fe3e4aSElliott Hughes gdef.AttachList = otl.buildAttachList(self.attachPoints_, self.glyphMap) 762*e1fe3e4aSElliott Hughes gdef.LigCaretList = otl.buildLigCaretList( 763*e1fe3e4aSElliott Hughes self.ligCaretCoords_, self.ligCaretPoints_, self.glyphMap 764*e1fe3e4aSElliott Hughes ) 765*e1fe3e4aSElliott Hughes gdef.MarkAttachClassDef = self.buildGDEFMarkAttachClassDef_() 766*e1fe3e4aSElliott Hughes gdef.MarkGlyphSetsDef = self.buildGDEFMarkGlyphSetsDef_() 767*e1fe3e4aSElliott Hughes gdef.Version = 0x00010002 if gdef.MarkGlyphSetsDef else 0x00010000 768*e1fe3e4aSElliott Hughes if self.varstorebuilder: 769*e1fe3e4aSElliott Hughes store = self.varstorebuilder.finish() 770*e1fe3e4aSElliott Hughes if store: 771*e1fe3e4aSElliott Hughes gdef.Version = 0x00010003 772*e1fe3e4aSElliott Hughes gdef.VarStore = store 773*e1fe3e4aSElliott Hughes varidx_map = store.optimize() 774*e1fe3e4aSElliott Hughes 775*e1fe3e4aSElliott Hughes gdef.remap_device_varidxes(varidx_map) 776*e1fe3e4aSElliott Hughes if "GPOS" in self.font: 777*e1fe3e4aSElliott Hughes self.font["GPOS"].table.remap_device_varidxes(varidx_map) 778*e1fe3e4aSElliott Hughes self.model_cache.clear() 779*e1fe3e4aSElliott Hughes if any( 780*e1fe3e4aSElliott Hughes ( 781*e1fe3e4aSElliott Hughes gdef.GlyphClassDef, 782*e1fe3e4aSElliott Hughes gdef.AttachList, 783*e1fe3e4aSElliott Hughes gdef.LigCaretList, 784*e1fe3e4aSElliott Hughes gdef.MarkAttachClassDef, 785*e1fe3e4aSElliott Hughes gdef.MarkGlyphSetsDef, 786*e1fe3e4aSElliott Hughes ) 787*e1fe3e4aSElliott Hughes ) or hasattr(gdef, "VarStore"): 788*e1fe3e4aSElliott Hughes result = newTable("GDEF") 789*e1fe3e4aSElliott Hughes result.table = gdef 790*e1fe3e4aSElliott Hughes return result 791*e1fe3e4aSElliott Hughes else: 792*e1fe3e4aSElliott Hughes return None 793*e1fe3e4aSElliott Hughes 794*e1fe3e4aSElliott Hughes def buildGDEFGlyphClassDef_(self): 795*e1fe3e4aSElliott Hughes if self.glyphClassDefs_: 796*e1fe3e4aSElliott Hughes classes = {g: c for (g, (c, _)) in self.glyphClassDefs_.items()} 797*e1fe3e4aSElliott Hughes else: 798*e1fe3e4aSElliott Hughes classes = {} 799*e1fe3e4aSElliott Hughes for lookup in self.lookups_: 800*e1fe3e4aSElliott Hughes classes.update(lookup.inferGlyphClasses()) 801*e1fe3e4aSElliott Hughes for markClass in self.parseTree.markClasses.values(): 802*e1fe3e4aSElliott Hughes for markClassDef in markClass.definitions: 803*e1fe3e4aSElliott Hughes for glyph in markClassDef.glyphSet(): 804*e1fe3e4aSElliott Hughes classes[glyph] = 3 805*e1fe3e4aSElliott Hughes if classes: 806*e1fe3e4aSElliott Hughes result = otTables.GlyphClassDef() 807*e1fe3e4aSElliott Hughes result.classDefs = classes 808*e1fe3e4aSElliott Hughes return result 809*e1fe3e4aSElliott Hughes else: 810*e1fe3e4aSElliott Hughes return None 811*e1fe3e4aSElliott Hughes 812*e1fe3e4aSElliott Hughes def buildGDEFMarkAttachClassDef_(self): 813*e1fe3e4aSElliott Hughes classDefs = {g: c for g, (c, _) in self.markAttach_.items()} 814*e1fe3e4aSElliott Hughes if not classDefs: 815*e1fe3e4aSElliott Hughes return None 816*e1fe3e4aSElliott Hughes result = otTables.MarkAttachClassDef() 817*e1fe3e4aSElliott Hughes result.classDefs = classDefs 818*e1fe3e4aSElliott Hughes return result 819*e1fe3e4aSElliott Hughes 820*e1fe3e4aSElliott Hughes def buildGDEFMarkGlyphSetsDef_(self): 821*e1fe3e4aSElliott Hughes sets = [] 822*e1fe3e4aSElliott Hughes for glyphs, id_ in sorted( 823*e1fe3e4aSElliott Hughes self.markFilterSets_.items(), key=lambda item: item[1] 824*e1fe3e4aSElliott Hughes ): 825*e1fe3e4aSElliott Hughes sets.append(glyphs) 826*e1fe3e4aSElliott Hughes return otl.buildMarkGlyphSetsDef(sets, self.glyphMap) 827*e1fe3e4aSElliott Hughes 828*e1fe3e4aSElliott Hughes def buildDebg(self): 829*e1fe3e4aSElliott Hughes if "Debg" not in self.font: 830*e1fe3e4aSElliott Hughes self.font["Debg"] = newTable("Debg") 831*e1fe3e4aSElliott Hughes self.font["Debg"].data = {} 832*e1fe3e4aSElliott Hughes self.font["Debg"].data[LOOKUP_DEBUG_INFO_KEY] = self.lookup_locations 833*e1fe3e4aSElliott Hughes 834*e1fe3e4aSElliott Hughes def buildLookups_(self, tag): 835*e1fe3e4aSElliott Hughes assert tag in ("GPOS", "GSUB"), tag 836*e1fe3e4aSElliott Hughes for lookup in self.lookups_: 837*e1fe3e4aSElliott Hughes lookup.lookup_index = None 838*e1fe3e4aSElliott Hughes lookups = [] 839*e1fe3e4aSElliott Hughes for lookup in self.lookups_: 840*e1fe3e4aSElliott Hughes if lookup.table != tag: 841*e1fe3e4aSElliott Hughes continue 842*e1fe3e4aSElliott Hughes lookup.lookup_index = len(lookups) 843*e1fe3e4aSElliott Hughes self.lookup_locations[tag][str(lookup.lookup_index)] = LookupDebugInfo( 844*e1fe3e4aSElliott Hughes location=str(lookup.location), 845*e1fe3e4aSElliott Hughes name=self.get_lookup_name_(lookup), 846*e1fe3e4aSElliott Hughes feature=None, 847*e1fe3e4aSElliott Hughes ) 848*e1fe3e4aSElliott Hughes lookups.append(lookup) 849*e1fe3e4aSElliott Hughes otLookups = [] 850*e1fe3e4aSElliott Hughes for l in lookups: 851*e1fe3e4aSElliott Hughes try: 852*e1fe3e4aSElliott Hughes otLookups.append(l.build()) 853*e1fe3e4aSElliott Hughes except OpenTypeLibError as e: 854*e1fe3e4aSElliott Hughes raise FeatureLibError(str(e), e.location) from e 855*e1fe3e4aSElliott Hughes except Exception as e: 856*e1fe3e4aSElliott Hughes location = self.lookup_locations[tag][str(l.lookup_index)].location 857*e1fe3e4aSElliott Hughes raise FeatureLibError(str(e), location) from e 858*e1fe3e4aSElliott Hughes return otLookups 859*e1fe3e4aSElliott Hughes 860*e1fe3e4aSElliott Hughes def makeTable(self, tag): 861*e1fe3e4aSElliott Hughes table = getattr(otTables, tag, None)() 862*e1fe3e4aSElliott Hughes table.Version = 0x00010000 863*e1fe3e4aSElliott Hughes table.ScriptList = otTables.ScriptList() 864*e1fe3e4aSElliott Hughes table.ScriptList.ScriptRecord = [] 865*e1fe3e4aSElliott Hughes table.FeatureList = otTables.FeatureList() 866*e1fe3e4aSElliott Hughes table.FeatureList.FeatureRecord = [] 867*e1fe3e4aSElliott Hughes table.LookupList = otTables.LookupList() 868*e1fe3e4aSElliott Hughes table.LookupList.Lookup = self.buildLookups_(tag) 869*e1fe3e4aSElliott Hughes 870*e1fe3e4aSElliott Hughes # Build a table for mapping (tag, lookup_indices) to feature_index. 871*e1fe3e4aSElliott Hughes # For example, ('liga', (2,3,7)) --> 23. 872*e1fe3e4aSElliott Hughes feature_indices = {} 873*e1fe3e4aSElliott Hughes required_feature_indices = {} # ('latn', 'DEU') --> 23 874*e1fe3e4aSElliott Hughes scripts = {} # 'latn' --> {'DEU': [23, 24]} for feature #23,24 875*e1fe3e4aSElliott Hughes # Sort the feature table by feature tag: 876*e1fe3e4aSElliott Hughes # https://github.com/fonttools/fonttools/issues/568 877*e1fe3e4aSElliott Hughes sortFeatureTag = lambda f: (f[0][2], f[0][1], f[0][0], f[1]) 878*e1fe3e4aSElliott Hughes for key, lookups in sorted(self.features_.items(), key=sortFeatureTag): 879*e1fe3e4aSElliott Hughes script, lang, feature_tag = key 880*e1fe3e4aSElliott Hughes # l.lookup_index will be None when a lookup is not needed 881*e1fe3e4aSElliott Hughes # for the table under construction. For example, substitution 882*e1fe3e4aSElliott Hughes # rules will have no lookup_index while building GPOS tables. 883*e1fe3e4aSElliott Hughes lookup_indices = tuple( 884*e1fe3e4aSElliott Hughes [l.lookup_index for l in lookups if l.lookup_index is not None] 885*e1fe3e4aSElliott Hughes ) 886*e1fe3e4aSElliott Hughes 887*e1fe3e4aSElliott Hughes size_feature = tag == "GPOS" and feature_tag == "size" 888*e1fe3e4aSElliott Hughes force_feature = self.any_feature_variations(feature_tag, tag) 889*e1fe3e4aSElliott Hughes if len(lookup_indices) == 0 and not size_feature and not force_feature: 890*e1fe3e4aSElliott Hughes continue 891*e1fe3e4aSElliott Hughes 892*e1fe3e4aSElliott Hughes for ix in lookup_indices: 893*e1fe3e4aSElliott Hughes try: 894*e1fe3e4aSElliott Hughes self.lookup_locations[tag][str(ix)] = self.lookup_locations[tag][ 895*e1fe3e4aSElliott Hughes str(ix) 896*e1fe3e4aSElliott Hughes ]._replace(feature=key) 897*e1fe3e4aSElliott Hughes except KeyError: 898*e1fe3e4aSElliott Hughes warnings.warn( 899*e1fe3e4aSElliott Hughes "feaLib.Builder subclass needs upgrading to " 900*e1fe3e4aSElliott Hughes "stash debug information. See fonttools#2065." 901*e1fe3e4aSElliott Hughes ) 902*e1fe3e4aSElliott Hughes 903*e1fe3e4aSElliott Hughes feature_key = (feature_tag, lookup_indices) 904*e1fe3e4aSElliott Hughes feature_index = feature_indices.get(feature_key) 905*e1fe3e4aSElliott Hughes if feature_index is None: 906*e1fe3e4aSElliott Hughes feature_index = len(table.FeatureList.FeatureRecord) 907*e1fe3e4aSElliott Hughes frec = otTables.FeatureRecord() 908*e1fe3e4aSElliott Hughes frec.FeatureTag = feature_tag 909*e1fe3e4aSElliott Hughes frec.Feature = otTables.Feature() 910*e1fe3e4aSElliott Hughes frec.Feature.FeatureParams = self.buildFeatureParams(feature_tag) 911*e1fe3e4aSElliott Hughes frec.Feature.LookupListIndex = list(lookup_indices) 912*e1fe3e4aSElliott Hughes frec.Feature.LookupCount = len(lookup_indices) 913*e1fe3e4aSElliott Hughes table.FeatureList.FeatureRecord.append(frec) 914*e1fe3e4aSElliott Hughes feature_indices[feature_key] = feature_index 915*e1fe3e4aSElliott Hughes scripts.setdefault(script, {}).setdefault(lang, []).append(feature_index) 916*e1fe3e4aSElliott Hughes if self.required_features_.get((script, lang)) == feature_tag: 917*e1fe3e4aSElliott Hughes required_feature_indices[(script, lang)] = feature_index 918*e1fe3e4aSElliott Hughes 919*e1fe3e4aSElliott Hughes # Build ScriptList. 920*e1fe3e4aSElliott Hughes for script, lang_features in sorted(scripts.items()): 921*e1fe3e4aSElliott Hughes srec = otTables.ScriptRecord() 922*e1fe3e4aSElliott Hughes srec.ScriptTag = script 923*e1fe3e4aSElliott Hughes srec.Script = otTables.Script() 924*e1fe3e4aSElliott Hughes srec.Script.DefaultLangSys = None 925*e1fe3e4aSElliott Hughes srec.Script.LangSysRecord = [] 926*e1fe3e4aSElliott Hughes for lang, feature_indices in sorted(lang_features.items()): 927*e1fe3e4aSElliott Hughes langrec = otTables.LangSysRecord() 928*e1fe3e4aSElliott Hughes langrec.LangSys = otTables.LangSys() 929*e1fe3e4aSElliott Hughes langrec.LangSys.LookupOrder = None 930*e1fe3e4aSElliott Hughes 931*e1fe3e4aSElliott Hughes req_feature_index = required_feature_indices.get((script, lang)) 932*e1fe3e4aSElliott Hughes if req_feature_index is None: 933*e1fe3e4aSElliott Hughes langrec.LangSys.ReqFeatureIndex = 0xFFFF 934*e1fe3e4aSElliott Hughes else: 935*e1fe3e4aSElliott Hughes langrec.LangSys.ReqFeatureIndex = req_feature_index 936*e1fe3e4aSElliott Hughes 937*e1fe3e4aSElliott Hughes langrec.LangSys.FeatureIndex = [ 938*e1fe3e4aSElliott Hughes i for i in feature_indices if i != req_feature_index 939*e1fe3e4aSElliott Hughes ] 940*e1fe3e4aSElliott Hughes langrec.LangSys.FeatureCount = len(langrec.LangSys.FeatureIndex) 941*e1fe3e4aSElliott Hughes 942*e1fe3e4aSElliott Hughes if lang == "dflt": 943*e1fe3e4aSElliott Hughes srec.Script.DefaultLangSys = langrec.LangSys 944*e1fe3e4aSElliott Hughes else: 945*e1fe3e4aSElliott Hughes langrec.LangSysTag = lang 946*e1fe3e4aSElliott Hughes srec.Script.LangSysRecord.append(langrec) 947*e1fe3e4aSElliott Hughes srec.Script.LangSysCount = len(srec.Script.LangSysRecord) 948*e1fe3e4aSElliott Hughes table.ScriptList.ScriptRecord.append(srec) 949*e1fe3e4aSElliott Hughes 950*e1fe3e4aSElliott Hughes table.ScriptList.ScriptCount = len(table.ScriptList.ScriptRecord) 951*e1fe3e4aSElliott Hughes table.FeatureList.FeatureCount = len(table.FeatureList.FeatureRecord) 952*e1fe3e4aSElliott Hughes table.LookupList.LookupCount = len(table.LookupList.Lookup) 953*e1fe3e4aSElliott Hughes return table 954*e1fe3e4aSElliott Hughes 955*e1fe3e4aSElliott Hughes def makeFeatureVariations(self, table, table_tag): 956*e1fe3e4aSElliott Hughes feature_vars = {} 957*e1fe3e4aSElliott Hughes has_any_variations = False 958*e1fe3e4aSElliott Hughes # Sort out which lookups to build, gather their indices 959*e1fe3e4aSElliott Hughes for (_, _, feature_tag), variations in self.feature_variations_.items(): 960*e1fe3e4aSElliott Hughes feature_vars[feature_tag] = [] 961*e1fe3e4aSElliott Hughes for conditionset, builders in variations.items(): 962*e1fe3e4aSElliott Hughes raw_conditionset = self.conditionsets_[conditionset] 963*e1fe3e4aSElliott Hughes indices = [] 964*e1fe3e4aSElliott Hughes for b in builders: 965*e1fe3e4aSElliott Hughes if b.table != table_tag: 966*e1fe3e4aSElliott Hughes continue 967*e1fe3e4aSElliott Hughes assert b.lookup_index is not None 968*e1fe3e4aSElliott Hughes indices.append(b.lookup_index) 969*e1fe3e4aSElliott Hughes has_any_variations = True 970*e1fe3e4aSElliott Hughes feature_vars[feature_tag].append((raw_conditionset, indices)) 971*e1fe3e4aSElliott Hughes 972*e1fe3e4aSElliott Hughes if has_any_variations: 973*e1fe3e4aSElliott Hughes for feature_tag, conditions_and_lookups in feature_vars.items(): 974*e1fe3e4aSElliott Hughes addFeatureVariationsRaw( 975*e1fe3e4aSElliott Hughes self.font, table, conditions_and_lookups, feature_tag 976*e1fe3e4aSElliott Hughes ) 977*e1fe3e4aSElliott Hughes 978*e1fe3e4aSElliott Hughes def any_feature_variations(self, feature_tag, table_tag): 979*e1fe3e4aSElliott Hughes for (_, _, feature), variations in self.feature_variations_.items(): 980*e1fe3e4aSElliott Hughes if feature != feature_tag: 981*e1fe3e4aSElliott Hughes continue 982*e1fe3e4aSElliott Hughes for conditionset, builders in variations.items(): 983*e1fe3e4aSElliott Hughes if any(b.table == table_tag for b in builders): 984*e1fe3e4aSElliott Hughes return True 985*e1fe3e4aSElliott Hughes return False 986*e1fe3e4aSElliott Hughes 987*e1fe3e4aSElliott Hughes def get_lookup_name_(self, lookup): 988*e1fe3e4aSElliott Hughes rev = {v: k for k, v in self.named_lookups_.items()} 989*e1fe3e4aSElliott Hughes if lookup in rev: 990*e1fe3e4aSElliott Hughes return rev[lookup] 991*e1fe3e4aSElliott Hughes return None 992*e1fe3e4aSElliott Hughes 993*e1fe3e4aSElliott Hughes def add_language_system(self, location, script, language): 994*e1fe3e4aSElliott Hughes # OpenType Feature File Specification, section 4.b.i 995*e1fe3e4aSElliott Hughes if script == "DFLT" and language == "dflt" and self.default_language_systems_: 996*e1fe3e4aSElliott Hughes raise FeatureLibError( 997*e1fe3e4aSElliott Hughes 'If "languagesystem DFLT dflt" is present, it must be ' 998*e1fe3e4aSElliott Hughes "the first of the languagesystem statements", 999*e1fe3e4aSElliott Hughes location, 1000*e1fe3e4aSElliott Hughes ) 1001*e1fe3e4aSElliott Hughes if script == "DFLT": 1002*e1fe3e4aSElliott Hughes if self.seen_non_DFLT_script_: 1003*e1fe3e4aSElliott Hughes raise FeatureLibError( 1004*e1fe3e4aSElliott Hughes 'languagesystems using the "DFLT" script tag must ' 1005*e1fe3e4aSElliott Hughes "precede all other languagesystems", 1006*e1fe3e4aSElliott Hughes location, 1007*e1fe3e4aSElliott Hughes ) 1008*e1fe3e4aSElliott Hughes else: 1009*e1fe3e4aSElliott Hughes self.seen_non_DFLT_script_ = True 1010*e1fe3e4aSElliott Hughes if (script, language) in self.default_language_systems_: 1011*e1fe3e4aSElliott Hughes raise FeatureLibError( 1012*e1fe3e4aSElliott Hughes '"languagesystem %s %s" has already been specified' 1013*e1fe3e4aSElliott Hughes % (script.strip(), language.strip()), 1014*e1fe3e4aSElliott Hughes location, 1015*e1fe3e4aSElliott Hughes ) 1016*e1fe3e4aSElliott Hughes self.default_language_systems_.add((script, language)) 1017*e1fe3e4aSElliott Hughes 1018*e1fe3e4aSElliott Hughes def get_default_language_systems_(self): 1019*e1fe3e4aSElliott Hughes # OpenType Feature File specification, 4.b.i. languagesystem: 1020*e1fe3e4aSElliott Hughes # If no "languagesystem" statement is present, then the 1021*e1fe3e4aSElliott Hughes # implementation must behave exactly as though the following 1022*e1fe3e4aSElliott Hughes # statement were present at the beginning of the feature file: 1023*e1fe3e4aSElliott Hughes # languagesystem DFLT dflt; 1024*e1fe3e4aSElliott Hughes if self.default_language_systems_: 1025*e1fe3e4aSElliott Hughes return frozenset(self.default_language_systems_) 1026*e1fe3e4aSElliott Hughes else: 1027*e1fe3e4aSElliott Hughes return frozenset({("DFLT", "dflt")}) 1028*e1fe3e4aSElliott Hughes 1029*e1fe3e4aSElliott Hughes def start_feature(self, location, name): 1030*e1fe3e4aSElliott Hughes self.language_systems = self.get_default_language_systems_() 1031*e1fe3e4aSElliott Hughes self.script_ = "DFLT" 1032*e1fe3e4aSElliott Hughes self.cur_lookup_ = None 1033*e1fe3e4aSElliott Hughes self.cur_feature_name_ = name 1034*e1fe3e4aSElliott Hughes self.lookupflag_ = 0 1035*e1fe3e4aSElliott Hughes self.lookupflag_markFilterSet_ = None 1036*e1fe3e4aSElliott Hughes if name == "aalt": 1037*e1fe3e4aSElliott Hughes self.aalt_location_ = location 1038*e1fe3e4aSElliott Hughes 1039*e1fe3e4aSElliott Hughes def end_feature(self): 1040*e1fe3e4aSElliott Hughes assert self.cur_feature_name_ is not None 1041*e1fe3e4aSElliott Hughes self.cur_feature_name_ = None 1042*e1fe3e4aSElliott Hughes self.language_systems = None 1043*e1fe3e4aSElliott Hughes self.cur_lookup_ = None 1044*e1fe3e4aSElliott Hughes self.lookupflag_ = 0 1045*e1fe3e4aSElliott Hughes self.lookupflag_markFilterSet_ = None 1046*e1fe3e4aSElliott Hughes 1047*e1fe3e4aSElliott Hughes def start_lookup_block(self, location, name): 1048*e1fe3e4aSElliott Hughes if name in self.named_lookups_: 1049*e1fe3e4aSElliott Hughes raise FeatureLibError( 1050*e1fe3e4aSElliott Hughes 'Lookup "%s" has already been defined' % name, location 1051*e1fe3e4aSElliott Hughes ) 1052*e1fe3e4aSElliott Hughes if self.cur_feature_name_ == "aalt": 1053*e1fe3e4aSElliott Hughes raise FeatureLibError( 1054*e1fe3e4aSElliott Hughes "Lookup blocks cannot be placed inside 'aalt' features; " 1055*e1fe3e4aSElliott Hughes "move it out, and then refer to it with a lookup statement", 1056*e1fe3e4aSElliott Hughes location, 1057*e1fe3e4aSElliott Hughes ) 1058*e1fe3e4aSElliott Hughes self.cur_lookup_name_ = name 1059*e1fe3e4aSElliott Hughes self.named_lookups_[name] = None 1060*e1fe3e4aSElliott Hughes self.cur_lookup_ = None 1061*e1fe3e4aSElliott Hughes if self.cur_feature_name_ is None: 1062*e1fe3e4aSElliott Hughes self.lookupflag_ = 0 1063*e1fe3e4aSElliott Hughes self.lookupflag_markFilterSet_ = None 1064*e1fe3e4aSElliott Hughes 1065*e1fe3e4aSElliott Hughes def end_lookup_block(self): 1066*e1fe3e4aSElliott Hughes assert self.cur_lookup_name_ is not None 1067*e1fe3e4aSElliott Hughes self.cur_lookup_name_ = None 1068*e1fe3e4aSElliott Hughes self.cur_lookup_ = None 1069*e1fe3e4aSElliott Hughes if self.cur_feature_name_ is None: 1070*e1fe3e4aSElliott Hughes self.lookupflag_ = 0 1071*e1fe3e4aSElliott Hughes self.lookupflag_markFilterSet_ = None 1072*e1fe3e4aSElliott Hughes 1073*e1fe3e4aSElliott Hughes def add_lookup_call(self, lookup_name): 1074*e1fe3e4aSElliott Hughes assert lookup_name in self.named_lookups_, lookup_name 1075*e1fe3e4aSElliott Hughes self.cur_lookup_ = None 1076*e1fe3e4aSElliott Hughes lookup = self.named_lookups_[lookup_name] 1077*e1fe3e4aSElliott Hughes if lookup is not None: # skip empty named lookup 1078*e1fe3e4aSElliott Hughes self.add_lookup_to_feature_(lookup, self.cur_feature_name_) 1079*e1fe3e4aSElliott Hughes 1080*e1fe3e4aSElliott Hughes def set_font_revision(self, location, revision): 1081*e1fe3e4aSElliott Hughes self.fontRevision_ = revision 1082*e1fe3e4aSElliott Hughes 1083*e1fe3e4aSElliott Hughes def set_language(self, location, language, include_default, required): 1084*e1fe3e4aSElliott Hughes assert len(language) == 4 1085*e1fe3e4aSElliott Hughes if self.cur_feature_name_ in ("aalt", "size"): 1086*e1fe3e4aSElliott Hughes raise FeatureLibError( 1087*e1fe3e4aSElliott Hughes "Language statements are not allowed " 1088*e1fe3e4aSElliott Hughes 'within "feature %s"' % self.cur_feature_name_, 1089*e1fe3e4aSElliott Hughes location, 1090*e1fe3e4aSElliott Hughes ) 1091*e1fe3e4aSElliott Hughes if self.cur_feature_name_ is None: 1092*e1fe3e4aSElliott Hughes raise FeatureLibError( 1093*e1fe3e4aSElliott Hughes "Language statements are not allowed " 1094*e1fe3e4aSElliott Hughes "within standalone lookup blocks", 1095*e1fe3e4aSElliott Hughes location, 1096*e1fe3e4aSElliott Hughes ) 1097*e1fe3e4aSElliott Hughes self.cur_lookup_ = None 1098*e1fe3e4aSElliott Hughes 1099*e1fe3e4aSElliott Hughes key = (self.script_, language, self.cur_feature_name_) 1100*e1fe3e4aSElliott Hughes lookups = self.features_.get((key[0], "dflt", key[2])) 1101*e1fe3e4aSElliott Hughes if (language == "dflt" or include_default) and lookups: 1102*e1fe3e4aSElliott Hughes self.features_[key] = lookups[:] 1103*e1fe3e4aSElliott Hughes else: 1104*e1fe3e4aSElliott Hughes self.features_[key] = [] 1105*e1fe3e4aSElliott Hughes self.language_systems = frozenset([(self.script_, language)]) 1106*e1fe3e4aSElliott Hughes 1107*e1fe3e4aSElliott Hughes if required: 1108*e1fe3e4aSElliott Hughes key = (self.script_, language) 1109*e1fe3e4aSElliott Hughes if key in self.required_features_: 1110*e1fe3e4aSElliott Hughes raise FeatureLibError( 1111*e1fe3e4aSElliott Hughes "Language %s (script %s) has already " 1112*e1fe3e4aSElliott Hughes "specified feature %s as its required feature" 1113*e1fe3e4aSElliott Hughes % ( 1114*e1fe3e4aSElliott Hughes language.strip(), 1115*e1fe3e4aSElliott Hughes self.script_.strip(), 1116*e1fe3e4aSElliott Hughes self.required_features_[key].strip(), 1117*e1fe3e4aSElliott Hughes ), 1118*e1fe3e4aSElliott Hughes location, 1119*e1fe3e4aSElliott Hughes ) 1120*e1fe3e4aSElliott Hughes self.required_features_[key] = self.cur_feature_name_ 1121*e1fe3e4aSElliott Hughes 1122*e1fe3e4aSElliott Hughes def getMarkAttachClass_(self, location, glyphs): 1123*e1fe3e4aSElliott Hughes glyphs = frozenset(glyphs) 1124*e1fe3e4aSElliott Hughes id_ = self.markAttachClassID_.get(glyphs) 1125*e1fe3e4aSElliott Hughes if id_ is not None: 1126*e1fe3e4aSElliott Hughes return id_ 1127*e1fe3e4aSElliott Hughes id_ = len(self.markAttachClassID_) + 1 1128*e1fe3e4aSElliott Hughes self.markAttachClassID_[glyphs] = id_ 1129*e1fe3e4aSElliott Hughes for glyph in glyphs: 1130*e1fe3e4aSElliott Hughes if glyph in self.markAttach_: 1131*e1fe3e4aSElliott Hughes _, loc = self.markAttach_[glyph] 1132*e1fe3e4aSElliott Hughes raise FeatureLibError( 1133*e1fe3e4aSElliott Hughes "Glyph %s already has been assigned " 1134*e1fe3e4aSElliott Hughes "a MarkAttachmentType at %s" % (glyph, loc), 1135*e1fe3e4aSElliott Hughes location, 1136*e1fe3e4aSElliott Hughes ) 1137*e1fe3e4aSElliott Hughes self.markAttach_[glyph] = (id_, location) 1138*e1fe3e4aSElliott Hughes return id_ 1139*e1fe3e4aSElliott Hughes 1140*e1fe3e4aSElliott Hughes def getMarkFilterSet_(self, location, glyphs): 1141*e1fe3e4aSElliott Hughes glyphs = frozenset(glyphs) 1142*e1fe3e4aSElliott Hughes id_ = self.markFilterSets_.get(glyphs) 1143*e1fe3e4aSElliott Hughes if id_ is not None: 1144*e1fe3e4aSElliott Hughes return id_ 1145*e1fe3e4aSElliott Hughes id_ = len(self.markFilterSets_) 1146*e1fe3e4aSElliott Hughes self.markFilterSets_[glyphs] = id_ 1147*e1fe3e4aSElliott Hughes return id_ 1148*e1fe3e4aSElliott Hughes 1149*e1fe3e4aSElliott Hughes def set_lookup_flag(self, location, value, markAttach, markFilter): 1150*e1fe3e4aSElliott Hughes value = value & 0xFF 1151*e1fe3e4aSElliott Hughes if markAttach: 1152*e1fe3e4aSElliott Hughes markAttachClass = self.getMarkAttachClass_(location, markAttach) 1153*e1fe3e4aSElliott Hughes value = value | (markAttachClass << 8) 1154*e1fe3e4aSElliott Hughes if markFilter: 1155*e1fe3e4aSElliott Hughes markFilterSet = self.getMarkFilterSet_(location, markFilter) 1156*e1fe3e4aSElliott Hughes value = value | 0x10 1157*e1fe3e4aSElliott Hughes self.lookupflag_markFilterSet_ = markFilterSet 1158*e1fe3e4aSElliott Hughes else: 1159*e1fe3e4aSElliott Hughes self.lookupflag_markFilterSet_ = None 1160*e1fe3e4aSElliott Hughes self.lookupflag_ = value 1161*e1fe3e4aSElliott Hughes 1162*e1fe3e4aSElliott Hughes def set_script(self, location, script): 1163*e1fe3e4aSElliott Hughes if self.cur_feature_name_ in ("aalt", "size"): 1164*e1fe3e4aSElliott Hughes raise FeatureLibError( 1165*e1fe3e4aSElliott Hughes "Script statements are not allowed " 1166*e1fe3e4aSElliott Hughes 'within "feature %s"' % self.cur_feature_name_, 1167*e1fe3e4aSElliott Hughes location, 1168*e1fe3e4aSElliott Hughes ) 1169*e1fe3e4aSElliott Hughes if self.cur_feature_name_ is None: 1170*e1fe3e4aSElliott Hughes raise FeatureLibError( 1171*e1fe3e4aSElliott Hughes "Script statements are not allowed " "within standalone lookup blocks", 1172*e1fe3e4aSElliott Hughes location, 1173*e1fe3e4aSElliott Hughes ) 1174*e1fe3e4aSElliott Hughes if self.language_systems == {(script, "dflt")}: 1175*e1fe3e4aSElliott Hughes # Nothing to do. 1176*e1fe3e4aSElliott Hughes return 1177*e1fe3e4aSElliott Hughes self.cur_lookup_ = None 1178*e1fe3e4aSElliott Hughes self.script_ = script 1179*e1fe3e4aSElliott Hughes self.lookupflag_ = 0 1180*e1fe3e4aSElliott Hughes self.lookupflag_markFilterSet_ = None 1181*e1fe3e4aSElliott Hughes self.set_language(location, "dflt", include_default=True, required=False) 1182*e1fe3e4aSElliott Hughes 1183*e1fe3e4aSElliott Hughes def find_lookup_builders_(self, lookups): 1184*e1fe3e4aSElliott Hughes """Helper for building chain contextual substitutions 1185*e1fe3e4aSElliott Hughes 1186*e1fe3e4aSElliott Hughes Given a list of lookup names, finds the LookupBuilder for each name. 1187*e1fe3e4aSElliott Hughes If an input name is None, it gets mapped to a None LookupBuilder. 1188*e1fe3e4aSElliott Hughes """ 1189*e1fe3e4aSElliott Hughes lookup_builders = [] 1190*e1fe3e4aSElliott Hughes for lookuplist in lookups: 1191*e1fe3e4aSElliott Hughes if lookuplist is not None: 1192*e1fe3e4aSElliott Hughes lookup_builders.append( 1193*e1fe3e4aSElliott Hughes [self.named_lookups_.get(l.name) for l in lookuplist] 1194*e1fe3e4aSElliott Hughes ) 1195*e1fe3e4aSElliott Hughes else: 1196*e1fe3e4aSElliott Hughes lookup_builders.append(None) 1197*e1fe3e4aSElliott Hughes return lookup_builders 1198*e1fe3e4aSElliott Hughes 1199*e1fe3e4aSElliott Hughes def add_attach_points(self, location, glyphs, contourPoints): 1200*e1fe3e4aSElliott Hughes for glyph in glyphs: 1201*e1fe3e4aSElliott Hughes self.attachPoints_.setdefault(glyph, set()).update(contourPoints) 1202*e1fe3e4aSElliott Hughes 1203*e1fe3e4aSElliott Hughes def add_feature_reference(self, location, featureName): 1204*e1fe3e4aSElliott Hughes if self.cur_feature_name_ != "aalt": 1205*e1fe3e4aSElliott Hughes raise FeatureLibError( 1206*e1fe3e4aSElliott Hughes 'Feature references are only allowed inside "feature aalt"', location 1207*e1fe3e4aSElliott Hughes ) 1208*e1fe3e4aSElliott Hughes self.aalt_features_.append((location, featureName)) 1209*e1fe3e4aSElliott Hughes 1210*e1fe3e4aSElliott Hughes def add_featureName(self, tag): 1211*e1fe3e4aSElliott Hughes self.featureNames_.add(tag) 1212*e1fe3e4aSElliott Hughes 1213*e1fe3e4aSElliott Hughes def add_cv_parameter(self, tag): 1214*e1fe3e4aSElliott Hughes self.cv_parameters_.add(tag) 1215*e1fe3e4aSElliott Hughes 1216*e1fe3e4aSElliott Hughes def add_to_cv_num_named_params(self, tag): 1217*e1fe3e4aSElliott Hughes """Adds new items to ``self.cv_num_named_params_`` 1218*e1fe3e4aSElliott Hughes or increments the count of existing items.""" 1219*e1fe3e4aSElliott Hughes if tag in self.cv_num_named_params_: 1220*e1fe3e4aSElliott Hughes self.cv_num_named_params_[tag] += 1 1221*e1fe3e4aSElliott Hughes else: 1222*e1fe3e4aSElliott Hughes self.cv_num_named_params_[tag] = 1 1223*e1fe3e4aSElliott Hughes 1224*e1fe3e4aSElliott Hughes def add_cv_character(self, character, tag): 1225*e1fe3e4aSElliott Hughes self.cv_characters_[tag].append(character) 1226*e1fe3e4aSElliott Hughes 1227*e1fe3e4aSElliott Hughes def set_base_axis(self, bases, scripts, vertical): 1228*e1fe3e4aSElliott Hughes if vertical: 1229*e1fe3e4aSElliott Hughes self.base_vert_axis_ = (bases, scripts) 1230*e1fe3e4aSElliott Hughes else: 1231*e1fe3e4aSElliott Hughes self.base_horiz_axis_ = (bases, scripts) 1232*e1fe3e4aSElliott Hughes 1233*e1fe3e4aSElliott Hughes def set_size_parameters( 1234*e1fe3e4aSElliott Hughes self, location, DesignSize, SubfamilyID, RangeStart, RangeEnd 1235*e1fe3e4aSElliott Hughes ): 1236*e1fe3e4aSElliott Hughes if self.cur_feature_name_ != "size": 1237*e1fe3e4aSElliott Hughes raise FeatureLibError( 1238*e1fe3e4aSElliott Hughes "Parameters statements are not allowed " 1239*e1fe3e4aSElliott Hughes 'within "feature %s"' % self.cur_feature_name_, 1240*e1fe3e4aSElliott Hughes location, 1241*e1fe3e4aSElliott Hughes ) 1242*e1fe3e4aSElliott Hughes self.size_parameters_ = [DesignSize, SubfamilyID, RangeStart, RangeEnd] 1243*e1fe3e4aSElliott Hughes for script, lang in self.language_systems: 1244*e1fe3e4aSElliott Hughes key = (script, lang, self.cur_feature_name_) 1245*e1fe3e4aSElliott Hughes self.features_.setdefault(key, []) 1246*e1fe3e4aSElliott Hughes 1247*e1fe3e4aSElliott Hughes # GSUB rules 1248*e1fe3e4aSElliott Hughes 1249*e1fe3e4aSElliott Hughes # GSUB 1 1250*e1fe3e4aSElliott Hughes def add_single_subst(self, location, prefix, suffix, mapping, forceChain): 1251*e1fe3e4aSElliott Hughes if self.cur_feature_name_ == "aalt": 1252*e1fe3e4aSElliott Hughes for from_glyph, to_glyph in mapping.items(): 1253*e1fe3e4aSElliott Hughes alts = self.aalt_alternates_.setdefault(from_glyph, []) 1254*e1fe3e4aSElliott Hughes if to_glyph not in alts: 1255*e1fe3e4aSElliott Hughes alts.append(to_glyph) 1256*e1fe3e4aSElliott Hughes return 1257*e1fe3e4aSElliott Hughes if prefix or suffix or forceChain: 1258*e1fe3e4aSElliott Hughes self.add_single_subst_chained_(location, prefix, suffix, mapping) 1259*e1fe3e4aSElliott Hughes return 1260*e1fe3e4aSElliott Hughes lookup = self.get_lookup_(location, SingleSubstBuilder) 1261*e1fe3e4aSElliott Hughes for from_glyph, to_glyph in mapping.items(): 1262*e1fe3e4aSElliott Hughes if from_glyph in lookup.mapping: 1263*e1fe3e4aSElliott Hughes if to_glyph == lookup.mapping[from_glyph]: 1264*e1fe3e4aSElliott Hughes log.info( 1265*e1fe3e4aSElliott Hughes "Removing duplicate single substitution from glyph" 1266*e1fe3e4aSElliott Hughes ' "%s" to "%s" at %s', 1267*e1fe3e4aSElliott Hughes from_glyph, 1268*e1fe3e4aSElliott Hughes to_glyph, 1269*e1fe3e4aSElliott Hughes location, 1270*e1fe3e4aSElliott Hughes ) 1271*e1fe3e4aSElliott Hughes else: 1272*e1fe3e4aSElliott Hughes raise FeatureLibError( 1273*e1fe3e4aSElliott Hughes 'Already defined rule for replacing glyph "%s" by "%s"' 1274*e1fe3e4aSElliott Hughes % (from_glyph, lookup.mapping[from_glyph]), 1275*e1fe3e4aSElliott Hughes location, 1276*e1fe3e4aSElliott Hughes ) 1277*e1fe3e4aSElliott Hughes lookup.mapping[from_glyph] = to_glyph 1278*e1fe3e4aSElliott Hughes 1279*e1fe3e4aSElliott Hughes # GSUB 2 1280*e1fe3e4aSElliott Hughes def add_multiple_subst( 1281*e1fe3e4aSElliott Hughes self, location, prefix, glyph, suffix, replacements, forceChain=False 1282*e1fe3e4aSElliott Hughes ): 1283*e1fe3e4aSElliott Hughes if prefix or suffix or forceChain: 1284*e1fe3e4aSElliott Hughes chain = self.get_lookup_(location, ChainContextSubstBuilder) 1285*e1fe3e4aSElliott Hughes sub = self.get_chained_lookup_(location, MultipleSubstBuilder) 1286*e1fe3e4aSElliott Hughes sub.mapping[glyph] = replacements 1287*e1fe3e4aSElliott Hughes chain.rules.append(ChainContextualRule(prefix, [{glyph}], suffix, [sub])) 1288*e1fe3e4aSElliott Hughes return 1289*e1fe3e4aSElliott Hughes lookup = self.get_lookup_(location, MultipleSubstBuilder) 1290*e1fe3e4aSElliott Hughes if glyph in lookup.mapping: 1291*e1fe3e4aSElliott Hughes if replacements == lookup.mapping[glyph]: 1292*e1fe3e4aSElliott Hughes log.info( 1293*e1fe3e4aSElliott Hughes "Removing duplicate multiple substitution from glyph" 1294*e1fe3e4aSElliott Hughes ' "%s" to %s%s', 1295*e1fe3e4aSElliott Hughes glyph, 1296*e1fe3e4aSElliott Hughes replacements, 1297*e1fe3e4aSElliott Hughes f" at {location}" if location else "", 1298*e1fe3e4aSElliott Hughes ) 1299*e1fe3e4aSElliott Hughes else: 1300*e1fe3e4aSElliott Hughes raise FeatureLibError( 1301*e1fe3e4aSElliott Hughes 'Already defined substitution for glyph "%s"' % glyph, location 1302*e1fe3e4aSElliott Hughes ) 1303*e1fe3e4aSElliott Hughes lookup.mapping[glyph] = replacements 1304*e1fe3e4aSElliott Hughes 1305*e1fe3e4aSElliott Hughes # GSUB 3 1306*e1fe3e4aSElliott Hughes def add_alternate_subst(self, location, prefix, glyph, suffix, replacement): 1307*e1fe3e4aSElliott Hughes if self.cur_feature_name_ == "aalt": 1308*e1fe3e4aSElliott Hughes alts = self.aalt_alternates_.setdefault(glyph, []) 1309*e1fe3e4aSElliott Hughes alts.extend(g for g in replacement if g not in alts) 1310*e1fe3e4aSElliott Hughes return 1311*e1fe3e4aSElliott Hughes if prefix or suffix: 1312*e1fe3e4aSElliott Hughes chain = self.get_lookup_(location, ChainContextSubstBuilder) 1313*e1fe3e4aSElliott Hughes lookup = self.get_chained_lookup_(location, AlternateSubstBuilder) 1314*e1fe3e4aSElliott Hughes chain.rules.append(ChainContextualRule(prefix, [{glyph}], suffix, [lookup])) 1315*e1fe3e4aSElliott Hughes else: 1316*e1fe3e4aSElliott Hughes lookup = self.get_lookup_(location, AlternateSubstBuilder) 1317*e1fe3e4aSElliott Hughes if glyph in lookup.alternates: 1318*e1fe3e4aSElliott Hughes raise FeatureLibError( 1319*e1fe3e4aSElliott Hughes 'Already defined alternates for glyph "%s"' % glyph, location 1320*e1fe3e4aSElliott Hughes ) 1321*e1fe3e4aSElliott Hughes # We allow empty replacement glyphs here. 1322*e1fe3e4aSElliott Hughes lookup.alternates[glyph] = replacement 1323*e1fe3e4aSElliott Hughes 1324*e1fe3e4aSElliott Hughes # GSUB 4 1325*e1fe3e4aSElliott Hughes def add_ligature_subst( 1326*e1fe3e4aSElliott Hughes self, location, prefix, glyphs, suffix, replacement, forceChain 1327*e1fe3e4aSElliott Hughes ): 1328*e1fe3e4aSElliott Hughes if prefix or suffix or forceChain: 1329*e1fe3e4aSElliott Hughes chain = self.get_lookup_(location, ChainContextSubstBuilder) 1330*e1fe3e4aSElliott Hughes lookup = self.get_chained_lookup_(location, LigatureSubstBuilder) 1331*e1fe3e4aSElliott Hughes chain.rules.append(ChainContextualRule(prefix, glyphs, suffix, [lookup])) 1332*e1fe3e4aSElliott Hughes else: 1333*e1fe3e4aSElliott Hughes lookup = self.get_lookup_(location, LigatureSubstBuilder) 1334*e1fe3e4aSElliott Hughes 1335*e1fe3e4aSElliott Hughes if not all(glyphs): 1336*e1fe3e4aSElliott Hughes raise FeatureLibError("Empty glyph class in substitution", location) 1337*e1fe3e4aSElliott Hughes 1338*e1fe3e4aSElliott Hughes # OpenType feature file syntax, section 5.d, "Ligature substitution": 1339*e1fe3e4aSElliott Hughes # "Since the OpenType specification does not allow ligature 1340*e1fe3e4aSElliott Hughes # substitutions to be specified on target sequences that contain 1341*e1fe3e4aSElliott Hughes # glyph classes, the implementation software will enumerate 1342*e1fe3e4aSElliott Hughes # all specific glyph sequences if glyph classes are detected" 1343*e1fe3e4aSElliott Hughes for g in itertools.product(*glyphs): 1344*e1fe3e4aSElliott Hughes lookup.ligatures[g] = replacement 1345*e1fe3e4aSElliott Hughes 1346*e1fe3e4aSElliott Hughes # GSUB 5/6 1347*e1fe3e4aSElliott Hughes def add_chain_context_subst(self, location, prefix, glyphs, suffix, lookups): 1348*e1fe3e4aSElliott Hughes if not all(glyphs) or not all(prefix) or not all(suffix): 1349*e1fe3e4aSElliott Hughes raise FeatureLibError( 1350*e1fe3e4aSElliott Hughes "Empty glyph class in contextual substitution", location 1351*e1fe3e4aSElliott Hughes ) 1352*e1fe3e4aSElliott Hughes lookup = self.get_lookup_(location, ChainContextSubstBuilder) 1353*e1fe3e4aSElliott Hughes lookup.rules.append( 1354*e1fe3e4aSElliott Hughes ChainContextualRule( 1355*e1fe3e4aSElliott Hughes prefix, glyphs, suffix, self.find_lookup_builders_(lookups) 1356*e1fe3e4aSElliott Hughes ) 1357*e1fe3e4aSElliott Hughes ) 1358*e1fe3e4aSElliott Hughes 1359*e1fe3e4aSElliott Hughes def add_single_subst_chained_(self, location, prefix, suffix, mapping): 1360*e1fe3e4aSElliott Hughes if not mapping or not all(prefix) or not all(suffix): 1361*e1fe3e4aSElliott Hughes raise FeatureLibError( 1362*e1fe3e4aSElliott Hughes "Empty glyph class in contextual substitution", location 1363*e1fe3e4aSElliott Hughes ) 1364*e1fe3e4aSElliott Hughes # https://github.com/fonttools/fonttools/issues/512 1365*e1fe3e4aSElliott Hughes # https://github.com/fonttools/fonttools/issues/2150 1366*e1fe3e4aSElliott Hughes chain = self.get_lookup_(location, ChainContextSubstBuilder) 1367*e1fe3e4aSElliott Hughes sub = chain.find_chainable_single_subst(mapping) 1368*e1fe3e4aSElliott Hughes if sub is None: 1369*e1fe3e4aSElliott Hughes sub = self.get_chained_lookup_(location, SingleSubstBuilder) 1370*e1fe3e4aSElliott Hughes sub.mapping.update(mapping) 1371*e1fe3e4aSElliott Hughes chain.rules.append( 1372*e1fe3e4aSElliott Hughes ChainContextualRule(prefix, [list(mapping.keys())], suffix, [sub]) 1373*e1fe3e4aSElliott Hughes ) 1374*e1fe3e4aSElliott Hughes 1375*e1fe3e4aSElliott Hughes # GSUB 8 1376*e1fe3e4aSElliott Hughes def add_reverse_chain_single_subst(self, location, old_prefix, old_suffix, mapping): 1377*e1fe3e4aSElliott Hughes if not mapping: 1378*e1fe3e4aSElliott Hughes raise FeatureLibError("Empty glyph class in substitution", location) 1379*e1fe3e4aSElliott Hughes lookup = self.get_lookup_(location, ReverseChainSingleSubstBuilder) 1380*e1fe3e4aSElliott Hughes lookup.rules.append((old_prefix, old_suffix, mapping)) 1381*e1fe3e4aSElliott Hughes 1382*e1fe3e4aSElliott Hughes # GPOS rules 1383*e1fe3e4aSElliott Hughes 1384*e1fe3e4aSElliott Hughes # GPOS 1 1385*e1fe3e4aSElliott Hughes def add_single_pos(self, location, prefix, suffix, pos, forceChain): 1386*e1fe3e4aSElliott Hughes if prefix or suffix or forceChain: 1387*e1fe3e4aSElliott Hughes self.add_single_pos_chained_(location, prefix, suffix, pos) 1388*e1fe3e4aSElliott Hughes else: 1389*e1fe3e4aSElliott Hughes lookup = self.get_lookup_(location, SinglePosBuilder) 1390*e1fe3e4aSElliott Hughes for glyphs, value in pos: 1391*e1fe3e4aSElliott Hughes if not glyphs: 1392*e1fe3e4aSElliott Hughes raise FeatureLibError( 1393*e1fe3e4aSElliott Hughes "Empty glyph class in positioning rule", location 1394*e1fe3e4aSElliott Hughes ) 1395*e1fe3e4aSElliott Hughes otValueRecord = self.makeOpenTypeValueRecord( 1396*e1fe3e4aSElliott Hughes location, value, pairPosContext=False 1397*e1fe3e4aSElliott Hughes ) 1398*e1fe3e4aSElliott Hughes for glyph in glyphs: 1399*e1fe3e4aSElliott Hughes try: 1400*e1fe3e4aSElliott Hughes lookup.add_pos(location, glyph, otValueRecord) 1401*e1fe3e4aSElliott Hughes except OpenTypeLibError as e: 1402*e1fe3e4aSElliott Hughes raise FeatureLibError(str(e), e.location) from e 1403*e1fe3e4aSElliott Hughes 1404*e1fe3e4aSElliott Hughes # GPOS 2 1405*e1fe3e4aSElliott Hughes def add_class_pair_pos(self, location, glyphclass1, value1, glyphclass2, value2): 1406*e1fe3e4aSElliott Hughes if not glyphclass1 or not glyphclass2: 1407*e1fe3e4aSElliott Hughes raise FeatureLibError("Empty glyph class in positioning rule", location) 1408*e1fe3e4aSElliott Hughes lookup = self.get_lookup_(location, PairPosBuilder) 1409*e1fe3e4aSElliott Hughes v1 = self.makeOpenTypeValueRecord(location, value1, pairPosContext=True) 1410*e1fe3e4aSElliott Hughes v2 = self.makeOpenTypeValueRecord(location, value2, pairPosContext=True) 1411*e1fe3e4aSElliott Hughes lookup.addClassPair(location, glyphclass1, v1, glyphclass2, v2) 1412*e1fe3e4aSElliott Hughes 1413*e1fe3e4aSElliott Hughes def add_specific_pair_pos(self, location, glyph1, value1, glyph2, value2): 1414*e1fe3e4aSElliott Hughes if not glyph1 or not glyph2: 1415*e1fe3e4aSElliott Hughes raise FeatureLibError("Empty glyph class in positioning rule", location) 1416*e1fe3e4aSElliott Hughes lookup = self.get_lookup_(location, PairPosBuilder) 1417*e1fe3e4aSElliott Hughes v1 = self.makeOpenTypeValueRecord(location, value1, pairPosContext=True) 1418*e1fe3e4aSElliott Hughes v2 = self.makeOpenTypeValueRecord(location, value2, pairPosContext=True) 1419*e1fe3e4aSElliott Hughes lookup.addGlyphPair(location, glyph1, v1, glyph2, v2) 1420*e1fe3e4aSElliott Hughes 1421*e1fe3e4aSElliott Hughes # GPOS 3 1422*e1fe3e4aSElliott Hughes def add_cursive_pos(self, location, glyphclass, entryAnchor, exitAnchor): 1423*e1fe3e4aSElliott Hughes if not glyphclass: 1424*e1fe3e4aSElliott Hughes raise FeatureLibError("Empty glyph class in positioning rule", location) 1425*e1fe3e4aSElliott Hughes lookup = self.get_lookup_(location, CursivePosBuilder) 1426*e1fe3e4aSElliott Hughes lookup.add_attachment( 1427*e1fe3e4aSElliott Hughes location, 1428*e1fe3e4aSElliott Hughes glyphclass, 1429*e1fe3e4aSElliott Hughes self.makeOpenTypeAnchor(location, entryAnchor), 1430*e1fe3e4aSElliott Hughes self.makeOpenTypeAnchor(location, exitAnchor), 1431*e1fe3e4aSElliott Hughes ) 1432*e1fe3e4aSElliott Hughes 1433*e1fe3e4aSElliott Hughes # GPOS 4 1434*e1fe3e4aSElliott Hughes def add_mark_base_pos(self, location, bases, marks): 1435*e1fe3e4aSElliott Hughes builder = self.get_lookup_(location, MarkBasePosBuilder) 1436*e1fe3e4aSElliott Hughes self.add_marks_(location, builder, marks) 1437*e1fe3e4aSElliott Hughes if not bases: 1438*e1fe3e4aSElliott Hughes raise FeatureLibError("Empty glyph class in positioning rule", location) 1439*e1fe3e4aSElliott Hughes for baseAnchor, markClass in marks: 1440*e1fe3e4aSElliott Hughes otBaseAnchor = self.makeOpenTypeAnchor(location, baseAnchor) 1441*e1fe3e4aSElliott Hughes for base in bases: 1442*e1fe3e4aSElliott Hughes builder.bases.setdefault(base, {})[markClass.name] = otBaseAnchor 1443*e1fe3e4aSElliott Hughes 1444*e1fe3e4aSElliott Hughes # GPOS 5 1445*e1fe3e4aSElliott Hughes def add_mark_lig_pos(self, location, ligatures, components): 1446*e1fe3e4aSElliott Hughes builder = self.get_lookup_(location, MarkLigPosBuilder) 1447*e1fe3e4aSElliott Hughes componentAnchors = [] 1448*e1fe3e4aSElliott Hughes if not ligatures: 1449*e1fe3e4aSElliott Hughes raise FeatureLibError("Empty glyph class in positioning rule", location) 1450*e1fe3e4aSElliott Hughes for marks in components: 1451*e1fe3e4aSElliott Hughes anchors = {} 1452*e1fe3e4aSElliott Hughes self.add_marks_(location, builder, marks) 1453*e1fe3e4aSElliott Hughes for ligAnchor, markClass in marks: 1454*e1fe3e4aSElliott Hughes anchors[markClass.name] = self.makeOpenTypeAnchor(location, ligAnchor) 1455*e1fe3e4aSElliott Hughes componentAnchors.append(anchors) 1456*e1fe3e4aSElliott Hughes for glyph in ligatures: 1457*e1fe3e4aSElliott Hughes builder.ligatures[glyph] = componentAnchors 1458*e1fe3e4aSElliott Hughes 1459*e1fe3e4aSElliott Hughes # GPOS 6 1460*e1fe3e4aSElliott Hughes def add_mark_mark_pos(self, location, baseMarks, marks): 1461*e1fe3e4aSElliott Hughes builder = self.get_lookup_(location, MarkMarkPosBuilder) 1462*e1fe3e4aSElliott Hughes self.add_marks_(location, builder, marks) 1463*e1fe3e4aSElliott Hughes if not baseMarks: 1464*e1fe3e4aSElliott Hughes raise FeatureLibError("Empty glyph class in positioning rule", location) 1465*e1fe3e4aSElliott Hughes for baseAnchor, markClass in marks: 1466*e1fe3e4aSElliott Hughes otBaseAnchor = self.makeOpenTypeAnchor(location, baseAnchor) 1467*e1fe3e4aSElliott Hughes for baseMark in baseMarks: 1468*e1fe3e4aSElliott Hughes builder.baseMarks.setdefault(baseMark, {})[ 1469*e1fe3e4aSElliott Hughes markClass.name 1470*e1fe3e4aSElliott Hughes ] = otBaseAnchor 1471*e1fe3e4aSElliott Hughes 1472*e1fe3e4aSElliott Hughes # GPOS 7/8 1473*e1fe3e4aSElliott Hughes def add_chain_context_pos(self, location, prefix, glyphs, suffix, lookups): 1474*e1fe3e4aSElliott Hughes if not all(glyphs) or not all(prefix) or not all(suffix): 1475*e1fe3e4aSElliott Hughes raise FeatureLibError( 1476*e1fe3e4aSElliott Hughes "Empty glyph class in contextual positioning rule", location 1477*e1fe3e4aSElliott Hughes ) 1478*e1fe3e4aSElliott Hughes lookup = self.get_lookup_(location, ChainContextPosBuilder) 1479*e1fe3e4aSElliott Hughes lookup.rules.append( 1480*e1fe3e4aSElliott Hughes ChainContextualRule( 1481*e1fe3e4aSElliott Hughes prefix, glyphs, suffix, self.find_lookup_builders_(lookups) 1482*e1fe3e4aSElliott Hughes ) 1483*e1fe3e4aSElliott Hughes ) 1484*e1fe3e4aSElliott Hughes 1485*e1fe3e4aSElliott Hughes def add_single_pos_chained_(self, location, prefix, suffix, pos): 1486*e1fe3e4aSElliott Hughes if not pos or not all(prefix) or not all(suffix): 1487*e1fe3e4aSElliott Hughes raise FeatureLibError( 1488*e1fe3e4aSElliott Hughes "Empty glyph class in contextual positioning rule", location 1489*e1fe3e4aSElliott Hughes ) 1490*e1fe3e4aSElliott Hughes # https://github.com/fonttools/fonttools/issues/514 1491*e1fe3e4aSElliott Hughes chain = self.get_lookup_(location, ChainContextPosBuilder) 1492*e1fe3e4aSElliott Hughes targets = [] 1493*e1fe3e4aSElliott Hughes for _, _, _, lookups in chain.rules: 1494*e1fe3e4aSElliott Hughes targets.extend(lookups) 1495*e1fe3e4aSElliott Hughes subs = [] 1496*e1fe3e4aSElliott Hughes for glyphs, value in pos: 1497*e1fe3e4aSElliott Hughes if value is None: 1498*e1fe3e4aSElliott Hughes subs.append(None) 1499*e1fe3e4aSElliott Hughes continue 1500*e1fe3e4aSElliott Hughes otValue = self.makeOpenTypeValueRecord( 1501*e1fe3e4aSElliott Hughes location, value, pairPosContext=False 1502*e1fe3e4aSElliott Hughes ) 1503*e1fe3e4aSElliott Hughes sub = chain.find_chainable_single_pos(targets, glyphs, otValue) 1504*e1fe3e4aSElliott Hughes if sub is None: 1505*e1fe3e4aSElliott Hughes sub = self.get_chained_lookup_(location, SinglePosBuilder) 1506*e1fe3e4aSElliott Hughes targets.append(sub) 1507*e1fe3e4aSElliott Hughes for glyph in glyphs: 1508*e1fe3e4aSElliott Hughes sub.add_pos(location, glyph, otValue) 1509*e1fe3e4aSElliott Hughes subs.append(sub) 1510*e1fe3e4aSElliott Hughes assert len(pos) == len(subs), (pos, subs) 1511*e1fe3e4aSElliott Hughes chain.rules.append( 1512*e1fe3e4aSElliott Hughes ChainContextualRule(prefix, [g for g, v in pos], suffix, subs) 1513*e1fe3e4aSElliott Hughes ) 1514*e1fe3e4aSElliott Hughes 1515*e1fe3e4aSElliott Hughes def add_marks_(self, location, lookupBuilder, marks): 1516*e1fe3e4aSElliott Hughes """Helper for add_mark_{base,liga,mark}_pos.""" 1517*e1fe3e4aSElliott Hughes for _, markClass in marks: 1518*e1fe3e4aSElliott Hughes for markClassDef in markClass.definitions: 1519*e1fe3e4aSElliott Hughes for mark in markClassDef.glyphs.glyphSet(): 1520*e1fe3e4aSElliott Hughes if mark not in lookupBuilder.marks: 1521*e1fe3e4aSElliott Hughes otMarkAnchor = self.makeOpenTypeAnchor( 1522*e1fe3e4aSElliott Hughes location, copy.deepcopy(markClassDef.anchor) 1523*e1fe3e4aSElliott Hughes ) 1524*e1fe3e4aSElliott Hughes lookupBuilder.marks[mark] = (markClass.name, otMarkAnchor) 1525*e1fe3e4aSElliott Hughes else: 1526*e1fe3e4aSElliott Hughes existingMarkClass = lookupBuilder.marks[mark][0] 1527*e1fe3e4aSElliott Hughes if markClass.name != existingMarkClass: 1528*e1fe3e4aSElliott Hughes raise FeatureLibError( 1529*e1fe3e4aSElliott Hughes "Glyph %s cannot be in both @%s and @%s" 1530*e1fe3e4aSElliott Hughes % (mark, existingMarkClass, markClass.name), 1531*e1fe3e4aSElliott Hughes location, 1532*e1fe3e4aSElliott Hughes ) 1533*e1fe3e4aSElliott Hughes 1534*e1fe3e4aSElliott Hughes def add_subtable_break(self, location): 1535*e1fe3e4aSElliott Hughes self.cur_lookup_.add_subtable_break(location) 1536*e1fe3e4aSElliott Hughes 1537*e1fe3e4aSElliott Hughes def setGlyphClass_(self, location, glyph, glyphClass): 1538*e1fe3e4aSElliott Hughes oldClass, oldLocation = self.glyphClassDefs_.get(glyph, (None, None)) 1539*e1fe3e4aSElliott Hughes if oldClass and oldClass != glyphClass: 1540*e1fe3e4aSElliott Hughes raise FeatureLibError( 1541*e1fe3e4aSElliott Hughes "Glyph %s was assigned to a different class at %s" 1542*e1fe3e4aSElliott Hughes % (glyph, oldLocation), 1543*e1fe3e4aSElliott Hughes location, 1544*e1fe3e4aSElliott Hughes ) 1545*e1fe3e4aSElliott Hughes self.glyphClassDefs_[glyph] = (glyphClass, location) 1546*e1fe3e4aSElliott Hughes 1547*e1fe3e4aSElliott Hughes def add_glyphClassDef( 1548*e1fe3e4aSElliott Hughes self, location, baseGlyphs, ligatureGlyphs, markGlyphs, componentGlyphs 1549*e1fe3e4aSElliott Hughes ): 1550*e1fe3e4aSElliott Hughes for glyph in baseGlyphs: 1551*e1fe3e4aSElliott Hughes self.setGlyphClass_(location, glyph, 1) 1552*e1fe3e4aSElliott Hughes for glyph in ligatureGlyphs: 1553*e1fe3e4aSElliott Hughes self.setGlyphClass_(location, glyph, 2) 1554*e1fe3e4aSElliott Hughes for glyph in markGlyphs: 1555*e1fe3e4aSElliott Hughes self.setGlyphClass_(location, glyph, 3) 1556*e1fe3e4aSElliott Hughes for glyph in componentGlyphs: 1557*e1fe3e4aSElliott Hughes self.setGlyphClass_(location, glyph, 4) 1558*e1fe3e4aSElliott Hughes 1559*e1fe3e4aSElliott Hughes def add_ligatureCaretByIndex_(self, location, glyphs, carets): 1560*e1fe3e4aSElliott Hughes for glyph in glyphs: 1561*e1fe3e4aSElliott Hughes if glyph not in self.ligCaretPoints_: 1562*e1fe3e4aSElliott Hughes self.ligCaretPoints_[glyph] = carets 1563*e1fe3e4aSElliott Hughes 1564*e1fe3e4aSElliott Hughes def makeLigCaret(self, location, caret): 1565*e1fe3e4aSElliott Hughes if not isinstance(caret, VariableScalar): 1566*e1fe3e4aSElliott Hughes return caret 1567*e1fe3e4aSElliott Hughes default, device = self.makeVariablePos(location, caret) 1568*e1fe3e4aSElliott Hughes if device is not None: 1569*e1fe3e4aSElliott Hughes return (default, device) 1570*e1fe3e4aSElliott Hughes return default 1571*e1fe3e4aSElliott Hughes 1572*e1fe3e4aSElliott Hughes def add_ligatureCaretByPos_(self, location, glyphs, carets): 1573*e1fe3e4aSElliott Hughes carets = [self.makeLigCaret(location, caret) for caret in carets] 1574*e1fe3e4aSElliott Hughes for glyph in glyphs: 1575*e1fe3e4aSElliott Hughes if glyph not in self.ligCaretCoords_: 1576*e1fe3e4aSElliott Hughes self.ligCaretCoords_[glyph] = carets 1577*e1fe3e4aSElliott Hughes 1578*e1fe3e4aSElliott Hughes def add_name_record(self, location, nameID, platformID, platEncID, langID, string): 1579*e1fe3e4aSElliott Hughes self.names_.append([nameID, platformID, platEncID, langID, string]) 1580*e1fe3e4aSElliott Hughes 1581*e1fe3e4aSElliott Hughes def add_os2_field(self, key, value): 1582*e1fe3e4aSElliott Hughes self.os2_[key] = value 1583*e1fe3e4aSElliott Hughes 1584*e1fe3e4aSElliott Hughes def add_hhea_field(self, key, value): 1585*e1fe3e4aSElliott Hughes self.hhea_[key] = value 1586*e1fe3e4aSElliott Hughes 1587*e1fe3e4aSElliott Hughes def add_vhea_field(self, key, value): 1588*e1fe3e4aSElliott Hughes self.vhea_[key] = value 1589*e1fe3e4aSElliott Hughes 1590*e1fe3e4aSElliott Hughes def add_conditionset(self, location, key, value): 1591*e1fe3e4aSElliott Hughes if "fvar" not in self.font: 1592*e1fe3e4aSElliott Hughes raise FeatureLibError( 1593*e1fe3e4aSElliott Hughes "Cannot add feature variations to a font without an 'fvar' table", 1594*e1fe3e4aSElliott Hughes location, 1595*e1fe3e4aSElliott Hughes ) 1596*e1fe3e4aSElliott Hughes 1597*e1fe3e4aSElliott Hughes # Normalize 1598*e1fe3e4aSElliott Hughes axisMap = { 1599*e1fe3e4aSElliott Hughes axis.axisTag: (axis.minValue, axis.defaultValue, axis.maxValue) 1600*e1fe3e4aSElliott Hughes for axis in self.axes 1601*e1fe3e4aSElliott Hughes } 1602*e1fe3e4aSElliott Hughes 1603*e1fe3e4aSElliott Hughes value = { 1604*e1fe3e4aSElliott Hughes tag: ( 1605*e1fe3e4aSElliott Hughes normalizeValue(bottom, axisMap[tag]), 1606*e1fe3e4aSElliott Hughes normalizeValue(top, axisMap[tag]), 1607*e1fe3e4aSElliott Hughes ) 1608*e1fe3e4aSElliott Hughes for tag, (bottom, top) in value.items() 1609*e1fe3e4aSElliott Hughes } 1610*e1fe3e4aSElliott Hughes 1611*e1fe3e4aSElliott Hughes # NOTE: This might result in rounding errors (off-by-ones) compared to 1612*e1fe3e4aSElliott Hughes # rules in Designspace files, since we're working with what's in the 1613*e1fe3e4aSElliott Hughes # `avar` table rather than the original values. 1614*e1fe3e4aSElliott Hughes if "avar" in self.font: 1615*e1fe3e4aSElliott Hughes mapping = self.font["avar"].segments 1616*e1fe3e4aSElliott Hughes value = { 1617*e1fe3e4aSElliott Hughes axis: tuple( 1618*e1fe3e4aSElliott Hughes piecewiseLinearMap(v, mapping[axis]) if axis in mapping else v 1619*e1fe3e4aSElliott Hughes for v in condition_range 1620*e1fe3e4aSElliott Hughes ) 1621*e1fe3e4aSElliott Hughes for axis, condition_range in value.items() 1622*e1fe3e4aSElliott Hughes } 1623*e1fe3e4aSElliott Hughes 1624*e1fe3e4aSElliott Hughes self.conditionsets_[key] = value 1625*e1fe3e4aSElliott Hughes 1626*e1fe3e4aSElliott Hughes def makeVariablePos(self, location, varscalar): 1627*e1fe3e4aSElliott Hughes if not self.varstorebuilder: 1628*e1fe3e4aSElliott Hughes raise FeatureLibError( 1629*e1fe3e4aSElliott Hughes "Can't define a variable scalar in a non-variable font", location 1630*e1fe3e4aSElliott Hughes ) 1631*e1fe3e4aSElliott Hughes 1632*e1fe3e4aSElliott Hughes varscalar.axes = self.axes 1633*e1fe3e4aSElliott Hughes if not varscalar.does_vary: 1634*e1fe3e4aSElliott Hughes return varscalar.default, None 1635*e1fe3e4aSElliott Hughes 1636*e1fe3e4aSElliott Hughes default, index = varscalar.add_to_variation_store( 1637*e1fe3e4aSElliott Hughes self.varstorebuilder, self.model_cache, self.font.get("avar") 1638*e1fe3e4aSElliott Hughes ) 1639*e1fe3e4aSElliott Hughes 1640*e1fe3e4aSElliott Hughes device = None 1641*e1fe3e4aSElliott Hughes if index is not None and index != 0xFFFFFFFF: 1642*e1fe3e4aSElliott Hughes device = buildVarDevTable(index) 1643*e1fe3e4aSElliott Hughes 1644*e1fe3e4aSElliott Hughes return default, device 1645*e1fe3e4aSElliott Hughes 1646*e1fe3e4aSElliott Hughes def makeOpenTypeAnchor(self, location, anchor): 1647*e1fe3e4aSElliott Hughes """ast.Anchor --> otTables.Anchor""" 1648*e1fe3e4aSElliott Hughes if anchor is None: 1649*e1fe3e4aSElliott Hughes return None 1650*e1fe3e4aSElliott Hughes variable = False 1651*e1fe3e4aSElliott Hughes deviceX, deviceY = None, None 1652*e1fe3e4aSElliott Hughes if anchor.xDeviceTable is not None: 1653*e1fe3e4aSElliott Hughes deviceX = otl.buildDevice(dict(anchor.xDeviceTable)) 1654*e1fe3e4aSElliott Hughes if anchor.yDeviceTable is not None: 1655*e1fe3e4aSElliott Hughes deviceY = otl.buildDevice(dict(anchor.yDeviceTable)) 1656*e1fe3e4aSElliott Hughes for dim in ("x", "y"): 1657*e1fe3e4aSElliott Hughes varscalar = getattr(anchor, dim) 1658*e1fe3e4aSElliott Hughes if not isinstance(varscalar, VariableScalar): 1659*e1fe3e4aSElliott Hughes continue 1660*e1fe3e4aSElliott Hughes if getattr(anchor, dim + "DeviceTable") is not None: 1661*e1fe3e4aSElliott Hughes raise FeatureLibError( 1662*e1fe3e4aSElliott Hughes "Can't define a device coordinate and variable scalar", location 1663*e1fe3e4aSElliott Hughes ) 1664*e1fe3e4aSElliott Hughes default, device = self.makeVariablePos(location, varscalar) 1665*e1fe3e4aSElliott Hughes setattr(anchor, dim, default) 1666*e1fe3e4aSElliott Hughes if device is not None: 1667*e1fe3e4aSElliott Hughes if dim == "x": 1668*e1fe3e4aSElliott Hughes deviceX = device 1669*e1fe3e4aSElliott Hughes else: 1670*e1fe3e4aSElliott Hughes deviceY = device 1671*e1fe3e4aSElliott Hughes variable = True 1672*e1fe3e4aSElliott Hughes 1673*e1fe3e4aSElliott Hughes otlanchor = otl.buildAnchor( 1674*e1fe3e4aSElliott Hughes anchor.x, anchor.y, anchor.contourpoint, deviceX, deviceY 1675*e1fe3e4aSElliott Hughes ) 1676*e1fe3e4aSElliott Hughes if variable: 1677*e1fe3e4aSElliott Hughes otlanchor.Format = 3 1678*e1fe3e4aSElliott Hughes return otlanchor 1679*e1fe3e4aSElliott Hughes 1680*e1fe3e4aSElliott Hughes _VALUEREC_ATTRS = { 1681*e1fe3e4aSElliott Hughes name[0].lower() + name[1:]: (name, isDevice) 1682*e1fe3e4aSElliott Hughes for _, name, isDevice, _ in otBase.valueRecordFormat 1683*e1fe3e4aSElliott Hughes if not name.startswith("Reserved") 1684*e1fe3e4aSElliott Hughes } 1685*e1fe3e4aSElliott Hughes 1686*e1fe3e4aSElliott Hughes def makeOpenTypeValueRecord(self, location, v, pairPosContext): 1687*e1fe3e4aSElliott Hughes """ast.ValueRecord --> otBase.ValueRecord""" 1688*e1fe3e4aSElliott Hughes if not v: 1689*e1fe3e4aSElliott Hughes return None 1690*e1fe3e4aSElliott Hughes 1691*e1fe3e4aSElliott Hughes vr = {} 1692*e1fe3e4aSElliott Hughes for astName, (otName, isDevice) in self._VALUEREC_ATTRS.items(): 1693*e1fe3e4aSElliott Hughes val = getattr(v, astName, None) 1694*e1fe3e4aSElliott Hughes if not val: 1695*e1fe3e4aSElliott Hughes continue 1696*e1fe3e4aSElliott Hughes if isDevice: 1697*e1fe3e4aSElliott Hughes vr[otName] = otl.buildDevice(dict(val)) 1698*e1fe3e4aSElliott Hughes elif isinstance(val, VariableScalar): 1699*e1fe3e4aSElliott Hughes otDeviceName = otName[0:4] + "Device" 1700*e1fe3e4aSElliott Hughes feaDeviceName = otDeviceName[0].lower() + otDeviceName[1:] 1701*e1fe3e4aSElliott Hughes if getattr(v, feaDeviceName): 1702*e1fe3e4aSElliott Hughes raise FeatureLibError( 1703*e1fe3e4aSElliott Hughes "Can't define a device coordinate and variable scalar", location 1704*e1fe3e4aSElliott Hughes ) 1705*e1fe3e4aSElliott Hughes vr[otName], device = self.makeVariablePos(location, val) 1706*e1fe3e4aSElliott Hughes if device is not None: 1707*e1fe3e4aSElliott Hughes vr[otDeviceName] = device 1708*e1fe3e4aSElliott Hughes else: 1709*e1fe3e4aSElliott Hughes vr[otName] = val 1710*e1fe3e4aSElliott Hughes 1711*e1fe3e4aSElliott Hughes if pairPosContext and not vr: 1712*e1fe3e4aSElliott Hughes vr = {"YAdvance": 0} if v.vertical else {"XAdvance": 0} 1713*e1fe3e4aSElliott Hughes valRec = otl.buildValue(vr) 1714*e1fe3e4aSElliott Hughes return valRec 1715