aboutsummaryrefslogtreecommitdiff
path: root/Lib/fontTools/pens/statisticsPen.py
blob: abd6ff5e71244bc1906c9d99ed442ea2e7477413 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
"""Pen calculating area, center of mass, variance and standard-deviation,
covariance and correlation, and slant, of glyph shapes."""
import math
from fontTools.pens.momentsPen import MomentsPen

__all__ = ["StatisticsPen"]


class StatisticsPen(MomentsPen):

	"""Pen calculating area, center of mass, variance and
	standard-deviation, covariance and correlation, and slant,
	of glyph shapes.

	Note that all the calculated values are 'signed'. Ie. if the
	glyph shape is self-intersecting, the values are not correct
	(but well-defined). As such, area will be negative if contour
	directions are clockwise.  Moreover, variance might be negative
	if the shapes are self-intersecting in certain ways."""

	def __init__(self, glyphset=None):
		MomentsPen.__init__(self, glyphset=glyphset)
		self.__zero()

	def _closePath(self):
		MomentsPen._closePath(self)
		self.__update()

	def __zero(self):
		self.meanX = 0
		self.meanY = 0
		self.varianceX = 0
		self.varianceY = 0
		self.stddevX = 0
		self.stddevY = 0
		self.covariance = 0
		self.correlation = 0
		self.slant = 0

	def __update(self):

		area = self.area
		if not area:
			self.__zero()
			return

		# Center of mass
		# https://en.wikipedia.org/wiki/Center_of_mass#A_continuous_volume
		self.meanX = meanX = self.momentX / area
		self.meanY = meanY = self.momentY / area

		#  Var(X) = E[X^2] - E[X]^2
		self.varianceX = varianceX = self.momentXX / area - meanX**2
		self.varianceY = varianceY = self.momentYY / area - meanY**2

		self.stddevX = stddevX = math.copysign(abs(varianceX)**.5, varianceX)
		self.stddevY = stddevY = math.copysign(abs(varianceY)**.5, varianceY)

		#  Covariance(X,Y) = ( E[X.Y] - E[X]E[Y] )
		self.covariance = covariance = self.momentXY / area - meanX*meanY

		#  Correlation(X,Y) = Covariance(X,Y) / ( stddev(X) * stddev(Y) )
		# https://en.wikipedia.org/wiki/Pearson_product-moment_correlation_coefficient
		correlation = covariance / (stddevX * stddevY)
		self.correlation = correlation if abs(correlation) > 1e-3 else 0

		slant = covariance / varianceY
		self.slant = slant if abs(slant) > 1e-3 else 0


def _test(glyphset, upem, glyphs):
	from fontTools.pens.transformPen import TransformPen
	from fontTools.misc.transform import Scale

	print('upem', upem)

	for glyph_name in glyphs:
		print()
		print("glyph:", glyph_name)
		glyph = glyphset[glyph_name]
		pen = StatisticsPen(glyphset=glyphset)
		transformer = TransformPen(pen, Scale(1./upem))
		glyph.draw(transformer)
		for item in ['area', 'momentX', 'momentY', 'momentXX', 'momentYY', 'momentXY', 'meanX', 'meanY', 'varianceX', 'varianceY', 'stddevX', 'stddevY', 'covariance', 'correlation', 'slant']:
			if item[0] == '_': continue
			print ("%s: %g" % (item, getattr(pen, item)))

def main(args):
	if not args:
		return
	filename, glyphs = args[0], args[1:]
	if not glyphs:
		glyphs = ['e', 'o', 'I', 'slash', 'E', 'zero', 'eight', 'minus', 'equal']
	from fontTools.ttLib import TTFont
	font = TTFont(filename)
	_test(font.getGlyphSet(), font['head'].unitsPerEm, glyphs)

if __name__ == '__main__':
	import sys
	main(sys.argv[1:])