-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathgetKerningPairsFromVFB.py
207 lines (160 loc) · 7.11 KB
/
getKerningPairsFromVFB.py
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
'''
Extract a list of all (flat) kerning pairs from a VFB’s kern object, and
report the absolute number of pairs. Run as a FontLab script.
'''
import itertools
from FL import fl
f = fl.font
fl.output = ''
class VFBkernReader(object):
# basically a copy of UFOKernReader
def __init__(self, groups, kerning, includeZero=False):
self.groups = groups
self.kerning = kerning
self.group_group_pairs = {}
self.group_glyph_pairs = {}
self.glyph_group_pairs = {}
self.glyph_glyph_pairs = {}
self.allKerningPairs = self.makePairDicts(includeZero)
self.output = self.makeOutput(self.allKerningPairs)
def makeOutput(self, kerningDict):
output = []
for (left, right), value in list(kerningDict.items()):
output.append('/%s /%s %s' % (left, right, value))
output.sort()
return output
def allCombinations(self, left, right):
leftGlyphs = self.groups.get(left, [left])
rightGlyphs = self.groups.get(right, [right])
combinations = list(itertools.product(leftGlyphs, rightGlyphs))
return combinations
def makePairDicts(self, includeZero):
kerningPairs = {}
for (left, right), value in list(self.kerning.items()):
if '@' in left and '@' in right:
# group-to-group-pair
for combo in self.allCombinations(left, right):
self.group_group_pairs[combo] = value
elif '@' in left and '@' not in right:
# group-to-glyph-pair
for combo in self.allCombinations(left, right):
self.group_glyph_pairs[combo] = value
elif '@' not in left and '@' in right:
# glyph-to-group-pair
for combo in self.allCombinations(left, right):
self.glyph_group_pairs[combo] = value
else:
# glyph-to-glyph-pair a.k.a. single pair
self.glyph_glyph_pairs[(left, right)] = value
# The updates occur from the most general pairs to the most specific.
# This means that any given class kerning values are overwritten with
# the intended exceptions.
kerningPairs.update(self.group_group_pairs)
kerningPairs.update(self.group_glyph_pairs)
kerningPairs.update(self.glyph_group_pairs)
kerningPairs.update(self.glyph_glyph_pairs)
if includeZero is False:
# delete any kerning values == 0.
# This cannot be done in the previous loop, since exceptions
# might set a previously established kerning pair to be 0.
cleanKerningPairs = dict(kerningPairs)
for pair in kerningPairs:
if kerningPairs[pair] == 0:
del cleanKerningPairs[pair]
return cleanKerningPairs
else:
return kerningPairs
class FLKerningData(object):
def __init__(self, font):
self.f = font
self._readFLGroups()
self._splitFLGroups()
self.leftKeyGlyphs = self._filterKeyGlyphs(self.leftGroups)
self.rightKeyGlyphs = self._filterKeyGlyphs(self.rightGroups)
self._readFLKerning()
def _isMMfont(self, font):
'Checks if the FontLab font is a Multiple Master font.'
if font[0].layers_number > 1:
return True
else:
return False
def _readFLGroups(self, *args):
self.groupToKeyglyph = {}
self.groups = {}
self.groupOrder = []
flClassStrings = [cString for cString in self.f.classes if cString[0] == '_']
for cString in flClassStrings:
FLclassName = cString.split(":")[0] # FL class name, e.g. _L_LC_LEFT
OTgroupName = '@%s' % FLclassName[1:] # OT group name, e.g. @L_LC_LEFT
markedGlyphList = cString.split(":")[1].split()
cleanGlyphList = [gName.strip("'") for gName in markedGlyphList]
# strips out the keyglyph marker
for gName in markedGlyphList:
if gName[-1] == "'": # finds keyglyph
keyGlyphName = gName.strip("'")
break
else:
keyGlyphName = markedGlyphList[0]
print("\tWARNING: Kerning class %s has no explicit key glyph.\n\tUsing first glyph found (%s)." % (cString, keyGlyphName))
self.groupOrder.append(OTgroupName)
self.groupToKeyglyph[OTgroupName] = keyGlyphName
self.groups[OTgroupName] = cleanGlyphList
def _splitFLGroups(self):
'''
Splits FontLab kerning classes into left and right sides; based on
the class name. Both sides are assigned to classes without an explicit
side-flag.'
'''
leftTagsList = ['_LEFT', '_1ST', '_L_']
rightTagsList = ['_RIGHT', '_2ND', '_R_']
self.leftGroups = []
self.rightGroups = []
for groupName in self.groups:
if any([tag in groupName for tag in leftTagsList]):
self.leftGroups.append(groupName)
elif any([tag in groupName for tag in rightTagsList]):
self.rightGroups.append(groupName)
else:
self.leftGroups.append(groupName)
self.rightGroups.append(groupName)
def _filterKeyGlyphs(self, groupList):
'''
Returns a dictionary {keyGlyph: FLClassName}
for a given list of classNames.
'''
filteredKeyGlyphs = {}
for groupName in groupList:
keyGlyphName = self.groupToKeyglyph[groupName]
filteredKeyGlyphs[keyGlyphName] = groupName
return filteredKeyGlyphs
def _readFLKerning(self):
'Reads FontLab kerning and converts it into a UFO-style kerning dict.'
self.kerning = {}
glyphs = self.f.glyphs
for gIndexLeft, glyphLeft in enumerate(glyphs):
gNameLeft = glyphLeft.name
flKerningArray = glyphs[gIndexLeft].kerning
for flKerningPair in flKerningArray:
gIndexRight = flKerningPair.key
gNameRight = glyphs[gIndexRight].name
if self._isMMfont(self.f):
kernValue = '<%s>' % ' '.join(map(str, flKerningPair.values))
# gl.kerning[p].values is an array holding kern values for each master
else:
kernValue = int(flKerningPair.value)
pair = self.leftKeyGlyphs.get(gNameLeft, gNameLeft), self.rightKeyGlyphs.get(gNameRight, gNameRight)
self.kerning[pair] = kernValue
def run():
kD = FLKerningData(f)
vkr = VFBkernReader(kD.groups, kD.kerning)
# vkr = VFBkernReader(kD.groups, kD.kerning, includeZero=True)
print('\n'.join(vkr.output), '\n')
print('Total amount of kerning pairs:', len(vkr.output))
dumpFileName = f.file_name + '.kerndump'
dumpFile = open(dumpFileName, 'w')
for (g1, g2), v in sorted(vkr.allKerningPairs.items()):
dumpFile.write("%s %s %s\n" % (g1, g2, v))
dumpFile.close()
print('\nList of kerning pairs written to\n{}'.format(dumpFileName))
if __name__ == '__main__':
run()