blob: e0b0d69ed90263541ce9cd87af2072ade70889ea [file] [log] [blame]
"""Draw statistical shape of a glyph as an ellipse."""
from fontTools.ttLib import TTFont
from fontTools.pens.recordingPen import RecordingPen
from fontTools.pens.cairoPen import CairoPen
from fontTools.pens.statisticsPen import StatisticsPen
import cairo
import math
import sys
font = TTFont(sys.argv[1])
unicode = sys.argv[2]
cmap = font["cmap"].getBestCmap()
gid = cmap[ord(unicode)]
hhea = font["hhea"]
glyphset = font.getGlyphSet()
with cairo.SVGSurface(
"example.svg", hhea.advanceWidthMax, hhea.ascent - hhea.descent
) as surface:
context = cairo.Context(surface)
context.translate(0, +font["hhea"].ascent)
context.scale(1, -1)
glyph = glyphset[gid]
recording = RecordingPen()
glyph.draw(recording)
context.translate((hhea.advanceWidthMax - glyph.width) * 0.5, 0)
pen = CairoPen(glyphset, context)
glyph.draw(pen)
context.fill()
stats = StatisticsPen(glyphset)
glyph.draw(stats)
# https://cookierobotics.com/007/
a = stats.varianceX
b = stats.covariance
c = stats.varianceY
delta = (((a - c) * 0.5) ** 2 + b * b) ** 0.5
lambda1 = (a + c) * 0.5 + delta # Major eigenvalue
lambda2 = (a + c) * 0.5 - delta # Minor eigenvalue
theta = math.atan2(lambda1 - a, b) if b != 0 else (math.pi * 0.5 if a < c else 0)
mult = 4 # Empirical by drawing '.'
transform = cairo.Matrix()
transform.translate(stats.meanX, stats.meanY)
transform.rotate(theta)
transform.scale(math.sqrt(lambda1), math.sqrt(lambda2))
transform.scale(mult, mult)
ellipse_area = math.sqrt(lambda1) * math.sqrt(lambda2) * math.pi / 4 * mult * mult
if stats.area:
context.save()
context.set_line_cap(cairo.LINE_CAP_ROUND)
context.transform(transform)
context.move_to(0, 0)
context.line_to(0, 0)
context.set_line_width(1)
context.set_source_rgba(1, 0, 0, abs(stats.area / ellipse_area))
context.stroke()
context.restore()
context.save()
context.set_line_cap(cairo.LINE_CAP_ROUND)
context.set_source_rgb(0.8, 0, 0)
context.translate(stats.meanX, stats.meanY)
context.move_to(0, 0)
context.line_to(0, 0)
context.set_line_width(15)
context.stroke()
context.transform(cairo.Matrix(1, 0, stats.slant, 1, 0, 0))
context.move_to(0, -stats.meanY + font["hhea"].ascent)
context.line_to(0, -stats.meanY + font["hhea"].descent)
context.set_line_width(5)
context.stroke()
context.restore()