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