kopia lustrzana https://github.com/peterhinch/micropython-font-to-py
Optional support for extended ASCII
rodzic
5c37366451
commit
4c966bbdcd
|
@ -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'\
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
125
font_to_py.py
125
font_to_py.py
|
@ -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.')
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue