File: tsic.py

package info (click to toggle)
python-vttmisc 0.0.5-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 84 kB
  • sloc: python: 428; makefile: 2
file content (327 lines) | stat: -rw-r--r-- 13,088 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
from fontTools.ttLib import TTFont, newTable
import xml.etree.cElementTree as ET
from fontTools.ttLib.tables.TupleVariation import POINT_RUN_COUNT_MASK, TupleVariation

# fontTools needs an axis object that stores the relevant axis info with certain functions. Here's what I was able to figure out it wants:

class axisStorage(object):
    def __init__(self, header, axisTags):
        self.header = header
        self.axisTags = axisTags
    def copy(self):
        return self
    def items(self):
        return self.header
    def keys(self):
        return self.axisTags
    def get(self,axis, values):
        if len(self.header) > 1:
            for item in self.header:
                if item[0] == axis:
                    return item[1]
        else:
            return self.header[0][1]

def interpolate (normalCVT:int, loc:list, interpolationAxes:list, cvt:int) -> None: 
    # I assume this is badly optimized but it seems to work. So lalalalalalalala. 

    axisDifferences = []
    for i, point in enumerate(loc[1]):
        # Here's what we need to do. For each axis, determine the CVT value of A and B, to figure out the medial (OHGOD)
        # A ------ Point ------ B
        #defaults
        lowerPoint = [0.0, normalCVT]
        upperPoint = [0.0, normalCVT]
        medialPoint = [point[1][1], normalCVT]

        if medialPoint[0] < 0.0:
            lowerPoint[0] = -1.0
            upperPoint[0] = 0.0
        elif medialPoint[0] > 0.0:
            lowerPoint[0] = 0.0
            upperPoint[0] = 1.0

        axisMap = [[lowerPoint[0], lowerPoint[1]], [upperPoint[0], upperPoint[1]]]

        # First we parse through the points that are on the major axis and see if they are applicable for this range
        applicable = []
        reject = False
        for location in interpolationAxes:
            reject = False
            if location[0][i] == 0.0: # if at 0.0, then must be on another axis (as there is no (0.0, 0.0))
                reject = True
            elif location[0][i] > 0.0 and medialPoint[0] < 0.0: # if above 0.0 when point is below 0.0
                reject = True
            elif location[0][i] < 0.0 and medialPoint[0] > 0.0: # opposite
                reject = True

            for value in location[0]:
                if value == location[0][i]:
                    pass
                else:
                    if value != 0.0: # all other axis values must be 0.0 or it is not applicable
                        reject = True

            if cvt not in location[1]:
                reject = True          # also gotta make sure the correct Offset is present
            if reject == False:
                applicable.append(location)
        # now that we know which (if any) values from the above are applicable, we position them on the axis
        if len(applicable) > 0:
            for location in applicable:
                insertAbove = []
                for loc in axisMap:
                    if location[0][i] == loc[0]:
                        loc[1] = location[2][location[1].index(cvt)]
                    if location[0][i] > loc[0]:
                        insertAbove = loc
                    if location[0][i] < loc[0]:
                        break
                if len(insertAbove) > 0:
                    axisMap.insert(axisMap.index(insertAbove)+1, [location[0][i], location[2][location[1].index(cvt)]])
        
        
        # now we can start evaluating the medialPoint
        done = False
        lowerLimit = []
        upperLimit = []
        diff = 0

        for location in axisMap:            # if point is at the same location as an existing point, exit out
            if point[1][1] == location[0]:
                diff = normalCVT - location[1]
                done = True
                break
        if done == False:                   # if not, we have to figure out which points it is between
            for location in axisMap:        # so we set a lower and upper limit location
                if point[1][1] > location[0]:
                    lowerLimit = location
                else:
                    break
            for location in axisMap[::-1]:
                if point[1][1] < location[0]:
                    upperLimit = location
                else:
                    break
            #if these values are the same, diff is 0 (default), otherwise have to calculate ratio and determine what the expected value of the medial point would be. 
            if lowerLimit[1] != upperLimit[1]:
                ratio = abs(upperLimit[0] - point[1][1]) / abs(upperLimit[0] - lowerLimit[0])
                diff = round((upperLimit[1] - lowerLimit[1]) * ratio)
        axisDifferences.append(diff)

    # since we need to account for differences across multiple axis, we sum up these differences and diff it from the normalCVT from the cvt table to find the adjusted CVT value
    expectedVal = normalCVT - sum(axisDifferences)
    return expectedVal
    

