from path import Path
from math import cos, sin
from vulk.graphic.texture import HighQualityTexture, TextureRegion
from vulk.graphic.d2.batch import CharBatch
[docs]class FontData():
"""Load a BMFont Text file into a FontData
The FontData can be rendered with the CharBatch.
See http://www.angelcode.com/products/bmfont/doc/file_format.html
"""
def __init__(self, context, filepath):
self.filepath = Path(filepath)
self.raw_data = FontData.load_bmfont(filepath)
self.pages = self._init_pages(context)
self.regions = self._init_regions()
self.chars = self._init_chars()
self.kernings = self._init_kernings()
self.base = int(self.raw_data['common']['base'])
def _init_pages(self, context):
"""Create Texture for each page
Args:
context (VulkContext): Vulk context
Returns:
Page indexed dict
"""
res = {}
dirpath = self.filepath.parent
for p in self.raw_data['page']:
res[p['id']] = HighQualityTexture(context, dirpath / p['file'])
return res
def _init_regions(self):
"""Create a TextureRegion for each char
Returns:
Char indexed dict
"""
res = {}
for c in self.raw_data['char']:
k = chr(c['id'])
t = self.pages[c['page']]
x = c['x']
y = c['y']
w = c['width']
h = c['height']
res[k] = TextureRegion.from_pixels(t, x, y, w, h)
return res
def _init_chars(self):
"""Create dict indexed key for each char
Returns:
Char indexed dict
"""
res = {}
for c in self.raw_data['char']:
k = chr(c['id'])
res[k] = c
return res
def _init_kernings(self):
"""Create tuple(char1, char2) indexed key for each kerning
Returns:
tuple indexed dict
"""
res = {}
for k in self.raw_data['kerning']:
c1 = chr(k['first'])
c2 = chr(k['second'])
res[(c1, c2)] = k['amount']
return res
[docs] def get_region(self, char):
"""Get texture region of char in this FontData
Args:
char (str): One character to find
"""
return self.regions[char]
[docs] def get_sizes(self, char):
"""Get size of char in this FontData
Args:
char (str): One character to find
"""
return (self.chars[char]['width'], self.chars[char]['height'])
[docs] def get_kerning(self, previous_char, current_char):
"""Get kerning between last and current char
Args:
previous_char (str): Previous character
current_char (str): Current character
"""
try:
x = self.kernings[(previous_char, current_char)]
except KeyError:
x = 0
return x
[docs] @staticmethod
def load_bmfont(filepath):
"""Convert the BMFont file into a dict
Args:
filename (str): BMFont file
"""
def extract(attributes):
result = {}
for attrib in attributes:
key, value = attrib.split("=")
try:
result[key] = int(value)
except ValueError:
strval = value.strip('"')
if ',' in strval:
arry = strval.split(',')
try:
arry = map(int, arry)
finally:
result[key] = arry
else:
result[key] = strval
return result
with open(filepath) as f:
lines = f.readlines()
atlas = {}
for line in lines:
attributes = [x for x in line.split()]
k = attributes[0]
res = extract(attributes[1:])
if k in ("char", "page", "kerning"):
if not atlas.get(k):
atlas[k] = []
atlas[k].append(res)
else:
atlas[k] = res
return atlas
[docs]class TextRenderer():
"""TextRenderer performs computation to draw text"""
def __init__(self, context, batch=None):
"""Initialize TextRenderer
Args:
context (VulkContext): Context
batch: Batch with `draw_char` function
"""
if not batch:
batch = CharBatch(context)
self.batch = batch
[docs] def begin(self, context, semaphores=None):
"""Start rendering
Args:
context (VulkContext): Context
semaphores (list): Semaphore list
"""
self.batch.begin(context, semaphores=semaphores)
[docs] def end(self):
"""Stop rendering
Returns:
Semaphore signaled when all drawing operations are finished
"""
return self.batch.end()
[docs] def draw(self, fontdata, text, x, y, size, r=1., g=1., b=1., a=1.,
rotation=0.):
"""Render text on screen
Args:
fontdata (FontData): Font to render
text (str): String to render
x (int): X position (from left)
y (int): Y position (from top)
size (float): Pixel size of font
r (float): Red channel
g (float): Green channel
b (float): Blue channel
a (float): Alpha channel
rotation (float): Rotation in radian (clockwise)
"""
x_abs = x
y_abs = y
x_current = 0
y_current = 0
scale = size / fontdata.raw_data['info']['size']
previous_char = None
for char in text:
# Compute kerning
kerning = 0
if previous_char is not None:
kerning = fontdata.get_kerning(previous_char, char)
# Compute position
char_info = fontdata.chars[char]
x = x_current + (char_info['xoffset'] + kerning) * scale
y = y_current + char_info['yoffset'] * scale
# Compute rotation
t = rotation
x2 = x * cos(t) - y * sin(t)
y2 = x * sin(t) + y * cos(t)
# Add absolution position
x2 += x_abs
y2 += y_abs
# Draw char
# TODO: rotation is not properly handled
# Rotation is done at char center, we should add offset
# to take rotation into account
# I don't have time to do the math work right now
self.batch.draw_char(fontdata, char, x2, y2, r, g, b, a,
scale, scale, t)
# Register variable
x_current += char_info['xadvance'] * scale
previous_char = char