update font_to_py to support non-sequential charsets

pull/3/head
Brian Cappello 2017-06-24 09:00:48 -04:00
rodzic c5b9ce159d
commit 0de8876e67
1 zmienionych plików z 51 dodań i 57 usunięć

Wyświetl plik

@ -255,13 +255,11 @@ class Glyph(object):
# height (in pixels) of all characters
# width (in pixels) for monospaced output (advance width of widest char)
class Font(dict):
def __init__(self, filename, size, minchar, maxchar, monospaced, defchar):
def __init__(self, filename, size, charset, monospaced):
super().__init__()
self._glyphs = {}
self._face = freetype.Face(filename)
if defchar is None: # Binary font
self.charset = [chr(char) for char in range(minchar, maxchar + 1)]
else:
self.charset = [chr(defchar)] + [chr(char) for char in range(minchar, maxchar + 1)]
self.charset = charset
self.max_width = self.get_dimensions(size)
self.width = self.max_width if monospaced else 0
for char in self.charset: # Populate dictionary
@ -274,31 +272,37 @@ class Font(dict):
for npass in range(10):
height += error
self._face.set_pixel_sizes(0, height)
max_ascent = 0
max_descent = 0
# For each character in the charset string we get the glyph
# and update the overall dimensions of the resulting bitmap.
max_width = 0
max_ascent = 0
for char in self.charset:
# for whatever wonderful reason, the fonts render differently if we only
# iterate over self.charset, so instead we use all of extended ASCII, cache
# the results, and cherry pick the ones we care about afterwards
for char in [chr(x) for x in range(32, 255)]:
glyph = self._glyph_for_character(char)
max_ascent = max(max_ascent, glyph.ascent)
max_descent = max(max_descent, glyph.descent)
# for a few chars e.g. _ glyph.width > glyph.advance_width
max_width = int(max(max_width, glyph.advance_width,
glyph.width))
self._glyphs[char] = {'glyph': glyph,
'width': int(max(glyph.advance_width, glyph.width)),
'ascent': glyph.ascent,
'descent': glyph.descent}
new_error = required_height - (max_ascent + max_descent)
if (new_error == 0) or (abs(new_error) - abs(error) == 0):
break
error = new_error
self.height = int(max_ascent + max_descent)
max_width = 0
for char in self.charset:
if self._glyphs[char]['width'] > max_width:
max_width = self._glyphs[char]['width']
st = 'Height set in {} passes. Actual height {} pixels.\nMax character width {} pixels.'
print(st.format(npass + 1, self.height, max_width))
self._max_descent = int(max_descent)
return max_width
def _glyph_for_character(self, char):
# Let FreeType load the glyph for the given character and tell it to
# render a monochromatic bitmap representation.
@ -307,7 +311,7 @@ class Font(dict):
return Glyph.from_glyphslot(self._face.glyph)
def _render_char(self, char):
glyph = self._glyph_for_character(char)
glyph = self._glyphs[char]['glyph']
char_width = int(max(glyph.width, glyph.advance_width)) # Actual width
width = self.width if self.width else char_width # Space required if monospaced
outbuffer = Bitmap(width, self.height)
@ -347,57 +351,58 @@ class Font(dict):
# PYTHON FILE WRITING
STR01 = """# Code generated by font-to-py.py.
# Font: {}
# Font: %(font)s
version = '0.2'
CHARSET = %(charset)s
"""
STR02 = """_mvfont = memoryview(_font)
def _chr_addr(ordch):
offset = 2 * (ordch - {})
return int.from_bytes(_index[offset:offset + 2], 'little')
def get_ch(ch):
ordch = ord(ch)
ordch = ordch + 1 if ordch >= {} and ordch <= {} else {}
offset = _chr_addr(ordch)
width = int.from_bytes(_font[offset:offset + 2], 'little')
next_offs = _chr_addr(ordch +1)
return _mvfont[offset + 2:next_offs], {}, width
def _char_bounds(ch):
index = CHARSET[ch]
offset = 2 * index
start = int.from_bytes(_index[offset:offset+2], 'little')
next_offset = 2 * (index + 1)
end = int.from_bytes(_index[next_offset:next_offset+2], 'little')
return start, end
def get_char(ch):
start, end = _char_bounds(ch)
width = int.from_bytes(_mvfont[start:start + 2], 'little')
return _mvfont[start + 2:end], %(height)s, width
"""
def write_func(stream, name, arg):
stream.write('def {}():\n return {}\n\n'.format(name, arg))
# filename, size, minchar=32, maxchar=126, monospaced=False, defchar=ord('?'):
def write_font(op_path, font_path, height, monospaced, hmap, reverse, minchar, maxchar, defchar):
def write_font(op_path, font_path, height, monospaced, hmap, reverse, charset):
try:
fnt = Font(font_path, height, minchar, maxchar, monospaced, defchar)
fnt = Font(font_path, height, charset, monospaced)
except freetype.ft_errors.FT_Exception:
print("Can't open", font_path)
return False
try:
with open(op_path, 'w') as stream:
write_data(stream, fnt, font_path, monospaced, hmap, reverse, minchar, maxchar)
write_data(stream, fnt, font_path, monospaced, hmap, reverse, charset)
except OSError:
print("Can't open", op_path, 'for writing')
return False
return True
def write_data(stream, fnt, font_path, monospaced, hmap, reverse, minchar, maxchar):
def write_data(stream, fnt, font_path, monospaced, hmap, reverse, charset):
height = fnt.height # Actual height, not target height
stream.write(STR01.format(os.path.split(font_path)[1]))
stream.write(STR01 % {'font': os.path.split(font_path)[1],
'charset': {ch: i for i, ch in enumerate(charset)}
})
stream.write('\n')
write_func(stream, 'height', height)
write_func(stream, 'max_width', fnt.max_width)
write_func(stream, 'hmap', hmap)
write_func(stream, 'reverse', reverse)
write_func(stream, 'monospaced', monospaced)
write_func(stream, 'min_ch', minchar)
write_func(stream, 'max_ch', maxchar)
data, index = fnt.build_arrays(hmap, reverse)
bw_font = ByteWriter(stream, '_font')
bw_font.odata(data)
@ -405,7 +410,7 @@ def write_data(stream, fnt, font_path, monospaced, hmap, reverse, minchar, maxch
bw_index = ByteWriter(stream, '_index')
bw_index.odata(index)
bw_index.eot()
stream.write(STR02.format(minchar, minchar, maxchar, minchar, height))
stream.write(STR02 % {'height': height})
# BINARY OUTPUT
# hmap reverse magic bytes
@ -474,22 +479,17 @@ if __name__ == "__main__":
parser.add_argument('-s', '--smallest',
type = int,
default = 32,
help = 'Ordinal value of smallest character default %(default)i')
help = 'Ordinal value of smallest character')
parser.add_argument('-l', '--largest',
type = int,
help = 'Ordinal value of largest character default %(default)i',
default = 126)
help = 'Ordinal value of largest character')
parser.add_argument('-e', '--errchar',
type = int,
help = 'Ordinal value of error character default %(default)i ("?")',
default = 63)
parser.add_argument('-c', '--charset',
help='List of characters to include in the generated font file',
default=[chr(x) for x in range(32, 127)])
args = parser.parse_args()
if not args.infile[0].isalpha():
quit('Font filenames must be valid Python variable names.')
if not os.path.isfile(args.infile):
quit("Font filename does not exist")
@ -512,20 +512,14 @@ if __name__ == "__main__":
if not os.path.splitext(args.outfile)[1].upper() == '.PY':
quit('Output filename must have a .py extension.')
if args.smallest < 0:
quit('--smallest must be >= 0')
if args.largest > 255:
quit('--largest must be < 256')
if args.errchar < 0 or args.errchar > 255:
quit('--errchar must be between 0 and 255')
if args.smallest and args.largest:
charset = [chr(x) for x in range(args.smallest, args.largest)]
else:
charset = args.charset
print('Writing Python font file.')
if not write_font(args.outfile, args.infile, args.height, args.fixed,
args.xmap, args.reverse, args.smallest, args.largest,
args.errchar):
args.xmap, args.reverse, charset):
sys.exit(1)
print(args.outfile, 'written successfully.')