Optional support for extended ASCII

pull/3/merge
Peter Hinch 2017-01-12 15:48:26 +00:00
rodzic 5c37366451
commit 4c966bbdcd
4 zmienionych plików z 131 dodań i 50 usunięć

Wyświetl plik

@ -137,7 +137,7 @@ has the following outline definition (in practice the bytes objects are large):
```python
# Code generated by font-to-py.py.
# Font: FreeSerif.ttf
version = '0.1'
version = '0.2'
def height():
return 21
@ -154,6 +154,12 @@ def reverse():
def monospaced():
return False
def min_ch():
return 32
def max_ch():
return 126
_font =\
b'\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x08\x00\xfe\xc7\x00\x7e\xc0\x00\x00\x00\x00\x00'\

Wyświetl plik

@ -10,6 +10,10 @@ required height in pixels and outputs a Python 3 source file. The pixel layout
is determined by command arguments. By default fonts are stored in variable
pitch form. This may be overidden by a command line argument.
By default the ASCII character set (ordinal values 32 to 126 inclusive) is
supported. Command line arguments can modify this range as required, if
necessary to include extended ASCII characters up to 255.
Further arguments ensure that the byte contents and layout are correct for the
target display hardware. Their usage should be specified in the documentation
for the device driver.
@ -23,24 +27,28 @@ 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.
3. Output file path. Filename must have a .py extension.
### Optional arguments:
* -f or --fixed If specified, all characters will have the same width. By
default fonts are assumed to be variable pitch.
* -x Specifies horizontal mapping (default is vertical).
* -b Specifies bit reversal in each font byte.
* -x or --xmap Specifies horizontal mapping (default is vertical).
* -r or --reverse Specifies bit reversal in each font byte.
* -s or --smallest Ordinal value of smallest character to be stored. Default
32 (ASCII space).
* -l or --largest Ordinal value of largest character to be stored. Default 126.
* -e or --errchar Ordinal value of character to be rendered if an attempt is
made to display an out-of-range character. Default 63 (ASCII "?").
Optional arguments other than the fixed pitch argument will be specified in the
device driver documentation. Bit reversal is required by some display hardware.
Any requirement for arguments -xr 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.
displayed on completion, along with the width of the widest character.
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.
@ -61,13 +69,23 @@ 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
There is an option to create a binary font file, specified with a ``-b`` or
``--binary`` command line argument. In this instance the output filename must
not have a ``.py`` extension. This is primarily intended for the e-paper driver
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.
The technique of accessing character data from a random access file is slow
and thus probably only applicable to devices such as e-paper where the update
time is slow.
Binary files currently support only the standard ASCII character set. There is
no error character: the device driver must ensure that seeks are within range.
Consequently the following arguments are invalid:
* -s or --smallest
* -l or --largest
* -e or --errchar
# Dependencies, links and licence

Wyświetl plik

@ -153,10 +153,10 @@ def test_font(fontfile, string):
# Create font file, render a string to REPL using it
# usage font_test.test_file('FreeSans.ttf', 20, 'xyz')
def test_file(fontfile, height, string, fixed=False, hmap=False,
reverse=False):
def test_file(fontfile, height, string, *, minchar=32, maxchar=126, defchar=ord('?'),
fixed=False, hmap=False, reverse=False):
if not write_font('myfont.py', fontfile, height, fixed,
hmap, reverse):
hmap, reverse, minchar, maxchar, defchar):
print('Failed to create font file.')
return

Wyświetl plik

@ -255,11 +255,10 @@ class Glyph(object):
# height (in pixels) of all characters
# width (in pixels) for monospaced output (advance width of widest char)
class Font(dict):
charset = [chr(char) for char in range(32, 127)]
def __init__(self, filename, size, monospaced=False):
def __init__(self, filename, size, minchar=32, maxchar=126, monospaced=False, defchar=ord('?')):
super().__init__()
self._face = freetype.Face(filename)
self.charset = [chr(defchar)] + [chr(char) for char in range(minchar, maxchar + 1)]
self.max_width = self.get_dimensions(size)
self.width = self.max_width if monospaced else 0
for char in self.charset: # Populate dictionary
@ -291,7 +290,8 @@ class Font(dict):
break
error = new_error
self.height = int(max_ascent + max_descent)
print('Height set in {} passes. Actual height {} pixels'.format(npass + 1, self.height))
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
@ -345,18 +345,18 @@ class Font(dict):
STR01 = """# Code generated by font-to-py.py.
# Font: {}
version = '0.1'
version = '0.2'
"""
STR02 = """_mvfont = memoryview(_font)
def _chr_addr(ordch):
offset = 2 * (ordch - 32)
offset = 2 * (ordch - {})
return int.from_bytes(_index[offset:offset + 2], 'little')
def get_ch(ch):
ordch = ord(ch)
ordch = ordch if ordch >= 32 and ordch <= 126 else ord('?')
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)
@ -367,23 +367,24 @@ def get_ch(ch):
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):
def write_font(op_path, font_path, height, monospaced, hmap, reverse, minchar, maxchar, defchar):
try:
fnt = Font(font_path, height, monospaced)
fnt = Font(font_path, height, minchar, maxchar, monospaced, defchar)
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)
write_data(stream, fnt, font_path, monospaced, hmap, reverse, minchar, maxchar)
except OSError:
print("Can't open", op_path, 'for writing')
return False
return True
def write_data(stream, fnt, font_path, monospaced, hmap, reverse):
def write_data(stream, fnt, font_path, monospaced, hmap, reverse, minchar, maxchar):
height = fnt.height # Actual height, not target height
stream.write(STR01.format(os.path.split(font_path)[1]))
stream.write('\n')
@ -392,6 +393,8 @@ def write_data(stream, fnt, font_path, monospaced, hmap, reverse):
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)
@ -399,7 +402,7 @@ def write_data(stream, fnt, font_path, monospaced, hmap, reverse):
bw_index = ByteWriter(stream, '_index')
bw_index.odata(index)
bw_index.eot()
stream.write(STR02.format(height))
stream.write(STR02.format(minchar, minchar, maxchar, minchar, height))
# BINARY OUTPUT
# hmap reverse magic bytes
@ -427,45 +430,99 @@ def write_binary_font(op_path, font_path, height, hmap, reverse):
# PARSE COMMAND LINE ARGUMENTS
def quit(msg):
print(msg)
sys.exit(1)
DESC = """font_to_py.py
Utility to convert ttf or otf font files to Python source.
Sample usage:
font_to_py.py FreeSans.ttf 23 freesans.py
This creates a font with nominal height 23 pixels. To specify monospaced
rendering issue
This creates a font with nominal height 23 pixels with these defaults:
Mapping is vertical, pitch variable, character set 32-126 inclusive.
Illegal characters will be rendered as "?".
To specify monospaced rendering issue:
font_to_py.py FreeSans.ttf 23 --fixed freesans.py
"""
BINARY = """Invalid arguments. Binary (random access) font files support the standard ASCII
character set (from 32 to 126 inclusive). This range cannot be overridden.
Random access font files don't support an error character.
"""
if __name__ == "__main__":
parser = argparse.ArgumentParser(__file__, description=DESC,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument('infile', type=str, help='input file path')
parser.add_argument('height', type=int, help='font height in pixels')
parser.add_argument('-x', '--xmap', action='store_true',
help='horizontal (x) mapping')
parser.add_argument('-r', '--reverse', action='store_true',
help='bit reversal')
parser.add_argument('-f', '--fixed', action='store_true',
help='Fixed width (monospaced) font')
parser.add_argument('infile', type=str, help='Input file path')
parser.add_argument('height', type=int, help='Font height in pixels')
parser.add_argument('outfile', type=str,
help='Path and name of output file')
parser.add_argument('-x', '--xmap', action='store_true',
help='Horizontal (x) mapping')
parser.add_argument('-r', '--reverse', action='store_true',
help='Bit reversal')
parser.add_argument('-f', '--fixed', action='store_true',
help='Fixed width (monospaced) font')
parser.add_argument('-b', '--binary', action='store_true',
help='Produce binary (random access) font file.')
parser.add_argument('-s', '--smallest',
type = int,
default = 32,
help = 'Ordinal value of smallest character default %(default)i')
parser.add_argument('-l', '--largest',
type = int,
help = 'Ordinal value of largest character default %(default)i',
default = 126)
parser.add_argument('-e', '--errchar',
type = int,
help = 'Ordinal value of error character default %(default)i ("?")',
default = 63)
args = parser.parse_args()
if not args.infile[0].isalpha():
print('Font filenames must be valid Python variable names.')
sys.exit(1)
quit('Font filenames must be valid Python variable names.')
if not os.path.isfile(args.infile):
print("Font filename does not exist")
sys.exit(1)
quit("Font filename does not exist")
if not os.path.splitext(args.infile)[1].upper() in ('.TTF', '.OTF'):
print("Font file should be a ttf or otf file.")
sys.exit(1)
if os.path.splitext(args.outfile)[1].upper() == '.PY': # Emit Python
if not write_font(args.outfile, args.infile, args.height, args.fixed,
args.xmap, args.reverse):
sys.exit(1)
else:
print('WARNING: output filename lacks .py extension. Writing binary font file.')
quit("Font file should be a ttf or otf file.")
if args.binary:
if os.path.splitext(args.outfile)[1].upper() == '.PY':
quit('Binary file must not have a .py extension.')
if args.smallest != 32 or args.largest != 126 or args.default != ord('?'):
quit(BINARY)
print('Writing binary font file.')
if not write_binary_font(args.outfile, args.infile, args.height,
args.xmap, args.reverse):
sys.exit(1)
else:
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')
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):
sys.exit(1)
print(args.outfile, 'written successfully.')