Skip to content

Instantly share code, notes, and snippets.

@rossant
Last active February 12, 2019 14:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rossant/a333e197b5aeae53003d to your computer and use it in GitHub Desktop.
Save rossant/a333e197b5aeae53003d to your computer and use it in GitHub Desktop.
vispy text
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import numpy as np
from vispy import gloo
from vispy import app
def makefont(filename, size):
from freetype import Face, FT_LOAD_RENDER, FT_LOAD_FORCE_AUTOHINT
rows, cols = 6, 16
# Load font and check it is monotype
face = Face(filename)
face.set_char_size(size * 64)
if not face.is_fixed_width:
raise 'Font is not monotype'
# Determine largest glyph size
width, height, ascender, descender = 0, 0, 0, 0
for c in range(32, 128):
face.load_char(chr(c), FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT)
bitmap = face.glyph.bitmap
width = max(width, bitmap.width)
ascender = max(ascender, face.glyph.bitmap_top)
descender = max(descender, bitmap.rows - face.glyph.bitmap_top)
height = ascender + descender
# Generate texture data
Z = np.zeros((height * rows, width * cols), dtype=np.uint8)
chars = ''
for j in range(rows):
for i in range(cols):
s = chr(32 + j * 16 + i)
chars += s
face.load_char(s,
FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT)
bitmap = face.glyph.bitmap
x = i * width + face.glyph.bitmap_left
y = j * height + ascender - face.glyph.bitmap_top
Z[y:y + bitmap.rows, x:x + bitmap.width].flat = bitmap.buffer
with open('chars.txt', 'w') as f:
f.write(chars)
return Z
class Font(object):
def __init__(self, name, size):
fn = '%s-%d.npy' % (name, size)
if not os.path.exists(fn):
Z = makefont('%s.ttf' % name, size)
np.save(fn, Z)
else:
Z = np.load(fn)
self._tex = Z
with open('chars.txt', 'r') as f:
self._chars = f.read()
def get_indices(self, s):
return [self._chars.index(char) for char in s]
if __name__ == '__main__':
import sys
size = int(sys.argv[1])
f = Font('SourceCodePro-Regular', size)
import sys
import numpy as np
import matplotlib.pyplot as plt
import seaborn
name = 'SourceCodePro-Regular'
font_size = int(sys.argv[1]) if len(sys.argv) >= 2 else 64
Z = np.load('%s-%d.npy' % (name, font_size))
plt.imshow(Z, interpolation='none')
plt.grid(False)
plt.show()
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import numpy as np
from vispy import gloo
from vispy import app
class Font(object):
def __init__(self, name, size):
fn = '%s-%d.npy' % (name, size)
self.tex = np.load(fn)
with open('chars.txt', 'r') as f:
self.chars = f.read()
def get_indices(self, s):
return [self.chars.index(char) for char in s]
VERT_SHADER = """
attribute vec2 a_position; // text position
attribute float a_glyph_index; // glyph index in the text
attribute float a_quad_index; // quad index in the glyph
attribute float a_char_index; // index of the glyph in the texture
uniform vec2 u_glyph_size; // (w, h)
uniform vec2 u_window; // (w, h)
varying vec2 v_tex_coords;
const float rows = 6;
const float cols = 16;
void main() {
float w = u_glyph_size.x / u_window.x;
float h = u_glyph_size.y / u_window.y;
float dx = mod(a_quad_index, 2.);
float dy = 0.;
if ((2. <= a_quad_index) && (a_quad_index <= 4.)) {
dy = 1.;
}
// Position of the glyph.
vec2 pos = a_position + vec2(a_glyph_index * w + dx * w, dy * h);
gl_Position = vec4(pos, 0., 1.);
// Index in the texture
float i = floor(a_char_index / cols);
float j = mod(a_char_index, cols);
// uv position in the texture for the glyph.
vec2 uv = vec2(j, rows - 1. - i);
uv /= vec2(cols, rows);
// Little margin to avoid edge effects between glyphs.
dx = .01 + .98 * dx;
dy = .01 + .98 * dy;
// Texture coordinates for the fragment shader.
vec2 duv = vec2(dx / cols, dy /rows);
v_tex_coords = uv + duv;
}
"""
FRAG_SHADER = """
uniform sampler2D u_tex;
varying vec2 v_tex_coords;
void main() {
gl_FragColor = texture2D(u_tex, v_tex_coords);
}
"""
class Canvas(app.Canvas):
def __init__(self, name, font_size):
app.Canvas.__init__(self, keys='interactive')
f = Font(name, font_size)
text = '123.456'
a_char_index = f.get_indices(text)
tex = f.tex
glyph_height = tex.shape[0] // 6
glyph_width = tex.shape[1] // 16
glyph_size = (glyph_width, glyph_height)
self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)
n_glyphs = len(a_char_index)
pos = (0., 0.)
a_position = np.repeat([pos], n_glyphs, axis=0)
a_glyph_index = np.arange(n_glyphs)
a_quad_index = np.arange(6)
a_position = np.repeat(a_position, 6, axis=0)
a_glyph_index = np.repeat(a_glyph_index, 6)
a_quad_index = np.tile(a_quad_index, n_glyphs)
a_char_index = np.repeat(a_char_index, 6)
n_vertices = n_glyphs * 6
assert a_position.shape == (n_vertices, 2)
assert a_glyph_index.shape == (n_vertices,)
assert a_quad_index.shape == (n_vertices,)
assert a_char_index.shape == (n_vertices,)
self.program['a_position'] = a_position.astype(np.float32)
self.program['a_glyph_index'] = a_glyph_index.astype(np.float32)
self.program['a_quad_index'] = a_quad_index.astype(np.float32)
self.program['a_char_index'] = a_char_index.astype(np.float32)
self.program['u_glyph_size'] = glyph_size
self.program['u_window'] = self.size
self.program['u_tex'] = gloo.Texture2D(tex[::-1, :])
self.show()
def on_resize(self, event):
width, height = event.size
gloo.set_viewport(0, 0, width, height)
self.program['u_window'] = self.size
self.update()
def on_draw(self, event):
gloo.clear()
self.program.draw('triangles')
if __name__ == '__main__':
import sys
name = 'SourceCodePro-Regular'
font_size = int(sys.argv[1]) if len(sys.argv) >= 2 else 48
c = Canvas(name, font_size)
if sys.flags.interactive != 1:
app.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment