builder.py 6.5 KB

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