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
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
|
#!/usr/bin/env python
__doc__ = '''Assign new working names to glyphs based on csv input file
- csv format oldname,newname'''
__url__ = 'http://github.com/silnrsi/pysilfont'
__copyright__ = 'Copyright (c) 2017 SIL International (http://www.sil.org)'
__license__ = 'Released under the MIT License (http://opensource.org/licenses/MIT)'
__author__ = 'Bob Hallissy'
from silfont.core import execute
from xml.etree import ElementTree as ET
import re
import os
from glob import glob
argspec = [
('ifont',{'help': 'Input font file'}, {'type': 'infont'}),
('ofont',{'help': 'Output font file','nargs': '?' }, {'type': 'outfont'}),
('-c', '--classfile', {'help': 'Classes file'}, {}),
('-i','--input',{'help': 'Input csv file'}, {'type': 'incsv', 'def': 'namemap.csv'}),
('--mergecomps',{'help': 'turn on component merge', 'action': 'store_true', 'default': False},{}),
('-l','--log',{'help': 'Log file'}, {'type': 'outfile', 'def': '_renameglyphs.log'})]
csvmap = "" # Variable used globally
def doit(args) :
global csvmap, ksetsbymember
font = args.ifont
incsv = args.input
logger = args.logger
mergemode = args.mergecomps
failerrors = 0 # Keep count of errors that should cause the script to fail
csvmap = {} # List of all real maps in incsv, so excluding headers, blank lines, comments and identity maps
nameMap = {} # remember all glyphs actually renamed
kerngroupsrenamed = {} # List of all kern groups actually renamed
# List of secondary layers (ie layers other than the default)
secondarylayers = [x for x in font.layers if x.layername != "public.default"]
# Obtain lib.plist glyph order(s) and psnames if they exist:
publicGlyphOrder = csGlyphOrder = psnames = displayStrings = None
if hasattr(font, 'lib'):
if 'public.glyphOrder' in font.lib:
publicGlyphOrder = font.lib.getval('public.glyphOrder') # This is an array
if 'com.schriftgestaltung.glyphOrder' in font.lib:
csGlyphOrder = font.lib.getval('com.schriftgestaltung.glyphOrder') # This is an array
if 'public.postscriptNames' in font.lib:
psnames = font.lib.getval('public.postscriptNames') # This is a dict keyed by glyphnames
if 'com.schriftgestaltung.customParameter.GSFont.DisplayStrings' in font.lib:
displayStrings = font.lib.getval('com.schriftgestaltung.customParameter.GSFont.DisplayStrings')
else:
logger.log("no lib.plist found in font", "W")
# Renaming within the UFO is done in two passes to make sure we can handle circular renames such as:
# someglyph.alt = someglyph
# someglyph = someglyph.alt
# Note that the various objects with glyph names are all done independently since
# the same glyph names are not necessarily in all structures.
# First pass: process all records of csv, and for each glyph that is to be renamed:
# If the new glyphname is not already present, go ahead and rename it now.
# If the new glyph name already exists, rename the glyph to a temporary name
# and put relevant details in saveforlater[]
saveforlaterFont = [] # For the font itself
saveforlaterPGO = [] # For public.GlyphOrder
saveforlaterCSGO = [] # For GlyphsApp GlyphOrder (com.schriftgestaltung.glyphOrder)
saveforlaterPSN = [] # For public.postscriptNames
deletelater = [] # Glyphs we'll delete after merging
for r in incsv:
oldname = r[0].strip()
newname = r[1].strip()
# ignore header row and rows where the newname is blank or a comment marker
if oldname == "Name" or oldname.startswith('#') or newname == "" or oldname == newname:
continue
if len(oldname)==0:
logger.log('empty glyph oldname in glyph_data; ignored (newname: %s)' % newname, 'W')
continue
csvmap[oldname]=newname
# Handle font first:
if oldname not in font.deflayer:
logger.log("glyph name not in font: " + oldname , "I")
elif newname not in font.deflayer:
inseclayers = False
for layer in secondarylayers:
if newname in layer:
logger.log("Glyph %s is already in non-default layers; can't rename %s" % (newname, oldname), "E")
failerrors += 1
inseclayers = True
continue
if not inseclayers:
# Ok, this case is easy: just rename the glyph in all layers
for layer in font.layers:
if oldname in layer: layer[oldname].name = newname
nameMap[oldname] = newname
logger.log("Pass 1 (Font): Renamed %s to %s" % (oldname, newname), "I")
elif mergemode:
mergeglyphs(font.deflayer[oldname], font.deflayer[newname])
for layer in secondarylayers:
if oldname in layer:
if newname in layer:
mergeglyphs(layer[oldname], layer[newname])
else:
layer[oldname].name = newname
nameMap[oldname] = newname
deletelater.append(oldname)
logger.log("Pass 1 (Font): merged %s to %s" % (oldname, newname), "I")
else:
# newname already in font -- but it might get renamed later in which case this isn't actually a problem.
# For now, then, rename glyph to a temporary name and remember it for second pass
tempname = gettempname(lambda n : n not in font.deflayer)
for layer in font.layers:
if oldname in layer:
layer[oldname].name = tempname
saveforlaterFont.append( (tempname, oldname, newname) )
# Similar algorithm for public.glyphOrder, if present:
if publicGlyphOrder:
if oldname not in publicGlyphOrder:
logger.log("glyph name not in publicGlyphorder: " + oldname , "I")
else:
x = publicGlyphOrder.index(oldname)
if newname not in publicGlyphOrder:
publicGlyphOrder[x] = newname
nameMap[oldname] = newname
logger.log("Pass 1 (PGO): Renamed %s to %s" % (oldname, newname), "I")
elif mergemode:
del publicGlyphOrder[x]
nameMap[oldname] = newname
logger.log("Pass 1 (PGO): Removed %s (now using %s)" % (oldname, newname), "I")
else:
tempname = gettempname(lambda n : n not in publicGlyphOrder)
publicGlyphOrder[x] = tempname
saveforlaterPGO.append( (x, oldname, newname) )
# And for GlyphsApp glyph order, if present:
if csGlyphOrder:
if oldname not in csGlyphOrder:
logger.log("glyph name not in csGlyphorder: " + oldname , "I")
else:
x = csGlyphOrder.index(oldname)
if newname not in csGlyphOrder:
csGlyphOrder[x] = newname
nameMap[oldname] = newname
logger.log("Pass 1 (csGO): Renamed %s to %s" % (oldname, newname), "I")
elif mergemode:
del csGlyphOrder[x]
nameMap[oldname] = newname
logger.log("Pass 1 (csGO): Removed %s (now using %s)" % (oldname, newname), "I")
else:
tempname = gettempname(lambda n : n not in csGlyphOrder)
csGlyphOrder[x] = tempname
saveforlaterCSGO.append( (x, oldname, newname) )
# And for psnames
if psnames:
if oldname not in psnames:
logger.log("glyph name not in psnames: " + oldname , "I")
elif newname not in psnames:
psnames[newname] = psnames.pop(oldname)
nameMap[oldname] = newname
logger.log("Pass 1 (psn): Renamed %s to %s" % (oldname, newname), "I")
elif mergemode:
del psnames[oldname]
nameMap[oldname] = newname
logger.log("Pass 1 (psn): Removed %s (now using %s)" % (oldname, newname), "I")
else:
tempname = gettempname(lambda n: n not in psnames)
psnames[tempname] = psnames.pop(oldname)
saveforlaterPSN.append( (tempname, oldname, newname))
# Second pass: now we can reprocess those things we saved for later:
# If the new glyphname is no longer present, we can complete the renaming
# Otherwise we've got a fatal error
for j in saveforlaterFont:
tempname, oldname, newname = j
if newname in font.deflayer: # Only need to check deflayer, since (if present) it would have been renamed in all
# Ok, this really is a problem
logger.log("Glyph %s already in font; can't rename %s" % (newname, oldname), "E")
failerrors += 1
else:
for layer in font.layers:
if tempname in layer:
layer[tempname].name = newname
nameMap[oldname] = newname
logger.log("Pass 2 (Font): Renamed %s to %s" % (oldname, newname), "I")
for j in saveforlaterPGO:
x, oldname, newname = j
if newname in publicGlyphOrder:
# Ok, this really is a problem
logger.log("Glyph %s already in public.GlyphOrder; can't rename %s" % (newname, oldname), "E")
failerrors += 1
else:
publicGlyphOrder[x] = newname
nameMap[oldname] = newname
logger.log("Pass 2 (PGO): Renamed %s to %s" % (oldname, newname), "I")
for j in saveforlaterCSGO:
x, oldname, newname = j
if newname in csGlyphOrder:
# Ok, this really is a problem
logger.log("Glyph %s already in com.schriftgestaltung.glyphOrder; can't rename %s" % (newname, oldname), "E")
failerrors += 1
else:
csGlyphOrder[x] = newname
nameMap[oldname] = newname
logger.log("Pass 2 (csGO): Renamed %s to %s" % (oldname, newname), "I")
for tempname, oldname, newname in saveforlaterPSN:
if newname in psnames:
# Ok, this really is a problem
logger.log("Glyph %s already in public.postscriptNames; can't rename %s" % (newname, oldname), "E")
failerrors += 1
else:
psnames[newname] = psnames.pop(tempname)
nameMap[oldname] = newname
logger.log("Pass 2 (psn): Renamed %s to %s" % (oldname, newname), "I")
# Rebuild font structures from the modified lists we have:
# Rebuild glyph order elements:
if publicGlyphOrder:
array = ET.Element("array")
for name in publicGlyphOrder:
ET.SubElement(array, "string").text = name
font.lib.setelem("public.glyphOrder", array)
if csGlyphOrder:
array = ET.Element("array")
for name in csGlyphOrder:
ET.SubElement(array, "string").text = name
font.lib.setelem("com.schriftgestaltung.glyphOrder", array)
# Rebuild postscriptNames:
if psnames:
dict = ET.Element("dict")
for n in psnames:
ET.SubElement(dict, "key").text = n
ET.SubElement(dict, "string").text = psnames[n]
font.lib.setelem("public.postscriptNames", dict)
# Iterate over all glyphs, and fix up any components that reference renamed glyphs
for layer in font.layers:
for name in layer:
glyph = layer[name]
for component in glyph.etree.findall('./outline/component[@base]'):
oldname = component.get('base')
if oldname in nameMap:
component.set('base', nameMap[oldname])
logger.log(f'renamed component base {oldname} to {component.get("base")} in glyph {name} layer {layer.layername}', 'I')
lib = glyph['lib']
if lib:
if 'com.schriftgestaltung.Glyphs.ComponentInfo' in lib:
cielem = lib['com.schriftgestaltung.Glyphs.ComponentInfo'][1]
for component in cielem:
for i in range(0,len(component),2):
if component[i].text == 'name':
oldname = component[i+1].text
if oldname in nameMap:
component[i+1].text = nameMap[oldname]
logger.log(f'renamed component info {oldname} to {nameMap[oldname]} in glyph {name} layer {layer.layername}', 'I')
# Delete anything we no longer need:
for name in deletelater:
for layer in font.layers:
if name in layer: layer.delGlyph(name)
logger.log("glyph %s removed" % name, "I")
# Other structures with glyphs in are handled by looping round the structures replacing glyphs rather than
# looping round incsv
# Update Display Strings
if displayStrings:
changed = False
glyphRE = re.compile(r'/([a-zA-Z0-9_.-]+)') # regex to match / followed by a glyph name
for i, dispstr in enumerate(displayStrings): # Passing the glyphSub function to .sub() causes it to
displayStrings[i] = glyphRE.sub(glyphsub, dispstr) # every non-overlapping occurrence of pattern
if displayStrings[i] != dispstr:
changed = True
if changed:
array = ET.Element("array")
for dispstr in displayStrings:
ET.SubElement(array, "string").text = dispstr
font.lib.setelem('com.schriftgestaltung.customParameter.GSFont.DisplayStrings', array)
logger.log("com.schriftgestaltung.customParameter.GSFont.DisplayStrings updated", "I")
# Process groups.plist and kerning.plist
# group names in the form public.kern[1|2].<glyph name> will automatically be renamed if the glyph name is in the csvmap
#
groups = kerning = None
kgroupprefixes = {"public.kern1.": 1, "public.kern2.": 2}
if "groups" in font.__dict__: groups = font.groups
if "kerning" in font.__dict__: kerning = font.kerning
if (groups or kerning) and mergemode:
logger.log("Note - Kerning and group data not processed when using mergecomps", "P")
elif groups or kerning:
kgroupsmap = ["", {}, {}] # Dicts of kern1/kern2 group renames. Outside the groups if statement, since also used with kerning.plist
if groups:
# Analyse existing data, building dict from existing data and building some indexes
gdict = {}
kgroupsbyglyph = ["", {}, {}] # First entry dummy, so index is 1 or 2 for kern1 and kern2
kgroupduplicates = ["", [], []] #
for gname in groups:
group = groups.getval(gname)
gdict[gname] = group
kprefix = gname[0:13]
if kprefix in kgroupprefixes:
ktype = kgroupprefixes[kprefix]
for glyph in group:
if glyph in kgroupsbyglyph[ktype]:
kgroupduplicates[ktype].append(glyph)
logger.log("In existing kern groups, %s is in more than one kern%s group" % (glyph, str(ktype)), "E")
failerrors += 1
else:
kgroupsbyglyph[ktype][glyph] = gname
# Now process the group data
glyphsrenamed = []
saveforlaterKgroups = []
for gname in list(gdict): # Loop round groups renaming glyphs within groups and kern group names
group = gdict[gname]
# Rename group if kern1 or kern2 group
kprefix = gname[:13]
if kprefix in kgroupprefixes:
ktype = kgroupprefixes[kprefix]
ksuffix = gname[13:]
if ksuffix in csvmap: # This is a kern group that we should rename
newgname = kprefix + csvmap[ksuffix]
if newgname in gdict: # Will need to be renamed in second pass
tempname = gettempname(lambda n : n not in gdict)
gdict[tempname] = gdict.pop(gname)
saveforlaterKgroups.append((tempname, gname, newgname))
else:
gdict[newgname] = gdict.pop(gname)
kerngroupsrenamed[gname] = newgname
logger.log("Pass 1 (Kern groups): Renamed %s to %s" % (gname, newgname), "I")
kgroupsmap[ktype][gname] = newgname
# Now rename glyphs within the group
# - This could lead to duplicate names, but that might be valid for arbitrary groups so not checked
# - kern group validity will be checked after all renaming is done
for (i, glyph) in enumerate(group):
if glyph in csvmap:
group[i] = csvmap[glyph]
if glyph not in glyphsrenamed: glyphsrenamed.append(glyph)
# Need to report glyphs renamed after the loop, since otherwise could report multiple times
for oldname in glyphsrenamed:
nameMap[oldname] = csvmap[oldname]
logger.log("Glyphs in groups: Renamed %s to %s" % (oldname, csvmap[oldname]), "I")
# Second pass for renaming kern groups. (All glyph renaming is done in first pass)
for (tempname, oldgname, newgname) in saveforlaterKgroups:
if newgname in gdict: # Can't rename
logger.log("Kern group %s already in groups.plist; can't rename %s" % (newgname, oldgname), "E")
failerrors += 1
else:
gdict[newgname] = gdict.pop(tempname)
kerngroupsrenamed[oldgname] = newgname
logger.log("Pass 2 (Kern groups): Renamed %s to %s" % (oldgname, newgname), "I")
# Finally check kern groups follow the UFO rules!
kgroupsbyglyph = ["", {}, {}] # Reset for new analysis
for gname in gdict:
group = gdict[gname]
kprefix = gname[:13]
if kprefix in kgroupprefixes:
ktype = kgroupprefixes[kprefix]
for glyph in group:
if glyph in kgroupsbyglyph[ktype]: # Glyph already in a kern group so we have a duplicate
if glyph not in kgroupduplicates[ktype]: # This is a newly-created duplicate so report
logger.log("After renaming, %s is in more than one kern%s group" % (glyph, str(ktype)), "E")
failerrors += 1
kgroupduplicates[ktype].append(glyph)
else:
kgroupsbyglyph[ktype][glyph] = gname
# Now need to recreate groups.plist from gdict
for group in list(groups): groups.remove(group) # Empty existing contents
for gname in gdict:
elem = ET.Element("array")
for glyph in gdict[gname]:
ET.SubElement(elem, "string").text = glyph
groups.setelem(gname, elem)
# Now process kerning data
if kerning:
k1map = kgroupsmap[1]
k2map = kgroupsmap[2]
kdict = {}
for setname in kerning: kdict[setname] = kerning.getval(setname) # Create a working dict from plist
saveforlaterKsets = []
# First pass on set names
for setname in list(kdict): # setname could be a glyph in csvmap or a kern1 group name in k1map
if setname in csvmap or setname in k1map:
newname = csvmap[setname] if setname in csvmap else k1map[setname]
if newname in kdict:
tempname = gettempname(lambda n : n not in kdict)
kdict[tempname] = kdict.pop(setname)
saveforlaterKsets.append((tempname, setname, newname))
else:
kdict[newname] = kdict.pop(setname)
if setname in csvmap: nameMap[setname] = newname # Change to kern set name will have been logged previously
logger.log("Pass 1 (Kern sets): Renamed %s to %s" % (setname, newname), "I")
# Now do second pass for set names
for (tempname, oldname, newname) in saveforlaterKsets:
if newname in kdict: # Can't rename
logger.log("Kern set %s already in kerning.plist; can't rename %s" % (newname, oldname), "E")
failerrors += 1
else:
kdict[newname] = kdict.pop(tempname)
if oldname in csvmap: nameMap[oldname] = newname
logger.log("Pass 1 (Kern sets): Renamed %s to %s" % (oldname, newname), "I")
# Rename kern set members next.
# Here, since a member could be in more than one set, take different approach to two passes.
# - In first pass, rename to a temp (and invalid) name so duplicates are not possible. Name to include
# old name for reporting purposes
# - In second pass, set to correct new name after checking for duplicates
# Do first pass for set names
tempnames = []
for setname in list(kdict):
kset = kdict[setname]
for mname in list(kset): # mname could be a glyph in csvmap or a kern2 group name in k2map
if mname in csvmap or mname in k2map:
newname = csvmap[mname] if mname in csvmap else k2map[mname]
newname = "^" + newname + "^" + mname
if newname not in tempnames: tempnames.append(newname)
kset[newname] = kset.pop(mname)
# Second pass to change temp names to correct final names
# We need an index of which sets each member is in
ksetsbymember = {}
for setname in kdict:
kset = kdict[setname]
for member in kset:
if member not in ksetsbymember:
ksetsbymember[member] = [setname]
else:
ksetsbymember[member].append(setname)
# Now do the renaming
for tname in tempnames:
(newname, oldname) = tname[1:].split("^")
if newname in ksetsbymember: # Can't rename
logger.log("Kern set %s already in kerning.plist; can't rename %s" % (newname, oldname), "E")
failerrors += 1
else:
for ksetname in ksetsbymember[tname]:
kset = kdict[ksetname]
kset[newname] = kset.pop(tname)
ksetsbymember[newname] = ksetsbymember.pop(tname)
if tname in csvmap: nameMap[oldname] = newname
logger.log("Kern set members: Renamed %s to %s" % (oldname, newname), "I")
# Now need to recreate kerning.plist from kdict
for kset in list(kerning): kerning.remove(kset) # Empty existing contents
for kset in kdict:
elem = ET.Element("dict")
for member in kdict[kset]:
ET.SubElement(elem, "key").text = member
ET.SubElement(elem, "integer").text = str(kdict[kset][member])
kerning.setelem(kset, elem)
if failerrors:
logger.log(str(failerrors) + " issues detected - see errors reported above", "S")
logger.log("%d glyphs renamed in UFO" % (len(nameMap)), "P")
if kerngroupsrenamed: logger.log("%d kern groups renamed in UFO" % (len(kerngroupsrenamed)), "P")
# If a classfile was provided, change names within it also
#
if args.classfile:
logger.log("Processing classfile {}".format(args.classfile), "P")
# In order to preserve comments we use our own TreeBuilder
class MyTreeBuilder(ET.TreeBuilder):
def comment(self, data):
self.start(ET.Comment, {})
self.data(data)
self.end(ET.Comment)
# RE to match separators between glyph names (whitespace):
notGlyphnameRE = re.compile(r'(\s+)')
# Keep a list of glyphnames that were / were not changed
changed = set()
notChanged = set()
# Process one token (might be whitespace separator, glyph name, or embedded classname starting with @):
def dochange(gname, logErrors = True):
if len(gname) == 0 or gname.isspace() or gname not in csvmap or gname.startswith('@'):
# No change
return gname
try:
newgname = csvmap[gname]
changed.add(gname)
return newgname
except KeyError:
if logErrors: notChanged.add(gname)
return gname
doc = ET.parse(args.classfile, parser=ET.XMLParser(target=MyTreeBuilder()))
for e in doc.iter(None):
if e.tag in ('class', 'property'):
if 'exts' in e.attrib:
logger.log("{} '{}' has 'exts' attribute which may need editing".format(e.tag.title(), e.get('name')), "W")
# Rather than just split() the text, we'll use re and thus try to preserve whitespace
e.text = ''.join([dochange(x) for x in notGlyphnameRE.split(e.text)])
elif e.tag is ET.Comment:
# Go ahead and look for glyph names in comment text but don't flag as error
e.text = ''.join([dochange(x, False) for x in notGlyphnameRE.split(e.text)])
# and process the tail as this might be valid part of class or property
e.tail = ''.join([dochange(x) for x in notGlyphnameRE.split(e.tail)])
if len(changed):
# Something in classes changed so rewrite it... saving backup
(dn,fn) = os.path.split(args.classfile)
dn = os.path.join(dn, args.paramsobj.sets['main']['backupdir'])
if not os.path.isdir(dn):
os.makedirs(dn)
# Work out backup name based on existing backups
backupname = os.path.join(dn,fn)
nums = [int(re.search(r'\.(\d+)~$',n).group(1)) for n in glob(backupname + ".*~")]
backupname += ".{}~".format(max(nums) + 1 if nums else 1)
logger.log("Backing up input classfile to {}".format(backupname), "P")
# Move the original file to backupname
os.rename(args.classfile, backupname)
# Write the output file
doc.write(args.classfile)
if len(notChanged):
logger.log("{} glyphs renamed, {} NOT renamed in {}: {}".format(len(changed), len(notChanged), args.classfile, ' '.join(notChanged)), "W")
else:
logger.log("All {} glyphs renamed in {}".format(len(changed), args.classfile), "P")
return font
def mergeglyphs(mergefrom, mergeto): # Merge any "moving" anchors (i.e., those starting with '_') into the glyph we're keeping
# Assumption: we are merging one or more component references to just one component; deleting the others
for a in mergefrom['anchor']:
aname = a.element.get('name')
if aname.startswith('_'):
# We want to copy this anchor to the glyph being kept:
for i, a2 in enumerate(mergeto['anchor']):
if a2.element.get('name') == aname:
# Overwrite existing anchor of same name
mergeto['anchor'][i] = a
break
else:
# Append anchor to glyph
mergeto['anchor'].append(a)
def gettempname(f):
''' return a temporary glyph name that, when passed to function f(), returns true'''
# Initialize function attribute for use as counter
if not hasattr(gettempname, "counter"): gettempname.counter = 0
while True:
name = "tempglyph%d" % gettempname.counter
gettempname.counter += 1
if f(name): return name
def glyphsub(m): # Function passed to re.sub() when updating display strings
global csvmap
gname = m.group(1)
return '/' + csvmap[gname] if gname in csvmap else m.group(0)
def cmd() : execute("UFO",doit,argspec)
if __name__ == "__main__": cmd()
|