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