kopia lustrzana https://github.com/peterhinch/micropython-font-to-py
update font_to_py to support non-sequential charsets
rodzic
c5b9ce159d
commit
0de8876e67
108
font_to_py.py
108
font_to_py.py
|
@ -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 _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_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 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.')
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue