diff --git a/README.md b/README.md index 72bd8dd..bb2f8df 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,12 @@ # Spinny boy this readme will contain documentation and stuff + +# Flashing programm on the ESP32 +``` +rshell -p /dev/ttyUSB0 # connect +rsync src /pyboard/ # sync code to ESP32 +repl # enter MicroPython REPL, press ctrl+D to soft reboot +``` + + diff --git a/src/font_to_py.py b/src/font_to_py.py new file mode 100755 index 0000000..6cb1130 --- /dev/null +++ b/src/font_to_py.py @@ -0,0 +1,540 @@ +#! /usr/bin/python3 +# -*- coding: utf-8 -*- +# 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 +# http://dbader.org/blog/monochrome-font-rendering-with-freetype-and-python + +# The MIT License (MIT) +# +# Copyright (c) 2016 Peter Hinch +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +import argparse +import sys +import os +import freetype + +# UTILITIES FOR WRITING PYTHON SOURCECODE TO A FILE + +# ByteWriter takes as input a variable name and data values and writes +# Python source to an output stream of the form +# my_variable = b'\x01\x02\x03\x04\x05\x06\x07\x08'\ + +# Lines are broken with \ for readability. + + +class ByteWriter(object): + bytes_per_line = 16 + + def __init__(self, stream, varname): + self.stream = stream + self.stream.write('{} =\\\n'.format(varname)) + self.bytecount = 0 # For line breaks + + def _eol(self): + self.stream.write("'\\\n") + + def _eot(self): + self.stream.write("'\n") + + def _bol(self): + self.stream.write("b'") + + # Output a single byte + def obyte(self, data): + if not self.bytecount: + self._bol() + self.stream.write('\\x{:02x}'.format(data)) + self.bytecount += 1 + self.bytecount %= self.bytes_per_line + if not self.bytecount: + self._eol() + + # Output from a sequence + def odata(self, bytelist): + for byt in bytelist: + self.obyte(byt) + + # ensure a correct final line + def eot(self): # User force EOL if one hasn't occurred + if self.bytecount: + self._eot() + self.stream.write('\n') + + +# Define a global +def var_write(stream, name, value): + stream.write('{} = {}\n'.format(name, value)) + +# FONT HANDLING + + +class Bitmap(object): + """ + A 2D bitmap image represented as a list of byte values. Each byte indicates + the state of a single pixel in the bitmap. A value of 0 indicates that the + pixel is `off` and any other value indicates that it is `on`. + """ + def __init__(self, width, height, pixels=None): + self.width = width + self.height = height + self.pixels = pixels or bytearray(width * height) + + def display(self): + """Print the bitmap's pixels.""" + for row in range(self.height): + for col in range(self.width): + char = '#' if self.pixels[row * self.width + col] else '.' + print(char, end='') + print() + print() + + def bitblt(self, src, row): + """Copy all pixels from `src` into this bitmap""" + srcpixel = 0 + dstpixel = row * self.width + row_offset = self.width - src.width + + for _ in range(src.height): + for _ in range(src.width): + self.pixels[dstpixel] = src.pixels[srcpixel] + srcpixel += 1 + dstpixel += 1 + dstpixel += row_offset + + # Horizontal mapping generator function + def get_hbyte(self, reverse): + for row in range(self.height): + col = 0 + while True: + bit = col % 8 + if bit == 0: + if col >= self.width: + break + byte = 0 + if col < self.width: + if reverse: + byte |= self.pixels[row * self.width + col] << bit + else: + # Normal map MSB of byte 0 is (0, 0) + byte |= self.pixels[row * self.width + col] << (7 - bit) + if bit == 7: + yield byte + col += 1 + + # Vertical mapping + def get_vbyte(self, reverse): + for col in range(self.width): + row = 0 + while True: + bit = row % 8 + if bit == 0: + if row >= self.height: + break + byte = 0 + if row < self.height: + if reverse: + byte |= self.pixels[row * self.width + col] << (7 - bit) + else: + # Normal map MSB of byte 0 is (0, 7) + byte |= self.pixels[row * self.width + col] << bit + if bit == 7: + yield byte + row += 1 + + +class Glyph(object): + def __init__(self, pixels, width, height, top, advance_width): + self.bitmap = Bitmap(width, height, pixels) + + # The glyph bitmap's top-side bearing, i.e. the vertical distance from + # the baseline to the bitmap's top-most scanline. + self.top = top + + # Ascent and descent determine how many pixels the glyph extends + # above or below the baseline. + self.descent = max(0, self.height - self.top) + self.ascent = max(0, max(self.top, self.height) - self.descent) + + # The advance width determines where to place the next character + # horizontally, that is, how many pixels we move to the right to + # draw the next glyph. + self.advance_width = advance_width + + @property + def width(self): + return self.bitmap.width + + @property + def height(self): + return self.bitmap.height + + @staticmethod + def from_glyphslot(slot): + """Construct and return a Glyph object from a FreeType GlyphSlot.""" + pixels = Glyph.unpack_mono_bitmap(slot.bitmap) + width, height = slot.bitmap.width, slot.bitmap.rows + top = slot.bitmap_top + + # The advance width is given in FreeType's 26.6 fixed point format, + # which means that the pixel values are multiples of 64. + advance_width = slot.advance.x / 64 + + return Glyph(pixels, width, height, top, advance_width) + + @staticmethod + def unpack_mono_bitmap(bitmap): + """ + Unpack a freetype FT_LOAD_TARGET_MONO glyph bitmap into a bytearray + where each pixel is represented by a single byte. + """ + # Allocate a bytearray of sufficient size to hold the glyph bitmap. + data = bytearray(bitmap.rows * bitmap.width) + + # Iterate over every byte in the glyph bitmap. Note that we're not + # iterating over every pixel in the resulting unpacked bitmap -- + # we're iterating over the packed bytes in the input bitmap. + for row in range(bitmap.rows): + for byte_index in range(bitmap.pitch): + + # Read the byte that contains the packed pixel data. + byte_value = bitmap.buffer[row * bitmap.pitch + byte_index] + + # We've processed this many bits (=pixels) so far. This + # determines where we'll read the next batch of pixels from. + num_bits_done = byte_index * 8 + + # Pre-compute where to write the pixels that we're going + # to unpack from the current byte in the glyph bitmap. + rowstart = row * bitmap.width + byte_index * 8 + + # Iterate over every bit (=pixel) that's still a part of the + # output bitmap. Sometimes we're only unpacking a fraction of + # a byte because glyphs may not always fit on a byte boundary. + # So we make sure to stop if we unpack past the current row + # of pixels. + for bit_index in range(min(8, bitmap.width - num_bits_done)): + + # Unpack the next pixel from the current glyph byte. + bit = byte_value & (1 << (7 - bit_index)) + + # Write the pixel to the output bytearray. We ensure that + # `off` pixels have a value of 0 and `on` pixels have a + # value of 1. + data[rowstart + bit_index] = 1 if bit else 0 + + return data + + +# A Font object is a dictionary of ASCII chars indexed by a character e.g. +# myfont['a'] +# Each entry comprises a list +# [0] A Bitmap instance containing the character +# [1] The width of the character data including advance (actual data stored) +# Public attributes: +# 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): + super().__init__() + self._face = freetype.Face(filename) + if defchar is None: # Binary font + self.charset = [chr(char) for char in range(minchar, maxchar + 1)] + else: + 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 + 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 + # and update the overall dimensions of the resulting bitmap. + max_width = 0 + max_ascent = 0 + for char in self.charset: + glyph = self._glyph_for_character(char) + 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)) + + new_error = required_height - (max_ascent + max_descent) + if (new_error == 0) or (abs(new_error) - abs(error) == 0): + break + error = new_error + self.height = int(max_ascent + max_descent) + 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 + + + def _glyph_for_character(self, char): + # Let FreeType load the glyph for the given character and tell it to + # render a monochromatic bitmap representation. + self._face.load_char(char, freetype.FT_LOAD_RENDER | + freetype.FT_LOAD_TARGET_MONO) + return Glyph.from_glyphslot(self._face.glyph) + + def _render_char(self, char): + glyph = self._glyph_for_character(char) + char_width = int(max(glyph.width, glyph.advance_width)) # Actual width + width = self.width if self.width else char_width # Space required if monospaced + outbuffer = Bitmap(width, self.height) + + # The vertical drawing position should place the glyph + # on the baseline as intended. + row = self.height - int(glyph.ascent) - self._max_descent + outbuffer.bitblt(glyph.bitmap, row) + self[char] = [outbuffer, width, char_width] + + def stream_char(self, char, hmap, reverse): + outbuffer, _, _ = self[char] + if hmap: + gen = outbuffer.get_hbyte(reverse) + else: + gen = outbuffer.get_vbyte(reverse) + yield from gen + + def build_arrays(self, hmap, reverse): + 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') + return data, index + + 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,)) + data += bytearray(self.stream_char(char, hmap, reverse)) + return data + +# PYTHON FILE WRITING + +STR01 = """# Code generated by font-to-py.py. +# Font: {} +version = '0.2' +""" + +STR02 = """_mvfont = memoryview(_font) + +def _chr_addr(ordch): + offset = 2 * (ordch - {}) + return int.from_bytes(_index[offset:offset + 2], 'little') + +def get_width(s): + width = 0 + for ch in s: + ordch = ord(ch) + ordch = ordch + 1 if ordch >= {} and ordch <= {} else 32 + offset = _chr_addr(ordch) + width += int.from_bytes(_font[offset:offset + 2], 'little') + return width + +def get_ch(ch): + ordch = ord(ch) + 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) + return _mvfont[offset + 2:next_offs], width + +""" + +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, minchar, maxchar, defchar): + try: + 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, 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, minchar, maxchar): + height = fnt.height # Actual height, not target height + stream.write(STR01.format(os.path.split(font_path)[1])) + 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, '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) + bw_font.eot() + bw_index = ByteWriter(stream, '_index') + bw_index.odata(index) + bw_index.eot() + stream.write(STR02.format(minchar, minchar, maxchar, minchar, maxchar, minchar)) + +# 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, 32, 126, True, None) # 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, sig) + stream.write(data) + except OSError: + print("Can't open", op_path, 'for writing') + return False + return True + +# 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 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('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(): + quit('Font filenames must be valid Python variable names.') + + if not os.path.isfile(args.infile): + quit("Font filename does not exist") + + if not os.path.splitext(args.infile)[1].upper() in ('.TTF', '.OTF'): + 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.errchar != 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.') + diff --git a/src/glcdfont.py b/src/glcdfont.py new file mode 100644 index 0000000..c76e4d6 --- /dev/null +++ b/src/glcdfont.py @@ -0,0 +1,294 @@ +# Original Adafruit_GFX 5x7 font + +def height(): + return 8 + +def max_width(): + return 6 + +def hmap(): + return False + +def reverse(): + return False + +def monospaced(): + return True + +def min_ch(): + return 0 + +def max_ch(): + return 255 + + +_font = \ + b'\x00\x00\x00\x00\x00'\ + b'\x3E\x5B\x4F\x5B\x3E'\ + b'\x3E\x6B\x4F\x6B\x3E'\ + b'\x1C\x3E\x7C\x3E\x1C'\ + b'\x18\x3C\x7E\x3C\x18'\ + b'\x1C\x57\x7D\x57\x1C'\ + b'\x1C\x5E\x7F\x5E\x1C'\ + b'\x00\x18\x3C\x18\x00'\ + b'\xFF\xE7\xC3\xE7\xFF'\ + b'\x00\x18\x24\x18\x00'\ + b'\xFF\xE7\xDB\xE7\xFF'\ + b'\x30\x48\x3A\x06\x0E'\ + b'\x26\x29\x79\x29\x26'\ + b'\x40\x7F\x05\x05\x07'\ + b'\x40\x7F\x05\x25\x3F'\ + b'\x5A\x3C\xE7\x3C\x5A'\ + b'\x7F\x3E\x1C\x1C\x08'\ + b'\x08\x1C\x1C\x3E\x7F'\ + b'\x14\x22\x7F\x22\x14'\ + b'\x5F\x5F\x00\x5F\x5F'\ + b'\x06\x09\x7F\x01\x7F'\ + b'\x00\x66\x89\x95\x6A'\ + b'\x60\x60\x60\x60\x60'\ + b'\x94\xA2\xFF\xA2\x94'\ + b'\x08\x04\x7E\x04\x08'\ + b'\x10\x20\x7E\x20\x10'\ + b'\x08\x08\x2A\x1C\x08'\ + b'\x08\x1C\x2A\x08\x08'\ + b'\x1E\x10\x10\x10\x10'\ + b'\x0C\x1E\x0C\x1E\x0C'\ + b'\x30\x38\x3E\x38\x30'\ + b'\x06\x0E\x3E\x0E\x06'\ + b'\x00\x00\x00\x00\x00'\ + b'\x00\x00\x5F\x00\x00'\ + b'\x00\x07\x00\x07\x00'\ + b'\x14\x7F\x14\x7F\x14'\ + b'\x24\x2A\x7F\x2A\x12'\ + b'\x23\x13\x08\x64\x62'\ + b'\x36\x49\x56\x20\x50'\ + b'\x00\x08\x07\x03\x00'\ + b'\x00\x1C\x22\x41\x00'\ + b'\x00\x41\x22\x1C\x00'\ + b'\x2A\x1C\x7F\x1C\x2A'\ + b'\x08\x08\x3E\x08\x08'\ + b'\x00\x80\x70\x30\x00'\ + b'\x08\x08\x08\x08\x08'\ + b'\x00\x00\x60\x60\x00'\ + b'\x20\x10\x08\x04\x02'\ + b'\x3E\x51\x49\x45\x3E'\ + b'\x00\x42\x7F\x40\x00'\ + b'\x72\x49\x49\x49\x46'\ + b'\x21\x41\x49\x4D\x33'\ + b'\x18\x14\x12\x7F\x10'\ + b'\x27\x45\x45\x45\x39'\ + b'\x3C\x4A\x49\x49\x31'\ + b'\x41\x21\x11\x09\x07'\ + b'\x36\x49\x49\x49\x36'\ + b'\x46\x49\x49\x29\x1E'\ + b'\x00\x00\x14\x00\x00'\ + b'\x00\x40\x34\x00\x00'\ + b'\x00\x08\x14\x22\x41'\ + b'\x14\x14\x14\x14\x14'\ + b'\x00\x41\x22\x14\x08'\ + b'\x02\x01\x59\x09\x06'\ + b'\x3E\x41\x5D\x59\x4E'\ + b'\x7C\x12\x11\x12\x7C'\ + b'\x7F\x49\x49\x49\x36'\ + b'\x3E\x41\x41\x41\x22'\ + b'\x7F\x41\x41\x41\x3E'\ + b'\x7F\x49\x49\x49\x41'\ + b'\x7F\x09\x09\x09\x01'\ + b'\x3E\x41\x41\x51\x73'\ + b'\x7F\x08\x08\x08\x7F'\ + b'\x00\x41\x7F\x41\x00'\ + b'\x20\x40\x41\x3F\x01'\ + b'\x7F\x08\x14\x22\x41'\ + b'\x7F\x40\x40\x40\x40'\ + b'\x7F\x02\x1C\x02\x7F'\ + b'\x7F\x04\x08\x10\x7F'\ + b'\x3E\x41\x41\x41\x3E'\ + b'\x7F\x09\x09\x09\x06'\ + b'\x3E\x41\x51\x21\x5E'\ + b'\x7F\x09\x19\x29\x46'\ + b'\x26\x49\x49\x49\x32'\ + b'\x03\x01\x7F\x01\x03'\ + b'\x3F\x40\x40\x40\x3F'\ + b'\x1F\x20\x40\x20\x1F'\ + b'\x3F\x40\x38\x40\x3F'\ + b'\x63\x14\x08\x14\x63'\ + b'\x03\x04\x78\x04\x03'\ + b'\x61\x59\x49\x4D\x43'\ + b'\x00\x7F\x41\x41\x41'\ + b'\x02\x04\x08\x10\x20'\ + b'\x00\x41\x41\x41\x7F'\ + b'\x04\x02\x01\x02\x04'\ + b'\x40\x40\x40\x40\x40'\ + b'\x00\x03\x07\x08\x00'\ + b'\x20\x54\x54\x78\x40'\ + b'\x7F\x28\x44\x44\x38'\ + b'\x38\x44\x44\x44\x28'\ + b'\x38\x44\x44\x28\x7F'\ + b'\x38\x54\x54\x54\x18'\ + b'\x00\x08\x7E\x09\x02'\ + b'\x18\xA4\xA4\x9C\x78'\ + b'\x7F\x08\x04\x04\x78'\ + b'\x00\x44\x7D\x40\x00'\ + b'\x20\x40\x40\x3D\x00'\ + b'\x7F\x10\x28\x44\x00'\ + b'\x00\x41\x7F\x40\x00'\ + b'\x7C\x04\x78\x04\x78'\ + b'\x7C\x08\x04\x04\x78'\ + b'\x38\x44\x44\x44\x38'\ + b'\xFC\x18\x24\x24\x18'\ + b'\x18\x24\x24\x18\xFC'\ + b'\x7C\x08\x04\x04\x08'\ + b'\x48\x54\x54\x54\x24'\ + b'\x04\x04\x3F\x44\x24'\ + b'\x3C\x40\x40\x20\x7C'\ + b'\x1C\x20\x40\x20\x1C'\ + b'\x3C\x40\x30\x40\x3C'\ + b'\x44\x28\x10\x28\x44'\ + b'\x4C\x90\x90\x90\x7C'\ + b'\x44\x64\x54\x4C\x44'\ + b'\x00\x08\x36\x41\x00'\ + b'\x00\x00\x77\x00\x00'\ + b'\x00\x41\x36\x08\x00'\ + b'\x02\x01\x02\x04\x02'\ + b'\x3C\x26\x23\x26\x3C'\ + b'\x1E\xA1\xA1\x61\x12'\ + b'\x3A\x40\x40\x20\x7A'\ + b'\x38\x54\x54\x55\x59'\ + b'\x21\x55\x55\x79\x41'\ + b'\x21\x54\x54\x78\x41'\ + b'\x21\x55\x54\x78\x40'\ + b'\x20\x54\x55\x79\x40'\ + b'\x0C\x1E\x52\x72\x12'\ + b'\x39\x55\x55\x55\x59'\ + b'\x39\x54\x54\x54\x59'\ + b'\x39\x55\x54\x54\x58'\ + b'\x00\x00\x45\x7C\x41'\ + b'\x00\x02\x45\x7D\x42'\ + b'\x00\x01\x45\x7C\x40'\ + b'\xF0\x29\x24\x29\xF0'\ + b'\xF0\x28\x25\x28\xF0'\ + b'\x7C\x54\x55\x45\x00'\ + b'\x20\x54\x54\x7C\x54'\ + b'\x7C\x0A\x09\x7F\x49'\ + b'\x32\x49\x49\x49\x32'\ + b'\x32\x48\x48\x48\x32'\ + b'\x32\x4A\x48\x48\x30'\ + b'\x3A\x41\x41\x21\x7A'\ + b'\x3A\x42\x40\x20\x78'\ + b'\x00\x9D\xA0\xA0\x7D'\ + b'\x39\x44\x44\x44\x39'\ + b'\x3D\x40\x40\x40\x3D'\ + b'\x3C\x24\xFF\x24\x24'\ + b'\x48\x7E\x49\x43\x66'\ + b'\x2B\x2F\xFC\x2F\x2B'\ + b'\xFF\x09\x29\xF6\x20'\ + b'\xC0\x88\x7E\x09\x03'\ + b'\x20\x54\x54\x79\x41'\ + b'\x00\x00\x44\x7D\x41'\ + b'\x30\x48\x48\x4A\x32'\ + b'\x38\x40\x40\x22\x7A'\ + b'\x00\x7A\x0A\x0A\x72'\ + b'\x7D\x0D\x19\x31\x7D'\ + b'\x26\x29\x29\x2F\x28'\ + b'\x26\x29\x29\x29\x26'\ + b'\x30\x48\x4D\x40\x20'\ + b'\x38\x08\x08\x08\x08'\ + b'\x08\x08\x08\x08\x38'\ + b'\x2F\x10\xC8\xAC\xBA'\ + b'\x2F\x10\x28\x34\xFA'\ + b'\x00\x00\x7B\x00\x00'\ + b'\x08\x14\x2A\x14\x22'\ + b'\x22\x14\x2A\x14\x08'\ + b'\xAA\x00\x55\x00\xAA'\ + b'\xAA\x55\xAA\x55\xAA'\ + b'\x00\x00\x00\xFF\x00'\ + b'\x10\x10\x10\xFF\x00'\ + b'\x14\x14\x14\xFF\x00'\ + b'\x10\x10\xFF\x00\xFF'\ + b'\x10\x10\xF0\x10\xF0'\ + b'\x14\x14\x14\xFC\x00'\ + b'\x14\x14\xF7\x00\xFF'\ + b'\x00\x00\xFF\x00\xFF'\ + b'\x14\x14\xF4\x04\xFC'\ + b'\x14\x14\x17\x10\x1F'\ + b'\x10\x10\x1F\x10\x1F'\ + b'\x14\x14\x14\x1F\x00'\ + b'\x10\x10\x10\xF0\x00'\ + b'\x00\x00\x00\x1F\x10'\ + b'\x10\x10\x10\x1F\x10'\ + b'\x10\x10\x10\xF0\x10'\ + b'\x00\x00\x00\xFF\x10'\ + b'\x10\x10\x10\x10\x10'\ + b'\x10\x10\x10\xFF\x10'\ + b'\x00\x00\x00\xFF\x14'\ + b'\x00\x00\xFF\x00\xFF'\ + b'\x00\x00\x1F\x10\x17'\ + b'\x00\x00\xFC\x04\xF4'\ + b'\x14\x14\x17\x10\x17'\ + b'\x14\x14\xF4\x04\xF4'\ + b'\x00\x00\xFF\x00\xF7'\ + b'\x14\x14\x14\x14\x14'\ + b'\x14\x14\xF7\x00\xF7'\ + b'\x14\x14\x14\x17\x14'\ + b'\x10\x10\x1F\x10\x1F'\ + b'\x14\x14\x14\xF4\x14'\ + b'\x10\x10\xF0\x10\xF0'\ + b'\x00\x00\x1F\x10\x1F'\ + b'\x00\x00\x00\x1F\x14'\ + b'\x00\x00\x00\xFC\x14'\ + b'\x00\x00\xF0\x10\xF0'\ + b'\x10\x10\xFF\x10\xFF'\ + b'\x14\x14\x14\xFF\x14'\ + b'\x10\x10\x10\x1F\x00'\ + b'\x00\x00\x00\xF0\x10'\ + b'\xFF\xFF\xFF\xFF\xFF'\ + b'\xF0\xF0\xF0\xF0\xF0'\ + b'\xFF\xFF\xFF\x00\x00'\ + b'\x00\x00\x00\xFF\xFF'\ + b'\x0F\x0F\x0F\x0F\x0F'\ + b'\x38\x44\x44\x38\x44'\ + b'\x7C\x2A\x2A\x3E\x14'\ + b'\x7E\x02\x02\x06\x06'\ + b'\x02\x7E\x02\x7E\x02'\ + b'\x63\x55\x49\x41\x63'\ + b'\x38\x44\x44\x3C\x04'\ + b'\x40\x7E\x20\x1E\x20'\ + b'\x06\x02\x7E\x02\x02'\ + b'\x99\xA5\xE7\xA5\x99'\ + b'\x1C\x2A\x49\x2A\x1C'\ + b'\x4C\x72\x01\x72\x4C'\ + b'\x30\x4A\x4D\x4D\x30'\ + b'\x30\x48\x78\x48\x30'\ + b'\xBC\x62\x5A\x46\x3D'\ + b'\x3E\x49\x49\x49\x00'\ + b'\x7E\x01\x01\x01\x7E'\ + b'\x2A\x2A\x2A\x2A\x2A'\ + b'\x44\x44\x5F\x44\x44'\ + b'\x40\x51\x4A\x44\x40'\ + b'\x40\x44\x4A\x51\x40'\ + b'\x00\x00\xFF\x01\x03'\ + b'\xE0\x80\xFF\x00\x00'\ + b'\x08\x08\x6B\x6B\x08'\ + b'\x36\x12\x36\x24\x36'\ + b'\x06\x0F\x09\x0F\x06'\ + b'\x00\x00\x18\x18\x00'\ + b'\x00\x00\x10\x10\x00'\ + b'\x30\x40\xFF\x01\x01'\ + b'\x00\x1F\x01\x01\x1E'\ + b'\x00\x19\x1D\x17\x12'\ + b'\x00\x3C\x3C\x3C\x3C'\ + b'\x00\x00\x00\x00\x00' + +_mvfont = memoryview(_font) + +def get_width(s): + return len(s)*6 + +def get_ch(ch): + ordch = ord(ch) + offset = ordch*5 + buf = bytearray(6) + buf[0] = 0 + buf[1:]=_mvfont[offset:offset+5] + return buf, 6 + diff --git a/src/ili934xnew.py b/src/ili934xnew.py new file mode 100644 index 0000000..4e89606 --- /dev/null +++ b/src/ili934xnew.py @@ -0,0 +1,327 @@ +# This is an adapted version of the ILI934X driver as below. +# It works with multiple fonts and also works with the esp32 H/W SPI implementation +# Also includes a word wrap print function +# Proportional fonts are generated by Peter Hinch's Font-to-py +# MIT License; Copyright (c) 2017 Jeffrey N. Magee + +# This file is part of MicroPython ILI934X driver +# Copyright (c) 2016 - 2017 Radomir Dopieralski, Mika Tuupola +# +# Licensed under the MIT license: +# http://www.opensource.org/licenses/mit-license.php +# +# Project home: +# https://github.com/tuupola/micropython-ili934x + +import time +import ustruct +import glcdfont +import framebuf +from micropython import const + +_RDDSDR = const(0x0f) # Read Display Self-Diagnostic Result +_SLPOUT = const(0x11) # Sleep Out +_GAMSET = const(0x26) # Gamma Set +_DISPOFF = const(0x28) # Display Off +_DISPON = const(0x29) # Display On +_CASET = const(0x2a) # Column Address Set +_PASET = const(0x2b) # Page Address Set +_RAMWR = const(0x2c) # Memory Write +_RAMRD = const(0x2e) # Memory Read +_MADCTL = const(0x36) # Memory Access Control +_VSCRSADD = const(0x37) # Vertical Scrolling Start Address +_PIXSET = const(0x3a) # Pixel Format Set +_PWCTRLA = const(0xcb) # Power Control A +_PWCRTLB = const(0xcf) # Power Control B +_DTCTRLA = const(0xe8) # Driver Timing Control A +_DTCTRLB = const(0xea) # Driver Timing Control B +_PWRONCTRL = const(0xed) # Power on Sequence Control +_PRCTRL = const(0xf7) # Pump Ratio Control +_PWCTRL1 = const(0xc0) # Power Control 1 +_PWCTRL2 = const(0xc1) # Power Control 2 +_VMCTRL1 = const(0xc5) # VCOM Control 1 +_VMCTRL2 = const(0xc7) # VCOM Control 2 +_FRMCTR1 = const(0xb1) # Frame Rate Control 1 +_DISCTRL = const(0xb6) # Display Function Control +_ENA3G = const(0xf2) # Enable 3G +_PGAMCTRL = const(0xe0) # Positive Gamma Control +_NGAMCTRL = const(0xe1) # Negative Gamma Control + +_CHUNK = const(1024) #maximum number of pixels per spi write + +def color565(r, g, b): + return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3 + +class ILI9341: + + def __init__(self, spi, cs, dc, rst, w, h, r): + self.spi = spi + self.cs = cs + self.dc = dc + self.rst = rst + self._init_width = w + self._init_height = h + self.width = w + self.height = h + self.rotation = r + self.cs.init(self.cs.OUT, value=1) + self.dc.init(self.dc.OUT, value=0) + self.rst.init(self.rst.OUT, value=0) + self.reset() + self.init() + self._scroll = 0 + self._buf = bytearray(_CHUNK * 2) + self._colormap = bytearray(b'\x00\x00\xFF\xFF') #default white foregraound, black background + self._x = 0 + self._y = 0 + self._font = glcdfont + self.scrolling = False + + def set_color(self,fg,bg): + self._colormap[0] = bg>>8 + self._colormap[1] = bg & 255 + self._colormap[2] = fg>>8 + self._colormap[3] = fg & 255 + + def set_pos(self,x,y): + self._x = x + self._y = y + + def reset_scroll(self): + self.scrolling = False + self._scroll = 0 + self.scroll(0) + + def set_font(self, font): + self._font = font + + def init(self): + for command, data in ( + (_RDDSDR, b"\x03\x80\x02"), + (_PWCRTLB, b"\x00\xc1\x30"), + (_PWRONCTRL, b"\x64\x03\x12\x81"), + (_DTCTRLA, b"\x85\x00\x78"), + (_PWCTRLA, b"\x39\x2c\x00\x34\x02"), + (_PRCTRL, b"\x20"), + (_DTCTRLB, b"\x00\x00"), + (_PWCTRL1, b"\x23"), + (_PWCTRL2, b"\x10"), + (_VMCTRL1, b"\x3e\x28"), + (_VMCTRL2, b"\x86")): + self._write(command, data) + + if self.rotation == 0: # 0 deg + self._write(_MADCTL, b"\x48") + self.width = self._init_height + self.height = self._init_width + elif self.rotation == 1: # 90 deg + self._write(_MADCTL, b"\x28") + self.width = self._init_width + self.height = self._init_height + elif self.rotation == 2: # 180 deg + self._write(_MADCTL, b"\x88") + self.width = self._init_height + self.height = self._init_width + elif self.rotation == 3: # 270 deg + self._write(_MADCTL, b"\xE8") + self.width = self._init_width + self.height = self._init_height + elif self.rotation == 4: # Mirrored + 0 deg + self._write(_MADCTL, b"\xC8") + self.width = self._init_height + self.height = self._init_width + elif self.rotation == 5: # Mirrored + 90 deg + self._write(_MADCTL, b"\x68") + self.width = self._init_width + self.height = self._init_height + elif self.rotation == 6: # Mirrored + 180 deg + self._write(_MADCTL, b"\x08") + self.width = self._init_height + self.height = self._init_width + elif self.rotation == 7: # Mirrored + 270 deg + self._write(_MADCTL, b"\xA8") + self.width = self._init_width + self.height = self._init_height + else: + self._write(_MADCTL, b"\x08") + + for command, data in ( + (_PIXSET, b"\x55"), + (_FRMCTR1, b"\x00\x18"), + (_DISCTRL, b"\x08\x82\x27"), + (_ENA3G, b"\x00"), + (_GAMSET, b"\x01"), + (_PGAMCTRL, b"\x0f\x31\x2b\x0c\x0e\x08\x4e\xf1\x37\x07\x10\x03\x0e\x09\x00"), + (_NGAMCTRL, b"\x00\x0e\x14\x03\x11\x07\x31\xc1\x48\x08\x0f\x0c\x31\x36\x0f")): + self._write(command, data) + self._write(_SLPOUT) + time.sleep_ms(120) + self._write(_DISPON) + + def reset(self): + self.rst(0) + time.sleep_ms(50) + self.rst(1) + time.sleep_ms(50) + + def _write(self, command, data=None): + self.dc(0) + self.cs(0) + self.spi.write(bytearray([command])) + self.cs(1) + if data is not None: + self._data(data) + + def _data(self, data): + self.dc(1) + self.cs(0) + self.spi.write(data) + self.cs(1) + + def _writeblock(self, x0, y0, x1, y1, data=None): + self._write(_CASET, ustruct.pack(">HH", x0, x1)) + self._write(_PASET, ustruct.pack(">HH", y0, y1)) + self._write(_RAMWR, data) + + def _readblock(self, x0, y0, x1, y1, data=None): + self._write(_CASET, ustruct.pack(">HH", x0, x1)) + self._write(_PASET, ustruct.pack(">HH", y0, y1)) + if data is None: + return self._read(_RAMRD, (x1 - x0 + 1) * (y1 - y0 + 1) * 3) + + def _read(self, command, count): + self.dc(0) + self.cs(0) + self.spi.write(bytearray([command])) + data = self.spi.read(count) + self.cs(1) + return data + + def pixel(self, x, y, color=None): + if color is None: + r, b, g = self._readblock(x, y, x, y) + return color565(r, g, b) + if not 0 <= x < self.width or not 0 <= y < self.height: + return + self._writeblock(x, y, x, y, ustruct.pack(">H", color)) + + def fill_rectangle(self, x, y, w, h, color=None): + x = min(self.width - 1, max(0, x)) + y = min(self.height - 1, max(0, y)) + w = min(self.width - x, max(1, w)) + h = min(self.height - y, max(1, h)) + if color: + color = ustruct.pack(">H", color) + else: + color = self._colormap[0:2] #background + for i in range(_CHUNK): + self._buf[2*i]=color[0]; self._buf[2*i+1]=color[1] + chunks, rest = divmod(w * h, _CHUNK) + self._writeblock(x, y, x + w - 1, y + h - 1, None) + if chunks: + for count in range(chunks): + self._data(self._buf) + if rest != 0: + mv = memoryview(self._buf) + self._data(mv[:rest*2]) + + def erase(self): + self.fill_rectangle(0, 0, self.width, self.height) + + def blit(self, bitbuff, x, y, w, h): + x = min(self.width - 1, max(0, x)) + y = min(self.height - 1, max(0, y)) + w = min(self.width - x, max(1, w)) + h = min(self.height - y, max(1, h)) + chunks, rest = divmod(w * h, _CHUNK) + self._writeblock(x, y, x + w - 1, y + h - 1, None) + written = 0 + for iy in range(h): + for ix in range(w): + index = ix+iy*w - written + if index >=_CHUNK: + self._data(self._buf) + written += _CHUNK + index -= _CHUNK + c = bitbuff.pixel(ix,iy) + self._buf[index*2] = self._colormap[c*2] + self._buf[index*2+1] = self._colormap[c*2+1] + rest = w*h - written + if rest != 0: + mv = memoryview(self._buf) + self._data(mv[:rest*2]) + + def chars(self, str, x, y): + str_w = self._font.get_width(str) + div, rem = divmod(self._font.height(),8) + nbytes = div+1 if rem else div + buf = bytearray(str_w * nbytes) + pos = 0 + for ch in str: + glyph, char_w = self._font.get_ch(ch) + for row in range(nbytes): + index = row*str_w + pos + for i in range(char_w): + buf[index+i] = glyph[nbytes*i+row] + pos += char_w + fb = framebuf.FrameBuffer(buf,str_w, self._font.height(), framebuf.MONO_VLSB) + self.blit(fb,x,y,str_w,self._font.height()) + return x+str_w + + def scroll(self, dy): + self._scroll = (self._scroll + dy) % self.height + self._write(_VSCRSADD, ustruct.pack(">H", self._scroll)) + + def next_line(self, cury, char_h): + global scrolling + if not self.scrolling: + res = cury + char_h + self.scrolling = (res >= self.height) + if self.scrolling: + self.scroll(char_h) + res = (self.height - char_h + self._scroll)%self.height + self.fill_rectangle(0, res, self.width, self._font.height()) + return res + + def write(self, text): #does character wrap, compatible with stream output + curx = self._x; cury = self._y + char_h = self._font.height() + width = 0 + written = 0 + for pos, ch in enumerate(text): + if ch == '\n': + if pos>0: + self.chars(text[written:pos],curx,cury) + curx = 0; written = pos+1; width = 0 + cury = self.next_line(cury,char_h) + else: + char_w = self._font.get_width(ch) + if curx + width + char_w >= self.width: + self.chars(text[written:pos], curx,cury) + curx = 0 ; written = pos; width = char_h + cury = self.next_line(cury,char_h) + else: + width += char_w + if written= self.width: + curx = self._x; cury = self.next_line(cury,char_h) + while self._font.get_width(word) > self.width: + self.chars(word[:self.width//char_w],curx,cury) + word = word[self.width//char_w:] + cury = self.next_line(cury,char_h) + if len(word)>0: + curx = self.chars(word+' ', curx,cury) + curx = self._x; cury = self.next_line(cury,char_h) + self._y = cury diff --git a/src/m5stack.py b/src/m5stack.py new file mode 100755 index 0000000..45ef163 --- /dev/null +++ b/src/m5stack.py @@ -0,0 +1,24 @@ + +# This file is part of MicroPython M5Stack package +# Copyright (c) 2017 Mika Tuupola +# +# Licensed under the MIT license: +# http://www.opensource.org/licenses/mit-license.php +# +# Project home: +# https://github.com/tuupola/micropython-m5stacj + +from micropython import const + +BUTTON_A_PIN = const(39) +BUTTON_B_PIN = const(38) +BUTTON_C_PIN = const(37) +SPEAKER_PIN = const(25) + +TFT_LED_PIN = const(32) +TFT_DC_PIN = const(27) +TFT_CS_PIN = const(14) +TFT_MOSI_PIN = const(23) +TFT_CLK_PIN = const(18) +TFT_RST_PIN = const(33) +TFT_MISO_PIN = const(19) diff --git a/src/main.py b/src/main.py index b6709fa..1cb6b15 100644 --- a/src/main.py +++ b/src/main.py @@ -1,299 +1,128 @@ -from machine import Pin, I2C, Timer, UART -import uasyncio -import json +# test of printing multiple fonts to the ILI9341 on an M5Stack using H/W SP +# MIT License; Copyright (c) 2017 Jeffrey N. Magee -import ssd1306 +from ili934xnew import ILI9341, color565 +from machine import Pin, SPI,Timer +import m5stack +import tt14 +import glcdfont +import tt14 +import tt24 +import tt32 from rotary_irq_esp import RotaryIRQ -from dshot import Dshot -from pid import PID - - -def splash(): - # display.fill(0) - # display.fill_rect(0, 0, 32, 32, 1) - # display.fill_rect(2, 2, 28, 28, 0) - # display.vline(9, 8, 22, 1) - # display.vline(16, 2, 22, 1) - # display.vline(23, 8, 22, 1) - # display.fill_rect(26, 24, 2, 4, 1) - # display.text("MicroPython", 40, 0, 1) - # display.text("SSD1306", 40, 12, 1) - # display.text("OLED 128x64", 40, 24, 1) - - display.fill(0) - display.text("SPIN", 34, 4, 1) - display.text("COATER", 50, 14, 1) - - display.show() - - -def start_view(state, rotary): - display.text("SPIN", 34, 4, 1) - display.text("COATER", 50, 14, 1) - - before = "> " if rotary.value() == 0 else " " - display.text(before + "Edit", 12, 36, 1) - before = "> " if rotary.value() == 1 else " " - display.text(before + "Start", 12, 46, 1) - - -def draw_edit_menu(state, rotary): - display.text("Deposit speed:", 0, 0, 1) - display.text("{: >{w}} RPM".format(config["deposit_rpm"], w=5), 56, 10, 1) - display.text("Coating speed:", 0, 21, 1) - display.text("{: >{w}} RPM".format(config["coating_rpm"], w=5), 56, 31, 1) - display.text("Coating time:", 0, 42, 1) - display.text("{: >{w}} sec".format(config["coating_time"], w=5), 56, 52, 1) - - -def edit_deposit_view(state, rotary): - config["deposit_rpm"] = rotary.value() * 100 - draw_edit_menu(state, rotary) - display.text(">", 40, 10, 1) - - -def edit_coating_rpm_view(state, rotary): - config["coating_rpm"] = rotary.value() * 100 - draw_edit_menu(state, rotary) - display.text(">", 40, 32, 1) - - -def edit_coating_time_view(state, rotary): - config["coating_time"] = rotary.value() - draw_edit_menu(state, rotary) - display.text(">", 40, 54, 1) - - -def draw_rpm(rpm): - display.text("RPM:{: >{w}.0f}".format(rpm, w=5), 30, 27, 1) - - -def deposit_view(state, rotary): - display.fill_rect(0, 0, 127, 14, 1) - display.text("Deposit", 36, 3, 0) - draw_rpm(state["rpm"]) - display.text("Press to", 32, 42, 1) - display.text("continue", 32, 52, 1) - - -def coating_view(state, rotary): - display.fill_rect(0, 0, 127, 14, 1) - display.text("Coating", 36, 3, 0) - draw_rpm(state["rpm"]) - display.text("{: >{w}} sec".format(state["timer"], w=4), 30, 48, 1) - - -def decode_ESC_telemetry(data, motor_poles=14): - if len(data) > 10: - # use latest telemetry - data = data[-10:] - - temperature = int(data[0]) # degrees Celsius - voltage = int((data[1] << 8) | data[2]) * 0.01 # Volt - current = ( - int((data[3] << 8) | data[4]) * 0.01 - ) # Amps, only available if the ESC has a current meter - consumption = int( - (data[5] << 8) | data[6] - ) # mAh, only available if the ESC has a current meter - erpm = int((data[7] << 8) | data[8]) * 100 - rpm = erpm / (motor_poles / 2) - crc = data[9] - - # print(" Temp (C):", temperature) - # print(" Voltage (V):", voltage) - # print(" Current (A):", current) - # print("Consumption (mAh):", consumption) - # print(" Erpm:", erpm) - # print(" RPM:", rpm) - # print(" CRC:", crc) - # print() - - return temperature, voltage, current, consumption, erpm, rpm - - -async def update_display(): - global state - global rotary - while True: - display.fill(0) - state["view"](state, rotary) - display.show() - await uasyncio.sleep_ms(33) - - -async def update_motor(): - global state - dshot = Dshot(pin=Pin(18)) - rpm_pid = PID( - Kp=config["PID"]["Kp"], - Ki=config["PID"]["Ki"], - Kd=config["PID"]["Kd"], - setpoint=0, - sample_time=None, - output_limits=(0.0, 1.0), - # proportional_on_measurement=True, - ) - while True: - rpm_pid.setpoint = state["target_rpm"] - - # read ESC telemetry - if uart.any() >= 10: - telemetry = decode_ESC_telemetry(uart.read()) - if telemetry is not None: - state["rpm"] = telemetry[5] - throttle = rpm_pid(state["rpm"]) - # print( - # "Throttle:", - # throttle, - # "pid components:", - # rpm_pid.components, - # "RPM:", - # state["rpm"], - # ) - - if state["target_rpm"] == 0 and state["rpm"] < 1000: - throttle = 0 - rpm_pid.reset() - dshot.set_throttle(throttle) - - await uasyncio.sleep_ms(1) - - +fonts = [glcdfont,tt14,tt24,tt32] def debounce_button(p): p.irq(trigger=Pin.IRQ_FALLING, handler=None) # remove irq timer0 = Timer(0) + print("Button debounced!") timer0.init(period=20, mode=Timer.ONE_SHOT, callback=lambda t: on_button_press(p)) - def on_button_press(p): p.irq(trigger=Pin.IRQ_FALLING, handler=debounce_button) # restore irq + print("Button pressed") if p.value() == 1: # debounce return - global state - global config - global rotary - if state["view"] == start_view: - if rotary.value() == 0: - state["view"] = edit_deposit_view - rotary.set( - min_val=0, - max_val=1000, - range_mode=RotaryIRQ.RANGE_BOUNDED, - value=int(0.01 * config["deposit_rpm"]), - ) - return - if rotary.value() == 1: - state["view"] = deposit_view - state["target_rpm"] = config["deposit_rpm"] - return - if state["view"] == edit_deposit_view: - state["view"] = edit_coating_rpm_view - rotary.set( - min_val=0, - max_val=1000, - range_mode=RotaryIRQ.RANGE_BOUNDED, - value=int(0.01 * config["coating_rpm"]), - ) - return - if state["view"] == edit_coating_rpm_view: - state["view"] = edit_coating_time_view - rotary.set( - min_val=0, - max_val=9999, - range_mode=RotaryIRQ.RANGE_BOUNDED, - value=config["coating_time"], - ) - return - if state["view"] == edit_coating_time_view: - save_config() - rotary.set(min_val=0, max_val=1, range_mode=RotaryIRQ.RANGE_BOUNDED, value=0) - state["view"] = start_view - return - if state["view"] == deposit_view: - state["view"] = coating_view - start_coating(state) - return - if state["view"] == coating_view: - stop_coating() - return + + # return + # global state + # global config + # global rotary + # if state["view"] == start_view: + # if rotary.value() == 0: + # state["view"] = edit_deposit_view + # rotary.set( + # min_val=0, + # max_val=1000, + # range_mode=RotaryIRQ.RANGE_BOUNDED, + # value=int(0.01 * config["deposit_rpm"]), + # ) + # return + # if rotary.value() == 1: + # state["view"] = deposit_view + # state["target_rpm"] = config["deposit_rpm"] + # return + # if state["view"] == edit_deposit_view: + # state["view"] = edit_coating_rpm_view + # rotary.set( + # min_val=0, + # max_val=1000, + # range_mode=RotaryIRQ.RANGE_BOUNDED, + # value=int(0.01 * config["coating_rpm"]), + # ) + # return + # if state["view"] == edit_coating_rpm_view: + # state["view"] = edit_coating_time_view + # rotary.set( + # min_val=0, + # max_val=9999, + # range_mode=RotaryIRQ.RANGE_BOUNDED, + # value=config["coating_time"], + # ) + # return + # if state["view"] == edit_coating_time_view: + # save_config() + # rotary.set(min_val=0, max_val=1, range_mode=RotaryIRQ.RANGE_BOUNDED, value=0) + # state["view"] = start_view + # return + # if state["view"] == deposit_view: + # state["view"] = coating_view + # start_coating(state) + # return + # if state["view"] == coating_view: + # stop_coating() + # return -def start_coating(state): - global timer1 - global timer2 - state["timer"] = config["coating_time"] +text = 'Now is the time for all good men to come to the aid of the party.' - timer1.init( - period=config["coating_time"] * 1000, - mode=Timer.ONE_SHOT, - callback=lambda t: stop_coating(), - ) - - def decrement_timer(t): - state["timer"] -= 1 - - timer2.init(period=1000, mode=Timer.PERIODIC, callback=decrement_timer) - - # state["throttle"] = 0.10 - state["target_rpm"] = config["coating_rpm"] +power = Pin(m5stack.TFT_LED_PIN, Pin.OUT) +power.value(1) -def stop_coating(): - global state - global rotary - global timer1 - global timer2 - timer1.deinit() - timer2.deinit() - state["target_rpm"] = 0 - rotary.set(min_val=0, max_val=1, range_mode=RotaryIRQ.RANGE_BOUNDED, value=0) - state["view"] = start_view +# No need to change the software. It's just a matter of different names.. Use this translation: +# SDO(MISO) +# LED BL +# SCK CLK/SCK +# SDI(MOSI) DIN/SDA +# DC D/C (A0) +# RESET RST +# CS CS +# GND GND +# VCC VCC +print("BOOOOOOTTTT") +spi = SPI( + 2, + baudrate=40000000, + miso=Pin(m5stack.TFT_MISO_PIN), + mosi=Pin(m5stack.TFT_MOSI_PIN), + sck=Pin(m5stack.TFT_CLK_PIN)) -def save_config(): - global config - with open("config.json", "w") as f: - json.dump(config, f) - - -# using default address 0x3c -i2c = I2C(1, sda=Pin(21), scl=Pin(22)) -display = ssd1306.SSD1306_I2C(128, 64, i2c) -display.rotate(0) - -timer1 = Timer(1) -timer2 = Timer(2) - - -splash() - +display = ILI9341( + spi, + cs=Pin(m5stack.TFT_CS_PIN), + dc=Pin(m5stack.TFT_DC_PIN), + rst=Pin(m5stack.TFT_RST_PIN), + w=128, + h=160, + r=3) rotary = RotaryIRQ( - pin_num_clk=14, + pin_num_clk=25, pin_num_dt=13, min_val=0, max_val=1, range_mode=RotaryIRQ.RANGE_BOUNDED, pull_up=True, ) - -button = Pin(19, Pin.IN, Pin.PULL_UP) +button = Pin(15, Pin.IN, Pin.PULL_UP) button.irq(trigger=Pin.IRQ_FALLING, handler=on_button_press) -uart = UART(1, baudrate=115200, rx=5) # to receive ESC telemetry -state = { - "view": start_view, - "rpm": 0, - "target_rpm": 0, - "timer": 0, -} +display.erase() +display.set_pos(0,0) +for ff in fonts: + display.set_font(ff) + display.print(text) -with open("config.json", "r") as f: - config = json.load(f) -event_loop = uasyncio.get_event_loop() -event_loop.create_task(update_display()) -event_loop.create_task(update_motor()) -event_loop.run_forever() diff --git a/src/rotary.py b/src/rotary.py index 0a566c2..f36b950 100644 --- a/src/rotary.py +++ b/src/rotary.py @@ -90,7 +90,7 @@ class Rotary(object): max_val=None, reverse=None, range_mode=None): # disable DT and CLK pin interrupts self._hal_disable_irq() - + #print("SETTING VALUE!") if value is not None: self._value = value if min_val is not None: @@ -124,6 +124,7 @@ class Rotary(object): self._listener.remove(l) def _process_rotary_pins(self, pin): + print(self._value) old_value = self._value clk_dt_pins = (self._hal_get_clk_value() << 1) | self._hal_get_dt_value() diff --git a/src/rotations_test.py b/src/rotations_test.py new file mode 100644 index 0000000..f8f1b93 --- /dev/null +++ b/src/rotations_test.py @@ -0,0 +1,16 @@ +from ili934xnew import ILI9341, color565 +from machine import Pin, SPI +import tt14 + +text = 'F' +spi = SPI(2, baudrate=20000000, miso=Pin(19),mosi=Pin(23), sck=Pin(18)) +display = ILI9341(spi, cs=Pin(2), dc=Pin(27), rst=Pin(33), w=320, h=240, r=0) +display.erase() +display.set_font(tt14) +display.set_pos(0,0) +display.print(text) +display.set_pos(0,20) +display.print(text) +display.set_pos(40,20) +display.print(text) + diff --git a/src/tt14.py b/src/tt14.py new file mode 100644 index 0000000..60100b1 --- /dev/null +++ b/src/tt14.py @@ -0,0 +1,159 @@ +# Code generated by font-to-py.py. +# Font: CM Sans Serif 2012.ttf +version = '0.2' + +def height(): + return 15 + +def max_width(): + return 12 + +def hmap(): + return False + +def reverse(): + return False + +def monospaced(): + return False + +def min_ch(): + return 32 + +def max_ch(): + return 126 + +_font =\ +b'\x08\x00\x08\x00\x0c\x00\x8c\x07\xcc\x07\xcc\x00\x7c\x00\x38\x00'\ +b'\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\xfc\x06'\ +b'\xfc\x06\x00\x00\x05\x00\x1c\x00\x3c\x00\x00\x00\x3c\x00\x00\x00'\ +b'\x07\x00\x40\x03\xf0\x03\xf0\x03\x30\x03\xf0\x03\x30\x03\x30\x00'\ +b'\x07\x00\x30\x00\x78\x03\x6c\x02\xfc\x07\xd8\x03\x90\x03\x00\x00'\ +b'\x0b\x00\x18\x00\x3c\x00\x3c\x04\x3c\x03\xd8\x01\xf0\x03\xd8\x07'\ +b'\xc4\x06\xc0\x07\x80\x03\x00\x00\x09\x00\x80\x03\xf8\x07\x7c\x06'\ +b'\x4c\x06\xfc\x06\x98\x07\xc0\x07\xc0\x05\x00\x04\x02\x00\x1c\x00'\ +b'\x3c\x00\x04\x00\xf0\x03\xfc\x0f\x0e\x1c\x02\x10\x05\x00\x02\x10'\ +b'\x0e\x1c\xfc\x0f\xf0\x03\x00\x00\x05\x00\x0c\x00\x3c\x00\x18\x00'\ +b'\x2c\x00\x0c\x00\x07\x00\xc0\x00\xc0\x00\xf0\x03\xf0\x03\xc0\x00'\ +b'\xc0\x00\x00\x00\x03\x00\x00\x16\x00\x0e\x00\x00\x04\x00\xc0\x00'\ +b'\xc0\x00\xc0\x00\xc0\x00\x03\x00\x00\x06\x00\x06\x00\x00\x04\x00'\ +b'\x00\x06\xe0\x07\xfc\x00\x0e\x00\x08\x00\xf0\x01\xf8\x03\x1c\x07'\ +b'\x0c\x06\x1c\x07\xf8\x03\xf0\x01\x00\x00\x04\x00\x18\x00\xfc\x07'\ +b'\xfc\x07\x00\x00\x08\x00\x18\x04\x1c\x07\x8c\x06\x8c\x06\x4c\x06'\ +b'\x7c\x06\x38\x06\x00\x00\x07\x00\x08\x02\x0c\x06\x0c\x06\x6c\x06'\ +b'\x6c\x06\xfc\x07\xd8\x03\x07\x00\x80\x01\xe0\x01\xb0\x01\x9c\x01'\ +b'\xfc\x07\xfc\x07\x80\x01\x07\x00\x00\x02\xfc\x06\x6c\x06\x6c\x06'\ +b'\x6c\x06\xcc\x03\xc0\x03\x08\x00\xf0\x01\xf8\x03\x6c\x06\x6c\x06'\ +b'\x6c\x06\xe8\x07\xc0\x03\x00\x00\x06\x00\x0c\x00\x0c\x07\xcc\x07'\ +b'\xfc\x00\x1c\x00\x0c\x00\x07\x00\x98\x03\xfc\x07\x6c\x06\x6c\x06'\ +b'\x6c\x06\xfc\x07\x98\x03\x08\x00\x78\x02\xfc\x06\xcc\x06\xcc\x06'\ +b'\xcc\x06\xf8\x03\xf0\x01\x00\x00\x03\x00\x30\x06\x30\x06\x00\x00'\ +b'\x03\x00\x30\x16\x30\x0e\x00\x00\x06\x00\xc0\x01\xc0\x01\x60\x03'\ +b'\x60\x03\x30\x06\x00\x00\x06\x00\x60\x03\x60\x03\x60\x03\x60\x03'\ +b'\x60\x03\x60\x03\x06\x00\x30\x06\x60\x03\x60\x03\x40\x01\xc0\x01'\ +b'\x00\x00\x08\x00\x08\x00\x0c\x00\x8c\x07\xcc\x07\xcc\x00\x7c\x00'\ +b'\x38\x00\x00\x00\x0b\x00\xe0\x00\xf8\x01\x18\x03\xec\x07\xbc\x07'\ +b'\xfc\x06\xfc\x07\x9c\x03\xf8\x01\xf0\x00\x00\x00\x08\x00\x00\x06'\ +b'\x80\x07\xf0\x01\x9c\x01\x8c\x01\xf8\x01\xc0\x07\x00\x07\x08\x00'\ +b'\xfc\x07\xfc\x07\x6c\x06\x6c\x06\x6c\x06\xfc\x07\xd8\x03\x00\x00'\ +b'\x09\x00\xe0\x00\xf8\x03\x18\x03\x0c\x06\x0c\x06\x0c\x06\x1c\x07'\ +b'\x18\x03\x20\x01\x09\x00\xfc\x07\xfc\x07\x0c\x06\x0c\x06\x1c\x07'\ +b'\xf8\x03\xf0\x01\x00\x00\x00\x00\x08\x00\xfc\x07\xfc\x07\x6c\x06'\ +b'\x6c\x06\x6c\x06\x6c\x06\x0c\x06\x00\x00\x07\x00\xfc\x07\xfc\x07'\ +b'\xcc\x00\xcc\x00\xcc\x00\xcc\x00\x0c\x00\x0a\x00\xe0\x01\xf8\x03'\ +b'\x18\x07\x0c\x06\x0c\x06\xcc\x06\xdc\x02\xd8\x07\xe0\x07\x00\x00'\ +b'\x09\x00\xfc\x07\xfc\x07\x60\x00\x60\x00\x60\x00\xfc\x07\xfc\x07'\ +b'\x00\x00\x00\x00\x03\x00\xfc\x07\xfc\x07\x00\x00\x07\x00\x80\x03'\ +b'\x80\x07\x00\x06\x00\x06\xfc\x07\xfc\x03\x00\x00\x08\x00\xfc\x07'\ +b'\xfc\x07\x60\x00\xf0\x00\xd8\x01\x0c\x07\x04\x06\x04\x04\x07\x00'\ +b'\xfc\x07\xfc\x07\x00\x06\x00\x06\x00\x06\x00\x06\x00\x00\x0b\x00'\ +b'\xfc\x07\xfc\x07\x3c\x00\xe0\x03\x00\x06\xe0\x03\x3c\x00\xfc\x07'\ +b'\xfc\x07\x00\x00\x00\x00\x09\x00\xfc\x07\xfc\x07\x38\x00\xe0\x00'\ +b'\x80\x03\xfc\x07\xfc\x07\x00\x00\x00\x00\x0a\x00\xf0\x01\xf8\x03'\ +b'\x1c\x07\x0c\x06\x0c\x06\x0c\x06\x1c\x07\xf8\x03\xf0\x01\x00\x00'\ +b'\x09\x00\xfc\x07\xfc\x07\xcc\x00\xcc\x00\xcc\x00\xfc\x00\x78\x00'\ +b'\x00\x00\x00\x00\x0a\x00\xf0\x01\xf8\x03\x1c\x07\x0c\x06\x8c\x06'\ +b'\x8c\x07\x1c\x07\xf8\x07\xf0\x05\x00\x00\x09\x00\xfc\x07\xfc\x07'\ +b'\xcc\x00\xcc\x00\xcc\x03\x7c\x07\x78\x04\x00\x00\x00\x00\x09\x00'\ +b'\x38\x01\x3c\x03\x4c\x06\x4c\x06\x4c\x06\x4c\x06\x98\x07\x90\x03'\ +b'\x00\x00\x07\x00\x0c\x00\x0c\x00\xfc\x07\xfc\x07\x0c\x00\x0c\x00'\ +b'\x00\x00\x09\x00\xfc\x03\xfc\x07\x00\x06\x00\x06\x00\x06\xfc\x07'\ +b'\xfc\x01\x00\x00\x00\x00\x08\x00\x0c\x00\x7c\x00\xe0\x03\x00\x07'\ +b'\x80\x07\xf8\x01\x3c\x00\x04\x00\x0b\x00\x0c\x00\xfc\x00\xe0\x07'\ +b'\x80\x07\xf8\x03\x1c\x00\xfc\x00\xc0\x07\x80\x07\xfc\x01\x1c\x00'\ +b'\x08\x00\x04\x04\x0c\x07\xb8\x03\xf0\x00\xf0\x01\x9c\x07\x0c\x06'\ +b'\x04\x04\x08\x00\x04\x00\x1c\x00\x78\x00\xe0\x07\xe0\x07\x78\x00'\ +b'\x1c\x00\x04\x00\x07\x00\x0c\x06\x0c\x07\x8c\x07\xcc\x06\x2c\x06'\ +b'\x1c\x06\x0c\x06\x04\x00\xff\x1f\xff\x1f\x03\x18\x00\x00\x04\x00'\ +b'\x0e\x00\xfe\x00\xe0\x07\x00\x07\x04\x00\x03\x18\xff\x1f\xff\x1f'\ +b'\x00\x00\x07\x00\x80\x00\xe0\x00\x7c\x00\x0c\x00\x7c\x00\xe0\x00'\ +b'\x80\x00\x0a\x00\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18'\ +b'\x00\x18\x00\x18\x00\x18\x00\x18\x05\x00\x04\x00\x0c\x00\x08\x00'\ +b'\x00\x00\x00\x00\x08\x00\x00\x03\xe0\x07\xb0\x06\xb0\x06\xb0\x02'\ +b'\xf0\x07\xe0\x07\x00\x00\x09\x00\xfe\x07\xfe\x07\x30\x02\x30\x06'\ +b'\x30\x06\xe0\x07\xc0\x01\x00\x00\x00\x00\x07\x00\xc0\x01\xe0\x03'\ +b'\x30\x06\x30\x06\x30\x06\x60\x03\x40\x01\x08\x00\xc0\x01\xf0\x07'\ +b'\x30\x06\x30\x06\x30\x06\xfe\x07\xfe\x07\x00\x00\x07\x00\xc0\x01'\ +b'\xe0\x03\xb0\x07\xb0\x07\xb0\x07\xe0\x03\xc0\x01\x04\x00\x20\x00'\ +b'\xfc\x07\xfe\x07\x00\x00\x08\x00\xc0\x11\xf0\x1f\x30\x36\x30\x36'\ +b'\x20\x36\xf0\x1f\xf0\x0f\x00\x00\x08\x00\xfe\x07\xfe\x07\x30\x00'\ +b'\x30\x00\xf0\x07\xe0\x07\x00\x00\x00\x00\x03\x00\xfc\x07\xfc\x07'\ +b'\x00\x00\x03\x00\xec\x7f\xec\x3f\x00\x00\x07\x00\xfe\x07\xfe\x07'\ +b'\xc0\x00\xe0\x01\xb0\x07\x10\x06\x10\x04\x03\x00\xfe\x07\xfe\x07'\ +b'\x00\x00\x0c\x00\xf0\x07\xf0\x07\x30\x00\x30\x00\xf0\x07\xe0\x07'\ +b'\x30\x00\x30\x00\xf0\x07\xe0\x07\x00\x00\x00\x00\x08\x00\xf0\x07'\ +b'\xf0\x07\x30\x00\x30\x00\xf0\x07\xe0\x07\x00\x00\x00\x00\x08\x00'\ +b'\xc0\x01\xe0\x03\x30\x06\x30\x06\x30\x06\xe0\x03\xc0\x01\x00\x00'\ +b'\x09\x00\xf0\x3f\xf0\x3f\x30\x06\x30\x06\x30\x06\xf0\x07\xc0\x01'\ +b'\x00\x00\x00\x00\x08\x00\xc0\x01\xf0\x07\x30\x06\x30\x06\x30\x06'\ +b'\xf0\x3f\xf0\x3f\x00\x00\x04\x00\xf0\x07\xf0\x07\x30\x00\x30\x00'\ +b'\x08\x00\x60\x00\x70\x03\xb0\x07\xf0\x06\xb0\x06\xa0\x07\x00\x03'\ +b'\x00\x00\x04\x00\x30\x00\xfe\x03\xfe\x07\x00\x00\x08\x00\xf0\x03'\ +b'\xf0\x07\x00\x06\x00\x06\xf0\x07\xf0\x07\x00\x00\x00\x00\x07\x00'\ +b'\x30\x00\xf0\x00\x80\x07\x00\x06\xc0\x03\xf0\x00\x10\x00\x0a\x00'\ +b'\x30\x00\xf0\x01\x80\x07\xc0\x07\xf0\x00\xf0\x00\x80\x07\x80\x07'\ +b'\xf0\x01\x30\x00\x07\x00\x10\x04\x30\x07\xe0\x03\xc0\x01\x70\x07'\ +b'\x10\x06\x00\x04\x07\x00\x30\x00\xf0\x30\xc0\x3f\x00\x1f\xe0\x03'\ +b'\x70\x00\x10\x00\x06\x00\x30\x06\x30\x07\xb0\x07\xf0\x06\x70\x06'\ +b'\x30\x06\x05\x00\x40\x00\xbe\x0f\xbe\x0f\x00\x00\x00\x00\x04\x00'\ +b'\xfe\x07\xfe\x07\x00\x00\x00\x00\x04\x00\xbe\x0f\xbe\x0f\x40\x00'\ +b'\x00\x00\x05\x00\x04\x00\x04\x00\x04\x00\x06\x00\x04\x00' + +_index =\ +b'\x00\x00\x12\x00\x1c\x00\x24\x00\x30\x00\x40\x00\x50\x00\x68\x00'\ +b'\x7c\x00\x82\x00\x8c\x00\x98\x00\xa4\x00\xb4\x00\xbc\x00\xc6\x00'\ +b'\xce\x00\xd8\x00\xea\x00\xf4\x00\x06\x01\x16\x01\x26\x01\x36\x01'\ +b'\x48\x01\x56\x01\x66\x01\x78\x01\x80\x01\x88\x01\x96\x01\xa4\x01'\ +b'\xb2\x01\xc4\x01\xdc\x01\xee\x01\x00\x02\x14\x02\x28\x02\x3a\x02'\ +b'\x4a\x02\x60\x02\x74\x02\x7c\x02\x8c\x02\x9e\x02\xae\x02\xc6\x02'\ +b'\xda\x02\xf0\x02\x04\x03\x1a\x03\x2e\x03\x42\x03\x52\x03\x66\x03'\ +b'\x78\x03\x90\x03\xa2\x03\xb4\x03\xc4\x03\xce\x03\xd8\x03\xe2\x03'\ +b'\xf2\x03\x08\x04\x14\x04\x26\x04\x3a\x04\x4a\x04\x5c\x04\x6c\x04'\ +b'\x76\x04\x88\x04\x9a\x04\xa2\x04\xaa\x04\xba\x04\xc2\x04\xdc\x04'\ +b'\xee\x04\x00\x05\x14\x05\x26\x05\x30\x05\x42\x05\x4c\x05\x5e\x05'\ +b'\x6e\x05\x84\x05\x94\x05\xa4\x05\xb2\x05\xbe\x05\xc8\x05\xd2\x05'\ +b'\xde\x05' + +_mvfont = memoryview(_font) + +def _chr_addr(ordch): + offset = 2 * (ordch - 32) + return int.from_bytes(_index[offset:offset + 2], 'little') + +def get_width(s): + width = 0 + for ch in s: + ordch = ord(ch) + ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 32 + offset = _chr_addr(ordch) + width += int.from_bytes(_font[offset:offset + 2], 'little') + return width + +def get_ch(ch): + ordch = ord(ch) + ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 32 + offset = _chr_addr(ordch) + width = int.from_bytes(_font[offset:offset + 2], 'little') + next_offs = _chr_addr(ordch +1) + return _mvfont[offset + 2:next_offs], width + diff --git a/src/tt24.py b/src/tt24.py new file mode 100644 index 0000000..bd6bd13 --- /dev/null +++ b/src/tt24.py @@ -0,0 +1,286 @@ +# Code generated by font-to-py.py. +# Font: CM Sans Serif 2012.ttf +version = '0.2' + +def height(): + return 24 + +def max_width(): + return 20 + +def hmap(): + return False + +def reverse(): + return False + +def monospaced(): + return False + +def min_ch(): + return 32 + +def max_ch(): + return 126 + +_font =\ +b'\x0c\x00\xc0\x00\x00\xf0\x00\x00\xf0\x00\x00\x78\x70\x07\x38\x78'\ +b'\x07\x38\x7c\x07\x78\x1e\x00\xf0\x0f\x00\xf0\x07\x00\xc0\x03\x00'\ +b'\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00'\ +b'\x00\x02\xf8\x3f\x07\xf8\x3f\x07\xf8\x3f\x02\x00\x00\x00\x00\x00'\ +b'\x00\x08\x00\xf8\x03\x00\xf8\x03\x00\x08\x00\x00\x00\x00\x00\xf8'\ +b'\x03\x00\xf8\x03\x00\x08\x00\x00\x00\x00\x00\x0c\x00\x00\xe0\x00'\ +b'\x80\xe3\x00\x80\xf3\x03\xe0\xff\x03\xe0\xff\x00\xe0\xe3\x00\x80'\ +b'\xe3\x03\x80\xff\x03\xe0\xff\x01\xe0\xe7\x00\x80\xe3\x00\x80\x03'\ +b'\x00\x0b\x00\x00\xe0\x00\xc0\xe3\x01\xe0\xc7\x01\x70\x8e\x03\xf8'\ +b'\xef\x07\xf8\xff\x07\x70\x8c\x03\xe0\x9d\x01\xc0\xf9\x01\x00\xf0'\ +b'\x00\x00\x00\x00\x12\x00\xe0\x01\x00\xf0\x03\x00\x38\x07\x00\x38'\ +b'\x07\x04\x38\x07\x07\x38\xc7\x07\xf0\xf3\x01\xe0\x79\x00\x00\x1e'\ +b'\x00\x80\x07\x00\xe0\xe3\x01\xf8\xf0\x03\x38\x38\x07\x08\x38\x07'\ +b'\x00\x38\x07\x00\xf0\x03\x00\xe0\x01\x00\x00\x00\x0f\x00\x00\xf0'\ +b'\x00\x00\xf8\x01\xe0\xfd\x03\xf0\x8f\x07\xf8\x07\x07\x38\x0e\x07'\ +b'\x38\x1f\x07\xf8\x3f\x07\xf0\xfb\x07\xe0\xf0\x07\x00\xe0\x03\x00'\ +b'\xf8\x07\x00\xf8\x07\x00\x78\x06\x00\x00\x04\x04\x00\xf8\x03\x00'\ +b'\xf8\x03\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x7f\x00\xe0\xff'\ +b'\x03\xf8\xff\x0f\x7c\x00\x1f\x0e\x00\x38\x02\x00\x20\x02\x00\x20'\ +b'\x07\x00\x02\x00\x20\x0e\x00\x38\x7c\x00\x1f\xf8\xff\x0f\xe0\xff'\ +b'\x03\x00\x7f\x00\x00\x00\x00\x09\x00\x60\x00\x00\x60\x00\x00\x78'\ +b'\x03\x00\xf0\x01\x00\xe0\x00\x00\xf8\x03\x00\x68\x01\x00\x60\x00'\ +b'\x00\x00\x00\x00\x0c\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00'\ +b'\x38\x00\x80\xff\x03\x80\xff\x03\x80\xff\x03\x00\x38\x00\x00\x38'\ +b'\x00\x00\x38\x00\x00\x38\x00\x00\x00\x00\x05\x00\x00\x00\x37\x00'\ +b'\x00\x3f\x00\x00\x1f\x00\x00\x00\x00\x00\x00\x07\x00\x00\x38\x00'\ +b'\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00'\ +b'\x38\x00\x05\x00\x00\x00\x07\x00\x00\x07\x00\x00\x07\x00\x00\x00'\ +b'\x00\x00\x00\x08\x00\x00\x00\x06\x00\xe0\x07\x00\xfc\x07\xc0\xff'\ +b'\x01\xf8\x3f\x00\xfc\x03\x00\x7c\x00\x00\x04\x00\x00\x0d\x00\x80'\ +b'\x7f\x00\xe0\xff\x01\xf0\xff\x03\xf8\xc0\x07\x38\x00\x07\x38\x00'\ +b'\x07\x38\x00\x07\xf8\xc0\x07\xf0\xff\x03\xe0\xff\x01\x80\x7f\x00'\ +b'\x00\x00\x00\x00\x00\x00\x08\x00\xc0\x01\x00\xc0\x01\x00\xe0\x01'\ +b'\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x00\x00\x00\x00\x00\x00'\ +b'\x0d\x00\xc0\x81\x07\xe0\xe1\x07\xf0\xe1\x07\x78\x70\x07\x38\x38'\ +b'\x07\x38\x38\x07\x38\x1c\x07\x78\x1e\x07\xf0\x0f\x07\xf0\x07\x07'\ +b'\xc0\x03\x07\x00\x00\x00\x00\x00\x00\x0c\x00\xc0\xc0\x00\xf0\xc0'\ +b'\x03\xf0\xc0\x03\x78\x80\x07\x38\x00\x07\x38\x0e\x07\x38\x0e\x07'\ +b'\x78\x9e\x07\xf0\xff\x03\xf0\xff\x03\xe0\xf1\x00\x00\x00\x00\x0c'\ +b'\x00\x00\xf0\x00\x00\xf8\x00\x00\xfe\x00\x00\xef\x00\xc0\xe7\x00'\ +b'\xf0\xe1\x00\xf8\xe0\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x00'\ +b'\xe0\x00\x00\xe0\x00\x0c\x00\x00\xce\x00\xf8\xcf\x03\xf8\xcf\x03'\ +b'\x38\x8e\x07\x38\x07\x07\x38\x07\x07\x38\x07\x07\x38\x8f\x07\x38'\ +b'\xfe\x03\x38\xfc\x01\x00\xf8\x00\x00\x00\x00\x0d\x00\x00\x7f\x00'\ +b'\xe0\xff\x01\xf0\xff\x03\x78\x8e\x07\x38\x07\x07\x38\x07\x07\x38'\ +b'\x07\x07\x78\x8f\x07\xf0\xfe\x03\xe0\xfc\x01\xc0\xf8\x00\x00\x00'\ +b'\x00\x00\x00\x00\x0b\x00\x38\x00\x00\x38\x00\x00\x38\xc0\x07\x38'\ +b'\xf8\x07\x38\xfe\x07\xb8\x3f\x00\xf8\x07\x00\xf8\x01\x00\xf8\x00'\ +b'\x00\x78\x00\x00\x38\x00\x00\x0c\x00\x00\xf0\x00\xe0\xf9\x01\xf0'\ +b'\xff\x03\xf0\x9f\x07\x38\x0e\x07\x38\x0e\x07\x38\x0e\x07\x38\x0e'\ +b'\x07\xf0\x9f\x07\xf0\xff\x03\xe0\xf9\x01\x00\xf0\x00\x0d\x00\xc0'\ +b'\x07\x00\xe0\x0f\x01\xf0\x1f\x03\x78\x3c\x07\x38\x38\x07\x38\x38'\ +b'\x07\x38\x38\x07\x38\x38\x07\x70\x9c\x07\xf0\xff\x03\xe0\xff\x01'\ +b'\x80\x7f\x00\x00\x00\x00\x05\x00\x00\x07\x07\x00\x07\x07\x00\x07'\ +b'\x07\x00\x00\x00\x00\x00\x00\x05\x00\x00\x07\x37\x00\x07\x3f\x00'\ +b'\x07\x1f\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x38\x00\x00\x38\x00'\ +b'\x00\x7c\x00\x00\x6c\x00\x00\xee\x00\x00\xee\x00\x00\xc7\x01\x00'\ +b'\xc7\x01\x80\x83\x03\x00\x00\x00\x0b\x00\x00\xce\x01\x00\xce\x01'\ +b'\x00\xce\x01\x00\xce\x01\x00\xce\x01\x00\xce\x01\x00\xce\x01\x00'\ +b'\xce\x01\x00\xce\x01\x00\xce\x01\x00\x00\x00\x0a\x00\x80\x83\x03'\ +b'\x80\x83\x03\x00\xc7\x01\x00\xc7\x01\x00\xee\x00\x00\xee\x00\x00'\ +b'\x7c\x00\x00\x7c\x00\x00\x38\x00\x00\x00\x00\x0c\x00\xc0\x00\x00'\ +b'\xf0\x00\x00\xf0\x00\x00\x78\x70\x07\x38\x78\x07\x38\x7c\x07\x78'\ +b'\x1e\x00\xf0\x0f\x00\xf0\x07\x00\xc0\x03\x00\x00\x00\x00\x00\x00'\ +b'\x00\x11\x00\x00\x3f\x00\xc0\xff\x00\xe0\xe1\x01\xf0\xc0\x03\x70'\ +b'\xbc\x03\x78\x7e\x07\x38\x7f\x07\xb8\x73\x07\xb8\x33\x07\xb8\x3f'\ +b'\x07\x38\x7f\x07\x70\xf3\x03\x70\x70\x03\xe0\x38\x02\xe0\x3f\x00'\ +b'\x80\x0f\x00\x00\x00\x00\x0f\x00\x00\x00\x06\x00\xc0\x07\x00\xf0'\ +b'\x07\x00\xfe\x03\xc0\xff\x00\xf8\xef\x00\xf8\xe1\x00\x78\xe0\x00'\ +b'\xf8\xe3\x00\xf0\xff\x00\xc0\xff\x00\x00\xfe\x07\x00\xf0\x07\x00'\ +b'\x80\x07\x00\x00\x04\x0f\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07'\ +b'\x38\x0e\x07\x38\x0e\x07\x38\x0e\x07\x38\x0e\x07\x38\x0e\x07\x38'\ +b'\x9e\x07\xf8\xff\x07\xf0\xff\x03\xe0\xf1\x01\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x10\x00\x00\x3f\x00\xc0\xff\x00\xe0\xff\x01\xf0'\ +b'\xe1\x03\x70\x80\x03\x78\x80\x07\x38\x00\x07\x38\x00\x07\x38\x00'\ +b'\x07\x38\x00\x07\x78\x80\x03\xf0\xc0\x03\xe0\xc0\x01\xc0\xc0\x00'\ +b'\x80\x40\x00\x00\x00\x00\x0f\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff'\ +b'\x07\x38\x00\x07\x38\x00\x07\x38\x00\x07\x38\x00\x07\x38\x00\x07'\ +b'\x78\x80\x07\xf0\xc0\x03\xe0\xff\x01\xc0\xff\x00\x00\x3f\x00\x00'\ +b'\x00\x00\x00\x00\x00\x0e\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07'\ +b'\x38\x0e\x07\x38\x0e\x07\x38\x0e\x07\x38\x0e\x07\x38\x0e\x07\x38'\ +b'\x0e\x07\x38\x0e\x07\x38\x00\x07\x38\x00\x07\x00\x00\x00\x00\x00'\ +b'\x00\x0d\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x38\x1c\x00\x38'\ +b'\x1c\x00\x38\x1c\x00\x38\x1c\x00\x38\x1c\x00\x38\x1c\x00\x38\x1c'\ +b'\x00\x38\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x3f\x00\xc0'\ +b'\xff\x00\xe0\xff\x01\xf0\xe1\x03\x70\x80\x03\x78\x80\x07\x38\x00'\ +b'\x07\x38\x00\x07\x38\x1c\x07\x38\x1c\x07\x78\x9c\x03\xf0\xdc\x01'\ +b'\xe0\xfc\x07\xc0\xfc\x07\x80\xfc\x07\x00\x00\x00\x00\x00\x00\x0f'\ +b'\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x00\x0e\x00\x00\x0e\x00'\ +b'\x00\x0e\x00\x00\x0e\x00\x00\x0e\x00\x00\x0e\x00\x00\x0e\x00\xf8'\ +b'\xff\x07\xf8\xff\x07\xf8\xff\x07\x00\x00\x00\x00\x00\x00\x06\x00'\ +b'\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x0c\x00\x00\xe0\x00\x00\xe0\x01\x00\xe0\x03\x00\x80\x07'\ +b'\x00\x00\x07\x00\x00\x07\x00\x00\x07\x00\x80\x07\xf8\xff\x03\xf8'\ +b'\xff\x03\xf8\xff\x00\x00\x00\x00\x0e\x00\xf8\xff\x07\xf8\xff\x07'\ +b'\xf8\xff\x07\x00\x1e\x00\x00\x0f\x00\x80\x1f\x00\xc0\x7f\x00\xe0'\ +b'\xf9\x00\xf0\xf0\x03\x78\xc0\x07\x38\x80\x07\x18\x00\x07\x08\x00'\ +b'\x04\x00\x00\x00\x0c\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x00'\ +b'\x00\x07\x00\x00\x07\x00\x00\x07\x00\x00\x07\x00\x00\x07\x00\x00'\ +b'\x07\x00\x00\x07\x00\x00\x07\x00\x00\x00\x12\x00\xf8\xff\x07\xf8'\ +b'\xff\x07\xf8\xff\x07\xf8\x01\x00\xf8\x1f\x00\xc0\xff\x00\x00\xfc'\ +b'\x07\x00\xe0\x07\x00\xe0\x07\x00\xfc\x07\xc0\xff\x00\xf8\x1f\x00'\ +b'\xf8\x01\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x00\x00\x00\x00'\ +b'\x00\x00\x0f\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\xf8\x00\x00'\ +b'\xe0\x03\x00\xc0\x0f\x00\x00\x3f\x00\x00\xfc\x00\x00\xf0\x01\x00'\ +b'\xe0\x07\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x00\x00\x00\x00\x00'\ +b'\x00\x11\x00\x00\x3f\x00\xc0\xff\x00\xe0\xff\x01\xf0\xe1\x03\x70'\ +b'\x80\x03\x78\x80\x07\x38\x00\x07\x38\x00\x07\x38\x00\x07\x78\x80'\ +b'\x07\x70\x80\x03\xf0\xe1\x03\xe0\xff\x01\xc0\xff\x00\x00\x3f\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0e\x00\xf8\xff\x07\xf8\xff\x07\xf8\xff'\ +b'\x07\x38\x1c\x00\x38\x1c\x00\x38\x1c\x00\x38\x1c\x00\x38\x1c\x00'\ +b'\x78\x1e\x00\xf0\x0f\x00\xf0\x0f\x00\xc0\x03\x00\x00\x00\x00\x00'\ +b'\x00\x00\x11\x00\x00\x3f\x00\xc0\xff\x00\xe0\xff\x01\xf0\xe1\x03'\ +b'\x70\x80\x03\x78\x80\x07\x38\x00\x07\x38\x10\x07\x38\x38\x07\x38'\ +b'\x70\x07\x78\xf0\x07\x70\xe0\x03\xf0\xe1\x03\xe0\xff\x07\xc0\xff'\ +b'\x03\x00\x3f\x02\x00\x00\x00\x0f\x00\xf8\xff\x07\xf8\xff\x07\xf8'\ +b'\xff\x07\x38\x1c\x00\x38\x1c\x00\x38\x1c\x00\x38\x3c\x00\x38\xfc'\ +b'\x00\x38\xfc\x03\x78\xfe\x07\xf0\x9f\x07\xf0\x0f\x07\xc0\x07\x04'\ +b'\x00\x00\x00\x00\x00\x00\x0e\x00\x00\xc0\x00\xe0\xc1\x01\xf0\xe3'\ +b'\x03\xf0\x87\x03\x78\x06\x07\x38\x0e\x07\x38\x0e\x07\x38\x0c\x07'\ +b'\x38\x0c\x07\x78\x9c\x07\xf0\xf8\x03\xe0\xf8\x03\xc0\xf0\x00\x00'\ +b'\x00\x00\x0c\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00'\ +b'\xf8\xff\x07\xf8\xff\x07\xf8\xff\x07\x38\x00\x00\x38\x00\x00\x38'\ +b'\x00\x00\x38\x00\x00\x00\x00\x00\x0e\x00\xf8\xff\x00\xf8\xff\x01'\ +b'\xf8\xff\x03\x00\x80\x07\x00\x00\x07\x00\x00\x07\x00\x00\x07\x00'\ +b'\x00\x07\x00\x80\x07\xf8\xff\x03\xf8\xff\x01\xf8\xff\x00\x00\x00'\ +b'\x00\x00\x00\x00\x0e\x00\x18\x00\x00\xf8\x00\x00\xf8\x07\x00\xf0'\ +b'\x3f\x00\x00\xff\x01\x00\xf8\x07\x00\x80\x07\x00\xe0\x07\x00\xfe'\ +b'\x07\xc0\x7f\x00\xf8\x0f\x00\xf8\x01\x00\x38\x00\x00\x08\x00\x00'\ +b'\x14\x00\x18\x00\x00\xf8\x01\x00\xf8\x1f\x00\xe0\xff\x00\x00\xfe'\ +b'\x07\x00\xe0\x07\x00\xf8\x07\xc0\xff\x07\xf8\x3f\x00\xf8\x03\x00'\ +b'\xf8\x01\x00\xf8\x3f\x00\xc0\xff\x07\x00\xf8\x07\x00\xc0\x07\x00'\ +b'\xfe\x07\xe0\xff\x01\xf8\x1f\x00\xf8\x01\x00\x38\x00\x00\x0e\x00'\ +b'\x08\x00\x04\x18\x00\x07\x78\x80\x07\xf8\xe0\x07\xf0\xf3\x01\xc0'\ +b'\xff\x00\x00\x3f\x00\x80\x7f\x00\xe0\xff\x00\xf0\xf1\x03\xf8\xc0'\ +b'\x07\x38\x80\x07\x18\x00\x06\x08\x00\x04\x0d\x00\x18\x00\x00\x38'\ +b'\x00\x00\xf8\x00\x00\xf0\x03\x00\xc0\x0f\x00\x00\xff\x07\x00\xfc'\ +b'\x07\x00\xff\x07\xc0\x0f\x00\xf0\x03\x00\xf8\x00\x00\x38\x00\x00'\ +b'\x18\x00\x00\x0d\x00\x00\x00\x07\x38\x80\x07\x38\xc0\x07\x38\xe0'\ +b'\x07\x38\x78\x07\x38\x3c\x07\x38\x1e\x07\x38\x0f\x07\xf8\x07\x07'\ +b'\xf8\x01\x07\xf8\x00\x07\x78\x00\x07\x38\x00\x07\x07\x00\xff\xff'\ +b'\x3f\xff\xff\x3f\xff\xff\x3f\x07\x00\x38\x07\x00\x38\x07\x00\x38'\ +b'\x00\x00\x00\x08\x00\x04\x00\x00\x3c\x00\x00\xfc\x01\x00\xfc\x1f'\ +b'\x00\xe0\xff\x00\x00\xfe\x07\x00\xf0\x07\x00\x00\x07\x07\x00\x07'\ +b'\x00\x38\x07\x00\x38\x07\x00\x38\xff\xff\x3f\xff\xff\x3f\xff\xff'\ +b'\x3f\x00\x00\x00\x0c\x00\x00\x18\x00\x00\x1f\x00\xc0\x1f\x00\xf8'\ +b'\x03\x00\xf8\x00\x00\x78\x00\x00\xf8\x03\x00\xe0\x0f\x00\x00\x1f'\ +b'\x00\x00\x1c\x00\x00\x10\x00\x00\x00\x00\x12\x00\x00\x00\x38\x00'\ +b'\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00'\ +b'\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38'\ +b'\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00'\ +b'\x00\x38\x09\x00\x04\x00\x00\x04\x00\x00\x0c\x00\x00\x1c\x00\x00'\ +b'\x18\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d'\ +b'\x00\x00\xc0\x01\x00\xe4\x03\x00\xe6\x07\x00\x37\x07\x80\x37\x07'\ +b'\x80\x33\x07\x80\x33\x07\x80\x33\x07\x80\x93\x03\x80\xff\x07\x00'\ +b'\xff\x07\x00\xfe\x07\x00\x00\x00\x0e\x00\xfc\xff\x07\xfc\xff\x07'\ +b'\xfc\xff\x07\x00\x87\x03\x80\x03\x07\x80\x03\x07\x80\x03\x07\x80'\ +b'\x03\x07\x80\x87\x07\x00\xff\x03\x00\xfe\x01\x00\xf8\x00\x00\x00'\ +b'\x00\x00\x00\x00\x0d\x00\x00\x78\x00\x00\xfe\x01\x00\xff\x03\x00'\ +b'\x87\x03\x80\x03\x07\x80\x03\x07\x80\x03\x07\x80\x03\x07\x80\x87'\ +b'\x07\x00\x87\x03\x00\x86\x01\x00\x88\x00\x00\x00\x00\x0e\x00\x00'\ +b'\x78\x00\x00\xfe\x01\x00\xff\x03\x00\x87\x03\x80\x87\x07\x80\x03'\ +b'\x07\x80\x03\x07\x80\x03\x07\x80\x87\x07\x00\x87\x03\xfc\xff\x07'\ +b'\xfc\xff\x07\xfc\xff\x07\x00\x00\x00\x0d\x00\x00\x30\x00\x00\xfc'\ +b'\x00\x00\xfe\x01\x00\xff\x03\x80\x77\x07\x80\x73\x07\x80\x73\x07'\ +b'\x80\x73\x07\x80\xf7\x07\x00\xff\x03\x00\x7e\x03\x00\x7c\x00\x00'\ +b'\x60\x00\x07\x00\x80\x03\x00\xf0\xff\x07\xfc\xff\x07\xfc\xff\x07'\ +b'\x9e\x03\x00\x8e\x03\x00\x00\x00\x00\x0e\x00\x00\x78\x00\x00\xfe'\ +b'\x01\x00\xff\x23\x00\x87\x73\x80\x87\xf7\x80\x03\xe7\x80\x03\xe7'\ +b'\x80\x03\xe7\x00\x87\xe7\x00\x87\xf3\x80\xff\x7f\x80\xff\x3f\x80'\ +b'\xff\x1f\x00\x00\x00\x0d\x00\xfc\xff\x07\xfc\xff\x07\xfc\xff\x07'\ +b'\x00\x07\x00\x80\x03\x00\x80\x03\x00\x80\x03\x00\x80\x07\x00\x00'\ +b'\xff\x07\x00\xff\x07\x00\xfc\x07\x00\x00\x00\x00\x00\x00\x05\x00'\ +b'\xb8\xff\x07\xb8\xff\x07\xb8\xff\x07\x00\x00\x00\x00\x00\x00\x05'\ +b'\x00\x00\x00\xe0\x00\x00\xe0\xb8\xff\xff\xb8\xff\xff\xb8\xff\x7f'\ +b'\x0c\x00\xfc\xff\x07\xfc\xff\x07\xfc\xff\x07\x00\x78\x00\x00\x3c'\ +b'\x00\x00\xfe\x00\x80\xff\x01\x80\xe7\x07\x80\x83\x07\x80\x01\x07'\ +b'\x80\x00\x04\x00\x00\x00\x05\x00\xfc\xff\x07\xfc\xff\x07\xfc\xff'\ +b'\x07\x00\x00\x00\x00\x00\x00\x14\x00\x80\xff\x07\x80\xff\x07\x80'\ +b'\xff\x07\x00\x07\x00\x80\x03\x00\x80\x03\x00\x80\x03\x00\x80\x07'\ +b'\x00\x00\xff\x07\x00\xfe\x07\x00\xff\x07\x80\x07\x00\x80\x03\x00'\ +b'\x80\x03\x00\x80\x07\x00\x00\xff\x07\x00\xff\x07\x00\xfc\x07\x00'\ +b'\x00\x00\x00\x00\x00\x0d\x00\x80\xff\x07\x80\xff\x07\x80\xff\x07'\ +b'\x00\x07\x00\x80\x03\x00\x80\x03\x00\x80\x03\x00\x80\x07\x00\x00'\ +b'\xff\x07\x00\xff\x07\x00\xfc\x07\x00\x00\x00\x00\x00\x00\x0d\x00'\ +b'\x00\x78\x00\x00\xfe\x01\x00\xff\x03\x00\x87\x03\x80\x03\x07\x80'\ +b'\x03\x07\x80\x03\x07\x80\x03\x07\x00\x87\x03\x00\xff\x03\x00\xfe'\ +b'\x01\x00\x78\x00\x00\x00\x00\x0e\x00\x80\xff\xff\x80\xff\xff\x80'\ +b'\xff\xff\x00\x87\x03\x80\x03\x07\x80\x03\x07\x80\x03\x07\x80\x03'\ +b'\x07\x80\x87\x07\x00\xff\x03\x00\xfe\x01\x00\x7c\x00\x00\x00\x00'\ +b'\x00\x00\x00\x0e\x00\x00\x78\x00\x00\xfe\x01\x00\xff\x03\x00\x87'\ +b'\x03\x80\x87\x07\x80\x03\x07\x80\x03\x07\x80\x03\x07\x80\x87\x07'\ +b'\x00\x87\x03\x80\xff\xff\x80\xff\xff\x80\xff\xff\x00\x00\x00\x08'\ +b'\x00\x80\xff\x07\x80\xff\x07\x80\xff\x07\x00\x07\x00\x80\x03\x00'\ +b'\x80\x03\x00\x80\x03\x00\x00\x00\x00\x0c\x00\x00\x80\x00\x00\x8e'\ +b'\x01\x00\x9f\x03\x80\x9f\x07\x80\x3b\x07\x80\x33\x07\x80\x33\x07'\ +b'\x80\x33\x07\x00\xf7\x07\x00\xe7\x03\x00\xc6\x01\x00\x00\x00\x07'\ +b'\x00\x80\x03\x00\xfc\xff\x00\xfc\xff\x03\xfc\xff\x07\x80\x03\x07'\ +b'\x80\x03\x07\x00\x00\x00\x0d\x00\x80\xff\x00\x80\xff\x03\x80\xff'\ +b'\x03\x00\x80\x07\x00\x00\x07\x00\x00\x07\x00\x00\x07\x00\x80\x03'\ +b'\x80\xff\x07\x80\xff\x07\x80\xff\x07\x00\x00\x00\x00\x00\x00\x0c'\ +b'\x00\x80\x01\x00\x80\x0f\x00\x80\x3f\x00\x00\xfe\x01\x00\xf0\x07'\ +b'\x00\x80\x07\x00\xc0\x07\x00\xf8\x07\x00\xff\x00\x80\x3f\x00\x80'\ +b'\x07\x00\x80\x00\x00\x12\x00\x80\x01\x00\x80\x0f\x00\x80\xff\x00'\ +b'\x00\xfe\x07\x00\xe0\x07\x00\xc0\x07\x00\xfc\x07\x80\xff\x00\x80'\ +b'\x0f\x00\x80\x3f\x00\x00\xff\x01\x00\xf0\x07\x00\x80\x07\x00\xf8'\ +b'\x07\x80\xff\x03\x80\x3f\x00\x80\x07\x00\x80\x00\x00\x0c\x00\x80'\ +b'\x00\x04\x80\x01\x07\x80\x87\x07\x80\xef\x07\x00\xfe\x01\x00\x7c'\ +b'\x00\x00\xfe\x00\x00\xff\x03\x80\xc7\x07\x80\x03\x07\x80\x00\x06'\ +b'\x00\x00\x04\x0c\x00\x80\x01\x00\x80\x07\xe0\x80\x3f\xe0\x00\xff'\ +b'\xf0\x00\xf8\xff\x00\xc0\x7f\x00\xc0\x1f\x00\xf8\x07\x00\xff\x00'\ +b'\x80\x1f\x00\x80\x07\x00\x80\x00\x00\x0b\x00\x80\x03\x07\x80\x83'\ +b'\x07\x80\xc3\x07\x80\xe3\x07\x80\xf3\x07\x80\x7b\x07\x80\x3f\x07'\ +b'\x80\x1f\x07\x80\x0f\x07\x80\x07\x07\x00\x00\x07\x08\x00\x00\x0c'\ +b'\x00\x00\x1e\x00\xfc\xff\x0f\xfe\xff\x1f\xfe\xf3\x1f\x06\x00\x38'\ +b'\x06\x00\x38\x00\x00\x00\x05\x00\xfc\xff\x07\xfc\xff\x07\xfc\xff'\ +b'\x07\x00\x00\x00\x00\x00\x00\x07\x00\x06\x00\x38\xfe\xf3\x1f\xfe'\ +b'\xff\x1f\xfc\xff\x0f\x00\x1e\x00\x00\x0c\x00\x00\x00\x00\x09\x00'\ +b'\x18\x00\x00\x0c\x00\x00\x0c\x00\x00\x0c\x00\x00\x18\x00\x00\x18'\ +b'\x00\x00\x1c\x00\x00\x04\x00\x00\x00\x00\x00' + +_index =\ +b'\x00\x00\x26\x00\x3d\x00\x51\x00\x6b\x00\x91\x00\xb4\x00\xec\x00'\ +b'\x1b\x01\x29\x01\x40\x01\x57\x01\x74\x01\x9a\x01\xab\x01\xc2\x01'\ +b'\xd3\x01\xed\x01\x16\x02\x30\x02\x59\x02\x7f\x02\xa5\x02\xcb\x02'\ +b'\xf4\x02\x17\x03\x3d\x03\x66\x03\x77\x03\x88\x03\xa8\x03\xcb\x03'\ +b'\xeb\x03\x11\x04\x46\x04\x75\x04\xa4\x04\xd6\x04\x05\x05\x31\x05'\ +b'\x5a\x05\x8f\x05\xbe\x05\xd2\x05\xf8\x05\x24\x06\x4a\x06\x82\x06'\ +b'\xb1\x06\xe6\x06\x12\x07\x47\x07\x76\x07\xa2\x07\xc8\x07\xf4\x07'\ +b'\x20\x08\x5e\x08\x8a\x08\xb3\x08\xdc\x08\xf3\x08\x0d\x09\x24\x09'\ +b'\x4a\x09\x82\x09\x9f\x09\xc8\x09\xf4\x09\x1d\x0a\x49\x0a\x72\x0a'\ +b'\x89\x0a\xb5\x0a\xde\x0a\xef\x0a\x00\x0b\x26\x0b\x37\x0b\x75\x0b'\ +b'\x9e\x0b\xc7\x0b\xf3\x0b\x1f\x0c\x39\x0c\x5f\x0c\x76\x0c\x9f\x0c'\ +b'\xc5\x0c\xfd\x0c\x23\x0d\x49\x0d\x6c\x0d\x86\x0d\x97\x0d\xae\x0d'\ +b'\xcb\x0d' + +_mvfont = memoryview(_font) + +def _chr_addr(ordch): + offset = 2 * (ordch - 32) + return int.from_bytes(_index[offset:offset + 2], 'little') + +def get_width(s): + width = 0 + for ch in s: + ordch = ord(ch) + ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 32 + offset = _chr_addr(ordch) + width += int.from_bytes(_font[offset:offset + 2], 'little') + return width + +def get_ch(ch): + ordch = ord(ch) + ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 32 + offset = _chr_addr(ordch) + width = int.from_bytes(_font[offset:offset + 2], 'little') + next_offs = _chr_addr(ordch +1) + return _mvfont[offset + 2:next_offs], width + diff --git a/src/tt32.py b/src/tt32.py new file mode 100644 index 0000000..e98667e --- /dev/null +++ b/src/tt32.py @@ -0,0 +1,430 @@ +# Code generated by font-to-py.py. +# Font: CM Sans Serif 2012.ttf +version = '0.2' + +def height(): + return 31 + +def max_width(): + return 26 + +def hmap(): + return False + +def reverse(): + return False + +def monospaced(): + return False + +def min_ch(): + return 32 + +def max_ch(): + return 126 + +_font =\ +b'\x0f\x00\x00\x07\x00\x00\xc0\x07\x00\x00\xe0\x07\x00\x00\xe0\x07'\ +b'\x00\x00\xf0\x01\x67\x00\xf0\xc0\xf7\x00\xf0\xe0\xf7\x00\xf0\xe0'\ +b'\x67\x00\xf0\xf1\x01\x00\xe0\xff\x00\x00\xe0\x7f\x00\x00\xc0\x3f'\ +b'\x00\x00\x00\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x08\x00\xf0\xff\xf3\x00\xf0\xff\xf3\x00\xf0\xff'\ +b'\xf3\x00\xf0\xff\x63\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0a\x00\xf0\x0f\x00\x00\xf0\x0f\x00\x00'\ +b'\xf0\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\x0f\x00\x00'\ +b'\xf0\x0f\x00\x00\xf0\x0f\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00'\ +b'\x0f\x00\x00\x00\x0e\x00\x00\x0e\x0e\x00\x00\x0e\x7e\x00\x00\xfe'\ +b'\x7f\x00\xe0\xff\x7f\x00\xe0\xff\x0f\x00\xe0\x0f\x0e\x00\x00\x0e'\ +b'\x0e\x00\x00\x0e\x7f\x00\x00\xfe\x7f\x00\xe0\xff\x7f\x00\xe0\xff'\ +b'\x0f\x00\xe0\x0f\x0e\x00\x00\x0e\x0e\x00\x00\x0e\x00\x00\x0e\x00'\ +b'\x00\x1f\x0e\x00\x80\x3f\x1e\x00\xc0\x3f\x3e\x00\xc0\x79\x3c\x00'\ +b'\xc0\x71\x38\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xc0\xe1\x38\x00'\ +b'\xc0\xe3\x38\x00\xc0\xe7\x3f\x00\x80\xc7\x1f\x00\x00\x86\x0f\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x17\x00\x80\x07\x00\x00\xc0\x0f'\ +b'\x00\x00\xe0\x1f\x00\x00\xf0\x3c\x00\x00\x70\x38\x80\x00\x70\x38'\ +b'\xe0\x00\x70\x38\xf8\x00\xf0\x3c\x7e\x00\xe0\x1f\x1f\x00\xc0\xcf'\ +b'\x0f\x00\x80\xf7\x03\x00\x00\xfc\x00\x00\x00\x3f\x1e\x00\x80\x8f'\ +b'\x7f\x00\xe0\x87\x7f\x00\xf0\xc1\xf3\x00\x70\xc0\xe1\x00\x10\xc0'\ +b'\xe1\x00\x00\xc0\xf3\x00\x00\x80\x7f\x00\x00\x80\x7f\x00\x00\x00'\ +b'\x1e\x00\x00\x00\x00\x00\x13\x00\x00\x00\x0f\x00\x00\x80\x3f\x00'\ +b'\x00\xc0\x3f\x00\x80\xe7\x7f\x00\xe0\x7f\xf8\x00\xe0\x3f\xf8\x00'\ +b'\xf0\x3f\xf0\x00\xf0\x78\xf0\x00\xf0\xf8\xf0\x00\xf0\xff\xf3\x00'\ +b'\xe0\xdf\xff\x00\xe0\x8f\xff\x00\x80\x07\x7e\x00\x00\xc0\xff\x00'\ +b'\x00\xc0\xff\x00\x00\xc0\xff\x00\x00\xc0\xc3\x00\x00\x00\x80\x00'\ +b'\x00\x00\x00\x00\x05\x00\xf0\x0f\x00\x00\xf0\x0f\x00\x00\xf0\x0f'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\xfc\x03\x00'\ +b'\x80\xff\x1f\x00\xe0\xff\x7f\x00\xf8\xff\xff\x01\xfc\x01\xf8\x03'\ +b'\x3e\x00\xc0\x07\x0e\x00\x00\x07\x02\x00\x00\x04\x00\x00\x00\x00'\ +b'\x09\x00\x02\x00\x00\x04\x0e\x00\x00\x07\x3e\x00\xc0\x07\xfc\x01'\ +b'\xf8\x03\xf8\xff\xff\x01\xe0\xff\x7f\x00\x80\xff\x1f\x00\x00\xfc'\ +b'\x03\x00\x00\x00\x00\x00\x0b\x00\x80\x01\x00\x00\xa0\x09\x00\x00'\ +b'\xf0\x1f\x00\x00\xe0\x0f\x00\x00\xc0\x07\x00\x00\xe0\x0f\x00\x00'\ +b'\xf0\x1d\x00\x00\xa0\x09\x00\x00\x80\x01\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x0f\x00\x00\xc0\x03\x00\x00\xc0\x03\x00\x00\xc0'\ +b'\x03\x00\x00\xc0\x03\x00\x00\xc0\x03\x00\x00\xfe\x7f\x00\x00\xfe'\ +b'\x7f\x00\x00\xfe\x7f\x00\x00\xfe\x7f\x00\x00\xc0\x03\x00\x00\xc0'\ +b'\x03\x00\x00\xc0\x03\x00\x00\xc0\x03\x00\x00\xc0\x03\x00\x00\x00'\ +b'\x00\x00\x06\x00\x00\x00\x60\x0c\x00\x00\xf0\x0c\x00\x00\xf0\x07'\ +b'\x00\x00\xe0\x03\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\xc0'\ +b'\x03\x00\x00\xc0\x03\x00\x00\xc0\x03\x00\x00\xc0\x03\x00\x00\xc0'\ +b'\x03\x00\x00\xc0\x03\x00\x00\xc0\x03\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x06\x00\x00\x00\x60\x00\x00\x00\xf0\x00\x00\x00\xf0\x00'\ +b'\x00\x00\x60\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00'\ +b'\xe0\x00\x00\x00\xfc\x00\x00\xc0\xff\x00\x00\xf8\xff\x00\x80\xff'\ +b'\x1f\x00\xf8\xff\x03\x00\xfc\x3f\x00\x00\xfc\x07\x00\x00\x7c\x00'\ +b'\x00\x00\x04\x00\x00\x00\x11\x00\x00\xfc\x03\x00\x80\xff\x1f\x00'\ +b'\xc0\xff\x3f\x00\xe0\xff\x7f\x00\xe0\x07\x7e\x00\xf0\x01\xf8\x00'\ +b'\xf0\x00\xf0\x00\xf0\x00\xf0\x00\xf0\x00\xf0\x00\xf0\x01\xf8\x00'\ +b'\xe0\x07\x7e\x00\xe0\xff\x7f\x00\xc0\xff\x3f\x00\x80\xff\x1f\x00'\ +b'\x00\xfc\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x80\x03'\ +b'\x00\x00\x80\x03\x00\x00\xc0\x03\x00\x00\xe0\xff\xff\x00\xf0\xff'\ +b'\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x10\x00\x00\x07\xf8\x00\xc0\x07\xfc\x00\xc0\x07\xfe\x00'\ +b'\xe0\x07\xff\x00\xf0\x81\xf7\x00\xf0\x80\xf3\x00\xf0\xc0\xf1\x00'\ +b'\xf0\xe0\xf1\x00\xf0\xe0\xf0\x00\xf0\xf1\xf0\x00\xe0\x7f\xf0\x00'\ +b'\xe0\x7f\xf0\x00\xc0\x3f\xf0\x00\x00\x0f\xf0\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x10\x00\x00\x00\x0e\x00\x00\x07\x3e\x00\xc0\x07'\ +b'\x3e\x00\xe0\x07\x7e\x00\xe0\x07\xf8\x00\xf0\x01\xf0\x00\xf0\xf0'\ +b'\xf0\x00\xf0\xf0\xf0\x00\xf0\xf0\xf0\x00\xf0\xf9\xf0\x00\xf0\xff'\ +b'\xf9\x00\xe0\xff\x7f\x00\xc0\xdf\x7f\x00\x80\x8f\x3f\x00\x00\x00'\ +b'\x0f\x00\x00\x00\x00\x00\x10\x00\x00\x00\x0f\x00\x00\x80\x0f\x00'\ +b'\x00\xe0\x0f\x00\x00\xf8\x0f\x00\x00\x7c\x0f\x00\x00\x3f\x0f\x00'\ +b'\x80\x0f\x0f\x00\xe0\x07\x0f\x00\xf0\x01\x0f\x00\xf0\xff\xff\x00'\ +b'\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\x00\x00\x0f\x00'\ +b'\x00\x00\x0f\x00\x00\x00\x00\x00\x0f\x00\x00\x70\x1c\x00\xf0\x7f'\ +b'\x3c\x00\xf0\x7f\x7c\x00\xf0\xff\x7c\x00\xf0\x78\xf8\x00\xf0\x3c'\ +b'\xf0\x00\xf0\x3c\xf0\x00\xf0\x3c\xf0\x00\xf0\x3c\xf0\x00\xf0\x7c'\ +b'\x78\x00\xf0\xf8\x7f\x00\xf0\xf0\x3f\x00\xf0\xe0\x1f\x00\x00\xc0'\ +b'\x0f\x00\x00\x00\x00\x00\x10\x00\x00\xfc\x07\x00\x00\xff\x1f\x00'\ +b'\xc0\xff\x3f\x00\xe0\xff\x7f\x00\xe0\xf3\x78\x00\xf0\x79\xf0\x00'\ +b'\xf0\x78\xf0\x00\xf0\x78\xf0\x00\xf0\x78\xf0\x00\xf0\xf9\xf8\x00'\ +b'\xe0\xf3\x7f\x00\xe0\xf3\x7f\x00\xc0\xe3\x3f\x00\x00\x83\x0f\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\xf0\x00\x00\x00\xf0\x00'\ +b'\x00\x00\xf0\x00\x00\x00\xf0\x00\xf8\x00\xf0\x80\xff\x00\xf0\xe0'\ +b'\xff\x00\xf0\xf8\xff\x00\xf0\xfc\x07\x00\xf0\x7e\x00\x00\xf0\x1f'\ +b'\x00\x00\xf0\x07\x00\x00\xf0\x03\x00\x00\xf0\x01\x00\x00\xf0\x00'\ +b'\x00\x00\x10\x00\x00\x00\x0f\x00\x80\xc7\x3f\x00\xc0\xef\x7f\x00'\ +b'\xe0\xff\x7f\x00\xe0\xff\xf8\x00\xf0\x7d\xf0\x00\xf0\x78\xf0\x00'\ +b'\xf0\x78\xf0\x00\xf0\x78\xf0\x00\xf0\x7d\xf0\x00\xe0\xff\xf8\x00'\ +b'\xe0\xff\x7f\x00\xc0\xef\x7f\x00\x80\xc7\x3f\x00\x00\x00\x0f\x00'\ +b'\x00\x00\x00\x00\x10\x00\x00\x3f\x00\x00\x80\xff\x30\x00\xc0\xff'\ +b'\x71\x00\xe0\xff\x71\x00\xf0\xe1\xf3\x00\xf0\xc1\xf3\x00\xf0\xc0'\ +b'\xf3\x00\xf0\xc0\xf3\x00\xf0\xc0\xf3\x00\xf0\xc1\xf9\x00\xe0\xe1'\ +b'\x7d\x00\xe0\xff\x7f\x00\xc0\xff\x3f\x00\x80\xff\x1f\x00\x00\xfc'\ +b'\x03\x00\x00\x00\x00\x00\x06\x00\x00\x18\x60\x00\x00\x3c\xf0\x00'\ +b'\x00\x3c\xf0\x00\x00\x18\x60\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x06\x00\x00\x18\x60\x0c\x00\x3c\xf0\x0c\x00\x3c\xf0\x07\x00\x18'\ +b'\xe0\x03\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\xc0\x03\x00'\ +b'\x00\xe0\x07\x00\x00\xe0\x07\x00\x00\xf0\x0f\x00\x00\xf0\x0f\x00'\ +b'\x00\x78\x1e\x00\x00\x78\x1e\x00\x00\x38\x1c\x00\x00\x3c\x3c\x00'\ +b'\x00\x1c\x38\x00\x00\x1e\x78\x00\x00\x00\x00\x00\x0e\x00\x00\x78'\ +b'\x1e\x00\x00\x78\x1e\x00\x00\x78\x1e\x00\x00\x78\x1e\x00\x00\x78'\ +b'\x1e\x00\x00\x78\x1e\x00\x00\x78\x1e\x00\x00\x78\x1e\x00\x00\x78'\ +b'\x1e\x00\x00\x78\x1e\x00\x00\x78\x1e\x00\x00\x78\x1e\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x1e\x78\x00\x00\x3c\x3c\x00'\ +b'\x00\x3c\x3c\x00\x00\x78\x1e\x00\x00\x78\x1e\x00\x00\x70\x0e\x00'\ +b'\x00\xf0\x0f\x00\x00\xe0\x07\x00\x00\xe0\x07\x00\x00\xc0\x07\x00'\ +b'\x00\xc0\x03\x00\x00\x00\x00\x00\x0f\x00\x00\x07\x00\x00\xc0\x07'\ +b'\x00\x00\xe0\x07\x00\x00\xe0\x07\x00\x00\xf0\x01\x67\x00\xf0\xc0'\ +b'\xf7\x00\xf0\xe0\xf7\x00\xf0\xe0\x67\x00\xf0\xf1\x01\x00\xe0\xff'\ +b'\x00\x00\xe0\x7f\x00\x00\xc0\x3f\x00\x00\x00\x1f\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x16\x00\x00\xf8\x01\x00\x00\xfe\x07\x00'\ +b'\x80\xff\x1f\x00\xc0\x0f\x1e\x00\xc0\x03\x3c\x00\xe0\xe1\x79\x00'\ +b'\xe0\xf8\x73\x00\xf0\xfc\xf7\x00\x70\x1c\xe7\x00\x70\x0e\xe7\x00'\ +b'\x70\x8e\xe3\x00\x70\xfe\xe1\x00\x70\xfc\xe3\x00\xf0\xfc\xe7\x00'\ +b'\xe0\x0c\x77\x00\xe0\x01\x77\x00\xc0\xc3\x23\x00\xc0\xff\x43\x00'\ +b'\x00\xff\x01\x00\x00\x7e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x13\x00\x00\x00\xc0\x00\x00\x00\xf8\x00\x00\x00\xfe\x00\x00\xc0'\ +b'\xff\x00\x00\xf8\x3f\x00\x00\xff\x0f\x00\xe0\xff\x0f\x00\xf0\x1f'\ +b'\x0f\x00\xf0\x03\x0f\x00\xf0\x00\x0f\x00\xf0\x07\x0f\x00\xf0\x3f'\ +b'\x0f\x00\xc0\xff\x0f\x00\x00\xfe\x0f\x00\x00\xf0\x7f\x00\x00\x80'\ +b'\xff\x00\x00\x00\xfc\x00\x00\x00\xf0\x00\x00\x00\x80\x00\x12\x00'\ +b'\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00'\ +b'\xf0\xf0\xf0\x00\xf0\xf0\xf0\x00\xf0\xf0\xf0\x00\xf0\xf0\xf0\x00'\ +b'\xf0\xf0\xf0\x00\xf0\xf0\xf0\x00\xf0\xf0\xf0\x00\xf0\xf9\xf0\x00'\ +b'\xf0\xff\xf9\x00\xe0\xff\x7f\x00\xc0\xdf\x7f\x00\x80\x8f\x3f\x00'\ +b'\x00\x00\x1f\x00\x00\x00\x00\x00\x14\x00\x00\xf8\x01\x00\x00\xfe'\ +b'\x07\x00\x80\xff\x1f\x00\xc0\xff\x3f\x00\xc0\x0f\x3f\x00\xe0\x03'\ +b'\x7c\x00\xe0\x01\x78\x00\xf0\x01\xf8\x00\xf0\x00\xf0\x00\xf0\x00'\ +b'\xf0\x00\xf0\x00\xf0\x00\xf0\x00\xf0\x00\xf0\x01\xf8\x00\xe0\x01'\ +b'\x78\x00\xe0\x07\x7e\x00\xc0\x07\x3e\x00\x80\x07\x1e\x00\x00\x03'\ +b'\x0c\x00\x00\x04\x04\x00\x00\x00\x00\x00\x13\x00\xf0\xff\xff\x00'\ +b'\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\x00\xf0\x00'\ +b'\xf0\x00\xf0\x00\xf0\x00\xf0\x00\xf0\x00\xf0\x00\xf0\x00\xf0\x00'\ +b'\xf0\x00\xf8\x00\xe0\x01\x78\x00\xe0\x07\x7e\x00\xc0\xff\x3f\x00'\ +b'\x80\xff\x1f\x00\x00\xff\x0f\x00\x00\xfc\x03\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\xf0\xff\xff\x00\xf0\xff'\ +b'\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xf0\xf0\x00\xf0\xf0'\ +b'\xf0\x00\xf0\xf0\xf0\x00\xf0\xf0\xf0\x00\xf0\xf0\xf0\x00\xf0\xf0'\ +b'\xf0\x00\xf0\xf0\xf0\x00\xf0\xf0\xf0\x00\xf0\xf0\xf0\x00\xf0\x00'\ +b'\xf0\x00\xf0\x00\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00'\ +b'\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00'\ +b'\xf0\xf0\x00\x00\xf0\xf0\x00\x00\xf0\xf0\x00\x00\xf0\xf0\x00\x00'\ +b'\xf0\xf0\x00\x00\xf0\xf0\x00\x00\xf0\xf0\x00\x00\xf0\xf0\x00\x00'\ +b'\xf0\xf0\x00\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\x00\x00\x00\x00'\ +b'\x15\x00\x00\xf8\x01\x00\x00\xfe\x0f\x00\x80\xff\x1f\x00\xc0\xff'\ +b'\x3f\x00\xc0\x0f\x7f\x00\xe0\x03\x7c\x00\xe0\x01\xf8\x00\xf0\x01'\ +b'\xf0\x00\xf0\x00\xf0\x00\xf0\x00\xf0\x00\xf0\xe0\xf1\x00\xf0\xe0'\ +b'\xf1\x00\xf0\xe1\x79\x00\xe0\xe1\x79\x00\xe0\xe3\x3d\x00\xc0\xe7'\ +b'\xff\x00\x80\xe7\xff\x00\x00\xe3\xff\x00\x00\xe4\xff\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x14\x00\xf0\xff\xff\x00\xf0\xff\xff\x00'\ +b'\xf0\xff\xff\x00\xf0\xff\xff\x00\x00\xf0\x00\x00\x00\xf0\x00\x00'\ +b'\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\xf0\x00\x00'\ +b'\x00\xf0\x00\x00\x00\xf0\x00\x00\xf0\xff\xff\x00\xf0\xff\xff\x00'\ +b'\xf0\xff\xff\x00\xf0\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00\xf0\xff\xff\x00\xf0\xff'\ +b'\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x00\x0f\x00\x00\x00\x3f\x00'\ +b'\x00\x00\x7f\x00\x00\x00\x7f\x00\x00\x00\xf8\x00\x00\x00\xf0\x00'\ +b'\x00\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\xf8\x00'\ +b'\xf0\xff\x7f\x00\xf0\xff\x7f\x00\xf0\xff\x3f\x00\xf0\xff\x0f\x00'\ +b'\x00\x00\x00\x00\x12\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff'\ +b'\xff\x00\xf0\xff\xff\x00\x00\xf0\x01\x00\x00\xfc\x00\x00\x00\xfe'\ +b'\x00\x00\x00\xff\x01\x00\x80\xff\x07\x00\xc0\xef\x0f\x00\xe0\x87'\ +b'\x3f\x00\xf0\x01\x7f\x00\xf0\x00\xfc\x00\x70\x00\xf8\x00\x30\x00'\ +b'\xf0\x00\x10\x00\xc0\x00\x10\x00\x80\x00\x00\x00\x00\x00\x10\x00'\ +b'\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00'\ +b'\x00\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\xf0\x00'\ +b'\x00\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\xf0\x00'\ +b'\x00\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x18\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff'\ +b'\xff\x00\xf0\x07\x00\x00\xf0\x7f\x00\x00\x80\xff\x07\x00\x00\xfc'\ +b'\x3f\x00\x00\xc0\xff\x00\x00\x00\xfe\x00\x00\x00\xfe\x00\x00\xc0'\ +b'\xff\x00\x00\xfc\x3f\x00\x80\xff\x07\x00\xf0\x7f\x00\x00\xf0\x07'\ +b'\x00\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff'\ +b'\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x14\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00'\ +b'\xf0\xff\xff\x00\xf0\x07\x00\x00\xc0\x1f\x00\x00\x00\x3f\x00\x00'\ +b'\x00\xfc\x00\x00\x00\xf0\x03\x00\x00\xc0\x0f\x00\x00\x80\x3f\x00'\ +b'\x00\x00\xfe\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00'\ +b'\xf0\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x16\x00\x00\xf8\x01\x00\x00\xfe\x07\x00\x80\xff'\ +b'\x1f\x00\xc0\xff\x3f\x00\xc0\x0f\x3f\x00\xe0\x03\x7c\x00\xe0\x01'\ +b'\x78\x00\xf0\x01\xf8\x00\xf0\x00\xf0\x00\xf0\x00\xf0\x00\xf0\x00'\ +b'\xf0\x00\xf0\x00\xf0\x00\xf0\x01\xf8\x00\xe0\x01\x78\x00\xe0\x03'\ +b'\x7c\x00\xc0\x0f\x3f\x00\xc0\xff\x3f\x00\x80\xff\x1f\x00\x00\xfe'\ +b'\x07\x00\x00\xf8\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x12\x00'\ +b'\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00'\ +b'\xf0\xe0\x01\x00\xf0\xe0\x01\x00\xf0\xe0\x01\x00\xf0\xe0\x01\x00'\ +b'\xf0\xe0\x01\x00\xf0\xe0\x01\x00\xf0\xf1\x01\x00\xe0\xff\x00\x00'\ +b'\xe0\xff\x00\x00\xc0\x7f\x00\x00\x00\x1f\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x15\x00\x00\xf8\x01\x00\x00\xff'\ +b'\x0f\x00\x80\xff\x1f\x00\xc0\xff\x3f\x00\xe0\x0f\x7e\x00\xe0\x03'\ +b'\x7c\x00\xf0\x01\xf8\x00\xf0\x00\xf0\x00\xf0\x00\xf1\x00\xf0\x80'\ +b'\xf3\x00\xf0\xc0\xf7\x00\xf0\x80\xff\x00\xf0\x01\xff\x00\xe0\x03'\ +b'\x7e\x00\xe0\x0f\x7e\x00\xc0\xff\x7f\x00\x80\xff\xff\x00\x00\xfe'\ +b'\x77\x00\x00\xf8\x21\x00\x00\x00\x00\x00\x00\x00\x00\x00\x12\x00'\ +b'\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\xff\xff\x00'\ +b'\xf0\xe0\x01\x00\xf0\xe0\x01\x00\xf0\xe0\x01\x00\xf0\xe0\x03\x00'\ +b'\xf0\xe0\x0f\x00\xf0\xe0\x3f\x00\xf0\xf1\x7f\x00\xe0\xff\xfc\x00'\ +b'\xe0\xff\xf0\x00\xc0\x7f\xe0\x00\x00\x1f\x80\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x12\x00\x00\x00\x04\x00\x80\x0f'\ +b'\x1e\x00\xc0\x1f\x3e\x00\xe0\x3f\x7e\x00\xe0\x3f\x7c\x00\xf0\x39'\ +b'\xf8\x00\xf0\x70\xf0\x00\xf0\x70\xf0\x00\xf0\x70\xf0\x00\xf0\x70'\ +b'\xf0\x00\xf0\x60\xf0\x00\xf0\xe1\xf8\x00\xe0\xe3\x78\x00\xe0\xc7'\ +b'\x7f\x00\xc0\xc3\x7f\x00\x80\x83\x3f\x00\x00\x02\x0f\x00\x00\x00'\ +b'\x00\x00\x0f\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\xf0\x00\x00\x00'\ +b'\xf0\x00\x00\x00\xf0\x00\x00\x00\xf0\xff\xff\x00\xf0\xff\xff\x00'\ +b'\xf0\xff\xff\x00\xf0\xff\xff\x00\xf0\x00\x00\x00\xf0\x00\x00\x00'\ +b'\xf0\x00\x00\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\x00\x00\x00\x00'\ +b'\x12\x00\xf0\xff\x07\x00\xf0\xff\x1f\x00\xf0\xff\x3f\x00\xf0\xff'\ +b'\x7f\x00\x00\x00\x7c\x00\x00\x00\xf8\x00\x00\x00\xf0\x00\x00\x00'\ +b'\xf0\x00\x00\x00\xf0\x00\x00\x00\xf0\x00\x00\x00\xf8\x00\x00\x00'\ +b'\x7c\x00\xf0\xff\x7f\x00\xf0\xff\x3f\x00\xf0\xff\x1f\x00\xf0\xff'\ +b'\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x30\x00\x00\x00'\ +b'\xf0\x01\x00\x00\xf0\x0f\x00\x00\xf0\x7f\x00\x00\xc0\xff\x03\x00'\ +b'\x00\xfe\x1f\x00\x00\xe0\xff\x00\x00\x00\xff\x00\x00\x00\xf8\x00'\ +b'\x00\x00\xff\x00\x00\xe0\xff\x00\x00\xfe\x1f\x00\xc0\xff\x03\x00'\ +b'\xf0\x7f\x00\x00\xf0\x0f\x00\x00\xf0\x01\x00\x00\x30\x00\x00\x00'\ +b'\x19\x00\x70\x00\x00\x00\xf0\x03\x00\x00\xf0\x3f\x00\x00\xf0\xff'\ +b'\x01\x00\x80\xff\x1f\x00\x00\xf8\xff\x00\x00\x80\xff\x00\x00\x00'\ +b'\xfc\x00\x00\xe0\xff\x00\x00\xff\xff\x00\xf0\xff\x07\x00\xf0\x3f'\ +b'\x00\x00\xf0\x03\x00\x00\xf0\x1f\x00\x00\xf0\xff\x03\x00\x80\xff'\ +b'\x7f\x00\x00\xf8\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\xf0'\ +b'\xff\x00\x00\xff\x3f\x00\xf0\xff\x07\x00\xf0\x7f\x00\x00\xf0\x07'\ +b'\x00\x00\xf0\x00\x00\x00\x11\x00\x10\x00\x80\x00\x30\x00\xe0\x00'\ +b'\xf0\x00\xf0\x00\xf0\x01\xfc\x00\xf0\x07\xfe\x00\xc0\x9f\x3f\x00'\ +b'\x80\xff\x0f\x00\x00\xfe\x07\x00\x00\xfc\x01\x00\x00\xfe\x07\x00'\ +b'\x80\xff\x0f\x00\xc0\x9f\x3f\x00\xf0\x07\x7f\x00\xf0\x03\xfc\x00'\ +b'\xf0\x00\xf8\x00\x70\x00\xe0\x00\x10\x00\x80\x00\x12\x00\x10\x00'\ +b'\x00\x00\x30\x00\x00\x00\xf0\x00\x00\x00\xf0\x03\x00\x00\xf0\x0f'\ +b'\x00\x00\xc0\x3f\x00\x00\x00\x7f\x00\x00\x00\xfc\xff\x00\x00\xf0'\ +b'\xff\x00\x00\xf0\xff\x00\x00\xfc\xff\x00\x00\x7f\x00\x00\xc0\x3f'\ +b'\x00\x00\xf0\x0f\x00\x00\xf0\x03\x00\x00\xf0\x00\x00\x00\x30\x00'\ +b'\x00\x00\x10\x00\x00\x00\x11\x00\x00\x00\xe0\x00\xf0\x00\xf8\x00'\ +b'\xf0\x00\xfc\x00\xf0\x00\xfe\x00\xf0\x00\xff\x00\xf0\x80\xff\x00'\ +b'\xf0\xc0\xf3\x00\xf0\xf0\xf1\x00\xf0\xf8\xf0\x00\xf0\x7c\xf0\x00'\ +b'\xf0\x3e\xf0\x00\xf0\x0f\xf0\x00\xf0\x07\xf0\x00\xf0\x03\xf0\x00'\ +b'\xf0\x01\xf0\x00\xf0\x00\xf0\x00\x00\x00\x00\x00\x09\x00\xff\xff'\ +b'\xff\x0f\xff\xff\xff\x0f\xff\xff\xff\x0f\xff\xff\xff\x0f\x0f\x00'\ +b'\x00\x0f\x0f\x00\x00\x0f\x0f\x00\x00\x0f\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x0a\x00\x04\x00\x00\x00\x3c\x00\x00\x00\xfc\x03\x00\x00'\ +b'\xfc\x1f\x00\x00\xf8\xff\x01\x00\xc0\xff\x1f\x00\x00\xfc\xff\x00'\ +b'\x00\xe0\xff\x00\x00\x00\xfe\x00\x00\x00\xe0\x00\x09\x00\x0f\x00'\ +b'\x00\x0f\x0f\x00\x00\x0f\x0f\x00\x00\x0f\xff\xff\xff\x0f\xff\xff'\ +b'\xff\x0f\xff\xff\xff\x0f\xff\xff\xff\x0f\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x0f\x00\x00\x80\x01\x00\x00\xf0\x01\x00\x00\xfc\x01\x00'\ +b'\x80\xff\x01\x00\xe0\x3f\x00\x00\xf0\x07\x00\x00\xf0\x01\x00\x00'\ +b'\xf0\x03\x00\x00\xf0\x1f\x00\x00\xc0\xff\x00\x00\x00\xfe\x01\x00'\ +b'\x00\xf8\x01\x00\x00\xc0\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00'\ +b'\x17\x00\x00\x00\x00\x0f\x00\x00\x00\x0f\x00\x00\x00\x0f\x00\x00'\ +b'\x00\x0f\x00\x00\x00\x0f\x00\x00\x00\x0f\x00\x00\x00\x0f\x00\x00'\ +b'\x00\x0f\x00\x00\x00\x0f\x00\x00\x00\x0f\x00\x00\x00\x0f\x00\x00'\ +b'\x00\x0f\x00\x00\x00\x0f\x00\x00\x00\x0f\x00\x00\x00\x0f\x00\x00'\ +b'\x00\x0f\x00\x00\x00\x0f\x00\x00\x00\x0f\x00\x00\x00\x0f\x00\x00'\ +b'\x00\x0f\x00\x00\x00\x0f\x00\x00\x00\x0f\x00\x00\x00\x0f\x0c\x00'\ +b'\x08\x00\x00\x00\x18\x00\x00\x00\x38\x00\x00\x00\x78\x00\x00\x00'\ +b'\x70\x00\x00\x00\x60\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x11\x00\x00\x40\x3c\x00\x00\x30\x7e\x00\x00\x38\x7f\x00\x00\x7c'\ +b'\xff\x00\x00\x3e\xf3\x00\x00\x1e\xf3\x00\x00\x1e\xf3\x00\x00\x1e'\ +b'\xf1\x00\x00\x9e\x71\x00\x00\xbe\x79\x00\x00\xfc\xff\x00\x00\xfc'\ +b'\xff\x00\x00\xf8\xff\x00\x00\xf0\xff\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x12\x00\xfc\xff\xff\x00\xfc\xff\xff\x00'\ +b'\xfc\xff\xff\x00\xfc\xff\xff\x00\x00\x7c\x3c\x00\x00\x3c\x78\x00'\ +b'\x00\x1e\xf0\x00\x00\x1e\xf0\x00\x00\x1e\xf0\x00\x00\x1e\xf0\x00'\ +b'\x00\x3e\xf8\x00\x00\x7c\x7c\x00\x00\xfc\x7f\x00\x00\xf8\x3f\x00'\ +b'\x00\xf0\x1f\x00\x00\xc0\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x10\x00\x00\xc0\x07\x00\x00\xf0\x1f\x00\x00\xf8\x3f\x00\x00\xfc'\ +b'\x7f\x00\x00\x7c\x7c\x00\x00\x3e\xf8\x00\x00\x1e\xf0\x00\x00\x1e'\ +b'\xf0\x00\x00\x1e\xf0\x00\x00\x1e\xf0\x00\x00\x3c\xf8\x00\x00\x7c'\ +b'\x7c\x00\x00\x78\x38\x00\x00\x30\x18\x00\x00\x20\x08\x00\x00\x00'\ +b'\x00\x00\x12\x00\x00\xc0\x07\x00\x00\xf0\x1f\x00\x00\xf8\x3f\x00'\ +b'\x00\xfc\x7f\x00\x00\x7e\xfc\x00\x00\x3e\xf8\x00\x00\x1e\xf0\x00'\ +b'\x00\x1e\xf0\x00\x00\x1e\xf0\x00\x00\x3c\x78\x00\x00\x7c\x7c\x00'\ +b'\xfc\xff\xff\x00\xfc\xff\xff\x00\xfc\xff\xff\x00\xfc\xff\xff\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\xe0'\ +b'\x0f\x00\x00\xf0\x1f\x00\x00\xf8\x3f\x00\x00\xfc\x7f\x00\x00\xdc'\ +b'\x7b\x00\x00\xde\xf3\x00\x00\xde\xf3\x00\x00\xde\xf3\x00\x00\xde'\ +b'\xf3\x00\x00\xde\xfb\x00\x00\xfc\x7b\x00\x00\xfc\x7b\x00\x00\xf8'\ +b'\x33\x00\x00\xf0\x03\x00\x00\x80\x03\x00\x00\x00\x00\x00\x09\x00'\ +b'\x00\x1c\x00\x00\x00\x1c\x00\x00\xc0\xff\xff\x00\xf0\xff\xff\x00'\ +b'\xf0\xff\xff\x00\xf8\xff\xff\x00\x78\x1c\x00\x00\x3c\x1c\x00\x00'\ +b'\x3c\x1c\x00\x00\x11\x00\x00\xc0\x07\x08\x00\xf0\x1f\x1c\x00\xf8'\ +b'\x3f\x3c\x00\xfc\x7f\x3c\x00\x7e\xfc\x7c\x00\x3e\xf8\x78\x00\x1e'\ +b'\xf0\x78\x00\x1e\xf0\x78\x00\x1e\xf0\x78\x00\x3c\x78\x7c\x00\x78'\ +b'\x7c\x3e\x00\xfe\xff\x3f\x00\xfe\xff\x1f\x00\xfe\xff\x0f\x00\xfe'\ +b'\xff\x03\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\xfc\xff\xff\x00'\ +b'\xfc\xff\xff\x00\xfc\xff\xff\x00\xfc\xff\xff\x00\x00\x3c\x00\x00'\ +b'\x00\x1e\x00\x00\x00\x1e\x00\x00\x00\x1e\x00\x00\x00\x1e\x00\x00'\ +b'\x00\x3e\x00\x00\x00\xfc\xff\x00\x00\xfc\xff\x00\x00\xf8\xff\x00'\ +b'\x00\xe0\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00\x60\xfe'\ +b'\xff\x00\xf0\xfe\xff\x00\xf0\xfe\xff\x00\x60\xfe\xff\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x78'\ +b'\x00\x00\x00\x78\x60\xfe\xff\x7f\xf0\xfe\xff\x3f\xf0\xfe\xff\x3f'\ +b'\x60\xfe\xff\x0f\x10\x00\xfc\xff\xff\x00\xfc\xff\xff\x00\xfc\xff'\ +b'\xff\x00\xfc\xff\xff\x00\x00\xc0\x07\x00\x00\xe0\x03\x00\x00\xf0'\ +b'\x07\x00\x00\xfc\x0f\x00\x00\xfe\x3f\x00\x00\x3e\xff\x00\x00\x1e'\ +b'\xfc\x00\x00\x0e\xf8\x00\x00\x06\xe0\x00\x00\x02\xc0\x00\x00\x00'\ +b'\x80\x00\x00\x00\x00\x00\x07\x00\xfc\xff\xff\x00\xfc\xff\xff\x00'\ +b'\xfc\xff\xff\x00\xfc\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x1a\x00\x00\xfe\xff\x00\x00\xfe\xff\x00\x00\xfe'\ +b'\xff\x00\x00\xfe\xff\x00\x00\x3c\x00\x00\x00\x1e\x00\x00\x00\x1e'\ +b'\x00\x00\x00\x1e\x00\x00\x00\x1e\x00\x00\x00\x3e\x00\x00\x00\xfc'\ +b'\xff\x00\x00\xfc\xff\x00\x00\xf8\xff\x00\x00\xfc\xff\x00\x00\x3e'\ +b'\x00\x00\x00\x1e\x00\x00\x00\x1e\x00\x00\x00\x1e\x00\x00\x00\x3e'\ +b'\x00\x00\x00\xfc\xff\x00\x00\xfc\xff\x00\x00\xf8\xff\x00\x00\xe0'\ +b'\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00'\ +b'\x00\xfe\xff\x00\x00\xfe\xff\x00\x00\xfe\xff\x00\x00\xfe\xff\x00'\ +b'\x00\x3c\x00\x00\x00\x1c\x00\x00\x00\x1e\x00\x00\x00\x1e\x00\x00'\ +b'\x00\x1e\x00\x00\x00\x3e\x00\x00\x00\xfc\xff\x00\x00\xfc\xff\x00'\ +b'\x00\xf8\xff\x00\x00\xe0\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x11\x00\x00\xc0\x07\x00\x00\xf0\x1f\x00\x00\xf8\x3f\x00\x00\xfc'\ +b'\x7f\x00\x00\x7c\x7c\x00\x00\x3e\xf8\x00\x00\x1e\xf0\x00\x00\x1e'\ +b'\xf0\x00\x00\x1e\xf0\x00\x00\x3e\xf8\x00\x00\x7c\x7c\x00\x00\xfc'\ +b'\x7f\x00\x00\xf8\x3f\x00\x00\xf0\x1f\x00\x00\xc0\x07\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x12\x00\x00\xfe\xff\x7f\x00\xfe\xff\x7f'\ +b'\x00\xfe\xff\x7f\x00\xfe\xff\x7f\x00\x7c\x7c\x00\x00\x3c\x78\x00'\ +b'\x00\x1e\xf0\x00\x00\x1e\xf0\x00\x00\x1e\xf0\x00\x00\x1e\xf0\x00'\ +b'\x00\x3e\xf8\x00\x00\x7c\x7c\x00\x00\xfc\x7f\x00\x00\xf8\x3f\x00'\ +b'\x00\xf0\x1f\x00\x00\xc0\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x11\x00\x00\xc0\x07\x00\x00\xf0\x1f\x00\x00\xf8\x3f\x00\x00\xfc'\ +b'\x7f\x00\x00\x7e\xfc\x00\x00\x3e\xf8\x00\x00\x1e\xf0\x00\x00\x1e'\ +b'\xf0\x00\x00\x1e\xf0\x00\x00\x3c\x78\x00\x00\x7c\x7c\x00\x00\xfe'\ +b'\xff\x7f\x00\xfe\xff\x7f\x00\xfe\xff\x7f\x00\xfe\xff\x7f\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0a\x00\x00\xfe\xff\x00\x00\xfe\xff\x00'\ +b'\x00\xfe\xff\x00\x00\xfe\xff\x00\x00\x3c\x00\x00\x00\x1e\x00\x00'\ +b'\x00\x1e\x00\x00\x00\x0e\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00'\ +b'\x0f\x00\x00\x00\x08\x00\x00\x70\x18\x00\x00\xfc\x38\x00\x00\xfc'\ +b'\x7d\x00\x00\xfe\xf9\x00\x00\x9e\xf1\x00\x00\x9e\xf1\x00\x00\x9e'\ +b'\xf1\x00\x00\x9e\xf3\x00\x00\x3e\xf3\x00\x00\x3c\x7f\x00\x00\x3c'\ +b'\x7f\x00\x00\x38\x3e\x00\x00\x20\x1c\x00\x00\x00\x00\x00\x09\x00'\ +b'\x00\x0e\x00\x00\x00\x0e\x00\x00\xfc\xff\x1f\x00\xfc\xff\x3f\x00'\ +b'\xfc\xff\x7f\x00\xfc\xff\xff\x00\x00\x0e\xf0\x00\x00\x0e\xf0\x00'\ +b'\x00\x0e\xf0\x00\x10\x00\x00\xfe\x0f\x00\x00\xfe\x3f\x00\x00\xfe'\ +b'\x7f\x00\x00\xfe\x7f\x00\x00\x00\xf8\x00\x00\x00\xf0\x00\x00\x00'\ +b'\xf0\x00\x00\x00\xf0\x00\x00\x00\x70\x00\x00\x00\x78\x00\x00\xfe'\ +b'\xff\x00\x00\xfe\xff\x00\x00\xfe\xff\x00\x00\xfe\xff\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x06\x00\x00\x00\x3e\x00\x00'\ +b'\x00\xfe\x00\x00\x00\xfe\x07\x00\x00\xf8\x3f\x00\x00\xc0\xff\x00'\ +b'\x00\x00\xfe\x00\x00\x00\xf0\x00\x00\x00\xfe\x00\x00\xc0\xff\x00'\ +b'\x00\xf8\x1f\x00\x00\xfe\x07\x00\x00\xfe\x00\x00\x00\x1e\x00\x00'\ +b'\x00\x06\x00\x00\x16\x00\x00\x06\x00\x00\x00\x7e\x00\x00\x00\xfe'\ +b'\x03\x00\x00\xfe\x1f\x00\x00\xf0\xff\x00\x00\x00\xff\x00\x00\x00'\ +b'\xf8\x00\x00\x80\xff\x00\x00\xf8\xff\x00\x00\xfe\x0f\x00\x00\xfe'\ +b'\x00\x00\x00\xfe\x00\x00\x00\xfe\x07\x00\x00\xf8\x7f\x00\x00\x80'\ +b'\xff\x00\x00\x00\xf8\x00\x00\x00\xff\x00\x00\xf0\xff\x00\x00\xfe'\ +b'\x3f\x00\x00\xfe\x03\x00\x00\x7e\x00\x00\x00\x0e\x00\x00\x0f\x00'\ +b'\x00\x02\x80\x00\x00\x06\xe0\x00\x00\x1e\xf0\x00\x00\x3e\xfc\x00'\ +b'\x00\xfe\x7e\x00\x00\xf8\x3f\x00\x00\xe0\x0f\x00\x00\xe0\x07\x00'\ +b'\x00\xf8\x1f\x00\x00\xfc\x7f\x00\x00\x7e\xfc\x00\x00\x1e\xf8\x00'\ +b'\x00\x0e\xe0\x00\x00\x02\xc0\x00\x00\x00\x80\x00\x0f\x00\x00\x06'\ +b'\x00\x00\x00\x3e\x00\x00\x00\xfe\x00\x78\x00\xfe\x07\x78\x00\xf8'\ +b'\x1f\x7c\x00\xc0\xff\x7f\x00\x00\xfe\x3f\x00\x00\xf0\x1f\x00\x00'\ +b'\xfe\x07\x00\xc0\xff\x00\x00\xf8\x1f\x00\x00\xfe\x07\x00\x00\xfe'\ +b'\x00\x00\x00\x1e\x00\x00\x00\x06\x00\x00\x0e\x00\x00\x00\xe0\x00'\ +b'\x00\x1e\xf0\x00\x00\x1e\xf8\x00\x00\x1e\xfc\x00\x00\x1e\xfe\x00'\ +b'\x00\x1e\xff\x00\x00\x9e\xf7\x00\x00\xde\xf3\x00\x00\xfe\xf1\x00'\ +b'\x00\xfe\xf0\x00\x00\x7e\xf0\x00\x00\x3e\xf0\x00\x00\x1e\xf0\x00'\ +b'\x00\x00\x00\x00\x0a\x00\x00\x60\x00\x00\x00\xf0\x00\x00\x00\xf0'\ +b'\x00\x00\xf8\xff\xff\x01\xfc\xff\xff\x03\xfe\x9f\xff\x07\xfe\x0f'\ +b'\xff\x07\x0e\x00\x00\x07\x0e\x00\x00\x07\x00\x00\x00\x00\x08\x00'\ +b'\xfc\xff\xff\x00\xfc\xff\xff\x00\xfc\xff\xff\x00\xfc\xff\xff\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x09\x00\x0e\x00\x00\x07\x0e\x00\x00\x07\xfe\x0f\xff\x07\xfe\x9f'\ +b'\xff\x07\xfc\xff\xff\x03\xf8\xff\xff\x01\x00\xf0\x00\x00\x00\xf0'\ +b'\x00\x00\x00\x60\x00\x00\x0c\x00\x30\x00\x00\x00\x38\x00\x00\x00'\ +b'\x18\x00\x00\x00\x18\x00\x00\x00\x18\x00\x00\x00\x30\x00\x00\x00'\ +b'\x30\x00\x00\x00\x30\x00\x00\x00\x38\x00\x00\x00\x18\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00' + +_index =\ +b'\x00\x00\x3e\x00\x64\x00\x86\x00\xb0\x00\xee\x00\x28\x01\x86\x01'\ +b'\xd4\x01\xea\x01\x10\x02\x36\x02\x64\x02\xa2\x02\xbc\x02\xe2\x02'\ +b'\xfc\x02\x26\x03\x6c\x03\x92\x03\xd4\x03\x16\x04\x58\x04\x96\x04'\ +b'\xd8\x04\x12\x05\x54\x05\x96\x05\xb0\x05\xca\x05\xfc\x05\x36\x06'\ +b'\x68\x06\xa6\x06\x00\x07\x4e\x07\x98\x07\xea\x07\x38\x08\x7e\x08'\ +b'\xc0\x08\x16\x09\x68\x09\x86\x09\xc4\x09\x0e\x0a\x50\x0a\xb2\x0a'\ +b'\x04\x0b\x5e\x0b\xa8\x0b\xfe\x0b\x48\x0c\x92\x0c\xd0\x0c\x1a\x0d'\ +b'\x60\x0d\xc6\x0d\x0c\x0e\x56\x0e\x9c\x0e\xc2\x0e\xec\x0e\x12\x0f'\ +b'\x50\x0f\xae\x0f\xe0\x0f\x26\x10\x70\x10\xb2\x10\xfc\x10\x3e\x11'\ +b'\x64\x11\xaa\x11\xec\x11\x0a\x12\x24\x12\x66\x12\x84\x12\xee\x12'\ +b'\x30\x13\x76\x13\xc0\x13\x06\x14\x30\x14\x6e\x14\x94\x14\xd6\x14'\ +b'\x14\x15\x6e\x15\xac\x15\xea\x15\x24\x16\x4e\x16\x70\x16\x96\x16'\ +b'\xc8\x16' + +_mvfont = memoryview(_font) + +def _chr_addr(ordch): + offset = 2 * (ordch - 32) + return int.from_bytes(_index[offset:offset + 2], 'little') + +def get_width(s): + width = 0 + for ch in s: + ordch = ord(ch) + ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 32 + offset = _chr_addr(ordch) + width += int.from_bytes(_font[offset:offset + 2], 'little') + return width + +def get_ch(ch): + ordch = ord(ch) + ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 32 + offset = _chr_addr(ordch) + width = int.from_bytes(_font[offset:offset + 2], 'little') + next_offs = _chr_addr(ordch +1) + return _mvfont[offset + 2:next_offs], width +