1from fontTools import ttLib 2from fontTools.ttLib.tables import otTables as ot 3 4# VariationStore 5 6def buildVarRegionAxis(axisSupport): 7 self = ot.VarRegionAxis() 8 self.StartCoord, self.PeakCoord, self.EndCoord = [float(v) for v in axisSupport] 9 return self 10 11def buildVarRegion(support, axisTags): 12 assert all(tag in axisTags for tag in support.keys()), ("Unknown axis tag found.", support, axisTags) 13 self = ot.VarRegion() 14 self.VarRegionAxis = [] 15 for tag in axisTags: 16 self.VarRegionAxis.append(buildVarRegionAxis(support.get(tag, (0,0,0)))) 17 return self 18 19def buildVarRegionList(supports, axisTags): 20 self = ot.VarRegionList() 21 self.RegionAxisCount = len(axisTags) 22 self.Region = [] 23 for support in supports: 24 self.Region.append(buildVarRegion(support, axisTags)) 25 self.RegionCount = len(self.Region) 26 return self 27 28 29def _reorderItem(lst, mapping): 30 return [lst[i] for i in mapping] 31 32def VarData_calculateNumShorts(self, optimize=False): 33 count = self.VarRegionCount 34 items = self.Item 35 bit_lengths = [0] * count 36 for item in items: 37 # The "+ (i < -1)" magic is to handle two's-compliment. 38 # That is, we want to get back 7 for -128, whereas 39 # bit_length() returns 8. Similarly for -65536. 40 # The reason "i < -1" is used instead of "i < 0" is that 41 # the latter would make it return 0 for "-1" instead of 1. 42 bl = [(i + (i < -1)).bit_length() for i in item] 43 bit_lengths = [max(*pair) for pair in zip(bl, bit_lengths)] 44 # The addition of 8, instead of seven, is to account for the sign bit. 45 # This "((b + 8) >> 3) if b else 0" when combined with the above 46 # "(i + (i < -1)).bit_length()" is a faster way to compute byte-lengths 47 # conforming to: 48 # 49 # byte_length = (0 if i == 0 else 50 # 1 if -128 <= i < 128 else 51 # 2 if -65536 <= i < 65536 else 52 # ...) 53 byte_lengths = [((b + 8) >> 3) if b else 0 for b in bit_lengths] 54 55 # https://github.com/fonttools/fonttools/issues/2279 56 longWords = any(b > 2 for b in byte_lengths) 57 58 if optimize: 59 # Reorder columns such that wider columns come before narrower columns 60 mapping = [] 61 mapping.extend(i for i,b in enumerate(byte_lengths) if b > 2) 62 mapping.extend(i for i,b in enumerate(byte_lengths) if b == 2) 63 mapping.extend(i for i,b in enumerate(byte_lengths) if b == 1) 64 65 byte_lengths = _reorderItem(byte_lengths, mapping) 66 self.VarRegionIndex = _reorderItem(self.VarRegionIndex, mapping) 67 self.VarRegionCount = len(self.VarRegionIndex) 68 for i in range(len(items)): 69 items[i] = _reorderItem(items[i], mapping) 70 71 if longWords: 72 self.NumShorts = max((i for i,b in enumerate(byte_lengths) if b > 2), default=-1) + 1 73 self.NumShorts |= 0x8000 74 else: 75 self.NumShorts = max((i for i,b in enumerate(byte_lengths) if b > 1), default=-1) + 1 76 77 self.VarRegionCount = len(self.VarRegionIndex) 78 return self 79 80ot.VarData.calculateNumShorts = VarData_calculateNumShorts 81 82def VarData_CalculateNumShorts(self, optimize=True): 83 """Deprecated name for VarData_calculateNumShorts() which 84 defaults to optimize=True. Use varData.calculateNumShorts() 85 or varData.optimize().""" 86 return VarData_calculateNumShorts(self, optimize=optimize) 87 88def VarData_optimize(self): 89 return VarData_calculateNumShorts(self, optimize=True) 90 91ot.VarData.optimize = VarData_optimize 92 93 94def buildVarData(varRegionIndices, items, optimize=True): 95 self = ot.VarData() 96 self.VarRegionIndex = list(varRegionIndices) 97 regionCount = self.VarRegionCount = len(self.VarRegionIndex) 98 records = self.Item = [] 99 if items: 100 for item in items: 101 assert len(item) == regionCount 102 records.append(list(item)) 103 self.ItemCount = len(self.Item) 104 self.calculateNumShorts(optimize=optimize) 105 return self 106 107 108def buildVarStore(varRegionList, varDataList): 109 self = ot.VarStore() 110 self.Format = 1 111 self.VarRegionList = varRegionList 112 self.VarData = list(varDataList) 113 self.VarDataCount = len(self.VarData) 114 return self 115 116 117# Variation helpers 118 119def buildVarIdxMap(varIdxes, glyphOrder): 120 self = ot.VarIdxMap() 121 self.mapping = {g:v for g,v in zip(glyphOrder, varIdxes)} 122 return self 123 124 125def buildDeltaSetIndexMap(varIdxes): 126 self = ot.DeltaSetIndexMap() 127 self.mapping = list(varIdxes) 128 self.Format = 1 if len(varIdxes) > 0xFFFF else 0 129 return self 130 131 132def buildVarDevTable(varIdx): 133 self = ot.Device() 134 self.DeltaFormat = 0x8000 135 self.StartSize = varIdx >> 16 136 self.EndSize = varIdx & 0xFFFF 137 return self 138