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