Last active
February 12, 2019 14:17
-
-
Save rossant/a333e197b5aeae53003d to your computer and use it in GitHub Desktop.
vispy text
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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