1"""Pen calculating area, center of mass, variance and standard-deviation,
2covariance and correlation, and slant, of glyph shapes."""
3from __future__ import print_function, division, absolute_import
4from fontTools.misc.py23 import *
5import math
6from fontTools.pens.momentsPen import MomentsPen
7
8__all__ = ["StatisticsPen"]
9
10
11class StatisticsPen(MomentsPen):
12
13	"""Pen calculating area, center of mass, variance and
14	standard-deviation, covariance and correlation, and slant,
15	of glyph shapes.
16
17	Note that all the calculated values are 'signed'. Ie. if the
18	glyph shape is self-intersecting, the values are not correct
19	(but well-defined). As such, area will be negative if contour
20	directions are clockwise.  Moreover, variance might be negative
21	if the shapes are self-intersecting in certain ways."""
22
23	def __init__(self, glyphset=None):
24		MomentsPen.__init__(self, glyphset=glyphset)
25		self.__zero()
26
27	def _closePath(self):
28		MomentsPen._closePath(self)
29		self.__update()
30
31	def __zero(self):
32		self.meanX = 0
33		self.meanY = 0
34		self.varianceX = 0
35		self.varianceY = 0
36		self.stddevX = 0
37		self.stddevY = 0
38		self.covariance = 0
39		self.correlation = 0
40		self.slant = 0
41
42	def __update(self):
43
44		area = self.area
45		if not area:
46			self.__zero()
47			return
48
49		# Center of mass
50		# https://en.wikipedia.org/wiki/Center_of_mass#A_continuous_volume
51		self.meanX = meanX = self.momentX / area
52		self.meanY = meanY = self.momentY / area
53
54		#  Var(X) = E[X^2] - E[X]^2
55		self.varianceX = varianceX = self.momentXX / area - meanX**2
56		self.varianceY = varianceY = self.momentYY / area - meanY**2
57
58		self.stddevX = stddevX = math.copysign(abs(varianceX)**.5, varianceX)
59		self.stddevY = stddevY = math.copysign(abs(varianceY)**.5, varianceY)
60
61		#  Covariance(X,Y) = ( E[X.Y] - E[X]E[Y] )
62		self.covariance = covariance = self.momentXY / area - meanX*meanY
63
64		#  Correlation(X,Y) = Covariance(X,Y) / ( stddev(X) * stddev(Y) )
65		# https://en.wikipedia.org/wiki/Pearson_product-moment_correlation_coefficient
66		correlation = covariance / (stddevX * stddevY)
67		self.correlation = correlation if abs(correlation) > 1e-3 else 0
68
69		slant = covariance / varianceY
70		self.slant = slant if abs(slant) > 1e-3 else 0
71
72
73def _test(glyphset, upem, glyphs):
74	from fontTools.pens.transformPen import TransformPen
75	from fontTools.misc.transform import Scale
76
77	print('upem', upem)
78
79	for glyph_name in glyphs:
80		print()
81		print("glyph:", glyph_name)
82		glyph = glyphset[glyph_name]
83		pen = StatisticsPen(glyphset=glyphset)
84		transformer = TransformPen(pen, Scale(1./upem))
85		glyph.draw(transformer)
86		for item in ['area', 'momentX', 'momentY', 'momentXX', 'momentYY', 'momentXY', 'meanX', 'meanY', 'varianceX', 'varianceY', 'stddevX', 'stddevY', 'covariance', 'correlation', 'slant']:
87			if item[0] == '_': continue
88			print ("%s: %g" % (item, getattr(pen, item)))
89
90def main(args):
91	if not args:
92		return
93	filename, glyphs = args[0], args[1:]
94	if not glyphs:
95		glyphs = ['e', 'o', 'I', 'slash', 'E', 'zero', 'eight', 'minus', 'equal']
96	from fontTools.ttLib import TTFont
97	font = TTFont(filename)
98	_test(font.getGlyphSet(), font['head'].unitsPerEm, glyphs)
99
100if __name__ == '__main__':
101	import sys
102	main(sys.argv[1:])
103