1"""Visualize DesignSpaceDocument and resulting VariationModel.""" 2 3from fontTools.varLib.models import VariationModel, supportScalar 4from fontTools.designspaceLib import DesignSpaceDocument 5from matplotlib import pyplot 6from mpl_toolkits.mplot3d import axes3d 7from itertools import cycle 8import math 9import logging 10import sys 11 12log = logging.getLogger(__name__) 13 14 15def stops(support, count=10): 16 a,b,c = support 17 18 return [a + (b - a) * i / count for i in range(count)] + \ 19 [b + (c - b) * i / count for i in range(count)] + \ 20 [c] 21 22 23def _plotLocationsDots(locations, axes, subplot, **kwargs): 24 for loc, color in zip(locations, cycle(pyplot.cm.Set1.colors)): 25 if len(axes) == 1: 26 subplot.plot( 27 [loc.get(axes[0], 0)], 28 [1.], 29 'o', 30 color=color, 31 **kwargs 32 ) 33 elif len(axes) == 2: 34 subplot.plot( 35 [loc.get(axes[0], 0)], 36 [loc.get(axes[1], 0)], 37 [1.], 38 'o', 39 color=color, 40 **kwargs 41 ) 42 else: 43 raise AssertionError(len(axes)) 44 45 46def plotLocations(locations, fig, names=None, **kwargs): 47 n = len(locations) 48 cols = math.ceil(n**.5) 49 rows = math.ceil(n / cols) 50 51 if names is None: 52 names = [None] * len(locations) 53 54 model = VariationModel(locations) 55 names = [names[model.reverseMapping[i]] for i in range(len(names))] 56 57 axes = sorted(locations[0].keys()) 58 if len(axes) == 1: 59 _plotLocations2D( 60 model, axes[0], fig, cols, rows, names=names, **kwargs 61 ) 62 elif len(axes) == 2: 63 _plotLocations3D( 64 model, axes, fig, cols, rows, names=names, **kwargs 65 ) 66 else: 67 raise ValueError("Only 1 or 2 axes are supported") 68 69 70def _plotLocations2D(model, axis, fig, cols, rows, names, **kwargs): 71 subplot = fig.add_subplot(111) 72 for i, (support, color, name) in enumerate( 73 zip(model.supports, cycle(pyplot.cm.Set1.colors), cycle(names)) 74 ): 75 if name is not None: 76 subplot.set_title(name) 77 subplot.set_xlabel(axis) 78 pyplot.xlim(-1.,+1.) 79 80 Xs = support.get(axis, (-1.,0.,+1.)) 81 X, Y = [], [] 82 for x in stops(Xs): 83 y = supportScalar({axis:x}, support) 84 X.append(x) 85 Y.append(y) 86 subplot.plot(X, Y, color=color, **kwargs) 87 88 _plotLocationsDots(model.locations, [axis], subplot) 89 90 91def _plotLocations3D(model, axes, fig, rows, cols, names, **kwargs): 92 ax1, ax2 = axes 93 94 axis3D = fig.add_subplot(111, projection='3d') 95 for i, (support, color, name) in enumerate( 96 zip(model.supports, cycle(pyplot.cm.Set1.colors), cycle(names)) 97 ): 98 if name is not None: 99 axis3D.set_title(name) 100 axis3D.set_xlabel(ax1) 101 axis3D.set_ylabel(ax2) 102 pyplot.xlim(-1.,+1.) 103 pyplot.ylim(-1.,+1.) 104 105 Xs = support.get(ax1, (-1.,0.,+1.)) 106 Ys = support.get(ax2, (-1.,0.,+1.)) 107 for x in stops(Xs): 108 X, Y, Z = [], [], [] 109 for y in Ys: 110 z = supportScalar({ax1:x, ax2:y}, support) 111 X.append(x) 112 Y.append(y) 113 Z.append(z) 114 axis3D.plot(X, Y, Z, color=color, **kwargs) 115 for y in stops(Ys): 116 X, Y, Z = [], [], [] 117 for x in Xs: 118 z = supportScalar({ax1:x, ax2:y}, support) 119 X.append(x) 120 Y.append(y) 121 Z.append(z) 122 axis3D.plot(X, Y, Z, color=color, **kwargs) 123 124 _plotLocationsDots(model.locations, [ax1, ax2], axis3D) 125 126 127def plotDocument(doc, fig, **kwargs): 128 doc.normalize() 129 locations = [s.location for s in doc.sources] 130 names = [s.name for s in doc.sources] 131 plotLocations(locations, fig, names, **kwargs) 132 133 134def main(args=None): 135 from fontTools import configLogger 136 137 if args is None: 138 args = sys.argv[1:] 139 140 # configure the library logger (for >= WARNING) 141 configLogger() 142 # comment this out to enable debug messages from logger 143 # log.setLevel(logging.DEBUG) 144 145 if len(args) < 1: 146 print("usage: fonttools varLib.plot source.designspace", file=sys.stderr) 147 print(" or") 148 print("usage: fonttools varLib.plot location1 location2 ...", file=sys.stderr) 149 sys.exit(1) 150 151 fig = pyplot.figure() 152 fig.set_tight_layout(True) 153 154 if len(args) == 1 and args[0].endswith('.designspace'): 155 doc = DesignSpaceDocument() 156 doc.read(args[0]) 157 plotDocument(doc, fig) 158 else: 159 axes = [chr(c) for c in range(ord('A'), ord('Z')+1)] 160 locs = [dict(zip(axes, (float(v) for v in s.split(',')))) for s in args] 161 plotLocations(locs, fig) 162 163 pyplot.show() 164 165if __name__ == '__main__': 166 import sys 167 sys.exit(main()) 168