• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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