fonttools/Lib/fontTools/pens/statisticsPen.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

141 lines
4.2 KiB
Python
Raw Normal View History

2017-02-26 10:41:11 -08:00
"""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):
2017-02-26 10:41:11 -08:00
"""Pen calculating area, center of mass, variance and
standard-deviation, covariance and correlation, and slant,
of glyph shapes.
2022-12-13 11:26:36 +00:00
2017-02-26 10:41:11 -08:00
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."""
2022-12-13 11:26:36 +00:00
def __init__(self, glyphset=None):
MomentsPen.__init__(self, glyphset=glyphset)
self.__zero()
2022-12-13 11:26:36 +00:00
def _closePath(self):
MomentsPen._closePath(self)
self.__update()
2022-12-13 11:26:36 +00:00
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
2022-12-13 11:26:36 +00:00
def __update(self):
2022-12-13 11:26:36 +00:00
area = self.area
if not area:
self.__zero()
return
2022-12-13 11:26:36 +00:00
# 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
2022-12-13 11:26:36 +00:00
# Var(X) = E[X^2] - E[X]^2
self.varianceX = varianceX = self.momentXX / area - meanX**2
self.varianceY = varianceY = self.momentYY / area - meanY**2
2022-12-13 11:26:36 +00:00
self.stddevX = stddevX = math.copysign(abs(varianceX) ** 0.5, varianceX)
self.stddevY = stddevY = math.copysign(abs(varianceY) ** 0.5, varianceY)
2022-12-13 11:26:36 +00:00
# Covariance(X,Y) = ( E[X.Y] - E[X]E[Y] )
self.covariance = covariance = self.momentXY / area - meanX * meanY
2022-12-13 11:26:36 +00:00
# Correlation(X,Y) = Covariance(X,Y) / ( stddev(X) * stddev(Y) )
# https://en.wikipedia.org/wiki/Pearson_product-moment_correlation_coefficient
2022-08-12 14:00:27 -06:00
if stddevX * stddevY == 0:
correlation = float("NaN")
else:
correlation = covariance / (stddevX * stddevY)
self.correlation = correlation if abs(correlation) > 1e-3 else 0
2022-12-13 11:26:36 +00:00
2022-08-12 14:00:27 -06:00
slant = covariance / varianceY if varianceY != 0 else float("NaN")
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
2022-12-13 11:26:36 +00:00
print("upem", upem)
2022-12-13 11:26:36 +00:00
wght_sum = 0
wght_sum_perceptual = 0
wdth_sum = 0
2023-07-19 20:03:38 -06:00
slnt_sum = 0
slnt_sum_perceptual = 0
for glyph_name in glyphs:
2022-12-13 11:26:36 +00:00
print()
print("glyph:", glyph_name)
glyph = glyphset[glyph_name]
pen = StatisticsPen(glyphset=glyphset)
transformer = TransformPen(pen, Scale(1.0 / upem))
glyph.draw(transformer)
for item in [
2022-12-13 11:26:36 +00:00
"area",
"momentX",
"momentY",
"momentXX",
"momentYY",
"momentXY",
"meanX",
"meanY",
"varianceX",
"varianceY",
"stddevX",
"stddevY",
"covariance",
"correlation",
2022-12-13 11:26:36 +00:00
"slant",
]:
print("%s: %g" % (item, getattr(pen, item)))
wght_sum += abs(pen.area)
wght_sum_perceptual += abs(pen.area) * glyph.width
wdth_sum += glyph.width
2023-07-19 20:03:38 -06:00
slnt_sum += pen.slant
slnt_sum_perceptual += pen.slant * glyph.width
print()
print("weight: %g" % (wght_sum * upem / wdth_sum))
print("weight (perceptual): %g" % (wght_sum_perceptual / wdth_sum))
print("width: %g" % (wdth_sum / upem / len(glyphs)))
2023-07-19 20:03:38 -06:00
print("slant: %g" % (slnt_sum / len(glyphs)))
print("slant (perceptual): %g" % (slnt_sum_perceptual / wdth_sum))
def main(args):
if not args:
return
filename, glyphs = args[0], args[1:]
from fontTools.ttLib import TTFont
2022-12-13 11:26:36 +00:00
font = TTFont(filename)
if not glyphs:
glyphs = font.getGlyphOrder()
_test(font.getGlyphSet(), font["head"].unitsPerEm, glyphs)
2022-12-13 11:26:36 +00:00
if __name__ == "__main__":
import sys
2022-12-13 11:26:36 +00:00
main(sys.argv[1:])