11#!/usr/bin/env python3
22
33'''
4- GOADB writer -- write a GOADB file using a UFO as an input.
4+ GOADB writer -- write a GlyphOrderAndAliasDB file using a UFO as an input.
55Relies on the agl glyph names which come in fontTools’ agl module.
66
77More reading on the GOADB format:
1313
1414import argparse
1515import re
16+ from pathlib import Path
1617from fontTools import agl
1718from defcon import Font , Glyph
1819
1920
20- def get_args ():
21+ def check_input_file (parser , file_name ):
22+ fn = Path (file_name )
23+ if fn .suffix .lower () != '.ufo' :
24+ parser .error (f'{ fn .name } is not a UFO file' )
25+ if not fn .exists ():
26+ parser .error (f'{ fn .name } does not exist' )
27+ return file_name
28+
29+
30+ def get_args (args = None ):
2131 parser = argparse .ArgumentParser (
2232 description = __doc__ ,
2333 )
@@ -37,12 +47,13 @@ def get_args():
3747 )
3848
3949 parser .add_argument (
40- 'ufo' ,
50+ 'input_file' ,
51+ type = lambda f : check_input_file (parser , f ),
4152 action = 'store' ,
4253 help = 'UFO file' ,
4354 )
4455
45- return parser .parse_args ()
56+ return parser .parse_args (args )
4657
4758
4859def get_glyph_order (f , include_template_glyphs = False ):
@@ -54,8 +65,9 @@ def get_glyph_order(f, include_template_glyphs=False):
5465
5566 In the case that the public.glyphOrder key does not exist, resort to
5667 unrefined sorting:
57- * by code point (if encoded)
58- * alphabetically by name (if unencoded)
68+ * encoded: by code point
69+ * unencoded: following code point sorting (deduced by glyph name)
70+ * unencoded: alphabetically by name (for the rest)
5971
6072 NB: defcon and RF have different ways of determining template glyphs.
6173 in defcon, f.glyphOrder includes template glyphs
@@ -84,15 +96,32 @@ def get_glyph_order(f, include_template_glyphs=False):
8496 else :
8597 # first, all encoded glyphs are sorted by code point
8698 # then, the rest is sorted alphabetically.
99+
100+ # all glyphs
87101 glyphs_encoded = sorted (
88102 [g for g in f if g .unicode ], key = lambda g : g .unicode )
89- glyphs_unencoded = sorted (
90- [g for g in f if g .unicode is None and g .name != '.notdef' ],
91- key = lambda g : g .name )
92- order = (
93- ['.notdef' ] +
94- [g .name for g in glyphs_encoded ] +
95- [g .name for g in glyphs_unencoded ])
103+ gnames_encoded = [g .name for g in glyphs_encoded ]
104+
105+ glyphs_unencoded = [
106+ g for g in f if g .unicode is None and g .name != '.notdef' ]
107+
108+ # more specific sub-groups:
109+ # glyphs which alternates of the encoded glyphs
110+ glyphs_alternates = [
111+ g for g in glyphs_unencoded if
112+ g .name .split ('.' )[0 ] in gnames_encoded ]
113+ # sort names by their suffix first,
114+ # then by the order of related names of encoded glyphs.
115+ gnames_alternates = sorted (
116+ [g .name for g in glyphs_alternates ],
117+ key = lambda gn : (gn .split ('.' )[1 ], gnames_encoded .index (gn .split ('.' )[0 ])))
118+
119+ # glyphs not related to encoded glyphs are sorted alphabetically
120+ glyphs_rest = [
121+ g for g in glyphs_unencoded if g not in glyphs_alternates ]
122+ gnames_rest = sorted ([g .name for g in glyphs_rest ])
123+
124+ order = ['.notdef' ] + gnames_encoded + gnames_alternates + gnames_rest
96125
97126 return order
98127
@@ -205,11 +234,11 @@ class GlyphBaptism(object):
205234
206235 '''
207236
208- def __init__ (self , gn_friendly , glyph = None , gn_final = None , cp_override = None ):
209- if glyph is None :
237+ def __init__ (self , gn_friendly , g = None , gn_final = None , cp_override = None ):
238+ if g is None :
210239 self .glyph = Glyph ()
211240 else :
212- self .glyph = glyph
241+ self .glyph = g
213242
214243 self .gn_friendly = gn_friendly
215244 self .gn_final = gn_final
@@ -269,11 +298,11 @@ def assign_final_and_cp_override(self):
269298 expected_codepoint = int (cp_hex , 16 )
270299 actual_codepoint = self .glyph .unicode
271300 if expected_codepoint == actual_codepoint :
272- # codepoint is the expected one.
301+ # codepoint is the expected one
273302 self .gn_final = self .gn_friendly
274303 else :
275- # codepoint is different from what we expect
276- # (weird but OK)
304+ # codepoint is different from what the name implies
305+ # (weird flex but OK)
277306 self .gn_final = get_uni_name (self .glyph .unicode )
278307
279308 # custom glyph name
@@ -303,7 +332,17 @@ def fill_gn_dict(gb, glyph_name_dict):
303332 return glyph_name_dict
304333
305334
306- def make_glyph_name_dict (f ):
335+ def get_glyph (f , gname ):
336+ try :
337+ glyph = f [gname ]
338+ except KeyError :
339+ # template glyph
340+ glyph = Glyph ()
341+ glyph .name = gname
342+ return glyph
343+
344+
345+ def make_glyph_name_dict (f , glyph_order ):
307346 '''
308347 make a dictionary:
309348 {friendly name: gb object}
@@ -329,18 +368,19 @@ def make_glyph_name_dict(f):
329368 # alternate, or is it an alternate ligature in itself?
330369 # I interpret it as a combination of two fs and one l.alt.
331370
332- base_glyphs = [g for g in f if not any (['.' in g . name , '_' in g . name ])]
371+ base_glyphs = [gn for gn in glyph_order if not any (['.' in gn , '_' in gn ])]
333372 alt_glyphs = [
334- g for g in f if '.' in g .name and
335- '_' not in g .name and
336- g .name != '.notdef' ]
337- liga_glyphs = {g for g in f if '_' in g .name }
373+ gn for gn in glyph_order if '.' in gn and '_' not in gn and
374+ gn != '.notdef' ]
375+ liga_glyphs = [gn for gn in glyph_order if '_' in gn ]
338376
339- for g in base_glyphs :
377+ for gn in base_glyphs :
378+ g = get_glyph (f , gn )
340379 gb = GlyphBaptism (g .name , g )
341380 glyph_name_dict = fill_gn_dict (gb , glyph_name_dict )
342381
343- for g in alt_glyphs :
382+ for gn in alt_glyphs :
383+ g = get_glyph (f , gn )
344384 stem , suffixes = g .name .split ('.' , 1 )
345385 if stem in glyph_name_dict :
346386 final_name_stem = glyph_name_dict .get (stem ).gn_final
@@ -357,7 +397,8 @@ def make_glyph_name_dict(f):
357397
358398 glyph_name_dict = fill_gn_dict (gb , glyph_name_dict )
359399
360- for g in liga_glyphs :
400+ for gn in liga_glyphs :
401+ g = get_glyph (f , gn )
361402 liga_chunks = g .name .split ('_' )
362403 liga_chunks_final = []
363404 for chunk in liga_chunks :
@@ -380,7 +421,7 @@ def make_glyph_name_dict(f):
380421 return glyph_name_dict
381422
382423
383- def get_goadb (glyph_order , glyph_name_dict ):
424+ def build_goadb (glyph_order , glyph_name_dict ):
384425 goadb = []
385426 for gname in glyph_order :
386427 gb = glyph_name_dict .get (gname )
@@ -391,12 +432,12 @@ def get_goadb(glyph_order, glyph_name_dict):
391432 return '\n ' .join (goadb )
392433
393434
394- def main ():
395- args = get_args ()
396- f = Font (args .ufo )
435+ def main (test_args = None ):
436+ args = get_args (test_args )
437+ f = Font (args .input_file )
397438 glyph_order = get_glyph_order (f , args .template )
398- glyph_name_dict = make_glyph_name_dict (f )
399- goadb = get_goadb (glyph_order , glyph_name_dict )
439+ glyph_name_dict = make_glyph_name_dict (f , glyph_order )
440+ goadb = build_goadb (glyph_order , glyph_name_dict )
400441 if args .output :
401442 with open (args .output , 'w' ) as blob :
402443 blob .write (goadb )
0 commit comments