def processMajor (majorLocations: list, locMap : list) -> None:
    for location in majorLocations:
        for x, l in enumerate(location[1]):
            map = list(locMap.values())[x]
            peak = float(l[1])
            posCount = 0
            negCount = 0
            negIntermed = False
            posIntermed = False

            for value in map:
                if peak > 0.0 and value > 0.0:
                    posCount += 1
                if value < 0.0 and value < 0.0:
                    negCount += 1
            if negCount > 1:
                negIntermed = True
            if posCount > 1:
                posIntermed = True
            minBound = 0.0
            maxBound = 0.0

            if peak > 0.0 and posIntermed == False:
                minBound = 0.0
                maxBound = peak
            elif peak < 0.0 and negIntermed == False:
                minBound = peak
                maxBound = 0.0
            elif peak > 0.0 and posIntermed == True:
                minBound = map[map.index(peak)-1]
                maxBound = peak
            elif peak < 0.0 and negIntermed == True:
                minBound = peak
                maxBound = map[map.index(peak)+1]
            elif peak == 0.0:
                minBound = 0.0
                maxBound = 0.0
            else:
                print ("Something weird happened here")
                print (map, l)

            l[1] = (minBound, peak, maxBound)
            l[0] = list(locMap.keys())[x]

def processMinor (minorLocations: list, locMap: list) -> None:
    for location in minorLocations:
        for x, l in enumerate(location[1]):
            map = list(locMap.values())[x]
            peak = float(l[1])
            minBound = 0.0
            maxBound = 0.0
            atMax = False
            if map[0] == peak or map[len(map)-1] == peak:
                atMax = True

            if peak > 0.0 and atMax == True:
                minBound = 0.0
                maxBound = peak
            elif peak < 0.0 and atMax == True:
                minBound = peak
                maxBound = 0.0
            elif peak < 0.0 and atMax == False:
                minBound = -1.0
                maxBound = 0.0
            elif peak < 0.0 and atMax == False:
                minBound = 1.0
                maxBound = 0.0
            elif peak == 0.0:
                minBound = 0.0
                maxBound = 0.0
            else:
                print ("Something weird happened here")
                print (map, l)

            l[1] = (minBound, peak, maxBound)
            l[0] = list(locMap.keys())[x]

