from fontTools import ttLib from fontTools.ttLib.tables import otTables as ot # VariationStore def buildVarRegionAxis(axisSupport): self = ot.VarRegionAxis() self.StartCoord, self.PeakCoord, self.EndCoord = [float(v) for v in axisSupport] return self def buildSparseVarRegionAxis(axisIndex, axisSupport): self = ot.SparseVarRegionAxis() self.AxisIndex = axisIndex self.StartCoord, self.PeakCoord, self.EndCoord = [float(v) for v in axisSupport] return self def buildVarRegion(support, axisTags): assert all(tag in axisTags for tag in support.keys()), ( "Unknown axis tag found.", support, axisTags, ) self = ot.VarRegion() self.VarRegionAxis = [] for tag in axisTags: self.VarRegionAxis.append(buildVarRegionAxis(support.get(tag, (0, 0, 0)))) return self def buildSparseVarRegion(support, axisTags): assert all(tag in axisTags for tag in support.keys()), ( "Unknown axis tag found.", support, axisTags, ) self = ot.SparseVarRegion() self.SparseVarRegionAxis = [] for i, tag in enumerate(axisTags): if tag not in support: continue self.SparseVarRegionAxis.append( buildSparseVarRegionAxis(i, support.get(tag, (0, 0, 0))) ) self.SparseRegionCount = len(self.SparseVarRegionAxis) return self def buildVarRegionList(supports, axisTags): self = ot.VarRegionList() self.RegionAxisCount = len(axisTags) self.Region = [] for support in supports: self.Region.append(buildVarRegion(support, axisTags)) self.RegionCount = len(self.Region) return self def buildSparseVarRegionList(supports, axisTags): self = ot.SparseVarRegionList() self.RegionAxisCount = len(axisTags) self.Region = [] for support in supports: self.Region.append(buildSparseVarRegion(support, axisTags)) self.RegionCount = len(self.Region) return self def _reorderItem(lst, mapping): return [lst[i] for i in mapping] def VarData_calculateNumShorts(self, optimize=False): count = self.VarRegionCount items = self.Item bit_lengths = [0] * count for item in items: # The "+ (i < -1)" magic is to handle two's-compliment. # That is, we want to get back 7 for -128, whereas # bit_length() returns 8. Similarly for -65536. # The reason "i < -1" is used instead of "i < 0" is that # the latter would make it return 0 for "-1" instead of 1. bl = [(i + (i < -1)).bit_length() for i in item] bit_lengths = [max(*pair) for pair in zip(bl, bit_lengths)] # The addition of 8, instead of seven, is to account for the sign bit. # This "((b + 8) >> 3) if b else 0" when combined with the above # "(i + (i < -1)).bit_length()" is a faster way to compute byte-lengths # conforming to: # # byte_length = (0 if i == 0 else # 1 if -128 <= i < 128 else # 2 if -65536 <= i < 65536 else # ...) byte_lengths = [((b + 8) >> 3) if b else 0 for b in bit_lengths] # https://github.com/fonttools/fonttools/issues/2279 longWords = any(b > 2 for b in byte_lengths) if optimize: # Reorder columns such that wider columns come before narrower columns mapping = [] mapping.extend(i for i, b in enumerate(byte_lengths) if b > 2) mapping.extend(i for i, b in enumerate(byte_lengths) if b == 2) mapping.extend(i for i, b in enumerate(byte_lengths) if b == 1) byte_lengths = _reorderItem(byte_lengths, mapping) self.VarRegionIndex = _reorderItem(self.VarRegionIndex, mapping) self.VarRegionCount = len(self.VarRegionIndex) for i in range(len(items)): items[i] = _reorderItem(items[i], mapping) if longWords: self.NumShorts = ( max((i for i, b in enumerate(byte_lengths) if b > 2), default=-1) + 1 ) self.NumShorts |= 0x8000 else: self.NumShorts = ( max((i for i, b in enumerate(byte_lengths) if b > 1), default=-1) + 1 ) self.VarRegionCount = len(self.VarRegionIndex) return self ot.VarData.calculateNumShorts = VarData_calculateNumShorts def VarData_CalculateNumShorts(self, optimize=True): """Deprecated name for VarData_calculateNumShorts() which defaults to optimize=True. Use varData.calculateNumShorts() or varData.optimize().""" return VarData_calculateNumShorts(self, optimize=optimize) def VarData_optimize(self): return VarData_calculateNumShorts(self, optimize=True) ot.VarData.optimize = VarData_optimize def buildVarData(varRegionIndices, items, optimize=True): self = ot.VarData() self.VarRegionIndex = list(varRegionIndices) regionCount = self.VarRegionCount = len(self.VarRegionIndex) records = self.Item = [] if items: for item in items: assert len(item) == regionCount records.append(list(item)) self.ItemCount = len(self.Item) self.calculateNumShorts(optimize=optimize) return self def buildVarStore(varRegionList, varDataList): self = ot.VarStore() self.Format = 1 self.VarRegionList = varRegionList self.VarData = list(varDataList) self.VarDataCount = len(self.VarData) return self def buildMultiVarData(varRegionIndices, items): self = ot.MultiVarData() self.Format = 1 self.VarRegionIndex = list(varRegionIndices) regionCount = self.VarRegionCount = len(self.VarRegionIndex) records = self.Item = [] if items: for item in items: assert len(item) == regionCount records.append(list(item)) self.ItemCount = len(self.Item) return self def buildMultiVarStore(varRegionList, multiVarDataList): self = ot.MultiVarStore() self.Format = 1 self.SparseVarRegionList = varRegionList self.MultiVarData = list(multiVarDataList) self.MultiVarDataCount = len(self.MultiVarData) return self # Variation helpers def buildVarIdxMap(varIdxes, glyphOrder): self = ot.VarIdxMap() self.mapping = {g: v for g, v in zip(glyphOrder, varIdxes)} return self def buildDeltaSetIndexMap(varIdxes): mapping = list(varIdxes) if all(i == v for i, v in enumerate(mapping)): return None self = ot.DeltaSetIndexMap() self.mapping = mapping self.Format = 1 if len(mapping) > 0xFFFF else 0 return self def buildVarDevTable(varIdx): self = ot.Device() self.DeltaFormat = 0x8000 self.StartSize = varIdx >> 16 self.EndSize = varIdx & 0xFFFF return self