467 lines
14 KiB
Python
467 lines
14 KiB
Python
"""
|
|
Vhex font converter
|
|
"""
|
|
|
|
from PIL import Image
|
|
|
|
from core.logger import log
|
|
|
|
__all__ = [
|
|
'conv_font_generate'
|
|
]
|
|
|
|
#---
|
|
# Internal vxconv.tmol handling routines
|
|
#---
|
|
|
|
def __font_fetch_info(asset):
|
|
""" Check and fetch font information
|
|
|
|
@arg
|
|
> asset (VxAsset) - asset information
|
|
|
|
@return
|
|
> dictionary with font information
|
|
"""
|
|
# generate font default information
|
|
font_info = {
|
|
# user can customise
|
|
'charset' : 'normal',
|
|
'grid_size_x' : 0,
|
|
'grid_size_y' : 0,
|
|
'grid_padding' : 1,
|
|
'grid_border' : 1,
|
|
'is_proportional' : False,
|
|
'line_height' : 0,
|
|
'char_spacing' : 1,
|
|
|
|
# generated "on-the-fly" by the conversion step
|
|
# @notes
|
|
# This is mainly to provide cache for the Vhex operating system to
|
|
# speed-up render calculation by avoiding recurent caculation.
|
|
'glyph_size' : 0,
|
|
'glyph_height' : 0,
|
|
'font_size' : 0,
|
|
'data' : []
|
|
}
|
|
|
|
# handle user meta-indication
|
|
if 'charset' in asset.meta:
|
|
if asset.meta['charset'] not in ['default', 'unicode']:
|
|
log.error(f"Unknown charset '{asset.meta['charset']}', abord")
|
|
return None
|
|
font_info['charset'] = asset.meta['charset']
|
|
if 'grid_size' not in asset.meta:
|
|
log.error("Missing critical grid size information, abord")
|
|
return None
|
|
grid_size = asset.meta['grid_size'].split('x')
|
|
font_info['grid_size_x'] = int(grid_size[0])
|
|
font_info['grid_size_y'] = int(grid_size[1])
|
|
if 'grid_padding' in asset.meta:
|
|
font_info['grid_padding'] = int(asset.meta['grid_padding'])
|
|
if 'grid_border' in asset.meta:
|
|
font_info['grid_border'] = int(asset.meta['grid_border'])
|
|
if 'proportional' in asset.meta:
|
|
font_info['is_proportional'] = asset.meta['proportional']
|
|
font_info['line_height'] = font_info['grid_size_y']
|
|
if 'line_height' in asset.meta:
|
|
font_info['line_height'] = asset.meta['line_height']
|
|
if 'char_spacing' in asset.meta:
|
|
font_info['char_spacing'] = asset.meta['char_spacing']
|
|
font_info['glyph_height'] = font_info['grid_size_y']
|
|
|
|
# return font information
|
|
return font_info
|
|
|
|
#---
|
|
# Internal glyph routines
|
|
#---
|
|
|
|
def __glyph_get_wgeometry(geometry_info, img_raw, img_size, pos, grid_size):
|
|
""" Generate glyph width geometry information
|
|
|
|
@args
|
|
> geometry_info (dict) - geometry information
|
|
> img_raw (list) - list of all pixel of the image
|
|
> img_size (tuple) - image width and image height
|
|
> pos (tuple) - glyph position information (X and Y in pixel)
|
|
> grid_size (tuple) - glyph grid size information (width and height)
|
|
|
|
@return
|
|
> Nothing
|
|
"""
|
|
|
|
geometry_info['wstart'] = -1
|
|
geometry_info['wend'] = -1
|
|
|
|
_px = pos[0]
|
|
_py = pos[1]
|
|
log.debug(f'[geometry] X:{pos[0]} Y:{int(pos[1]/img_size[0])}')
|
|
log.debug(f' - grid_size = {grid_size}')
|
|
for _ in range(0, grid_size[1]):
|
|
for offx in range(0, grid_size[0]):
|
|
if img_raw[_py + (_px + offx)][:3] == (255, 255, 255):
|
|
continue
|
|
if geometry_info['wstart'] < 0 or offx < geometry_info['wstart']:
|
|
geometry_info['wstart'] = offx
|
|
if geometry_info['wstart'] < 0 or offx > geometry_info['wend']:
|
|
geometry_info['wend'] = offx
|
|
_py += img_size[0]
|
|
geometry_info['wend'] += 1
|
|
log.debug(f' - geometry = {geometry_info}')
|
|
|
|
def __glyph_encode(data_info, img_info, geometry, posx, posy):
|
|
""" Encode glyph bitmap
|
|
|
|
@args
|
|
> data_info (dict) - internal data information (list, index and shift)
|
|
> img_info (dict) - image-related information (object and raw content)
|
|
> geometry (dict) - geometry information
|
|
> posx (int) - X-axis position in pixel
|
|
> posy (int) - Y-axis position in pixel
|
|
|
|
@return
|
|
> Nothing
|
|
"""
|
|
# fetch information
|
|
img = img_info['obj']
|
|
img_raw = img_info['raw']
|
|
data = data_info['table']
|
|
data_idx = data_info['idx']
|
|
data_shift = data_info['shift']
|
|
wstart = geometry['wstart']
|
|
wend = geometry['wend']
|
|
|
|
# encode the glyph
|
|
yoff = 0
|
|
log.debug(f'[encode] X:{posx} Y:{int(posy/img.size[0])}')
|
|
for _h in range(geometry['hstart'], geometry['hend']):
|
|
for _w in range(wstart, wend):
|
|
if img_raw[(posy + yoff) + (posx + _w)][:3] == (0, 0, 0):
|
|
log.debug('#', end='')
|
|
data[data_idx] |= 0x80000000 >> data_shift
|
|
else:
|
|
log.debug('.', end='')
|
|
data[data_idx] &= ~(0x80000000 >> data_shift)
|
|
if (data_shift := data_shift + 1) >= 32:
|
|
data_shift = 0
|
|
data_idx += 1
|
|
log.debug('')
|
|
yoff += img.size[0]
|
|
|
|
# commit modification
|
|
data_info['idx'] = data_idx
|
|
data_info['shift'] = data_shift
|
|
|
|
#---
|
|
# Intenal font conversion
|
|
#---
|
|
|
|
def __font_convert_proportional(packed_info):
|
|
""" Generate proportional font
|
|
|
|
Proportional font means that each character have its own width size (but
|
|
have a common height). We need to performs more complexe handling than the
|
|
monospaced one.
|
|
|
|
@args
|
|
> asset (VxAsset) - asset information
|
|
> font_information (dict) - font indication
|
|
|
|
@return
|
|
> 0 if success, negative value otherwise
|
|
"""
|
|
# unpack information
|
|
font_info = packed_info[0]
|
|
img_info = packed_info[1]
|
|
glyph_info = packed_info[2]
|
|
data_info = packed_info[4]
|
|
geometry_info = packed_info[5]
|
|
|
|
# isolate needed information
|
|
img = img_info['obj']
|
|
img_raw = img_info['raw']
|
|
nb_col = packed_info[3][0]
|
|
nb_row = packed_info[3][1]
|
|
gwidth = glyph_info[0]
|
|
gheight = glyph_info[1]
|
|
|
|
# main loop, walk glyph per glyph
|
|
_py = (font_info['grid_border'] + font_info['grid_padding']) * img.size[0]
|
|
for _ in range(0, nb_row):
|
|
_px = font_info['grid_border'] + font_info['grid_padding']
|
|
for _ in range(0, nb_col):
|
|
# generate width geometry information
|
|
__glyph_get_wgeometry(
|
|
geometry_info,
|
|
img_raw,
|
|
img.size,
|
|
(_px, _py),
|
|
(font_info['grid_size_x'], font_info['grid_size_y'])
|
|
)
|
|
|
|
# save critical glyph geometry information that will be encoded in
|
|
# the final C source file
|
|
font_info['glyph_props'].append((
|
|
geometry_info['wend'] - geometry_info['wstart'],
|
|
data_info['idx'],
|
|
data_info['shift']
|
|
))
|
|
|
|
# encode glyph information
|
|
__glyph_encode(data_info, img_info, geometry_info, _px, _py)
|
|
|
|
# update loop information
|
|
font_info['glyph_count'] += 1
|
|
_px += gwidth
|
|
_py += gheight * img.size[0]
|
|
return 0
|
|
|
|
def __font_convert_monospaced(packed_info):
|
|
""" Generate proportional font
|
|
|
|
Proportional font means that each character have its own width size (but
|
|
have a common height). We need to performs more complexe handling than the
|
|
monospaced one.
|
|
|
|
@args
|
|
> asset (VxAsset) - asset information
|
|
> font_information (dict) - font indication
|
|
|
|
@return
|
|
> 0 if success, negative value otherwise
|
|
"""
|
|
# unpack information
|
|
font_info = packed_info[0]
|
|
img_info = packed_info[1]
|
|
glyph_info = packed_info[2]
|
|
grid_info = packed_info[3]
|
|
data_info = packed_info[4]
|
|
geometry_info = packed_info[5]
|
|
|
|
# isolate needed information
|
|
img = img_info['obj']
|
|
nb_row = grid_info[1]
|
|
nb_col = grid_info[0]
|
|
gwidth = glyph_info[0]
|
|
gheight = glyph_info[1]
|
|
|
|
# main loop, walk glyph per glyph
|
|
_py = (font_info['grid_border'] + font_info['grid_padding']) * img.size[0]
|
|
for _ in range(0, nb_row):
|
|
_px = font_info['grid_border'] + font_info['grid_padding']
|
|
for _ in range(0, nb_col):
|
|
__glyph_encode(data_info, img_info, geometry_info, _px, _py)
|
|
font_info['glyph_count'] += 1
|
|
_px += gwidth
|
|
_py += gheight * img.size[0]
|
|
return 0
|
|
|
|
def __font_convert(asset, font_info):
|
|
""" Generate font information
|
|
|
|
@args
|
|
> asset (VxAsset) - asset information
|
|
> font_info (dict) - font information
|
|
|
|
@return
|
|
> 0 if success, negative value otherwise
|
|
"""
|
|
# generate image information
|
|
img = Image.open(asset.path)
|
|
img_raw = img.getdata()
|
|
img_info = {
|
|
'obj' : img,
|
|
'raw' : img_raw
|
|
}
|
|
|
|
# pre-calculate the "real" glyph width and height using padding information
|
|
glyph_info = [0, 0]
|
|
glyph_info[0] = font_info['grid_size_x'] + font_info['grid_padding']
|
|
glyph_info[1] = font_info['grid_size_y'] + font_info['grid_padding']
|
|
gheight = glyph_info[1]
|
|
gwidth = glyph_info[0]
|
|
log.debug(f"gwidth = {gwidth} && gheight = {gheight}")
|
|
|
|
# pre-calculate the number of row and column of the font
|
|
grid_info = [0, 0]
|
|
grid_info[0] = int((img.size[0] - (font_info['grid_border'] * 2)) / gwidth)
|
|
grid_info[1] = int((img.size[1] - (font_info['grid_border'] * 2)) / gheight)
|
|
nb_col = grid_info[0]
|
|
nb_row = grid_info[1]
|
|
log.debug(f"nb_row = {nb_row} && nb_col = {nb_col}")
|
|
|
|
# pre-calculate and prepare per-glyph information
|
|
# @note
|
|
# The generated data is designed for 4-alignement padding. This to have
|
|
# speed-up on drawing function.
|
|
font_info['glyph_size'] = font_info['grid_size_x'] * font_info['grid_size_y']
|
|
font_info['font_size'] = font_info['glyph_size'] * nb_row * nb_col
|
|
font_info['glyph_count'] = 0
|
|
font_info['glyph_props'] = []
|
|
font_info['data'] = [0] * int((font_info['font_size'] + 31) / 32)
|
|
log.debug(f"data original = {id(font_info['data'])}")
|
|
|
|
# generate data information
|
|
data_info = {
|
|
'table' : font_info['data'],
|
|
'idx' : 0,
|
|
'shift' : 0
|
|
}
|
|
log.debug(f"data packed = {id(data_info['table'])}")
|
|
|
|
# generate geometry information
|
|
geometry_info = {
|
|
'hstart' : 0,
|
|
'hend' : font_info['grid_size_y'],
|
|
'wstart' : 0,
|
|
'wend' : font_info['grid_size_x'],
|
|
}
|
|
|
|
# select the converter
|
|
converter = __font_convert_monospaced
|
|
if font_info['is_proportional']:
|
|
converter = __font_convert_proportional
|
|
|
|
# convert font
|
|
converter((
|
|
font_info,
|
|
img_info,
|
|
glyph_info,
|
|
grid_info,
|
|
data_info,
|
|
geometry_info
|
|
))
|
|
|
|
log.debug(f"data packed end = {id(data_info['table'])}")
|
|
return 0
|
|
|
|
#---
|
|
# Source file generation
|
|
#---
|
|
|
|
def __font_generate_unicode_source(_):
|
|
"""Unicode special chaset directory"""
|
|
log.error("unicode conversion not implemented yet o(x_x)o")
|
|
return ''
|
|
|
|
def __font_generate_normal_source(font_info):
|
|
"""Print chaset is a image file
|
|
|
|
"""
|
|
content = "\t.glyph = {\n"
|
|
content += f"\t\t.height = {font_info['glyph_height']},\n"
|
|
content += f"\t\t.line_height = {font_info['line_height']},\n"
|
|
|
|
# encode font bitmap
|
|
line = 0
|
|
log.debug(f"data = {font_info['data']}")
|
|
content += "\t\t.data = (uint32_t[]){\n"
|
|
for pixel in font_info['data']:
|
|
if line == 0:
|
|
content += '\t\t\t'
|
|
if line >= 1:
|
|
content += ' '
|
|
content += f"{pixel:#010x},"
|
|
if (line := line + 1) == 4:
|
|
content += '\n'
|
|
line = 0
|
|
if line != 0:
|
|
content += '\n'
|
|
content += '\t\t},\n'
|
|
|
|
# indicate the number of glyph in the bitmap
|
|
content += f"\t\t.count = {font_info['glyph_count']},\n"
|
|
|
|
# encode proportional information if needed
|
|
if font_info['is_proportional']:
|
|
content += '\t\t.prop = (struct __workaround[]){\n'
|
|
for prop in font_info['glyph_props']:
|
|
content += "\t\t\t{\n"
|
|
content += f"\t\t\t\t.width = {prop[0]},\n"
|
|
content += f"\t\t\t\t.index = {prop[1]},\n"
|
|
content += f"\t\t\t\t.shift = {prop[2]},\n"
|
|
content += "\t\t\t},\n"
|
|
else:
|
|
content += "\t\t.mono = {,\n"
|
|
content += f"\t\t\t.width = {font_info['glyph_width']},\n"
|
|
content += f"\t\t\t.size = {font_info['glyph_size']},\n"
|
|
content += "\t\t},\n"
|
|
content += "\t},\n"
|
|
|
|
# skip unicode struct
|
|
content += "\t.unicode = {\n"
|
|
content += "\t\t.blocks = NULL,\n"
|
|
content += "\t\t.block_count = 0,\n"
|
|
content += "\t}\n"
|
|
return content
|
|
|
|
def __font_generate_source_file(asset, font_info):
|
|
"""Generate font source file content
|
|
|
|
@args
|
|
> asset (VxAsset) - asset information
|
|
> info (dict) - hold font information
|
|
|
|
@return
|
|
> file C content string
|
|
"""
|
|
# generate basic header
|
|
content = "#include <vhex/display/font.h>\n"
|
|
content += "\n"
|
|
content += f"/* {asset.name} - Vhex asset\n"
|
|
content += " This object has been converted by using the vxSDK "
|
|
content += "converter */\n"
|
|
content += f"struct font const {asset.name} = " + "{\n"
|
|
content += f"\t.name = \"{asset.name}\",\n"
|
|
|
|
# shape information
|
|
content += "\t.shape = {\n"
|
|
content += "\t\t.bold = 0,\n"
|
|
content += "\t\t.italic = 0,\n"
|
|
content += "\t\t.serif = 0,\n"
|
|
content += "\t\t.mono = 0,\n"
|
|
content += f"\t\t.prop = {int(font_info['is_proportional'])},\n"
|
|
content += "\t},\n"
|
|
|
|
# manage display indication
|
|
content += f"\t.char_spacing = {font_info['char_spacing']},\n"
|
|
|
|
# handle special charset behaviour
|
|
if font_info['charset'] == 'unicode':
|
|
content += __font_generate_unicode_source(font_info)
|
|
else:
|
|
content += __font_generate_normal_source(font_info)
|
|
|
|
# closure and return
|
|
content += '};\n'
|
|
return content
|
|
|
|
#---
|
|
# Public
|
|
#---
|
|
|
|
def conv_font_generate(asset, prefix_output):
|
|
""" Convert an image asset to a C source file
|
|
|
|
@args
|
|
> asset (_VxAsset) - minimal asset information
|
|
> prefix_output (str) - prefix for source file generation
|
|
|
|
@return
|
|
> pathname of the generated file
|
|
"""
|
|
# generate font information
|
|
if not (font_info := __font_fetch_info(asset)):
|
|
return ''
|
|
if __font_convert(asset, font_info) != 0:
|
|
return ''
|
|
content = __font_generate_source_file(asset, font_info)
|
|
|
|
# create the source file
|
|
asset_src = f'{prefix_output}/{asset.name}_vxfont.c'
|
|
with open(asset_src, "w", encoding='utf8') as file:
|
|
file.write(content)
|
|
log.debug(f"source file generated at {asset_src}")
|
|
return asset_src
|