def makeCVAR (varFont: TTFont, tree: ET.ElementTree) -> None:
    root = tree.getroot()
    TSIC = root.find("TSIC")

    # This used to be simple, then I discovered intermediate CVTs, and then more than one axis

    # Making sure that there are not axes in the TSIC table that aren't in the font (fvar).
    # Assuming a VTT-made TSIC table this shouldn't be the case, but better to check than not. 
    keyValues = []
    for axis in varFont["fvar"].axes:
        keyValues.append(axis.axisTag)

    # Here we assemble a axis-specific map of the interpolation space where CVTs are modified
    # As we sort through the RecordLocations, we will modify this map as necessary. 
    locMap = {}
    for axis in TSIC.findall("AxisArray"): 
        locMap.update({
            axis.get("value") : [0.0]
            })

    # Processing through the RecordLocations, we fill in the locMap above, as well as create a 
    # set of axisLocations that we'll use to generate the header file
    majorLocations = []
    minorLocations = []
    for i, loc in enumerate(TSIC.findall("RecordLocations")):
        axisLocation = []
        major = False
        for axis in loc.findall("Axis"):    # For the location map, we really only care about points on the major axes
            if axis.get("value") == "0.0" or axis.get("value") == "0" or len(keyValues) == 1:
                major = True
        
        # once we know a given record has a value of 0.0 (eg, on major axis), we add it to the locMap
        if major == True:          
            for axis in loc.findall("Axis"):
                if float(axis.get("value")) not in list(locMap.values())[int(axis.get("index"))]:
                        list(locMap.values())[int(axis.get("index"))].append(float(axis.get("value")))
                axisLocation.append([
                    int(axis.get("index")),
                    float(axis.get("value"))
                ])
            majorLocations.append([i,axisLocation])
            
        elif major == False:
            for axis in loc.findall("Axis"):
                axisLocation.append([
                    int(axis.get("index")),
                    float(axis.get("value"))
                ])
            minorLocations.append([i,axisLocation])

    #do a quick sort to make sure everything is in order. 
    for i, value in enumerate(list(locMap.values())):
        value.sort()
        list(locMap.values())[i] = value

    # Now that we've established the major axis locMap, we can create mappings for those positions
    processMajor(majorLocations, locMap)
    processMinor(minorLocations, locMap)

    # Now that we have the locMap and the locations, we can build the headers for the TupleVariation file

    # Just doing some assembly of the CVT values
    CVT_num = []
    CVT_val = []
    for rec in TSIC.findall("Record"):
        RecNum = []
        RecVal = []
        for num in rec.findall("CVTArray"):
            RecNum.append(int(num.get("value")))
        for pos in rec.findall("CVTValueArray"):
            RecVal.append(int(pos.get("value")))
        CVT_num.append(RecNum)
        CVT_val.append(RecVal)

    variations = []
    interpolationAxes = []
    # interpolationAxis = [[ [-1.0, 0.0], [0, 50], [100, 200] ]]
    # this says, [position, CVTs, CVT values]

    # NOW LET'S GET TUPLING

    for l in majorLocations:
        supportHeader = []
        recordIndex = l[0]
        coordinates = []
        for axis in l[1]:
            # Handily, we've already built the header file above
            supportHeader.append(([axis[0], axis[1]]))
            coordinates.append(axis[1][1]) #we need this for later
        interpolationAxes.append([coordinates, CVT_num[l[0]], CVT_val[l[0]]])

        # assemble the delta information for the second half of TupleVariation 
        delta = []
        for i in range(0, len(varFont["cvt "])-1):
            if i in CVT_num[recordIndex]:
                deltaVal = CVT_val[recordIndex][CVT_num[recordIndex].index(i)]
                normalCVT = varFont["cvt "].__getitem__(i)
                delta.append(deltaVal - normalCVT)
            else:
                delta.append(None)
        
        support = axisStorage(supportHeader,keyValues)
        var = TupleVariation(support, delta)
        variations.append(var)

    for l in minorLocations:
        supportHeader = []
        recordIndex = l[0]
        for axis in l[1]:
            # Handily, we've already built the header file above
            supportHeader.append(([axis[0], axis[1]]))
            
        # assemble the delta information for the second half of TupleVariation 
        delta = []
        for i in range(0, len(varFont["cvt "])-1):
            if i in CVT_num[recordIndex]:
                deltaVal = CVT_val[recordIndex][CVT_num[recordIndex].index(i)]
                normalCVT = varFont["cvt "].__getitem__(i)

                #the delta is actually not from the normalCVT, but the *expected* CVT, which is determined by the major axis and any modifications therein present. YIKES
                adjustedCVT = interpolate(normalCVT, l, interpolationAxes, i)

                delta.append(deltaVal - adjustedCVT)
            else:
                delta.append(None)
            
        support = axisStorage(supportHeader,keyValues)
        var = TupleVariation(support, delta)
        variations.append(var)

    varFont["cvar"] = newTable('cvar')
    varFont["cvar"].version = 1
    varFont["cvar"].variations = variations