From 67ea34499333634f391b8a66448c6c21e600e7e2 Mon Sep 17 00:00:00 2001 From: Peter Hinch Date: Sun, 20 Nov 2016 17:12:32 +0000 Subject: [PATCH] Height determination improved. Better user feedback. --- DRIVERS.md | 35 +++++++++++++++++++++++++++++++++-- FONT_TO_PY.md | 11 ++++++++++- font_to_py.py | 30 +++++++++++++++++++----------- 3 files changed, 62 insertions(+), 14 deletions(-) diff --git a/DRIVERS.md b/DRIVERS.md index d200cbd..54a0e1c 100644 --- a/DRIVERS.md +++ b/DRIVERS.md @@ -78,6 +78,14 @@ Where the buffer is located on the display device the means of controlling the text insertion point will be device dependent. The driver will need to implement the functionality of the ``Writer`` class itself. +## Fixed width fonts + +If a Python font file is created with the ``-f`` argument, all characters will +be saved with the width of the widest. In general it is not necessary to +specify this option. The driver can perform fixed pich rendering by rendering +the character as variable pitch, then advancing the pixel column by the value +returned by ``font.max_width()``. + ## Drivers with local buffers The writer of a device driver need not be concerned with the structure of a @@ -127,7 +135,7 @@ things as the text insertion point. Consideration should be given to employing the same interface as the ``Writer`` class to simplify the porting of user code between displays with differing hardware. -## Font files +## Python Font files Assume the user has run the utility to produce a file ``myfont.py`` This then has the following outline definition (in practice the bytes objects are large): @@ -182,6 +190,28 @@ character check. and contains all the bytes required to render the character including trailing space. +## Binary font files + +These are unlikely to find application beyond the e-paper driver, but for +completeness the format is as follows. They are binary files with a four byte +header and 126 fixed length records. The header consists of two file identifiers +enabling the file format to be checked, followed by bytes specifying the width +and height. The length of each record is (width + 1) bytes. + +The file indentifiers depend on the -x and -r arguments specified to ``font_to_py.py`` +and are as follows: + +hmap reverse byte +-x -r 0 1 +0 0 0x3f 0xe7 +1 0 0x40 0xe7 +0 1 0x41 0xe7 +1 1 0x42 0xe7 + +Each record starts with a width byte specifying the x dimension of the glyph if +rendered proportionally spaced, followed by the glyph data. This data includes +trailing space ensuring that all records have the size specified in the header. + ## Mapping A character occupies a space where (0, 0) represents the coordinates of the top @@ -215,4 +245,5 @@ The approach has been tested on SSD1306 devices using both the pseudo-horizontal and true vertical mapping. The ``font_to_py`` utility has been extensively tested with each of the mapping -options. +options. It has been used with drivers for SSD1306 OLEDs, SSD1963 LCD displays, +and the e-paper display. diff --git a/FONT_TO_PY.md b/FONT_TO_PY.md index c16ff50..a816ecd 100644 --- a/FONT_TO_PY.md +++ b/FONT_TO_PY.md @@ -24,7 +24,7 @@ Example usage to produce a file ``myfont.py`` with height of 23 pixels: 1. Font file path. Must be a ttf or otf file. 2. Height in pixels. 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. + will be created. ### Optional arguments: @@ -36,6 +36,15 @@ Example usage to produce a file ``myfont.py`` with height of 23 pixels: Optional arguments other than the fixed pitch argument will be specified in the device driver documentation. Bit reversal is required by some display hardware. +### Output + +The specified height is a target. The algorithm gets as close to the target +height as possible (usually within one pixel). The actual height achieved is +displayed on completion. + +A warning is output if the output filename does not have a .py extension as the +creation of a binary font file may not be intended. + ## The font file Assume that the you have employed the utility to create a file ``myfont.py``. In diff --git a/font_to_py.py b/font_to_py.py index 2a9219a..57af704 100755 --- a/font_to_py.py +++ b/font_to_py.py @@ -280,18 +280,19 @@ class Font(dict): max_ascent = 0 for char in self.charset: glyph = self._glyph_for_character(char) - max_ascent = max(max_ascent, int(glyph.ascent)) - max_descent = max(max_descent, int(glyph.descent)) + 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)) - error = required_height - (max_ascent + max_descent) - if error == 0: + new_error = required_height - (max_ascent + max_descent) + if (new_error == 0) or (abs(new_error) - abs(error) == 0): break - print('Height set in {} passes'.format(npass)) - self.height = max_ascent + max_descent - self._max_descent = max_descent + error = new_error + self.height = int(max_ascent + max_descent) + print('Height set in {} passes. Actual height {} pixels'.format(npass + 1, self.height)) + self._max_descent = int(max_descent) return max_width @@ -332,8 +333,8 @@ class Font(dict): index += (len(data)).to_bytes(2, byteorder='little') return data, index - def build_binary_array(self, hmap, reverse): - data = bytearray((0x3f, 0xe7, self.max_width, self.height)) + def build_binary_array(self, hmap, reverse, sig): + data = bytearray((0x3f + sig, 0xe7, self.max_width, self.height)) for char in self.charset: width = self[char][2] data += bytes((width,)) @@ -401,16 +402,23 @@ def write_data(stream, fnt, font_path, monospaced, hmap, reverse): stream.write(STR02.format(height, height)) # BINARY OUTPUT - +# hmap reverse magic bytes +# 0 0 0x3f 0xe7 +# 1 0 0x40 0xe7 +# 0 1 0x41 0xe7 +# 1 1 0x42 0xe7 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 + sig = 1 if hmap else 0 + if reverse: + sig += 2 try: with open(op_path, 'wb') as stream: - data = fnt.build_binary_array(hmap, reverse) + data = fnt.build_binary_array(hmap, reverse, sig) stream.write(data) except OSError: print("Can't open", op_path, 'for writing')