Skip to content

Instantly share code, notes, and snippets.

@DavidBuchanan314
Last active January 8, 2023 04:41
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 DavidBuchanan314/f1bd4d398230e3109f6972aea795b4a8 to your computer and use it in GitHub Desktop.
Save DavidBuchanan314/f1bd4d398230e3109f6972aea795b4a8 to your computer and use it in GitHub Desktop.
Demo of rendering TrueType fonts in the terminal, in a figlet-like way. (p.s. it segfaults occasionally, lol)
import cairocffi
import pangocffi
import pangocairocffi
import sys
import os
try:
width, height = os.get_terminal_size().columns, 1024
except OSError:
# There doesn't seem to be a neat way of figuring out the size of some text without
# first setting up a surface. As such, we set up a hopefully-bigger-than-needed surface
# and crop it at the end
width, height = 1024, 1024
MONOCHROME = False
# Create the surface and get the context
if MONOCHROME:
surface = cairocffi.ImageSurface(cairocffi.FORMAT_A1, width, height)
else:
surface = cairocffi.ImageSurface(cairocffi.FORMAT_ARGB32, width, height)
context = cairocffi.Context(surface)
options = cairocffi.FontOptions()
options.set_antialias(cairocffi.ANTIALIAS_NONE)
options.set_hint_style(cairocffi.HINT_STYLE_FULL)
options.set_hint_metrics(cairocffi.HINT_METRICS_ON)
context.set_font_options(options)
# Build the layout
layout = pangocairocffi.create_layout(context)
layout.width = pangocffi.units_from_double(width)
layout._set_wrap(pangocffi.WrapMode.WORD_CHAR)
layout.apply_markup(f'<span font="{sys.argv[1]}" foreground="white">{sys.argv[2]}</span>') # XXX: use a proper XML library or something here!!!
# find actual size of text
w, h = map(int, map(pangocffi.units_to_double, layout.get_size()))
# Render the layout
pangocairocffi.show_layout(context, layout)
surface.flush() # is this actually necessary?
def is_simple(r, g, b, a):
return (r, g, b, a) in [(255, 255, 255, 255), (0, 0, 0, 0)]
if MONOCHROME:
data = bytes(surface.get_data())
stride = surface.get_stride()
GLYPYS = [
[" ", "▄"],
["▀", "█"],
]
for y in range(0, h + 1, 2):
row = ""
for x in range(w):
top = (data[(y+0)*stride + x // 8] >> (x % 8)) & 1
bottom = (data[(y+1)*stride + x // 8] >> (x % 8)) & 1
row += GLYPYS[top][bottom]
print(row)
else:
data = bytes(surface.get_data())
stride = surface.get_stride()
GLYPYS = [
[" ", "▄"],
["▀", "█"],
]
for y in range(0, h + 1, 2):
row = ""
prev_simple = True
for x in range(w):
i = (y+0)*stride + x * 4
b0, g0, r0, a0 = data[i:i+4]
i = (y+1)*stride + x * 4
b1, g1, r1, a1 = data[i:i+4]
simple = is_simple(r0, g0, b0, a0) and is_simple(r1, g1, b1, a1)
if simple:
if not prev_simple:
row += "\x1b[0m"
row += GLYPYS[r0&1][r1&1]
else:
row += f"\x1b[38;2;{r0};{g0};{b0}m\x1b[48;2;{r1};{g1};{b1}m▀"
prev_simple = simple
row += "\x1b[0m"
print(row)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment