1from datetime import datetime 2from babelfont.font import Font 3from babelfont.layer import Layer 4from babelfont.lib import Lib 5from babelfont.glyph import Glyph 6from babelfont.point import Point 7from babelfont.contour import Contour 8from babelfont.component import Component 9from babelfont.anchor import Anchor 10import json 11from babelfont.utils import _toFlagBits 12 13 14def can_load(filename): 15 return filename.endswith(".vfj") 16 17def can_save(filename): 18 return None 19 20def load(filename, **kwargs): 21 vfj = json.load(open(filename, "r"))["font"] 22 if "master" not in kwargs or not kwargs["master"]: 23 wanted = vfj["defaultMaster"] 24 else: 25 wanted = kwargs["master"] 26 master = None 27 for m in vfj["masters"]: 28 if m["fontMaster"]["name"] == wanted: 29 master = m 30 break 31 if not master: 32 raise ValueError(f"Master {wanted} not found in {filename}") 33 return _load_vfj(vfj, master["fontMaster"]) 34 35 36def save(font, filename): 37 gsfont = _save_gsfont(font) 38 gsfont.save(filename) 39 40 41# vfj -> babelfont 42 43 44def _load_vfj(vfj, master): 45 bbf = Font() 46 47 bbf.info.familyName = vfj["info"]["tfn"] 48 # sgn? 49 bbf.info.styleName = master["name"] 50 bbf.info.copyright = vfj["info"]["copyright"] 51 bbf.info.trademark = vfj["info"]["trademark"] 52 53 bbf.lib.glyphOrder = [x["name"] for x in vfj["glyphs"]] 54 bbf.lib["com.fontlab.appVersion"] = vfj["meta"]["metaFormatVersion"] 55 bbf.lib["com.fontlab.appBuild"] = vfj["meta"]["metaBuild"] 56 57 bbf.info.openTypeHeadCreated = vfj["info"]["creationDate"] 58 bbf.info.openTypeNameDesigner = vfj["info"]["designer"] 59 bbf.info.openTypeNameDesignerURL = vfj["info"]["designerURL"] 60 bbf.info.openTypeNameManufacturer = vfj["info"]["manufacturer"] 61 bbf.info.openTypeNameManufacturerURL = vfj["info"]["manufacturerURL"] 62 bbf.info.versionMajor = vfj["info"]["versionMajor"] 63 bbf.info.versionMinor = vfj["info"]["versionMinor"] 64 bbf.info.year = vfj["info"]["year"] 65 bbf.info.openTypeNameVersion = vfj["info"]["version"] 66 67 bbf.info.openTypeOS2UnicodeRanges = _toFlagBits( 68 int(vfj["info"]["unicodeRange"], 16) 69 ) 70 bbf.info.openTypeOS2CodePageRanges = _toFlagBits( 71 int(vfj["info"]["codepageRange"], 16) 72 ) 73 if vfj["info"]["useTypoMetrics"]: 74 bbf.info.openTypeOS2Selection = [7] 75 bbf.info.openTypeOS2Type = _toFlagBits(int(vfj["info"]["embedding"], 16)) 76 77 bbf.info.unitsPerEm = vfj["upm"] 78 79 _load_groups(bbf.groups, vfj["classes"]) 80 81 # XXX Add groups here 82 bbf.features.text = "\n".join([f["feature"] for f in vfj["openTypeFeatures"]]) 83 84 # Only support one layer for now 85 layer = Layer() 86 layer._name = master["name"] 87 88 bbf.info.ascender = master["ascender"] 89 bbf.info.capHeight = master["capsHeight"] 90 bbf.info.descender = master["descender"] 91 bbf.info.xHeight = master["xHeight"] 92 93 bbf.info.postscriptFontName = master["psn"] 94 95 # measurement, safeTop, safeBottom? 96 bbf.info.openTypeOS2WeightClass = master["weight"] 97 bbf.info.openTypeOS2WidthClass = master["width"] 98 bbf.info.openTypeOS2TypoLineGap = master["lineGap"] 99 bbf.info.postscriptUnderlineThickness = master["underlineThickness"] 100 bbf.info.postscriptUnderlinePosition = master["underlinePosition"] 101 bbf.info.panose = [int(i) for i in master["panose"].split()] 102 # guidelines, mask?, 103 bbf.info.openTypeHheaAscender = master["otherData"]["hhea_ascender"] 104 bbf.info.openTypeHheaDescender = master["otherData"]["hhea_descender"] 105 bbf.info.openTypeHheaLineGap = master["otherData"]["hhea_line_gap"] 106 bbf.info.openTypeOS2StrikeoutSize = master["otherData"]["strikeout_size"] 107 bbf.info.openTypeOS2StrikeoutPosition = master["otherData"]["strikeout_position"] 108 # XXX superscripts, subscriptsmas 109 110 bbf._layers.append(layer) 111 bbf._layerOrder.append(master["name"]) 112 113 for g in vfj["glyphs"]: 114 layer._glyphs[g["name"]] = _load_glyph(g, layer, master) 115 116 # if master.kerning: 117 # _load_kerning(bbf.kerning, master) 118 return bbf 119 120 121def _load_glyph(g, layer, master): # -> Glyph 122 glyph = Glyph() 123 glyph._layer = layer 124 glyph._name = g["name"] 125 glayer = None 126 127 for l in g["layers"]: 128 if l["name"] == master["name"]: 129 glayer = l 130 if not glayer: 131 raise ValueError 132 133 if "unicode" in g: 134 glyph._unicodes = [int(g["unicode"], 16)] 135 else: 136 glyph._unicodes = [] 137 glyph._width = glayer["advanceWidth"] 138 glyph._height = master["ascender"] - master["descender"] # ? 139 glyph._lib = Lib() 140 glyph._lib.glyph = glyph 141 142 if hasattr(g, "openTypeGlyphClass"): 143 if g.openTypeGlyphClass == 1: 144 glyph._lib["public.openTypeCategory"] = "base" 145 if g.openTypeGlyphClass == 2: 146 glyph._lib["public.openTypeCategory"] = "ligature" 147 if g.openTypeGlyphClass == 3: 148 glyph._lib["public.openTypeCategory"] = "mark" 149 150 glyph._contours = [] 151 glyph._components = [] 152 if "elements" in glayer: 153 for element in glayer["elements"]: 154 if "component" in element: 155 glyph._components.append(_load_component(element, glyph)) 156 else: 157 for c in element["elementData"]["contours"]: 158 glyph._contours.append(_load_contour(glyph, c["nodes"])) 159 # Guidelines 160 161 return glyph 162 163 164def _load_contour(glyph, segments): 165 contour = Contour() 166 contour._glyph = glyph 167 contour._points = [] 168 169 for s in segments: 170 if s[-1] == "x": # sx? 171 s = s[:-3] 172 173 if s[-1] == "s": 174 smooth = True 175 s = s[:-2] 176 else: 177 smooth = False 178 if s[-1] == "c": # Close 179 s = s[:-2] 180 points = s.split(" ") 181 for ix, position in enumerate(points): 182 p = Point() 183 p._contour = contour 184 p.x, p.y = [float(pos) for pos in position.split(" ")] 185 if len(points) == 1: 186 p.type = "line" 187 elif ix == 2: 188 p.type = "curve" 189 else: 190 p.type = "offcurve" 191 if ix == len(points) - 1 and smooth: 192 p.smooth = True 193 else: 194 p.smooth = False 195 contour._points.append(p) 196 if len(contour._points) and contour._points[-1].type == "offcurve": 197 contour._points[0].type = "curve" 198 199 contour._correct_direction() 200 return contour 201 202 203def _load_component(c, glyph): 204 component = Component() 205 component._glyph = glyph 206 component._baseGlyph = c["component"]["glyphName"] 207 if "transform" in c: 208 component._transformation = tuple( 209 ( 210 1, 211 0, 212 0, 213 1, 214 c["transform"].get("xOffset", 0), 215 c["transform"].get("yOffset", 0), 216 ) 217 ) 218 return component 219 220 221def _load_gsanchor(gsanchor, glyph): 222 anchor = Anchor() 223 anchor._glyph = glyph 224 anchor.x = gsanchor.position.x 225 anchor.y = gsanchor.position.y 226 anchor.name = gsanchor.name 227 return anchor 228 229 230def _load_groups(groups, classes): 231 for c in classes: 232 groups[c["name"]] = c["names"] 233 234 235# def _load_kerning(kerning, gskerning): 236# for left in gskerning.keys(): 237# assert left[0] != "@" 238# for right, value in gskerning[left].items(): 239# assert right[0] != "@" 240# kerning[(left,right)] = value 241 242# babelfont -> json 243