I'm writting a simple bitmap font renderer in pySFML and wanted to ask is there a better and faster way to approach this problem.
I'm using VertexArray and create a quad for each character in a string. Each quad has appropriate texture coordinates applied.
Example font (PNG file):
Font rendering code:
import sfml
class BitmapFont(object):
'''
Loads a bitmap font.
`chars` is string with all characters available in the font file, example: '+0123456789x'.
`widths` is mapping between characters and character width in pixels.
'''
def __init__(self, path, chars, widths, colors=1, kerning=0):
self.texture = sfml.Texture.from_file(path)
self.colors = colors
self.height = self.texture.height / self.colors
self.chars = chars
self.kerning = kerning
self.widths = widths
self.glyphs = []
y = 0
for color in range(self.colors):
x = 0
self.glyphs.append({})
for char in self.chars:
glyph_pos = x, y
glyph_size = self.widths[char], self.height
glyph = sfml.Rectangle(glyph_pos, glyph_size)
self.glyphs[color][char] = glyph
x += glyph.width
y += self.height
class BitmapText(sfml.TransformableDrawable):
'''Used to render text with `BitmapFonts`.'''
def __init__(self, string='', font=None, color=0, align='left', position=(0, 0)):
super().__init__()
self.vertices = sfml.VertexArray(sfml.PrimitiveType.QUADS, 4)
self.font = font
self.color = color
self._string = ''
self.string = string
self.position = position
@property
def string(self):
return self._string
@string.setter
def string(self, value):
'''Calculates new vertices each time string has changed.'''
# This function is slowest and probably can be optimized.
if value == self._string:
return
if len(value) != len(self._string):
self.vertices.resize(4 * len(value))
self._string = value
x = 0
y = 0
vertices = self.vertices
glyphs = self.font.glyphs[self.color]
for i, char in enumerate(self._string):
glyph = glyphs[char]
p = i * 4
vertices[p + 0].position = x, y
vertices[p + 1].position = x + glyph.width, y
vertices[p + 2].position = x + glyph.width, y + glyph.height
vertices[p + 3].position = x, y + glyph.height
vertices[p + 0].tex_coords = glyph.left, glyph.top
vertices[p + 1].tex_coords = glyph.right, glyph.top
vertices[p + 2].tex_coords = glyph.right, glyph.bottom
vertices[p + 3].tex_coords = glyph.left, glyph.bottom
x += glyph.width + self.font.kerning
def draw(self, target, states):
'''Draws whole string using texture from a font.'''
states.texture = self.font.texture
states.transform = self.transform
target.draw(self.vertices, states)
Simple benchmark with FPS counter:
from random import random, randint
import sfml
from font import BitmapFont, BitmapText
font = sfml.Font.from_file('arial.ttf')
bitmap_font = BitmapFont('font.png', chars='-x+0123456789 ', kerning=-3,
widths={'x': 21, '+': 18, '0': 18, '1': 14, '2': 18, '3': 18, '4': 19, '5': 18, '6': 18,
'7': 17, '8': 18, '9': 18, '-': 17, ' ': 8})
window = sfml.RenderWindow(sfml.VideoMode(960, 640), 'Font test')
fps_text = sfml.Text('', font, 18)
fps_text.position = 10, 10
fps_text.color = sfml.Color.WHITE
fps_text_shadow = sfml.Text('', font, 18)
fps_text_shadow.position = 12, 12
fps_text_shadow.color = sfml.Color.BLACK
frame = fps = frame_time = 0
clock = sfml.Clock()
texts = [BitmapText('x01234 56789', font=bitmap_font, color=randint(0, bitmap_font.colors - 1)) for i in range(1000)]
while window.is_open:
for event in window.events:
if type(event) is sfml.CloseEvent:
window.close()
time_delta = clock.restart().seconds
if time_delta > .2:
continue
frame_time += time_delta
if frame_time >= 1:
fps = frame
frame_time = frame = 0
fps_text_shadow.string = fps_text.string = 'FPS: {fps}'.format(fps=fps)
else:
frame += 1
window.clear(sfml.Color(63, 63, 63))
for t in texts:
t.position = random() * 960, random() * 640
t.string = str(randint(0, 10000000))
window.draw(t)
window.draw(fps_text_shadow)
window.draw(fps_text)
window.display()
I'm using Python 3.3, pySFML 1.3, SFML 2.0 and Windows.
Laurent Gomila (author of SFML) confirmed in other forum, that my approach to bitmap fonts is same as vector fonts implementation in SFML (namely VertexArray and quad for each character).