| """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() |