xref: /aosp_15_r20/external/fonttools/Lib/fontTools/varLib/builder.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
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