xref: /aosp_15_r20/external/fonttools/Snippets/statShape.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughes"""Draw statistical shape of a glyph as an ellipse."""
2*e1fe3e4aSElliott Hughes
3*e1fe3e4aSElliott Hughesfrom fontTools.ttLib import TTFont
4*e1fe3e4aSElliott Hughesfrom fontTools.pens.recordingPen import RecordingPen
5*e1fe3e4aSElliott Hughesfrom fontTools.pens.cairoPen import CairoPen
6*e1fe3e4aSElliott Hughesfrom fontTools.pens.statisticsPen import StatisticsPen
7*e1fe3e4aSElliott Hughesimport cairo
8*e1fe3e4aSElliott Hughesimport math
9*e1fe3e4aSElliott Hughesimport sys
10*e1fe3e4aSElliott Hughes
11*e1fe3e4aSElliott Hughes
12*e1fe3e4aSElliott Hughesfont = TTFont(sys.argv[1])
13*e1fe3e4aSElliott Hughesunicode = sys.argv[2]
14*e1fe3e4aSElliott Hughes
15*e1fe3e4aSElliott Hughescmap = font["cmap"].getBestCmap()
16*e1fe3e4aSElliott Hughesgid = cmap[ord(unicode)]
17*e1fe3e4aSElliott Hughes
18*e1fe3e4aSElliott Hugheshhea = font["hhea"]
19*e1fe3e4aSElliott Hughesglyphset = font.getGlyphSet()
20*e1fe3e4aSElliott Hugheswith cairo.SVGSurface(
21*e1fe3e4aSElliott Hughes    "example.svg", hhea.advanceWidthMax, hhea.ascent - hhea.descent
22*e1fe3e4aSElliott Hughes) as surface:
23*e1fe3e4aSElliott Hughes    context = cairo.Context(surface)
24*e1fe3e4aSElliott Hughes    context.translate(0, +font["hhea"].ascent)
25*e1fe3e4aSElliott Hughes    context.scale(1, -1)
26*e1fe3e4aSElliott Hughes
27*e1fe3e4aSElliott Hughes    glyph = glyphset[gid]
28*e1fe3e4aSElliott Hughes
29*e1fe3e4aSElliott Hughes    recording = RecordingPen()
30*e1fe3e4aSElliott Hughes    glyph.draw(recording)
31*e1fe3e4aSElliott Hughes
32*e1fe3e4aSElliott Hughes    context.translate((hhea.advanceWidthMax - glyph.width) * 0.5, 0)
33*e1fe3e4aSElliott Hughes
34*e1fe3e4aSElliott Hughes    pen = CairoPen(glyphset, context)
35*e1fe3e4aSElliott Hughes    glyph.draw(pen)
36*e1fe3e4aSElliott Hughes    context.fill()
37*e1fe3e4aSElliott Hughes
38*e1fe3e4aSElliott Hughes    stats = StatisticsPen(glyphset)
39*e1fe3e4aSElliott Hughes    glyph.draw(stats)
40*e1fe3e4aSElliott Hughes
41*e1fe3e4aSElliott Hughes    # https://cookierobotics.com/007/
42*e1fe3e4aSElliott Hughes    a = stats.varianceX
43*e1fe3e4aSElliott Hughes    b = stats.covariance
44*e1fe3e4aSElliott Hughes    c = stats.varianceY
45*e1fe3e4aSElliott Hughes    delta = (((a - c) * 0.5) ** 2 + b * b) ** 0.5
46*e1fe3e4aSElliott Hughes    lambda1 = (a + c) * 0.5 + delta  # Major eigenvalue
47*e1fe3e4aSElliott Hughes    lambda2 = (a + c) * 0.5 - delta  # Minor eigenvalue
48*e1fe3e4aSElliott Hughes    theta = math.atan2(lambda1 - a, b) if b != 0 else (math.pi * 0.5 if a < c else 0)
49*e1fe3e4aSElliott Hughes    mult = 4  # Empirical by drawing '.'
50*e1fe3e4aSElliott Hughes    transform = cairo.Matrix()
51*e1fe3e4aSElliott Hughes    transform.translate(stats.meanX, stats.meanY)
52*e1fe3e4aSElliott Hughes    transform.rotate(theta)
53*e1fe3e4aSElliott Hughes    transform.scale(math.sqrt(lambda1), math.sqrt(lambda2))
54*e1fe3e4aSElliott Hughes    transform.scale(mult, mult)
55*e1fe3e4aSElliott Hughes
56*e1fe3e4aSElliott Hughes    ellipse_area = math.sqrt(lambda1) * math.sqrt(lambda2) * math.pi / 4 * mult * mult
57*e1fe3e4aSElliott Hughes
58*e1fe3e4aSElliott Hughes    if stats.area:
59*e1fe3e4aSElliott Hughes        context.save()
60*e1fe3e4aSElliott Hughes        context.set_line_cap(cairo.LINE_CAP_ROUND)
61*e1fe3e4aSElliott Hughes        context.transform(transform)
62*e1fe3e4aSElliott Hughes        context.move_to(0, 0)
63*e1fe3e4aSElliott Hughes        context.line_to(0, 0)
64*e1fe3e4aSElliott Hughes        context.set_line_width(1)
65*e1fe3e4aSElliott Hughes        context.set_source_rgba(1, 0, 0, abs(stats.area / ellipse_area))
66*e1fe3e4aSElliott Hughes        context.stroke()
67*e1fe3e4aSElliott Hughes        context.restore()
68*e1fe3e4aSElliott Hughes
69*e1fe3e4aSElliott Hughes        context.save()
70*e1fe3e4aSElliott Hughes        context.set_line_cap(cairo.LINE_CAP_ROUND)
71*e1fe3e4aSElliott Hughes        context.set_source_rgb(0.8, 0, 0)
72*e1fe3e4aSElliott Hughes        context.translate(stats.meanX, stats.meanY)
73*e1fe3e4aSElliott Hughes
74*e1fe3e4aSElliott Hughes        context.move_to(0, 0)
75*e1fe3e4aSElliott Hughes        context.line_to(0, 0)
76*e1fe3e4aSElliott Hughes        context.set_line_width(15)
77*e1fe3e4aSElliott Hughes        context.stroke()
78*e1fe3e4aSElliott Hughes
79*e1fe3e4aSElliott Hughes        context.transform(cairo.Matrix(1, 0, stats.slant, 1, 0, 0))
80*e1fe3e4aSElliott Hughes        context.move_to(0, -stats.meanY + font["hhea"].ascent)
81*e1fe3e4aSElliott Hughes        context.line_to(0, -stats.meanY + font["hhea"].descent)
82*e1fe3e4aSElliott Hughes        context.set_line_width(5)
83*e1fe3e4aSElliott Hughes        context.stroke()
84*e1fe3e4aSElliott Hughes
85*e1fe3e4aSElliott Hughes        context.restore()
86