Skip to content

Instantly share code, notes, and snippets.

@petrblahos
Last active November 11, 2023 16:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save petrblahos/1cbcce1eb0b4a5f54de5e6c4fbbb71e2 to your computer and use it in GitHub Desktop.
Save petrblahos/1cbcce1eb0b4a5f54de5e6c4fbbb71e2 to your computer and use it in GitHub Desktop.
Draw glyphs on a pygame surface using fonttools and SVG.
from collections import defaultdict
import io
import sys
import numpy as np
from scipy.special import comb
import pygame
from fontTools.pens.svgPathPen import (SVGPathPen, )
from fontTools.pens.basePen import (BasePen, )
from fontTools.ttLib import TTFont
class PGMFont:
WIDTH = 1280
HEIGHT = 600
def __init__(self, font):
self.font = font
self.units_per_em = self.font['head'].unitsPerEm
self.ascent = self.font["hhea"].ascent
self.descent = self.font["hhea"].descent
self.cmap = self.font['cmap'].getBestCmap()
self.glyph_set = self.font.getGlyphSet()
self.kerning_pairs = self.prepare_kerning(self.font)
def prepare_kerning(self, font: TTFont) -> dict:
pairs = defaultdict(int)
if not "kern" in self.font:
return pairs
for kern_table in self.font['kern'].kernTables:
assert kern_table.version == 0, "kern table version other than 0 not supported"
pairs.update(kern_table.kernTable)
return pairs
def prepare_window(self):
self.screen = pygame.display.set_mode((self.WIDTH, self.HEIGHT))
self.screen.fill((0, 0, 255))
def create_svg_pair(self, text):
"""
Create 2 SVG files, one for stroke, one for fill.
"""
cmds_stroke = []
cmds_fill = []
pen = SVGPathPen(self.font.getGlyphSet())
x0 = 0
last_glyph = None
for unicode in text:
glyph_name = self.cmap.get(ord(unicode))
if not glyph_name:
glyph_name = ".notdef"
glyph = self.glyph_set[glyph_name]
pen = SVGPathPen(self.glyph_set)
glyph.draw(pen)
commands = pen.getCommands()
x0 += self.kerning_pairs[(last_glyph, glyph_name)]
s = '<g transform="translate(%d %d) scale(1 -1)"><path d="%s" %%s /></g>\n' % (
x0,
self.ascent,
commands,
)
x0 += glyph.width
cmds_stroke.append(s % 'stroke="#FFF" fill="none" stroke-width="50"')
cmds_fill.append(s % 'stroke="none" fill="#F00"')
last_glyph = glyph_name
svg_stroke = "\n".join([
"""<?xml version="1.0" encoding="UTF-8"?>""",
"""<svg width="%d" height="%d" xmlns="http://www.w3.org/2000/svg">""" % (x0, self.ascent - self.descent),
"\n".join(cmds_stroke) + "</svg>",
])
svg_fill = "\n".join([
"""<?xml version="1.0" encoding="UTF-8"?>""",
"""<svg width="%d" height="%d" xmlns="http://www.w3.org/2000/svg">""" % (x0, self.ascent - self.descent),
"\n".join(cmds_fill) + "</svg>",
])
return (svg_stroke, svg_fill)
def draw_text(self, surface, text):
(svg_stroke, svg_fill) = self.create_svg_pair(text)
img = pygame.image.load(io.BytesIO(svg_stroke.encode("utf-8")))
surface.blit(img, (0, 0))
img = pygame.image.load(io.BytesIO(svg_fill.encode("utf-8")))
surface.blit(img, (0, 0))
def draw_something(self):
surface = pygame.Surface((self.units_per_em * 10, self.units_per_em * 2))
surface.fill((0, 0, 0))
self.draw_text(surface, "VADoO0-")
scale = min((self.HEIGHT / surface.get_height(), self.WIDTH / surface.get_width()))
surface = pygame.transform.smoothscale(
surface,
(int(surface.get_width() * scale), int(surface.get_height() * scale)),
)
self.screen.blit(surface, (0, 0))
def run(self):
clock = pygame.time.Clock()
do_run = True
while do_run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
do_run = False
break
self.draw_something()
pygame.display.flip()
if "__main__" == __name__:
f = TTFont("/usr/share/fonts/truetype/liberation2/LiberationSans-Regular.ttf")
pygame.init()
pgm = PGMFont(f)
pgm.prepare_window()
pgm.run()
pygame.quit()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment