1*e1fe3e4aSElliott Hughes"""Visualize DesignSpaceDocument and resulting VariationModel.""" 2*e1fe3e4aSElliott Hughes 3*e1fe3e4aSElliott Hughesfrom fontTools.varLib.models import VariationModel, supportScalar 4*e1fe3e4aSElliott Hughesfrom fontTools.designspaceLib import DesignSpaceDocument 5*e1fe3e4aSElliott Hughesfrom matplotlib import pyplot 6*e1fe3e4aSElliott Hughesfrom mpl_toolkits.mplot3d import axes3d 7*e1fe3e4aSElliott Hughesfrom itertools import cycle 8*e1fe3e4aSElliott Hughesimport math 9*e1fe3e4aSElliott Hughesimport logging 10*e1fe3e4aSElliott Hughesimport sys 11*e1fe3e4aSElliott Hughes 12*e1fe3e4aSElliott Hugheslog = logging.getLogger(__name__) 13*e1fe3e4aSElliott Hughes 14*e1fe3e4aSElliott Hughes 15*e1fe3e4aSElliott Hughesdef stops(support, count=10): 16*e1fe3e4aSElliott Hughes a, b, c = support 17*e1fe3e4aSElliott Hughes 18*e1fe3e4aSElliott Hughes return ( 19*e1fe3e4aSElliott Hughes [a + (b - a) * i / count for i in range(count)] 20*e1fe3e4aSElliott Hughes + [b + (c - b) * i / count for i in range(count)] 21*e1fe3e4aSElliott Hughes + [c] 22*e1fe3e4aSElliott Hughes ) 23*e1fe3e4aSElliott Hughes 24*e1fe3e4aSElliott Hughes 25*e1fe3e4aSElliott Hughesdef _plotLocationsDots(locations, axes, subplot, **kwargs): 26*e1fe3e4aSElliott Hughes for loc, color in zip(locations, cycle(pyplot.cm.Set1.colors)): 27*e1fe3e4aSElliott Hughes if len(axes) == 1: 28*e1fe3e4aSElliott Hughes subplot.plot([loc.get(axes[0], 0)], [1.0], "o", color=color, **kwargs) 29*e1fe3e4aSElliott Hughes elif len(axes) == 2: 30*e1fe3e4aSElliott Hughes subplot.plot( 31*e1fe3e4aSElliott Hughes [loc.get(axes[0], 0)], 32*e1fe3e4aSElliott Hughes [loc.get(axes[1], 0)], 33*e1fe3e4aSElliott Hughes [1.0], 34*e1fe3e4aSElliott Hughes "o", 35*e1fe3e4aSElliott Hughes color=color, 36*e1fe3e4aSElliott Hughes **kwargs, 37*e1fe3e4aSElliott Hughes ) 38*e1fe3e4aSElliott Hughes else: 39*e1fe3e4aSElliott Hughes raise AssertionError(len(axes)) 40*e1fe3e4aSElliott Hughes 41*e1fe3e4aSElliott Hughes 42*e1fe3e4aSElliott Hughesdef plotLocations(locations, fig, names=None, **kwargs): 43*e1fe3e4aSElliott Hughes n = len(locations) 44*e1fe3e4aSElliott Hughes cols = math.ceil(n**0.5) 45*e1fe3e4aSElliott Hughes rows = math.ceil(n / cols) 46*e1fe3e4aSElliott Hughes 47*e1fe3e4aSElliott Hughes if names is None: 48*e1fe3e4aSElliott Hughes names = [None] * len(locations) 49*e1fe3e4aSElliott Hughes 50*e1fe3e4aSElliott Hughes model = VariationModel(locations) 51*e1fe3e4aSElliott Hughes names = [names[model.reverseMapping[i]] for i in range(len(names))] 52*e1fe3e4aSElliott Hughes 53*e1fe3e4aSElliott Hughes axes = sorted(locations[0].keys()) 54*e1fe3e4aSElliott Hughes if len(axes) == 1: 55*e1fe3e4aSElliott Hughes _plotLocations2D(model, axes[0], fig, cols, rows, names=names, **kwargs) 56*e1fe3e4aSElliott Hughes elif len(axes) == 2: 57*e1fe3e4aSElliott Hughes _plotLocations3D(model, axes, fig, cols, rows, names=names, **kwargs) 58*e1fe3e4aSElliott Hughes else: 59*e1fe3e4aSElliott Hughes raise ValueError("Only 1 or 2 axes are supported") 60*e1fe3e4aSElliott Hughes 61*e1fe3e4aSElliott Hughes 62*e1fe3e4aSElliott Hughesdef _plotLocations2D(model, axis, fig, cols, rows, names, **kwargs): 63*e1fe3e4aSElliott Hughes subplot = fig.add_subplot(111) 64*e1fe3e4aSElliott Hughes for i, (support, color, name) in enumerate( 65*e1fe3e4aSElliott Hughes zip(model.supports, cycle(pyplot.cm.Set1.colors), cycle(names)) 66*e1fe3e4aSElliott Hughes ): 67*e1fe3e4aSElliott Hughes if name is not None: 68*e1fe3e4aSElliott Hughes subplot.set_title(name) 69*e1fe3e4aSElliott Hughes subplot.set_xlabel(axis) 70*e1fe3e4aSElliott Hughes pyplot.xlim(-1.0, +1.0) 71*e1fe3e4aSElliott Hughes 72*e1fe3e4aSElliott Hughes Xs = support.get(axis, (-1.0, 0.0, +1.0)) 73*e1fe3e4aSElliott Hughes X, Y = [], [] 74*e1fe3e4aSElliott Hughes for x in stops(Xs): 75*e1fe3e4aSElliott Hughes y = supportScalar({axis: x}, support) 76*e1fe3e4aSElliott Hughes X.append(x) 77*e1fe3e4aSElliott Hughes Y.append(y) 78*e1fe3e4aSElliott Hughes subplot.plot(X, Y, color=color, **kwargs) 79*e1fe3e4aSElliott Hughes 80*e1fe3e4aSElliott Hughes _plotLocationsDots(model.locations, [axis], subplot) 81*e1fe3e4aSElliott Hughes 82*e1fe3e4aSElliott Hughes 83*e1fe3e4aSElliott Hughesdef _plotLocations3D(model, axes, fig, rows, cols, names, **kwargs): 84*e1fe3e4aSElliott Hughes ax1, ax2 = axes 85*e1fe3e4aSElliott Hughes 86*e1fe3e4aSElliott Hughes axis3D = fig.add_subplot(111, projection="3d") 87*e1fe3e4aSElliott Hughes for i, (support, color, name) in enumerate( 88*e1fe3e4aSElliott Hughes zip(model.supports, cycle(pyplot.cm.Set1.colors), cycle(names)) 89*e1fe3e4aSElliott Hughes ): 90*e1fe3e4aSElliott Hughes if name is not None: 91*e1fe3e4aSElliott Hughes axis3D.set_title(name) 92*e1fe3e4aSElliott Hughes axis3D.set_xlabel(ax1) 93*e1fe3e4aSElliott Hughes axis3D.set_ylabel(ax2) 94*e1fe3e4aSElliott Hughes pyplot.xlim(-1.0, +1.0) 95*e1fe3e4aSElliott Hughes pyplot.ylim(-1.0, +1.0) 96*e1fe3e4aSElliott Hughes 97*e1fe3e4aSElliott Hughes Xs = support.get(ax1, (-1.0, 0.0, +1.0)) 98*e1fe3e4aSElliott Hughes Ys = support.get(ax2, (-1.0, 0.0, +1.0)) 99*e1fe3e4aSElliott Hughes for x in stops(Xs): 100*e1fe3e4aSElliott Hughes X, Y, Z = [], [], [] 101*e1fe3e4aSElliott Hughes for y in Ys: 102*e1fe3e4aSElliott Hughes z = supportScalar({ax1: x, ax2: y}, support) 103*e1fe3e4aSElliott Hughes X.append(x) 104*e1fe3e4aSElliott Hughes Y.append(y) 105*e1fe3e4aSElliott Hughes Z.append(z) 106*e1fe3e4aSElliott Hughes axis3D.plot(X, Y, Z, color=color, **kwargs) 107*e1fe3e4aSElliott Hughes for y in stops(Ys): 108*e1fe3e4aSElliott Hughes X, Y, Z = [], [], [] 109*e1fe3e4aSElliott Hughes for x in Xs: 110*e1fe3e4aSElliott Hughes z = supportScalar({ax1: x, ax2: y}, support) 111*e1fe3e4aSElliott Hughes X.append(x) 112*e1fe3e4aSElliott Hughes Y.append(y) 113*e1fe3e4aSElliott Hughes Z.append(z) 114*e1fe3e4aSElliott Hughes axis3D.plot(X, Y, Z, color=color, **kwargs) 115*e1fe3e4aSElliott Hughes 116*e1fe3e4aSElliott Hughes _plotLocationsDots(model.locations, [ax1, ax2], axis3D) 117*e1fe3e4aSElliott Hughes 118*e1fe3e4aSElliott Hughes 119*e1fe3e4aSElliott Hughesdef plotDocument(doc, fig, **kwargs): 120*e1fe3e4aSElliott Hughes doc.normalize() 121*e1fe3e4aSElliott Hughes locations = [s.location for s in doc.sources] 122*e1fe3e4aSElliott Hughes names = [s.name for s in doc.sources] 123*e1fe3e4aSElliott Hughes plotLocations(locations, fig, names, **kwargs) 124*e1fe3e4aSElliott Hughes 125*e1fe3e4aSElliott Hughes 126*e1fe3e4aSElliott Hughesdef _plotModelFromMasters2D(model, masterValues, fig, **kwargs): 127*e1fe3e4aSElliott Hughes assert len(model.axisOrder) == 1 128*e1fe3e4aSElliott Hughes axis = model.axisOrder[0] 129*e1fe3e4aSElliott Hughes 130*e1fe3e4aSElliott Hughes axis_min = min(loc.get(axis, 0) for loc in model.locations) 131*e1fe3e4aSElliott Hughes axis_max = max(loc.get(axis, 0) for loc in model.locations) 132*e1fe3e4aSElliott Hughes 133*e1fe3e4aSElliott Hughes import numpy as np 134*e1fe3e4aSElliott Hughes 135*e1fe3e4aSElliott Hughes X = np.arange(axis_min, axis_max, (axis_max - axis_min) / 100) 136*e1fe3e4aSElliott Hughes Y = [] 137*e1fe3e4aSElliott Hughes 138*e1fe3e4aSElliott Hughes for x in X: 139*e1fe3e4aSElliott Hughes loc = {axis: x} 140*e1fe3e4aSElliott Hughes v = model.interpolateFromMasters(loc, masterValues) 141*e1fe3e4aSElliott Hughes Y.append(v) 142*e1fe3e4aSElliott Hughes 143*e1fe3e4aSElliott Hughes subplot = fig.add_subplot(111) 144*e1fe3e4aSElliott Hughes subplot.plot(X, Y, "-", **kwargs) 145*e1fe3e4aSElliott Hughes 146*e1fe3e4aSElliott Hughes 147*e1fe3e4aSElliott Hughesdef _plotModelFromMasters3D(model, masterValues, fig, **kwargs): 148*e1fe3e4aSElliott Hughes assert len(model.axisOrder) == 2 149*e1fe3e4aSElliott Hughes axis1, axis2 = model.axisOrder[0], model.axisOrder[1] 150*e1fe3e4aSElliott Hughes 151*e1fe3e4aSElliott Hughes axis1_min = min(loc.get(axis1, 0) for loc in model.locations) 152*e1fe3e4aSElliott Hughes axis1_max = max(loc.get(axis1, 0) for loc in model.locations) 153*e1fe3e4aSElliott Hughes axis2_min = min(loc.get(axis2, 0) for loc in model.locations) 154*e1fe3e4aSElliott Hughes axis2_max = max(loc.get(axis2, 0) for loc in model.locations) 155*e1fe3e4aSElliott Hughes 156*e1fe3e4aSElliott Hughes import numpy as np 157*e1fe3e4aSElliott Hughes 158*e1fe3e4aSElliott Hughes X = np.arange(axis1_min, axis1_max, (axis1_max - axis1_min) / 100) 159*e1fe3e4aSElliott Hughes Y = np.arange(axis2_min, axis2_max, (axis2_max - axis2_min) / 100) 160*e1fe3e4aSElliott Hughes X, Y = np.meshgrid(X, Y) 161*e1fe3e4aSElliott Hughes Z = [] 162*e1fe3e4aSElliott Hughes 163*e1fe3e4aSElliott Hughes for row_x, row_y in zip(X, Y): 164*e1fe3e4aSElliott Hughes z_row = [] 165*e1fe3e4aSElliott Hughes Z.append(z_row) 166*e1fe3e4aSElliott Hughes for x, y in zip(row_x, row_y): 167*e1fe3e4aSElliott Hughes loc = {axis1: x, axis2: y} 168*e1fe3e4aSElliott Hughes v = model.interpolateFromMasters(loc, masterValues) 169*e1fe3e4aSElliott Hughes z_row.append(v) 170*e1fe3e4aSElliott Hughes Z = np.array(Z) 171*e1fe3e4aSElliott Hughes 172*e1fe3e4aSElliott Hughes axis3D = fig.add_subplot(111, projection="3d") 173*e1fe3e4aSElliott Hughes axis3D.plot_surface(X, Y, Z, **kwargs) 174*e1fe3e4aSElliott Hughes 175*e1fe3e4aSElliott Hughes 176*e1fe3e4aSElliott Hughesdef plotModelFromMasters(model, masterValues, fig, **kwargs): 177*e1fe3e4aSElliott Hughes """Plot a variation model and set of master values corresponding 178*e1fe3e4aSElliott Hughes to the locations to the model into a pyplot figure. Variation 179*e1fe3e4aSElliott Hughes model must have axisOrder of size 1 or 2.""" 180*e1fe3e4aSElliott Hughes if len(model.axisOrder) == 1: 181*e1fe3e4aSElliott Hughes _plotModelFromMasters2D(model, masterValues, fig, **kwargs) 182*e1fe3e4aSElliott Hughes elif len(model.axisOrder) == 2: 183*e1fe3e4aSElliott Hughes _plotModelFromMasters3D(model, masterValues, fig, **kwargs) 184*e1fe3e4aSElliott Hughes else: 185*e1fe3e4aSElliott Hughes raise ValueError("Only 1 or 2 axes are supported") 186*e1fe3e4aSElliott Hughes 187*e1fe3e4aSElliott Hughes 188*e1fe3e4aSElliott Hughesdef main(args=None): 189*e1fe3e4aSElliott Hughes from fontTools import configLogger 190*e1fe3e4aSElliott Hughes 191*e1fe3e4aSElliott Hughes if args is None: 192*e1fe3e4aSElliott Hughes args = sys.argv[1:] 193*e1fe3e4aSElliott Hughes 194*e1fe3e4aSElliott Hughes # configure the library logger (for >= WARNING) 195*e1fe3e4aSElliott Hughes configLogger() 196*e1fe3e4aSElliott Hughes # comment this out to enable debug messages from logger 197*e1fe3e4aSElliott Hughes # log.setLevel(logging.DEBUG) 198*e1fe3e4aSElliott Hughes 199*e1fe3e4aSElliott Hughes if len(args) < 1: 200*e1fe3e4aSElliott Hughes print("usage: fonttools varLib.plot source.designspace", file=sys.stderr) 201*e1fe3e4aSElliott Hughes print(" or") 202*e1fe3e4aSElliott Hughes print("usage: fonttools varLib.plot location1 location2 ...", file=sys.stderr) 203*e1fe3e4aSElliott Hughes print(" or") 204*e1fe3e4aSElliott Hughes print( 205*e1fe3e4aSElliott Hughes "usage: fonttools varLib.plot location1=value1 location2=value2 ...", 206*e1fe3e4aSElliott Hughes file=sys.stderr, 207*e1fe3e4aSElliott Hughes ) 208*e1fe3e4aSElliott Hughes sys.exit(1) 209*e1fe3e4aSElliott Hughes 210*e1fe3e4aSElliott Hughes fig = pyplot.figure() 211*e1fe3e4aSElliott Hughes fig.set_tight_layout(True) 212*e1fe3e4aSElliott Hughes 213*e1fe3e4aSElliott Hughes if len(args) == 1 and args[0].endswith(".designspace"): 214*e1fe3e4aSElliott Hughes doc = DesignSpaceDocument() 215*e1fe3e4aSElliott Hughes doc.read(args[0]) 216*e1fe3e4aSElliott Hughes plotDocument(doc, fig) 217*e1fe3e4aSElliott Hughes else: 218*e1fe3e4aSElliott Hughes axes = [chr(c) for c in range(ord("A"), ord("Z") + 1)] 219*e1fe3e4aSElliott Hughes if "=" not in args[0]: 220*e1fe3e4aSElliott Hughes locs = [dict(zip(axes, (float(v) for v in s.split(",")))) for s in args] 221*e1fe3e4aSElliott Hughes plotLocations(locs, fig) 222*e1fe3e4aSElliott Hughes else: 223*e1fe3e4aSElliott Hughes locations = [] 224*e1fe3e4aSElliott Hughes masterValues = [] 225*e1fe3e4aSElliott Hughes for arg in args: 226*e1fe3e4aSElliott Hughes loc, v = arg.split("=") 227*e1fe3e4aSElliott Hughes locations.append(dict(zip(axes, (float(v) for v in loc.split(","))))) 228*e1fe3e4aSElliott Hughes masterValues.append(float(v)) 229*e1fe3e4aSElliott Hughes model = VariationModel(locations, axes[: len(locations[0])]) 230*e1fe3e4aSElliott Hughes plotModelFromMasters(model, masterValues, fig) 231*e1fe3e4aSElliott Hughes 232*e1fe3e4aSElliott Hughes pyplot.show() 233*e1fe3e4aSElliott Hughes 234*e1fe3e4aSElliott Hughes 235*e1fe3e4aSElliott Hughesif __name__ == "__main__": 236*e1fe3e4aSElliott Hughes import sys 237*e1fe3e4aSElliott Hughes 238*e1fe3e4aSElliott Hughes sys.exit(main()) 239