2022-08-26 11:17:26 -06:00

241 lines
6.4 KiB
Python

"""Visualize DesignSpaceDocument and resulting VariationModel."""
from fontTools.varLib.models import VariationModel, supportScalar
from fontTools.designspaceLib import DesignSpaceDocument
from matplotlib import pyplot
from mpl_toolkits.mplot3d import axes3d
from itertools import cycle
import math
import logging
import sys
log = logging.getLogger(__name__)
def stops(support, count=10):
a,b,c = support
return [a + (b - a) * i / count for i in range(count)] + \
[b + (c - b) * i / count for i in range(count)] + \
[c]
def _plotLocationsDots(locations, axes, subplot, **kwargs):
for loc, color in zip(locations, cycle(pyplot.cm.Set1.colors)):
if len(axes) == 1:
subplot.plot(
[loc.get(axes[0], 0)],
[1.],
'o',
color=color,
**kwargs
)
elif len(axes) == 2:
subplot.plot(
[loc.get(axes[0], 0)],
[loc.get(axes[1], 0)],
[1.],
'o',
color=color,
**kwargs
)
else:
raise AssertionError(len(axes))
def plotLocations(locations, fig, names=None, **kwargs):
n = len(locations)
cols = math.ceil(n**.5)
rows = math.ceil(n / cols)
if names is None:
names = [None] * len(locations)
model = VariationModel(locations)
names = [names[model.reverseMapping[i]] for i in range(len(names))]
axes = sorted(locations[0].keys())
if len(axes) == 1:
_plotLocations2D(
model, axes[0], fig, cols, rows, names=names, **kwargs
)
elif len(axes) == 2:
_plotLocations3D(
model, axes, fig, cols, rows, names=names, **kwargs
)
else:
raise ValueError("Only 1 or 2 axes are supported")
def _plotLocations2D(model, axis, fig, cols, rows, names, **kwargs):
subplot = fig.add_subplot(111)
for i, (support, color, name) in enumerate(
zip(model.supports, cycle(pyplot.cm.Set1.colors), cycle(names))
):
if name is not None:
subplot.set_title(name)
subplot.set_xlabel(axis)
pyplot.xlim(-1.,+1.)
Xs = support.get(axis, (-1.,0.,+1.))
X, Y = [], []
for x in stops(Xs):
y = supportScalar({axis:x}, support)
X.append(x)
Y.append(y)
subplot.plot(X, Y, color=color, **kwargs)
_plotLocationsDots(model.locations, [axis], subplot)
def _plotLocations3D(model, axes, fig, rows, cols, names, **kwargs):
ax1, ax2 = axes
axis3D = fig.add_subplot(111, projection='3d')
for i, (support, color, name) in enumerate(
zip(model.supports, cycle(pyplot.cm.Set1.colors), cycle(names))
):
if name is not None:
axis3D.set_title(name)
axis3D.set_xlabel(ax1)
axis3D.set_ylabel(ax2)
pyplot.xlim(-1.,+1.)
pyplot.ylim(-1.,+1.)
Xs = support.get(ax1, (-1.,0.,+1.))
Ys = support.get(ax2, (-1.,0.,+1.))
for x in stops(Xs):
X, Y, Z = [], [], []
for y in Ys:
z = supportScalar({ax1:x, ax2:y}, support)
X.append(x)
Y.append(y)
Z.append(z)
axis3D.plot(X, Y, Z, color=color, **kwargs)
for y in stops(Ys):
X, Y, Z = [], [], []
for x in Xs:
z = supportScalar({ax1:x, ax2:y}, support)
X.append(x)
Y.append(y)
Z.append(z)
axis3D.plot(X, Y, Z, color=color, **kwargs)
_plotLocationsDots(model.locations, [ax1, ax2], axis3D)
def plotDocument(doc, fig, **kwargs):
doc.normalize()
locations = [s.location for s in doc.sources]
names = [s.name for s in doc.sources]
plotLocations(locations, fig, names, **kwargs)
def _plotModelFromMasters2D(model, masterValues, fig, **kwargs):
assert len(model.axisOrder) == 1
axis = model.axisOrder[0]
axis_min = min(loc.get(axis, 0) for loc in model.locations)
axis_max = max(loc.get(axis, 0) for loc in model.locations)
import numpy as np
X = np.arange(axis_min, axis_max, (axis_max - axis_min) / 100)
Y = []
for x in X:
loc = {axis: x}
v = model.interpolateFromMasters(loc, masterValues)
Y.append(v)
subplot = fig.add_subplot(111)
subplot.plot(X, Y, '-', **kwargs)
def _plotModelFromMasters3D(model, masterValues, fig, **kwargs):
assert len(model.axisOrder) == 2
axis1, axis2 = model.axisOrder[0], model.axisOrder[1]
axis1_min = min(loc.get(axis1, 0) for loc in model.locations)
axis1_max = max(loc.get(axis1, 0) for loc in model.locations)
axis2_min = min(loc.get(axis2, 0) for loc in model.locations)
axis2_max = max(loc.get(axis2, 0) for loc in model.locations)
import numpy as np
X = np.arange(axis1_min, axis1_max, (axis1_max - axis1_min) / 100)
Y = np.arange(axis2_min, axis2_max, (axis2_max - axis2_min) / 100)
X, Y = np.meshgrid(X, Y)
Z = []
for row_x,row_y in zip(X,Y):
z_row = []
Z.append(z_row)
for x,y in zip(row_x,row_y):
loc = {axis1: x, axis2: y}
v = model.interpolateFromMasters(loc, masterValues)
z_row.append(v)
Z = np.array(Z)
axis3D = fig.add_subplot(111, projection='3d')
axis3D.plot_surface(X, Y, Z, **kwargs)
def plotModelFromMasters(model, masterValues, fig, **kwargs):
"""Plot a variation model and set of master values corresponding
to the locations to the model into a pyplot figure. Variation
model must have axisOrder of size 1 or 2."""
if len(model.axisOrder) == 1:
_plotModelFromMasters2D(model, masterValues, fig, **kwargs)
elif len(model.axisOrder) == 2:
_plotModelFromMasters3D(model, masterValues, fig, **kwargs)
else:
raise ValueError("Only 1 or 2 axes are supported")
def main(args=None):
from fontTools import configLogger
if args is None:
args = sys.argv[1:]
# configure the library logger (for >= WARNING)
configLogger()
# comment this out to enable debug messages from logger
# log.setLevel(logging.DEBUG)
if len(args) < 1:
print("usage: fonttools varLib.plot source.designspace", file=sys.stderr)
print(" or")
print("usage: fonttools varLib.plot location1 location2 ...", file=sys.stderr)
print(" or")
print("usage: fonttools varLib.plot location1=value1 location2=value2 ...", file=sys.stderr)
sys.exit(1)
fig = pyplot.figure()
fig.set_tight_layout(True)
if len(args) == 1 and args[0].endswith('.designspace'):
doc = DesignSpaceDocument()
doc.read(args[0])
plotDocument(doc, fig)
else:
axes = [chr(c) for c in range(ord('A'), ord('Z')+1)]
if '=' not in args[0]:
locs = [dict(zip(axes, (float(v) for v in s.split(',')))) for s in args]
plotLocations(locs, fig)
else:
locations = []
masterValues = []
for arg in args:
loc,v = arg.split('=')
locations.append(dict(zip(axes, (float(v) for v in loc.split(',')))))
masterValues.append(float(v))
model = VariationModel(locations, axes[:len(locations[0])])
plotModelFromMasters(model, masterValues, fig)
pyplot.show()
if __name__ == '__main__':
import sys
sys.exit(main())