diff --git a/FONT_TO_PY.md b/FONT_TO_PY.md index 843b784..1f512ad 100644 --- a/FONT_TO_PY.md +++ b/FONT_TO_PY.md @@ -55,13 +55,25 @@ Example usage to produce a file `myfont.py` with height of 23 pixels: * -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 "?"). + * -c or --charset Option to restrict the characters in the font to a specific + set. See below. + +The -c option reduces the size of the font file. If the font file is frozen as +bytecode this will not reduce RAM usage but it will conserve flash. An example +usage for a digital clock font: + +```shell +font_to_py.py Arial.ttf 20 arial_clock.py -c 1234567890: +``` +If a character set is specified, `--smallest` and `--largest` should not be +specified: these values are computed from the charcater set. Any requirement for arguments -xr will be specified in the device driver documentation. Bit reversal is required by some display hardware. -Note that producing extended ASCII characters (ordinal value > 127) from ttf -files seems unreliable. If the expected results are not achieved, use an otf -font. This appears to be a limitation of the `freetype` library. +There have been reports that producing extended ASCII characters (ordinal +value > 127) from ttf files is unreliable. If the expected results are not +achieved, use an otf font. This may be a limitation of the `freetype` library. ### Output diff --git a/font_to_py.py b/font_to_py.py index 55a1006..5a272a8 100755 --- a/font_to_py.py +++ b/font_to_py.py @@ -255,13 +255,22 @@ 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, minchar, maxchar, monospaced, defchar, charset): super().__init__() self._face = freetype.Face(filename) + self.minchar = minchar + self.maxchar = maxchar + self.monospaced = monospaced + self.def_charset = charset if defchar is None: # Binary font self.charset = [chr(char) for char in range(minchar, maxchar + 1)] - else: + elif charset == '': self.charset = [chr(defchar)] + [chr(char) for char in range(minchar, maxchar + 1)] + else: + n = sorted([ord(x) for x in chr(defchar) + charset]) + self.minchar = n[0] + self.maxchar = n[-1] + self.charset = [chr(defchar)] + [chr(char) if chr(char) in charset else '' for char in range(n[0], n[-1] + 1)] self.max_width = self.get_dimensions(size) self.width = self.max_width if monospaced else 0 for char in self.charset: # Populate dictionary @@ -330,10 +339,13 @@ class Font(dict): data = bytearray() index = bytearray((0, 0)) for char in self.charset: - width = self[char][1] - data += (width).to_bytes(2, byteorder='little') - data += bytearray(self.stream_char(char, hmap, reverse)) - index += (len(data)).to_bytes(2, byteorder='little') + if char == '': + index += (len(data)).to_bytes(2, byteorder='little') + else: + width = self[char][1] + data += (width).to_bytes(2, byteorder='little') + data += bytearray(self.stream_char(char, hmap, reverse)) + index += (len(data)).to_bytes(2, byteorder='little') return data, index def build_binary_array(self, hmap, reverse, sig): @@ -347,7 +359,7 @@ class Font(dict): # PYTHON FILE WRITING STR01 = """# Code generated by font-to-py.py. -# Font: {} +# Font: {}{} version = '0.2' """ @@ -372,30 +384,34 @@ def write_func(stream, 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, minchar, maxchar, defchar, charset): try: - fnt = Font(font_path, height, minchar, maxchar, monospaced, defchar) + fnt = Font(font_path, height, minchar, maxchar, monospaced, defchar, charset) 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, hmap, reverse) 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, hmap, reverse): height = fnt.height # Actual height, not target height - stream.write(STR01.format(os.path.split(font_path)[1])) + minchar = fnt.minchar + maxchar = fnt.maxchar + charset = fnt.def_charset + st = '' if charset == '' else ' Char set: {}'.format(charset) + stream.write(STR01.format(os.path.split(font_path)[1], st)) 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, 'monospaced', fnt.monospaced) write_func(stream, 'min_ch', minchar) write_func(stream, 'max_ch', maxchar) data, index = fnt.build_arrays(hmap, reverse) @@ -487,6 +503,11 @@ if __name__ == "__main__": help = 'Ordinal value of error character default %(default)i ("?")', default = 63) + parser.add_argument('-c', '--charset', + type = str, + help = 'Character set. e.g. 1234567890: to restrict for a clock display.', + default = '') + args = parser.parse_args() if not args.infile[0].isalpha(): quit('Font filenames must be valid Python variable names.') @@ -501,7 +522,7 @@ if __name__ == "__main__": 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.errchar != ord('?'): + if args.smallest != 32 or args.largest != 126 or args.errchar != ord('?') or args.charset: quit(BINARY) print('Writing binary font file.') @@ -522,11 +543,13 @@ if __name__ == "__main__": if args.errchar < 0 or args.errchar > 255: quit('--errchar must be between 0 and 255') + if args.charset and (args.smallest != 32 or args.largest != 126): + print('WARNING: specified smallest and largest values ignored.') 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.errchar, args.charset): sys.exit(1) print(args.outfile, 'written successfully.')