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