Precise height determination. Binary file output option.

pull/3/merge
Peter Hinch 2016-11-18 18:10:03 +00:00
rodzic 7fa84269fd
commit 4058de707a
2 zmienionych plików z 83 dodań i 30 usunięć

Wyświetl plik

@ -23,7 +23,8 @@ Example usage to produce a file ``myfont.py`` with height of 23 pixels:
1. Font file path. Must be a ttf or otf file. 1. Font file path. Must be a ttf or otf file.
2. Height in pixels. 2. Height in pixels.
3. Output file path. Must have a .py extension. 3. Output file path. Must have a .py extension otherwise a binary font file
will be created; in this instance a warning message is output.
### Optional arguments: ### Optional arguments:
@ -49,6 +50,16 @@ to render strings on demand. A practical example may be studied
[here](https://github.com/peterhinch/micropython-samples/blob/master/SSD1306/ssd1306_test.py). [here](https://github.com/peterhinch/micropython-samples/blob/master/SSD1306/ssd1306_test.py).
The detailed layout of the Python file may be seen [here](./DRIVERS.md). The detailed layout of the Python file may be seen [here](./DRIVERS.md).
### Binary font files
If the output filename does not have a ``.py`` extension a binary font file is
created. This is primarily intended for the e-paper driver. Specifically in
applications where the file is to be stored on the display's internal flash
memory rather than using frozen Python modules.
The technique of accessing character data from a random access file is only
applicable to devices such as e-paper where the update time is slow.
# Dependencies, links and licence # Dependencies, links and licence
The code is released under the MIT licence. It requires Python 3.2 or later. The code is released under the MIT licence. It requires Python 3.2 or later.

Wyświetl plik

@ -2,6 +2,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Needs freetype-py>=1.0 # Needs freetype-py>=1.0
# Implements multi-pass solution to setting an exact font height
# Some code adapted from Daniel Bader's work at the following URL # Some code adapted from Daniel Bader's work at the following URL
# http://dbader.org/blog/monochrome-font-rendering-with-freetype-and-python # http://dbader.org/blog/monochrome-font-rendering-with-freetype-and-python
@ -258,25 +260,40 @@ class Font(dict):
def __init__(self, filename, size, monospaced=False): def __init__(self, filename, size, monospaced=False):
super().__init__() super().__init__()
self._face = freetype.Face(filename) self._face = freetype.Face(filename)
self._face.set_pixel_sizes(0, size) self.max_width = self.get_dimensions(size)
self._max_descent = 0 self.width = self.max_width if monospaced else 0
for char in self.charset: # Populate dictionary
self._render_char(char)
# n-pass solution to setting a precise height.
def get_dimensions(self, required_height):
error = 0
height = required_height
for npass in range(10):
height += error
self._face.set_pixel_sizes(0, height)
max_descent = 0
# For each character in the charset string we get the glyph # For each character in the charset string we get the glyph
# and update the overall dimensions of the resulting bitmap. # and update the overall dimensions of the resulting bitmap.
self.max_width = 0 max_width = 0
max_ascent = 0 max_ascent = 0
for char in self.charset: for char in self.charset:
glyph = self._glyph_for_character(char) glyph = self._glyph_for_character(char)
max_ascent = max(max_ascent, int(glyph.ascent)) max_ascent = max(max_ascent, int(glyph.ascent))
self._max_descent = max(self._max_descent, int(glyph.descent)) max_descent = max(max_descent, int(glyph.descent))
# for a few chars e.g. _ glyph.width > glyph.advance_width # for a few chars e.g. _ glyph.width > glyph.advance_width
self.max_width = int(max(self.max_width, glyph.advance_width, max_width = int(max(max_width, glyph.advance_width,
glyph.width)) glyph.width))
self.height = max_ascent + self._max_descent error = required_height - (max_ascent + max_descent)
self.width = self.max_width if monospaced else 0 if error == 0:
for char in self.charset: break
self._render_char(char) print('Height set in {} passes'.format(npass))
self.height = max_ascent + max_descent
self._max_descent = max_descent
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
@ -287,20 +304,18 @@ class Font(dict):
def _render_char(self, char): def _render_char(self, char):
glyph = self._glyph_for_character(char) glyph = self._glyph_for_character(char)
if self.width: # Monospaced char_width = int(max(glyph.width, glyph.advance_width)) # Actual width
width = self.width width = self.width if self.width else char_width # Space required if monospaced
else:
width = int(max(glyph.width, glyph.advance_width))
outbuffer = Bitmap(width, self.height) outbuffer = Bitmap(width, self.height)
# The vertical drawing position should place the glyph # The vertical drawing position should place the glyph
# on the baseline as intended. # on the baseline as intended.
row = self.height - int(glyph.ascent) - self._max_descent row = self.height - int(glyph.ascent) - self._max_descent
outbuffer.bitblt(glyph.bitmap, row) outbuffer.bitblt(glyph.bitmap, row)
self[char] = [outbuffer, width] self[char] = [outbuffer, width, char_width]
def stream_char(self, char, hmap, reverse): def stream_char(self, char, hmap, reverse):
outbuffer, _ = self[char] outbuffer, _, _ = self[char]
if hmap: if hmap:
gen = outbuffer.get_hbyte(reverse) gen = outbuffer.get_hbyte(reverse)
else: else:
@ -317,6 +332,14 @@ class Font(dict):
index += (len(data)).to_bytes(2, byteorder='little') index += (len(data)).to_bytes(2, byteorder='little')
return data, index return data, index
def build_binary_array(self, hmap, reverse):
data = bytearray((0x3f, 0xe7, self.max_width, self.height))
for char in self.charset:
width = self[char][2]
data += bytes((width,))
data += bytearray(self.stream_char(char, hmap, reverse))
return data
# PYTHON FILE WRITING # PYTHON FILE WRITING
STR01 = """# Code generated by font-to-py.py. STR01 = """# Code generated by font-to-py.py.
@ -377,6 +400,22 @@ def write_data(stream, fnt, font_path, monospaced, hmap, reverse):
bw_index.eot() bw_index.eot()
stream.write(STR02.format(height, height)) stream.write(STR02.format(height, height))
# BINARY OUTPUT
def write_binary_font(op_path, font_path, height, hmap, reverse):
try:
fnt = Font(font_path, height, True) # All chars have same width
except freetype.ft_errors.FT_Exception:
print("Can't open", font_path)
return False
try:
with open(op_path, 'wb') as stream:
data = fnt.build_binary_array(hmap, reverse)
stream.write(data)
except OSError:
print("Can't open", op_path, 'for writing')
return False
return True
# PARSE COMMAND LINE ARGUMENTS # PARSE COMMAND LINE ARGUMENTS
@ -412,10 +451,13 @@ if __name__ == "__main__":
if not os.path.splitext(args.infile)[1].upper() in ('.TTF', '.OTF'): if not os.path.splitext(args.infile)[1].upper() in ('.TTF', '.OTF'):
print("Font file should be a ttf or otf file.") print("Font file should be a ttf or otf file.")
sys.exit(1) sys.exit(1)
if not os.path.splitext(args.outfile)[1].upper() == '.PY': if os.path.splitext(args.outfile)[1].upper() == '.PY': # Emit Python
print("Output filename should have a .py extension.")
sys.exit(1)
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.xmap, args.reverse):
sys.exit(1) sys.exit(1)
else:
print('WARNING: output filename lacks .py extension. Writing binary font file.')
if not write_binary_font(args.outfile, args.infile, args.height,
args.xmap, args.reverse):
sys.exit(1)
print(args.outfile, 'written successfully.') print(args.outfile, 'written successfully.')