1import glyphsLib
2from datetime import datetime
3from babelfont.font import Font
4from babelfont.layer import Layer
5from babelfont.lib import Lib
6from babelfont.glyph import Glyph
7from babelfont.point import Point
8from babelfont.contour import Contour
9from babelfont.component import Component
10from babelfont.anchor import Anchor
11from glyphsLib.types import Transform
12
13def can_load(filename):
14    return filename.endswith(".glyphs")
15
16def can_save(filename):
17    return filename.endswith(".glyphs")
18
19
20def load(filename, **kwargs):
21    gsfont = glyphsLib.GSFont(filename)
22    gsmaster = None
23    if "master" not in kwargs or not kwargs["master"]:
24        gsmaster = gsfont.masters[0]
25    else:
26        wanted = kwargs["master"]
27        for m in gsfont.masters:
28            if m.name == wanted:
29                gsmaster = m
30                break
31        if not gsmaster:
32            raise ValueError(f"Master {wanted} not found in {filename}")
33    return _load_gsfont(gsmaster)
34
35def save(font, filename):
36    gsfont = _save_gsfont(font)
37    gsfont.save(filename)
38
39# glyphsLib -> babelfont
40
41
42def _load_gsfont(gsfontmaster):
43    bbf = Font(gsfontmaster)
44
45    # XXX Create: features
46
47    bbf.info.familyName = gsfontmaster.font.familyName
48    bbf.info.styleName = gsfontmaster.name
49    bbf.lib.glyphOrder = [x.name for x in gsfontmaster.font.glyphs]
50    bbf.lib["com.schriftgestaltung.appVersion"] = gsfontmaster.font.appVersion
51    bbf.lib["com.schriftgestaltung.DisplayStrings"] = gsfontmaster.font.DisplayStrings
52    bbf.lib["com.schriftgestaltung.fontMasterID"] = gsfontmaster.id
53
54    bbf.info.openTypeHeadCreated = _glyphs_date_to_ufo(gsfontmaster.font.date)
55    bbf.info.unitsPerEm = gsfontmaster.font.upm
56    bbf.info.copyright = gsfontmaster.font.copyright
57    bbf.info.versionMajor = gsfontmaster.font.versionMajor
58    bbf.info.versionMinor = gsfontmaster.font.versionMinor
59    bbf.info.openTypeNameDesigner = gsfontmaster.font.designer
60    bbf.info.openTypeNameDesignerURL = gsfontmaster.font.designerURL
61    bbf.info.openTypeNameManufacturer = gsfontmaster.font.manufacturer
62    bbf.info.openTypeNameManufacturerURL = gsfontmaster.font.manufacturerURL
63    _load_groups(bbf.groups, gsfontmaster.font.classes)
64
65
66    # Only support one layer for now
67    layer = Layer()
68    layer._name = gsfontmaster.name
69
70    bbf.info.ascender = gsfontmaster.ascender
71    bbf.info.capHeight = gsfontmaster.capHeight
72    bbf.info.descender = gsfontmaster.descender
73    bbf.info.xHeight = gsfontmaster.xHeight
74
75    bbf._layers.append(layer)
76    bbf._layerOrder.append(gsfontmaster.name)
77
78    for g in gsfontmaster.font.glyphs:
79        layer._glyphs[g.name] = _load_gslayer(g.layers[gsfontmaster.id], layer)
80        if g.leftKerningGroup:
81            groupname = "public.kern1."+g.leftKerningGroup
82            if not groupname in bbf.groups:
83                bbf.groups[groupname] = []
84            bbf.groups[groupname] = [*bbf.groups[groupname], g.name]
85        if g.rightKerningGroup:
86            groupname = "public.kern2."+g.rightKerningGroup
87            if not groupname in bbf.groups:
88                bbf.groups[groupname] = []
89            bbf.groups[groupname] = [*bbf.groups[groupname], g.name]
90
91
92    for g in gsfontmaster.font.glyphs:
93        _finalise_glyph(g.layers[gsfontmaster.id], layer._glyphs[g.name])
94
95    if gsfontmaster.id in gsfontmaster.font.kerning:
96        _load_kerning(bbf.kerning, gsfontmaster.font.kerning[gsfontmaster.id], gsfontmaster.font)
97    return bbf
98
99
100def _load_gslayer(gslayer, layer):  # -> Glyph
101    glyph = Glyph()
102    glyph._layer = layer
103    glyph._name = gslayer.parent.name
104    if gslayer.parent.unicode:
105        glyph._unicodes = [int(gslayer.parent.unicode,16)]
106    else:
107        glyph._unicodes = []
108    glyph._width = gslayer.width
109    glyph._height = gslayer.master.ascender - gslayer.master.descender  # ?
110    glyph.exported = gslayer.parent.export
111    glyph._lib = Lib()
112    glyph._lib.glyph = glyph
113    if gslayer.parent.lastChange:
114        glyph._lib["com.schriftgestaltung.lastChange"] = gslayer.parent.lastChange
115
116    c = gslayer.parent.category
117    sc = gslayer.parent.subCategory
118    if c:
119        glyph._lib["com.schriftgestaltung.Glyphs.category"] = c
120    if sc:
121        glyph._lib["com.schriftgestaltung.Glyphs.subcategory"] = sc
122    if sc == "Ligature":
123        glyph._lib["public.openTypeCategory"] = "ligature"
124    if c == "Mark":
125        glyph._lib["public.openTypeCategory"] = "mark"
126    else:
127        glyph._lib["public.openTypeCategory"] = "base"
128    glyph._contours = [_load_gspath(p, glyph) for p in gslayer.paths]
129    glyph._anchors = [_load_gsanchor(a, glyph) for a in gslayer.anchors]
130    return glyph
131
132# Have to load components after all glyphs are processed
133def _finalise_glyph(gslayer, glyph):
134    # XXX guidelines, image
135    glyph._components = [_load_gscomponent(c, glyph) for c in gslayer.components]
136    return glyph
137
138def _load_gspath(gspath, glyph):
139    contour = Contour()
140    contour._glyph = glyph
141    contour._points = [_load_gspoint(p, contour) for p in gspath.nodes]
142    contour._clockwise = gspath.direction == 1
143    return contour
144
145
146def _load_gscomponent(gscomponent, glyph):
147    component = Component()
148    component._glyph = glyph
149    component._baseGlyph = gscomponent.componentName
150    component._transformation = tuple(gscomponent.transform.value)
151    return component
152
153
154def _load_gsanchor(gsanchor, glyph):
155    anchor = Anchor()
156    anchor._glyph = glyph
157    anchor.x = gsanchor.position.x
158    anchor.y = gsanchor.position.y
159    anchor.name = gsanchor.name
160    return anchor
161
162
163def _load_gspoint(gspoint, contour):
164    point = Point()
165    point._contour = contour
166    point._x = gspoint.position.x
167    point._y = gspoint.position.y
168    point.type = gspoint.type
169    point.smooth = gspoint.smooth
170    return point
171
172
173def _load_groups(groups, classes):
174    for c in classes:
175        groups[c.name] = c.code.split() # Urgh
176
177def _load_kerning(kerning, gskerning, gsfont):
178    for left in gskerning.keys():
179        if "@" in left:
180            ufoleft = "public.kern1."+left[7:]
181        else:
182            ufoleft = left
183        for right, value in gskerning[left].items():
184            if "@" in right:
185                right = "public.kern2."+right[7:]
186            kerning[(ufoleft,right)] = value
187
188# babelfont -> glyphsLib
189
190
191def _save_point(point):
192    p = glyphsLib.GSNode ((point.x, point.y), point.type)
193    p.smooth = point.smooth
194    return p
195
196
197def _save_contour(contour):
198    path = glyphsLib.GSPath()
199    path.nodes = [_save_point(p) for p in contour._points]
200    # Check node order
201    # XXX closed
202    return path
203
204def _save_anchor(anchor):
205    gsanchor = glyphsLib.GSAnchor()
206    gsanchor.position.x = anchor.x
207    gsanchor.position.y = anchor.y
208    gsanchor.name = anchor.name
209    return gsanchor
210
211def _save_component(component):
212    c = glyphsLib.GSComponent(component.baseGlyph)
213    if component.transformation != (1,0,0,1,0,0):
214        c.transform = Transform(*component.transformation)
215    return c
216
217def _save_glyph(glyph, gsfont, bbf):
218    # This needs to go into a layer and a glyph
219    masterId = gsfont.masters[0].id
220    gslayer = glyphsLib.GSLayer()
221    gsglyph = glyphsLib.GSGlyph()
222    gslayer.layerId = masterId
223    gsglyph.layers.append(gslayer)
224    if glyph._unicodes:
225        gsglyph.unicode = "%04x" % glyph._unicodes[0]
226    if "com.schriftgestaltung.lastChange" in glyph.lib:
227        gsglyph.lastChange = glyph.lib["com.schriftgestaltung.lastChange"]
228    if "com.schriftgestaltung.Glyphs.category" in glyph.lib:
229        gsglyph.category = glyph._lib["com.schriftgestaltung.Glyphs.category"]
230    if "com.schriftgestaltung.Glyphs.subcategory" in glyph.lib:
231        gsglyph.subCategory = glyph._lib["com.schriftgestaltung.Glyphs.subcategory"]
232
233    gslayer.paths = [_save_contour(c) for c in glyph.contours]
234    gslayer.components = [_save_component(c) for c in glyph.components]
235    gslayer.anchors = [_save_anchor(a) for a in glyph.anchors]
236    gslayer.name = glyph.name
237    gslayer.RSB = glyph.rightMargin
238    gslayer.LSB = glyph.leftMargin
239    gslayer.width = glyph.width
240    # Attach to master ID
241    gsglyph.name = glyph.name
242    gsfont.glyphs.append(gsglyph)
243    # Set kerning groups
244    for name, contents in bbf.groups.items():
245        if not "public.kern" in name: continue
246        if glyph.name in contents:
247            if "public.kern1" in name:
248                gsglyph.leftKerningGroup = name[13:]
249            else:
250                gsglyph.rightKerningGroup = name[13:]
251
252def _save_gsfont(font):
253    f = glyphsLib.GSFont()
254    f.familyName = font.info.familyName
255    if "com.schriftgestaltung.appVersion" in font.lib:
256        f.appVersion = font.lib["com.schriftgestaltung.appVersion"]
257    if "com.schriftgestaltung.DisplayStrings" in font.lib:
258        f.DisplayStrings = font.lib["com.schriftgestaltung.DisplayStrings"]
259    if font.info.openTypeHeadCreated:
260        f.date = _ufo_date_to_glyphs(font.info.openTypeHeadCreated)
261    f.upm = font.info.unitsPerEm
262    fontmaster = glyphsLib.GSFontMaster()
263    if "com.schriftgestaltung.fontMasterID" in font.lib:
264        fontmaster.id = font.lib["com.schriftgestaltung.fontMasterID"]
265
266    f.masters = [fontmaster]
267    # f.instances = [glyphsLib.GSInstance()]
268    # f.instances[0].instanceInterpolations = {}
269    fontmaster.ascender = font.info.ascender
270    fontmaster.xHeight = font.info.xHeight
271    fontmaster.capHeight = font.info.capHeight
272    fontmaster.descender = font.info.descender
273    for glyph in font.defaultLayer:
274        _save_glyph(glyph, f, bbf=font)
275    for g in font.groups:
276        if "public.kern" in g:
277            continue
278        f.classes.append(glyphsLib.GSClass(g, " ".join(font.groups[g])))
279    _save_kerning(font.kerning, f, fontmaster.id)
280    return f
281
282def _save_kerning(kerning, font, mid):
283    for (l,r), value in kerning.items():
284        if "public.kern1" in l:
285            l = "@MMK_L_"+l[13:]
286        if "public.kern2" in r:
287            r = "@MMK_R_"+r[13:]
288
289        font.setKerningForPair(mid,l,r,value)
290
291# Random stuff
292
293def _glyphs_date_to_ufo(d):
294    if d:
295        return d.strftime('%Y/%m/%d %H:%M:%S')
296    return None
297
298def _ufo_date_to_glyphs(d):
299    return datetime.strptime(d, '%Y/%m/%d %H:%M:%S').strftime('%Y-%m-%d %H:%M:%S +0000')
300