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