Skip to content

Commit

Permalink
Load axes and glyphs
Browse files Browse the repository at this point in the history
  • Loading branch information
simoncozens committed Apr 15, 2024
1 parent 7d0b6db commit 2779e40
Showing 1 changed file with 106 additions and 0 deletions.
106 changes: 106 additions & 0 deletions src/babelfont/convertors/fontlab/vfb.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
from collections import defaultdict
import datetime
import re
import math
import uuid

from vfbLib.vfb.vfb import Vfb
from fontTools.misc.transform import Transform

from babelfont import Axis, Glyph, Layer, Master, Node, Shape, Guide
from babelfont.Glyph import GlyphList
from babelfont.BaseObject import Color, I18NDictionary, OTValue
from babelfont.convertors import BaseConvertor

tags = {
"Weight": "wght",
"Width": "wdth",
"Optical Size": "opsz",
"Serif": "SERF",
}

ignore = [
"Encoding Mac",
"Encoding",
Expand All @@ -27,6 +37,10 @@
"Glyph Origin",
"Glyph Anchors Supplemental",
"Links",
"PostScript Info", # for now; one per master
]
store_in_scratch = [
"Axis Mappings Count",
]
names = {
"description": "description",
Expand All @@ -42,6 +56,17 @@
"versionFull": "version",
}

# hhea_line_gap
# hhea_ascender
# hhea_descender
# openTypeFeatures
# OpenType Class (names -> glyphs)


# Axis count
# Axis Name
# Axis Mappings Count (items per axis?)


class FontlabVFB(BaseConvertor):
suffix = ".vfb"
Expand All @@ -50,6 +75,15 @@ def _load(self):
self.vfb = Vfb(self.filename)
self.vfb.decompile()
self.current_glyph = None
self.current_master = None
scratch = defaultdict(list)
self.glyph_names = []
# Quickly set up GID->name mapping
for e in self.vfb.entries:
if e.key == "Glyph":
self.glyph_names.append(e.decompiled["name"])

# Now parse the whole thing
for e in self.vfb.entries:
name = e.key
if name is None:
Expand All @@ -60,6 +94,9 @@ def _load(self):

if name in ignore or re.match("^\d+$", name):
continue
if name in store_in_scratch:
scratch[name].append(data)
continue

if name == "psn":
# Postscript name, hey we don't have that.
Expand All @@ -69,6 +106,30 @@ def _load(self):
setattr(
self.font.names, names[name], I18NDictionary.with_default(data)
)
# Axes
elif name == "Axis Name":
axis = Axis(name, tags.get(name, name.upper()[:4])) # Fix up tag!
self.font.axes.append(axis)
elif name == "Axis Mappings":
counts = scratch["Axis Mappings Count"][0]
for axis, count in zip(self.font.axes, counts):
if count > 0:
mappings = data[:count]
axis.map = [(u, round(d * 1000)) for u, d in mappings]
user_coords = [c[0] for c in mappings]
axis.min = min(user_coords, default=0)
axis.default = axis.minimum
axis.max = max(user_coords, default=1000)
data = data[10:]
elif name == "Master Name":
self.current_master = Master(name=data, id=uuid.uuid1())
self.font.masters.append(self.current_master)
elif name == "Master Location":
_, location = data
master = self.current_master
master.location = {}
for axis, value in zip(self.font.axes, location):
master.location[axis.tag] = value
elif name == "ffn": # Full family name?
pass
elif name == "upm":
Expand All @@ -84,6 +145,7 @@ def _load(self):
elif name == "Glyph":
self.current_glyph = Glyph(name=data["name"])
self.font.glyphs.append(self.current_glyph)
self._load_glyph(data)
elif name == "Glyph Unicode":
self.current_glyph.codepoints = data
elif name == "Italic Angle":
Expand All @@ -92,4 +154,48 @@ def _load(self):
else:
print(name, data)

# Sort the glyphs by Unicode ID and name, because there's no
# other order in there.
glyphorder = list(self.font.glyphs.items())
glyphorder.sort(key=lambda k: (k[1].codepoints, k[0]))
newlist = GlyphList()
for g in glyphorder:
newlist.append(g[1])
self.font.glyphs = newlist

return self.font

def _load_glyph(self, data):
metrics = data["metrics"]
masters = self.font.masters
layers = [
Layer(width=metric[0], height=metric[1], id=master.id)
for master, metric in zip(masters, metrics)
]
pens = [layer.getPen() for layer in layers]
print(self.current_glyph.name)
for node in data["nodes"]:
for pen, point in zip(pens, node["points"]):
cmd = getattr(pen, node["type"] + "To")
if node["type"] == "move" and pen.contour:
pen.closePath()
if node["type"] == "curve":
pen.curveTo(point[1], point[2], point[0])
else:
cmd(*point)
for pen in pens:
if pen.contour:
pen.closePath()
for component in data.get("components", []):
for ix, pen in enumerate(pens):
pen.addComponent(
self.glyph_names[component["gid"]],
Transform(
dx=component["offsetX"][ix],
dy=component["offsetY"][ix],
xx=component["scaleX"][ix],
yy=component["scaleY"][ix],
),
)
for layer in layers:
self.current_glyph.layers.append(layer)

0 comments on commit 2779e40

Please sign in to comment.