From c341ee9bd2dfedeb522f4326f526795e122542e7 Mon Sep 17 00:00:00 2001 From: Peter Hinch Date: Tue, 14 Aug 2018 17:22:21 +0100 Subject: [PATCH] Writer version 0.3 --- DRIVERS.md | 250 ------------------- FONT_TO_PY.md | 42 ++-- README.md | 30 +-- driver_test.py | 84 ------- writer/DRIVERS.md | 193 +++++++++++++++ writer/WRITER.md | 290 ++++++++++++++++++++++ writer/courier20.py | 308 +++++++++++++++++++++++ writer/font10.py | 229 +++++++++++++++++ writer/font6.py | 176 +++++++++++++ writer/freesans20.py | 280 +++++++++++++++++++++ writer/images/IMG_2861.JPG | Bin 0 -> 19274 bytes writer/images/IMG_2866.JPG | Bin 0 -> 16092 bytes writer/images/fields.JPG | Bin 0 -> 19337 bytes writer/images/mixed.JPG | Bin 0 -> 18552 bytes writer/images/rjust.JPG | Bin 0 -> 18007 bytes writer/ssd1306_setup.py | 69 ++++++ writer/writer.py | 343 ++++++++++++++++++++++++++ writer/writer_demo.py | 56 +++++ writer.py => writer/writer_minimal.py | 43 ++-- writer/writer_tests.py | 292 ++++++++++++++++++++++ 20 files changed, 2298 insertions(+), 387 deletions(-) delete mode 100644 DRIVERS.md delete mode 100644 driver_test.py create mode 100644 writer/DRIVERS.md create mode 100644 writer/WRITER.md create mode 100644 writer/courier20.py create mode 100644 writer/font10.py create mode 100644 writer/font6.py create mode 100644 writer/freesans20.py create mode 100644 writer/images/IMG_2861.JPG create mode 100644 writer/images/IMG_2866.JPG create mode 100644 writer/images/fields.JPG create mode 100644 writer/images/mixed.JPG create mode 100644 writer/images/rjust.JPG create mode 100644 writer/ssd1306_setup.py create mode 100644 writer/writer.py create mode 100644 writer/writer_demo.py rename writer.py => writer/writer_minimal.py (73%) create mode 100644 writer/writer_tests.py diff --git a/DRIVERS.md b/DRIVERS.md deleted file mode 100644 index 335c37d..0000000 --- a/DRIVERS.md +++ /dev/null @@ -1,250 +0,0 @@ -# DRIVERS.md - -This document comprises two sections, the first for users of an existing device -driver and the second for writers of device drivers. - -# User Documentation: the Writer class - -This class facilitates rendering characters from Python font files to a device, -assuming the device has a driver conforming to the specification below. Typical -use is as follows: - -```python -from writer import Writer -from device_driver import Display -import freeserif -import freesans20 -display = Display(args_required_by_driver) -wri_serif = Writer(display, freeserif) -wri_sans = Writer(display, freesans20) -Writer.set_clip(True, True) -wri_serif.printstring('Tuesday\n') -wri_sans.printstring('8 Nov 2016\n') -wri_sans.printstring('10.30am') - -display.show() # Display the result -``` - -The file ``driver_test.py`` illustrates the use of font files with an SSD1306 -display and a complete example of an SSD1306 driver may be found -[here](https://github.com/peterhinch/micropython-samples/tree/master/SSD1306). - -## Class Methods - -The ``Writer`` class exposes the following class methods: - - 1. ``set_textpos`` Args: ``row``, ``col``. This determines where on screen any - subsequent text is to be rendered. The initial value is (0, 0) - the top left - corner. Arguments are in pixels with positive values representing down and - right respectively. They reference the top left hand corner of the first - character to be output. - 2. ``set_clip`` Args: boolean ``row_clip``, ``col_clip``. If these are - ``True``, characters will be clipped if they extend beyond the boundaries of - the physical display. If ``col_clip`` is ``False`` characters will wrap onto - the next line. If ``row_clip`` is ``False`` the display will, where necessary, - scroll up to ensure the line is rendered. - -As class methods these settings apply to all font objects. The insertion point -of characters is maintained regardless of the font in use. - -## Method - - 1. ``printstring`` Arg: a text string. Outputs a text string at the current - insertion point. Newline characters are honoured. - -## Note on the Writer class - -This is more a proof of concept than a final implementation. Obvious -enhancements include rendering to a rectangular area, support for proper word -wrap and support for format control characters such as tabs. - -# Device Driver Implementation - -Display devices comprise two varieties, depending on whether the framebuffer is -located on the controlling system or on the physical display device. In the -former case the ``Writer`` class simplifies the design of the driver. It merely -has to expose certin attributes and methods with ``Writer`` instances taking -care of text rendering. It is strongly recommended that such device drivers use -the oficial ``framebuf`` module, as per the official SSD1306 driver which -exposes the required components. - -Where the buffer is located on the display device the means of controlling the -text insertion point will be device dependent. The driver will need to -implement the functionality of the ``Writer`` class itself. - -## Fixed width fonts - -If a Python font file is created with the ``-f`` argument, all characters will -be saved with the width of the widest. In general it is not necessary to -specify this option. The driver can perform fixed pich rendering by rendering -the character as variable pitch, then advancing the pixel column by the value -returned by ``font.max_width()``. - -## Drivers with local buffers - -The writer of a device driver need not be concerned with the structure of a -Python font file so long as the driver exposes certain attributes and methods -required by the ``Writer`` class. These are as follows: - -Attributes: - - 1. ``buffer`` The underlying ``bytearray`` instance holding the display - buffer. - 2. ``height`` The screen height in pixels. - 3. ``width`` The screen width in pixels. - -Methods: - - 1. ``show`` Display the current buffer contents. - 2. ``scroll`` Arguments ``x``, ``y`` amount to scroll horizontal and vertical. - 3. ``fill`` Argument ``col`` colour 1 == fill 0 == clear. - -An example of such a driver, using the official ``framebuf`` module, is the -SSD1306 driver (drivers/display/ssd1306.py in the source tree). - -The driver documentation should specify the arguments for font_to_py.py to -ensure users create font files with a layout corresponding to that of the -buffer/device. - -## Drivers for remote buffers - -### Specifying the font file - -Each font file has a ``get_ch()`` function accepting an ASCII character as its -argument. It returns a memoryview instance providing access to a bytearray -corresponding to the individual glyph. The layout of this data is determined by -the command line arguments presented to the ``font_to_py.py`` utility. It is -the responsibility of the driver to copy that data to the physical device. - -The purpose of the ``font_to_py.py`` command line arguments specified to the -user is to ensure that the data layout is optimised for the device so that this -copy operation is a fast bytewise copy or SPI/I2C transfer. The driver -documentation should therefore specify these arguments to ensure the layout is -optimal. Mapping may be horizontal or vertical, and the bit order of individual -bytes may be defined. These are detailed below. - -In the case of devices with their own frame buffer the ``Writer`` class will need -to be re-written or adapted to match the hardware's method of tracking such -things as the text insertion point. Consideration should be given to employing -the same interface as the ``Writer`` class to simplify the porting of user code -between displays with differing hardware. - -## Python Font files - -Assume the user has run the utility to produce a file ``myfont.py`` This then -has the following outline definition (in practice the bytes objects are large): - -```python - # Code generated by font-to-py.py. - # Font: FreeSerif.ttf -version = '0.2' - -def height(): - return 21 - -def max_width(): - return 22 - -def hmap(): - return False - -def reverse(): - return False - -def monospaced(): - return False - -def min_ch(): - return 32 - -def max_ch(): - return 126 - -_font =\ -b'\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ -b'\x00\x00\x00\x00\x08\x00\xfe\xc7\x00\x7e\xc0\x00\x00\x00\x00\x00'\ - -_index =\ -b'\x00\x00\x14\x00\x2e\x00\x4b\x00\x71\x00\x97\x00\xd2\x00\x0a\x01'\ -b'\x1b\x01\x35\x01\x4f\x01\x75\x01\x9e\x01\xb2\x01\xcc\x01\xe0\x01'\ - -_mvfont = memoryview(_font) - # Boilerplate code omitted - -def get_ch(ch): - # validate ch, if out of range use '?' - # get offsets into _font and retrieve char width - # Return: memoryview of bitmap, height and width - return mvfont[offset + 2, next_offset], height, width -``` - -``height`` and ``width`` are specified in bits (pixels). - -In the case of monospaced fonts the ``max_width`` function returns the width of -every character. For variable pitch fonts it returns the width of the widest -character. Device drivers can use this to rapidly determine whether a string -will fit the available space. If it will fit on the assumption that all chars -are maximum width, it can be rendered rapidly without doing a character by -character check. - -``get_ch()`` returns a memoryview of an individual glyph with its dimensions -and contains all the bytes required to render the character including trailing -space. - -## Binary font files - -These are unlikely to find application beyond the e-paper driver, but for -completeness the format is as follows. They are binary files with a four byte -header and 126 fixed length records. The header consists of two file identifiers -enabling the file format to be checked, followed by bytes specifying the width -and height. The length of each record is (width + 1) bytes. - -The file indentifiers depend on the -x and -r arguments specified to ``font_to_py.py`` -and are as follows: - -hmap reverse byte --x -r 0 1 -0 0 0x3f 0xe7 -1 0 0x40 0xe7 -0 1 0x41 0xe7 -1 1 0x42 0xe7 - -Each record starts with a width byte specifying the x dimension of the glyph if -rendered proportionally spaced, followed by the glyph data. This data includes -trailing space ensuring that all records have the size specified in the header. - -## Mapping - -A character occupies a space where (0, 0) represents the coordinates of the top -left hand corner of the bitmap. It comprises a set of pixels where increasing x -values represent locations to the right of the origin and increasing y values -represent downward positions. Mapping defines the relationship between this -abstract two dimensional array of bits and the physical linear sequence of bytes. - -Vertical mapping means that the LSB of first byte is pixel (0,0), MSB of first -byte is (0, 7). The second byte (assuming the height is greater than 8 pixels) -is (0, 8) to (0, 15). Once the column is complete the next byte represents -(1, 0) to (1, 7). - -Horizontal mapping means that the MSB of byte 0 is pixel (0,0) with LSB at -(7,0), with the second byte covering (8, 0) to (15, 0) if the width is greater -than 8. - -Bit reversal provides for the case where the bit order of each byte is reversed -i.e. a byte comprising bits [b7b6b5b4b3b2b1b0] becomes [b0b1b2b3b4b5b6b7]. - -# Specification and Project Notes - -The design aims primarily to minimise RAM usage. Minimising the size of the -bytecode is a secondary aim. Indexed addressing is used to reduce this in -the case of proportional fonts, at a small cost in performance. The size of the -Python source file is a lesser consideration, with readability being prioritised -over size. Hence they are "pretty formatted" with the large bytes objects -split over multiple lines for readability. - -The approach has been tested on SSD1306 devices using both the pseudo-horizontal -and true vertical mapping. - -The ``font_to_py`` utility has been extensively tested with each of the mapping -options. It has been used with drivers for SSD1306 OLEDs, SSD1963 LCD displays, -and the e-paper display. diff --git a/FONT_TO_PY.md b/FONT_TO_PY.md index 89f3503..843b784 100644 --- a/FONT_TO_PY.md +++ b/FONT_TO_PY.md @@ -5,10 +5,12 @@ is to save RAM on resource-limited targets: the font file may be incorporated into a firmware build such that it occupies flash memory rather than scarce RAM. Python code built into firmware is known as frozen bytecode. -# Dependency +###### [Main README](./README.md) -The utility requires `freetype` which may be installed using `pip3`. On Linux -at a root prompt: +# Dependencies + +The utility requires Python 3.2 or greater, also `freetype` which may be +installed using `pip3`. On Linux at a root prompt: ```shell # apt-get install python3-pip @@ -81,7 +83,7 @@ import myfont The `myfont` module name will then be used to instantiate a `Writer` object to render strings on demand. A practical example may be studied -[here](https://github.com/peterhinch/micropython-samples/blob/master/SSD1306/ssd1306_test.py). +[here](https://github.com/peterhinch/micropython-samples/blob/master/SSD1306/ssd1306_demo.py). The detailed layout of the Python file may be seen [here](./DRIVERS.md). ### Binary font files @@ -116,39 +118,45 @@ may be viewed [here](https://dbader.org/blog/monochrome-font-rendering-with-free # Appendix: RAM utilisation Test Results -A font file was created, frozen as bytecode and deployed to a version 1.0 -Pyboard. The font was saved as variable pitch with a height of 19 pixels. The -following code was then pasted at the REPL: +The supplied `freesans20.py` and `courier20.py` files were frozen as bytecode +on a Pyboard V1.0. The following code was pasted at the REPL: ```python import gc, micropython gc.collect() micropython.mem_info() -import freeserif +import freesans20 + +gc.collect() +micropython.mem_info() + +import courier20 gc.collect() micropython.mem_info() def foo(): - addr, height, width = freeserif.get_ch('a') + addr, height, width = freesans20.get_ch('a') foo() gc.collect() micropython.mem_info() -print(len(freeserif._font) + len(freeserif._index)) +print(len(freesans20._font) + len(freesans20._index)) ``` -The memory used was 5408, 5648, and 5696 bytes. As increments over the initial -state this corresponds to 240 and 288 bytes. The `print` statement shows the -RAM which would be consumed by the data arrays: this was 3271 bytes. +The memory used was 1712, 2048, 2400 and 2416 bytes. As increments over the +prior state this corresponds to 336, 352 and 16 bytes. The `print` statement +shows the RAM which would be consumed by the data arrays: this was 3766 bytes +for `freesans20`. The `foo()` function emulates the behaviour of a device driver in rendering a -character to a display. The local variables constitute memory which will be -reclaimed on exit from the function. Its additional RAM use was 48 bytes. +character to a display. The local variables constitute memory which is +reclaimed on exit from the function. Its additional RAM use was 16 bytes. ## Conclusion -With a font of height 19 pixels RAM saving was an order of magnitude. The -saving will be greater if larger fonts are used +With a font of height 20 pixels RAM saving was an order of magnitude. The +saving will be greater if larger fonts are used as RAM usage is independent of +the array sizes. diff --git a/README.md b/README.md index 57b84d2..49befa8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # MicroPython font handling -This is an attempt to offer a standard method of creating and deploying fonts -to MicroPython display drivers. +This repository defines a method of creating and deploying fonts for use with +MicroPython display drivers. A PC utility converts industry standard font files +to Python sourcecode and a MicroPython module enables these to be rendered to +suitable device drivers, notably OLED displays using the SSD1306 chip. # Introduction @@ -19,28 +21,28 @@ directory. On import very little RAM is used, yet the data may be accessed fast. Note that the use of frozen bytecode is entirely optional: font files may be imported in the normal way if RAM usage is not an issue. -It is intended that the resultant file be usable with two varieties of display -devices and drivers. These comprise: +The resultant file is usable with two varieties of display device drivers: - 1. Drivers using `bytearray` instances as frame buffers, including the - official `framebuffer` class. + 1. Drivers where the display class is subclassed from the official + `framebuffer` class. 2. Drivers for displays where the frame buffer is implemented in the display device hardware. -# The proposed solution +# Solution -This consists of three components: +This comprises three components: - 1. font_to_py.py This is a utility intended to be run on a PC and converts a + 1. [font_to_py.py](./FONT_TO_PY.md) This utility runs on a PC and converts a font file to Python source. See below. - 2. The Writer class (writer.py) This facilitates writing text to a device - given a suitably designed device driver. See [here](./DRIVERS.md). - 3. A device driver specification. This includes an example for rendering text - to an SSD1306 device with arbitrary fonts. Also described in the above reference. + 2. [The Writer class](./writer/WRITER.md) This facilitates rendering text to a + device having a suitably designed device driver. + 3. [Device driver notes](./writer/DRIVERS.md). Notes for authors of display + device drivers. Provides details of the font file format and information on + ensuring comptibility with the `Writer` classes. # font_to_py.py -This is a command line utility written in Python 3 to be run on a PC. It takes +This command line utility is written in Python 3 and runs on a PC. It takes as input a font file in `ttf` or `otf` form together with a height in pixels and outputs a Python source file containing the font data. Fixed and variable pitch rendering are supported. The design has the following aims: diff --git a/driver_test.py b/driver_test.py deleted file mode 100644 index 04a6ec4..0000000 --- a/driver_test.py +++ /dev/null @@ -1,84 +0,0 @@ -# driver_test.py Simple test for rendering text to an ssd1306 display in -# arbitrary fonts -# V0.1 Peter Hinch Nov 2016 - -# 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. - -# This demo uses a modified version of the official SSD1306 driver to fix an -# issue with the scroll method. Once the official version is fixed, it should -# be substituted for ssd1306_drv - -import machine -import utime -from writer import Writer -from ssd1306 import SSD1306_I2C, SSD1306_SPI -import freeserif -import freesans20 -import inconsolata16 - -# Display parameters -WIDTH = const(128) -SPI = False -# Note that HEIGHT is set below - -if SPI: - # Pyb SSD - # 3v3 Vin - # Gnd Gnd - # X1 DC - # X2 CS - # X3 Rst - # X6 CLK - # X8 DATA - HEIGHT = 32 - pdc = machine.Pin('X1', machine.Pin.OUT_PP) - pcs = machine.Pin('X2', machine.Pin.OUT_PP) - prst = machine.Pin('X3', machine.Pin.OUT_PP) - spi = machine.SPI(1) - display = SSD1306_SPI(WIDTH, HEIGHT, spi, pdc, prst, pcs) -else: # I2C - # Pyb SSD - # 3v3 Vin - # Gnd Gnd - # Y9 CLK - # Y10 DATA - HEIGHT = 64 - pscl = machine.Pin('Y9', machine.Pin.OUT_PP) - psda = machine.Pin('Y10', machine.Pin.OUT_PP) - i2c = machine.I2C(scl=pscl, sda=psda) - display = SSD1306_I2C(WIDTH, HEIGHT, i2c) - -serif = Writer(display, freeserif) -sans = Writer(display, freesans20) -Writer.set_clip(True, True) # Disable auto scrolling and wrapping. -serif.printstring('Tuesday\n') -sans.printstring('8 Nov 2016\n') -sans.printstring('10.30am') -display.show() - - -def scroll_test(x, y): - t = utime.ticks_us() - display.scroll(x, y) # 125ms - print(utime.ticks_diff(utime.ticks_us(), t)) - display.show() diff --git a/writer/DRIVERS.md b/writer/DRIVERS.md new file mode 100644 index 0000000..297e54c --- /dev/null +++ b/writer/DRIVERS.md @@ -0,0 +1,193 @@ +# Device Driver Implementation + +Display devices comprise two varieties, depending on whether the hardware +includes a frame buffer or whether the frame buffer is located on the +controlling system. + +In the latter case the [Writer](./WRITER.md) class extends the capability of +the driver to use multiple fonts plus additional functionality. + +Where the buffer is located on the display device, the means of controlling the +text insertion point and the means of performing partial buffer updates will be +device dependent. If the functionality of the `Writer` class is required it +must be implemented at device driver level. + +###### [Main README](../README.md) + +## Drivers for unbuffered displays + +Where the buffer is held on the MicroPython host the driver should be +subclassed from the official `framebuf` module. An example of such a driver is +the [official SSD1306 driver](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py). +In addition the driver class should have bound variables `width` and `height` +containing the size of the display in pixels. + +The device driver defines a buffer of the correct size to hold a full frame of +data and instantiates the `framebuf.FrameBuffer` superclass to reference it. +The `FrameBuffer` mode is selected to match the layout of the target display. +The driver implements a `show` method to efficiently copy the buffer contents +to the display hardware. + +This design enables the supplied `Writer` and `CWriter` classes to be used for +rendering arbitrary fonts to the display. The author of the device driver need +not be concerned with the format of Python font files. + +The `Writer` and `CWriter` classes require horizontally mapped fonts. This is +regardless of the mapping used in the device driver's `FrameBuffer`: the +`Writer.printstring` method deals transparently with any mismatch. + +## Drivers for buffered displays + +Authors of such drivers will need to have an understanding of the font file +format. + +### Specifying the font layout + +Each font file has a `get_ch()` function accepting an ASCII character as its +argument. It returns a memoryview instance providing access to a bytearray +corresponding to the individual glyph. The layout of this data is determined by +the command line arguments presented to the `font_to_py.py` utility. It is +the responsibility of the driver to copy that data to the physical device. + +The purpose of the `font_to_py.py` command line arguments specified to the +user is to ensure that the data layout is optimised for the device so that this +copy operation is a fast bytewise copy or SPI/I2C transfer. The driver +documentation should therefore specify these arguments to ensure the layout is +optimal. Mapping may be horizontal or vertical, and the bit order of individual +bytes may be defined. These are detailed below. + +In the case of devices with their own frame buffer the `Writer` class will need +to be re-written or adapted to match the hardware's method of tracking such +things as the text insertion point. Consideration should be given to employing +the same interface as the `Writer` class to simplify the porting of user code +between displays with differing hardware. + +## Python Font files + +Assume the user has run the utility to produce a file `myfont.py` This then +has the following outline definition (in practice the bytes objects are large): + +```python + # Code generated by font-to-py.py. + # Font: FreeSerif.ttf +version = '0.2' + +def height(): + return 21 + +def max_width(): + return 22 + +def hmap(): + return False + +def reverse(): + return False + +def monospaced(): + return False + +def min_ch(): + return 32 + +def max_ch(): + return 126 + +_font =\ +b'\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x08\x00\xfe\xc7\x00\x7e\xc0\x00\x00\x00\x00\x00'\ + +_index =\ +b'\x00\x00\x14\x00\x2e\x00\x4b\x00\x71\x00\x97\x00\xd2\x00\x0a\x01'\ +b'\x1b\x01\x35\x01\x4f\x01\x75\x01\x9e\x01\xb2\x01\xcc\x01\xe0\x01'\ + +_mvfont = memoryview(_font) + # Boilerplate code omitted + +def get_ch(ch): + # validate ch, if out of range use '?' + # get offsets into _font and retrieve char width + # Return: memoryview of bitmap, height and width + return mvfont[offset + 2, next_offset], height, width +``` + +`height` and `width` are specified in bits (pixels). + +In the case of monospaced fonts the `max_width` function returns the width of +every character. For variable pitch fonts it returns the width of the widest +character. Device drivers can use this to rapidly determine whether a string +will fit the available space. If it will fit on the assumption that all chars +are maximum width, it can be rendered rapidly without doing a character by +character check. + +`get_ch()` returns a memoryview of an individual glyph with its dimensions +and contains all the bytes required to render the character including trailing +space. + +## Fixed width fonts + +If a Python font file is created with the `-f` argument, all characters will +be saved with the width of the widest. In general it is not necessary to +specify this option. The driver can perform fixed pich rendering by rendering +the character as variable pitch, then blanking and advancing the pixel column +by the value returned by `font.max_width()`. + +## Binary font files + +This format is unlikely to find application beyond the e-paper driver. It was +designed for micropower applications where the Pyboard has no SD card. Fonts +are stored as random access files on power-switched Flash storage or SD card. +This method is probably too slow for anything other than e-paper displays. + +The format is as follows. Files are binary with a four byte header and 126 +fixed length records. The header consists of two file identifiers enabling the +file format to be checked, followed by bytes specifying the width and height. +The length of each record is (width + 1) bytes. + +The file indentifiers depend on the -x and -r arguments specified to `font_to_py.py` +and are as follows: + +hmap reverse byte +-x -r 0 1 +0 0 0x3f 0xe7 +1 0 0x40 0xe7 +0 1 0x41 0xe7 +1 1 0x42 0xe7 + +Each record starts with a width byte specifying the x dimension of the glyph if +rendered proportionally spaced, followed by the glyph data. This data includes +trailing space ensuring that all records have the size specified in the header. + +## Mapping (Python and Binary fonts) + +A character occupies a space where (0, 0) represents the coordinates of the top +left hand corner of the bitmap. It comprises a set of pixels where increasing x +values represent locations to the right of the origin and increasing y values +represent downward positions. Mapping defines the relationship between this +abstract two dimensional array of bits and the physical linear sequence of bytes. + +Vertical mapping means that the LSB of first byte is pixel (0,0), MSB of first +byte is (0, 7). The second byte (assuming the height is greater than 8 pixels) +is (0, 8) to (0, 15). Once the column is complete the next byte represents +(1, 0) to (1, 7). + +Horizontal mapping means that the MSB of byte 0 is pixel (0,0) with LSB at +(7,0), with the second byte covering (8, 0) to (15, 0) if the width is greater +than 8. + +Bit reversal provides for the case where the bit order of each byte is reversed +i.e. a byte comprising bits [b7b6b5b4b3b2b1b0] becomes [b0b1b2b3b4b5b6b7]. + +# Specification and Project Notes + +The design aims primarily to minimise RAM usage. Minimising the size of the +bytecode is a secondary aim. Indexed addressing is used to reduce this in +the case of proportional fonts, at a small cost in performance. The size of the +Python source file is a lesser consideration, with readability being prioritised +over size. Hence they are "pretty formatted" with the large bytes objects +split over multiple lines for readability. + +Fonts created with the `font_to_py` utility have been extensively tested with +each of the mapping options. They are used with drivers for SSD1306 OLEDs, +SSD1963 LCD displays, the official LCD160CR and the Digital Artists 2.7 inch +e-paper display. diff --git a/writer/WRITER.md b/writer/WRITER.md new file mode 100644 index 0000000..76e2dbd --- /dev/null +++ b/writer/WRITER.md @@ -0,0 +1,290 @@ +# Writer and Cwriter classes + +These classes facilitate rendering Python font files to displays where the +display driver is subclassed from the `framebuf` class. An example is the +official [SSD1306 driver](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py). + +Example code and images are for 128*64 SSD1306 OLED displays. + +![Image](images/IMG_2866.JPG) +Scrolling text, multiple fonts. + +![Image](images/IMG_2861.JPG) +A field containing variable length text with a border. + +![Image](images/rjust.JPG) +Right justified text. + +![Image](images/mixed.JPG) +Mixed text and graphics. + +![Image](images/fields.JPG) +Labels and Fields. + +# Contents + + 1. [Introduction](./WRITER.md#1-introduction) + 1.1 [Hardware](./WRITER.md#11-hardware) + 1.2 [Files](./WRITER.md#11-files) + 1.3 [Fonts](./WRITER.md#11-fonts) + 2. [Writer and CWriter classes](./WRITER.md#2-writer-and-cwriter-classes) + 2.1 [The Writer class](./WRITER.md#21-the-writer-class) For monochrome displays. + 2.1.1 [Static Method](./WRITER.md#211-static-method) + 2.1.2.[Constructor](./WRITER.md#212-constructor) + 2.1.3 [Methods](./WRITER.md#213-methods) + 2.2 [The CWriter class](./WRITER.md#22-the-cwriter-class) For colour displays + and for upside-down rendering. + 2.2.1 [Static Method](./WRITER.md#221-static-method) + 2.2.2 [Constructor](./WRITER.md#222-constructor) + 2.2.3 [Methods](./WRITER.md#223-methods) + 3. [The Label and Field classes](./WRITER.md#3-the-label-and-field-classes) + 3.1 [The Label class](./WRITER.md#31-the-label-class) + 3.2 [The Field class](./WRITER.md#32-the-field-class) + 4. [Notes](./WRITER.md#4-notes) + +###### [Main README](../README.md) + +# 1. Introduction + +The original `Writer` class was a proof of concept intended to demonstrate +rendering, on an SSD1306 OLED display, fonts created by`font_to_py.py`. + +This update for practical applications has the following features: + * Genarality: capable of working with any `framebuf` derived driver. + * Multiple display operation. + * Text display of fixed and variable pitch fonts with wrapping and vertical + scrolling. + * Wrap/clip options: clip, character wrap or word wrap. + * Tab support. + * String metrics to enable right or centre justification. + * Inverse (background color on foreground color) display. + * Labels: render static text at a fixed location. + * Fields - render dynamically changing text to a fixed rectangular region. + * Inverted display option. + +## 1.1 Hardware + +Tests and demos assume a 128*64 SSD1306 OLED display connected via I2C or SPI. +Wiring is specified in `ssd1306_setup.py`. Edit this to use a different bus or +for a non-Pyboard target. At the time of writing the default of software I2C +should be used: the official SSD1306 driver is not compatible with hardware I2C +(see [Notes](./WRITER.md#4-notes)). + +## 1.2 Files + + 1. `writer.py` Supports `Writer` and `CWriter` classes. + 2. `ssd1306_setup.py` Hardware initialisation for SSD1306. Requires the + official [SSD1306 driver](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py). + 3. `writer_demo.py` Demo using a 128*64 SSD1306 OLED display. Import to see + usage information. + 4. `writer_tests.py` Test/demo scripts. Import to see usage information. + 5. `writer_minimal.py` A minimal version for highly resource constrained + devices. + +Sample fonts: + 1. `freesans20.py` Variable pitch font file. + 2. `courier20.py` Fixed pitch font file. + 3. `font10.py` Smaller variable pitch fonts. + 4. `font6.py` + +## 1.3 Fonts + +Python font files should be created using `font-to-py.py` using horizontal +mapping (`-x` option). The `-r` option is not required. If RAM is critical +fonts may be frozen as bytecode reducing the RAM impact of each font to about +340 bytes. + +###### [Contents](./WRITER.md#contents) + +# 2. Writer and CWriter classes + +The `Writer` class provides fast rendering to monochrome displays using bit +blitting. Most applications will use this class. + +The `CWriter` class is a subclass of `Writer`. It can optionally support color +displays. It provides additional functionality in the form of an upside-down +display option. Owing to limitations in the `frmebuf.blit` method the +`CWriter` class renders glyphs one pixel at a time; rendering is therefore +slower than the `Writer` class. + +Multiple screens are supported. On any screen multiple `Writer` or `CWriter` +instances may be used, each using a different font. A class variable holds the +state of each screen to ensure that the insertion point is managed across +multiple instances/fonts. + +###### [Contents](./WRITER.md#contents) + +## 2.1 The Writer class + +This class facilitates rendering characters from Python font files to a device, +assuming the device has a driver subclassed from `framebuf`. It supports three +ways of handling text which would overflow the display: clipping, character +wrapping and simple word wrapping. + +It handles newline and tab characters, black-on-white inversion, and field +blanking to enable variable length contents to be updated at a fixed location. + +Typical use with an SSD1306 display and the official driver is as follows: + +```python +from ssd1306_setup import WIDTH, HEIGHT, setup +from writer import Writer +import freesans20 # Font to use + +use_spi=False # Tested with a 128*64 I2C connected SSD1306 display +ssd = setup(use_spi) # Instantiate display: must inherit from framebuf +# Demo drawing geometric shpes +rhs = WIDTH -1 +ssd.line(rhs - 20, 0, rhs, 20, 1) # Demo underlying framebuf methods +square_side = 10 +ssd.fill_rect(rhs - square_side, 0, square_side, square_side, 1) +# Instantiate a writer for a specific font +wri = Writer(ssd, freesans20) # verbose = False to suppress console output +Writer.set_textpos(ssd, 0, 0) # In case a previous test has altered this +wri.printstring('Sunday\n12 Aug 2018\n10.30am') +ssd.show() +``` + +The file `writer_demo.py` illustrates the use of font files with a 128*64 +SSD1306 OLED display and the official +[SSD1306 driver](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py). + +### 2.1.1 Static Method + +The `Writer` class exposes the following static method: + + 1. `set_textpos` Args: `device`,`row=None`, `col=None`. The `device` is the + display instance. This method determines where on screen subsequent text is to + be rendered. The initial value is (0, 0) - the top left corner. Arguments are + in pixels with positive values representing down and right respectively. The + insertion point defines the top left hand corner of the next character to be + output. + + Where `None` is passed, the setting is left unchanged. + Return: `row`, `col` current settings. + + The insertion point applies to all `Writer` instances having the same device. + The insertion point on a given screen is maintained regardless of the font in + use. + +### 2.1.2 Constructor + +This takes the following args: + 1. `device` The hardware device driver instance for the screen in use. + 2. `font` A Python font instance. + 3. `verbose=True` If `True` the constructor emits console printout. + +### 2.1.3 Methods + + 1. `printstring` Args: `string`, `invert=False`. Outputs a text string at the + current insertion point. Newline and Tab characters are honoured. If `invert` + is `True` the text is output as black on white. + 2. `height` No args. Returns the font height in pixels. + 3. `stringlen` Arg: `string`. Returns the length of a string in pixels. Used + for right or centre justification. + 4. `set_clip` Args: `row_clip=None`, `col_clip=None`, `wrap=None`. If + `row_clip` and/or `col_clip` are `True`, characters will be clipped if they + extend beyond the boundaries of the physical display. If `col_clip` is + `False` characters will wrap onto the next line. If `row_clip` is `False` the + display will, where necessary, scroll up to ensure the line is rendered. If + `wrap` is `True` word-wrapping will be performed, assuming words are separated + by spaces. + If any arg is `None`, that value will be left unchanged. + Returns the current values of `row_clip`, `col_clip` and `wrap`. + 5. `tabsize` Arg `value=None`. If `value` is an integer sets the tab size. + Returns the current tab size (initial default is 4). Tabs only work properly + with fixed pitch fonts. + +###### [Contents](./WRITER.md#contents) + +## 2.2 The CWriter class + +This extends the `Writer` class by adding support for upside-down and/or color +displays. + +### 2.2.1 Static method + +The following static method is added: + 1. `invert_display` Args `device`, `value=True`. The `device` is the display + instance. If `value` is set, causes text to be rendered upside down. The + `set_textpos` method should be called to ensure that text is rendered from the + bottom right hand corner (viewing the display in its normal orientation). + + If a display is to be run inverted, this method must be called prior to + instantiating a `Writer` for this display. + +### 2.2.2 Constructor + +This takes the same args as the `Writer` constructor: + 1. `device` The hardware device driver instance for the screen in use. + 2. `font` A Python font instance. + 3. `verbose=True` If `True` the constructor emits console printout. + +### 2.2.3 Methods + +All methods of the base class are supported. Additional method: + 1. `setcolor` Args: `fgcolor=None`, `bgcolor=None`. Sets the foreground and + background colors. If either is `None` that value is left unchanged. Initial + constructor defaults are 1 and 0 for monochrome displays. Returns foreground + and background color values. + +The `printstring` method works as per the base class except that the string is +rendered in foreground color on background color (or reversed if `invert` is +`True`). + +# 3. Label and Field classes + +These support applications where text is to be rendered at specific screen +locations. These classes change the text insertion point as required and are +therefore not intended for use with the writer's `printstring` method. + +## 3.1 The Label class + +This renders a fixed text string to a defined screen location. + +Constructor args: + 1. `writer` The `Writer` instance (font and screen) to use. + 2. `row` Location on screen. + 3. `col` + 4. `text` Text to display + 5. `invert=False` Display in inverted or normal style. + +The constructor displays the string at the required location. + +Method: + 1. `show` No args. For future use. + +## 3.2 The Field class + +Constructor args: + 1. `writer` The `Writer` instance (font and screen) to use. + 2. `row` Location on screen. + 3. `col` + 4. `max_text` Defines the longest text string the field must display. Can + either be an integer number of pixels or a text string. In the latter case the + length of the string in pixels is calculated and stored. + 5. `border=False` Optional sigle pixel border around text. + +The constructor does not cause anything to be displayed. The location of a +field is that of the top left hand corner of its text contents. If a border is +drawn, it extends in all directions beyond the text size by two pixels. + +Methods: + 1. `value` Args `text`, `invert=False`. Causes the text to be displayed in + normal or inverted style. + 2. `show` No args. For future use. + +# 4. Notes + +Possible future enhancements: + 1. General rendering to a rectangular area. This may be problematic as the + `framebuf` scroll method is only capable of scrolling the entire buffer. + 2. Extend word wrapping to cases where words are separated by tabs or hyphens. + 3. An asynchronous version. + +As stated above the official SSD1306 drriver is incompatible with hardware I2C +and this problem cannot efficiently be fixed. [PR4020](https://github.com/micropython/micropython/pull/4020) +proposes an enhncement which will facilitate an improved SSD1306 driver capable +of using hard or soft I2C. + +###### [Contents](./WRITER.md#contents) diff --git a/writer/courier20.py b/writer/courier20.py new file mode 100644 index 0000000..258c09b --- /dev/null +++ b/writer/courier20.py @@ -0,0 +1,308 @@ +# Code generated by font-to-py.py. +# Font: Courier Prime.ttf +version = '0.2' + +def height(): + return 20 + +def max_width(): + return 14 + +def hmap(): + return True + +def reverse(): + return False + +def monospaced(): + return True + +def min_ch(): + return 32 + +def max_ch(): + return 126 + +_font =\ +b'\x0e\x00\x00\x00\x00\x00\x7c\x00\xfe\x00\xc7\x00\xc3\x00\x03\x00'\ +b'\x07\x00\x1e\x00\x18\x00\x18\x00\x18\x00\x3c\x00\x3c\x00\x18\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x60\x00\x60\x00\x60\x00'\ +b'\x60\x00\x60\x00\x60\x00\x60\x00\x60\x00\x00\x00\x60\x00\xf0\x00'\ +b'\xf0\x00\x60\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00'\ +b'\x00\x00\x00\x00\xe6\x00\xe6\x00\x66\x00\x66\x00\x66\x00\x66\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\x0e\x00\x00\x00\x03\x30\x02\x20'\ +b'\x02\x20\x06\x60\x3f\xf8\x3f\xf8\x0c\xc0\x08\x80\x7f\xf0\xff\xf0'\ +b'\x19\x80\x11\x00\x33\x00\x33\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x0e\x00\x0c\x00\x0c\x00\x3d\x80\x7f\x80\xcd\x80\xcc\x80'\ +b'\xec\x00\x7f\x00\x0f\x80\x0c\xc0\xcc\xc0\xcd\xc0\xff\x80\xcf\x00'\ +b'\x0c\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00'\ +b'\x00\x00\x38\x00\xfe\x18\xc6\x30\xc6\x60\xfe\xc0\x39\x80\x03\x00'\ +b'\x06\x70\x1d\xfc\x39\x8c\x71\x8c\x61\xfc\x00\x70\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x1e\x00\x3f\x00'\ +b'\x63\x00\x63\x00\x60\x00\x30\x00\x31\xc0\x49\xc0\xc7\x00\xc3\x00'\ +b'\xe3\x00\x7f\xc0\x39\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x0e\x00\x00\x00\x00\x00\x60\x00\x60\x00\x60\x00\x60\x00\x60\x00'\ +b'\x60\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\x0e\x00\x04\x00\x0e\x00'\ +b'\x18\x00\x30\x00\x30\x00\x60\x00\x60\x00\xc0\x00\xc0\x00\xc0\x00'\ +b'\xc0\x00\xc0\x00\xc0\x00\x60\x00\x60\x00\x30\x00\x38\x00\x1c\x00'\ +b'\x0e\x00\x04\x00\x0e\x00\x40\x00\xe0\x00\x30\x00\x18\x00\x18\x00'\ +b'\x0c\x00\x0c\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00'\ +b'\x0c\x00\x0c\x00\x18\x00\x38\x00\x70\x00\xe0\x00\x80\x00\x0e\x00'\ +b'\x00\x00\x00\x00\x0c\x00\x0c\x00\x0c\x00\xed\xc0\x7f\x80\x0c\x00'\ +b'\x1e\x00\x33\x00\x23\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00'\ +b'\x0c\x00\x0c\x00\x0c\x00\x0c\x00\xff\xc0\xff\xc0\x0c\x00\x0c\x00'\ +b'\x0c\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x0e\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\x78\x00\x70\x00'\ +b'\x60\x00\x60\x00\xc0\x00\xc0\x00\x00\x00\x00\x00\x0e\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc0\xff\xc0'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x60\x00'\ +b'\xf0\x00\xf0\x00\x60\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x0e\x00\x00\xc0\x01\x80\x01\x80\x03\x00\x03\x00\x02\x00\x06\x00'\ +b'\x04\x00\x0c\x00\x0c\x00\x18\x00\x18\x00\x30\x00\x30\x00\x20\x00'\ +b'\x60\x00\x40\x00\xc0\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00'\ +b'\x1e\x00\x3f\x00\x61\x80\xe1\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\ +b'\xc0\xc0\xe1\xc0\x61\x80\x3f\x00\x1e\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x0c\x00\x7c\x00\xec\x00'\ +b'\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00'\ +b'\xff\xc0\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00'\ +b'\x00\x00\x00\x00\x3e\x00\xff\x00\xc3\x80\xc1\x80\x01\x80\x01\x00'\ +b'\x02\x00\x04\x00\x08\x00\x11\x80\x21\x80\xff\x80\xff\x80\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x3e\x00'\ +b'\x7f\x00\x61\x80\x61\x80\x01\x80\x1f\x00\x1f\x00\x03\x80\x01\x80'\ +b'\x01\x80\xc3\x80\xff\x00\x3e\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x0e\x00\x00\x00\x00\x00\x03\x00\x07\x00\x0b\x00\x1b\x00'\ +b'\x13\x00\x23\x00\x63\x00\xff\xc0\xff\xe0\x03\x00\x03\x00\x0f\xc0'\ +b'\x0f\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00'\ +b'\x00\x00\x7f\x80\x7f\x80\x60\x00\x60\x00\x7e\x00\x7f\x00\x63\x80'\ +b'\x01\x80\x01\x80\x01\x80\xc3\x80\xff\x00\x3c\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x07\x80\x1f\x80'\ +b'\x3c\x00\x70\x00\x60\x00\xcf\x00\xff\x80\xe1\xc0\xc0\xc0\xc0\xc0'\ +b'\x61\xc0\x7f\x80\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x0e\x00\x00\x00\x00\x00\xff\x80\xff\x80\xc1\x80\xc1\x00\x03\x00'\ +b'\x02\x00\x06\x00\x06\x00\x0c\x00\x0c\x00\x08\x00\x18\x00\x10\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00'\ +b'\x1e\x00\x3f\x00\x61\x80\x61\x80\x73\x80\x3f\x00\x7f\x00\xe3\x80'\ +b'\xc1\x80\xc1\x80\xe3\x80\x7f\x00\x3e\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x3e\x00\x7f\x80\xe1\x80'\ +b'\xc0\xc0\xc0\xc0\xe1\xc0\x7f\xc0\x3c\xc0\x01\x80\x03\x80\x0f\x00'\ +b'\x7e\x00\x78\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x60\x00\xf0\x00\xf0\x00'\ +b'\x60\x00\x00\x00\x00\x00\x60\x00\xf0\x00\xf0\x00\x60\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x30\x00\x78\x00\x78\x00\x30\x00\x00\x00\x00\x00'\ +b'\x00\x00\x78\x00\x70\x00\x60\x00\x60\x00\xc0\x00\xc0\x00\x00\x00'\ +b'\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x60\x01\xe0\x07\x80'\ +b'\x1e\x00\x78\x00\xe0\x00\x78\x00\x0e\x00\x03\x80\x00\xe0\x00\x40'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc0\xff\xc0\x00\x00\x00\x00'\ +b'\x00\x00\xff\xc0\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x80\x00'\ +b'\xe0\x00\x38\x00\x0e\x00\x03\xc0\x00\xc0\x03\x80\x0e\x00\x38\x00'\ +b'\xe0\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x0e\x00\x00\x00\x00\x00\x7c\x00\xfe\x00\xc7\x00\xc3\x00\x03\x00'\ +b'\x07\x00\x1e\x00\x18\x00\x18\x00\x18\x00\x3c\x00\x3c\x00\x18\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00'\ +b'\x00\x00\x0f\x80\x1f\xc0\x38\xe0\x66\xf0\x6f\xb0\xcd\x30\xd9\x30'\ +b'\xd9\x30\xdb\x70\xdf\xe0\x6c\xc0\x70\x00\x3f\xc0\x0f\x80\x00\x00'\ +b'\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x3f\x00\x3f\x00\x07\x80'\ +b'\x0c\x80\x0c\x80\x18\xc0\x18\x40\x3f\xe0\x3f\xe0\x30\x60\x60\x30'\ +b'\xf8\xf8\xf8\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00'\ +b'\x00\x00\x00\x00\xff\x00\xff\xc0\x30\xc0\x30\xc0\x30\xc0\x3f\x80'\ +b'\x3f\xc0\x30\xe0\x30\x60\x30\x60\x30\xe0\xff\xc0\xff\x80\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x1f\x60'\ +b'\x3f\xe0\x70\xe0\x60\x60\xc0\x60\xc0\x40\xc0\x00\xc0\x00\xc0\x00'\ +b'\x60\x00\x70\x60\x3f\xe0\x1f\x80\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x0e\x00\x00\x00\x00\x00\xff\x00\xff\x80\x31\xc0\x30\xe0'\ +b'\x30\x60\x30\x60\x30\x60\x30\x60\x30\x60\x30\xe0\x31\xc0\xff\x80'\ +b'\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00'\ +b'\x00\x00\xff\xe0\xff\xe0\x30\x60\x33\x60\x33\x00\x3f\x00\x3f\x00'\ +b'\x33\x00\x33\x00\x30\x60\x30\x60\xff\xe0\xff\xe0\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\xff\xe0\xff\xe0'\ +b'\x30\x60\x33\x60\x33\x00\x3f\x00\x3f\x00\x33\x00\x33\x00\x30\x00'\ +b'\x30\x00\xfe\x00\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x0e\x00\x00\x00\x00\x00\x1e\xc0\x3f\xc0\x71\xc0\x60\xc0\xc0\xc0'\ +b'\xc0\x00\xc0\x00\xc7\xf0\xc7\xf0\x60\xc0\x70\xc0\x3f\xc0\x1f\x80'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00'\ +b'\xfd\xf8\xfd\xf8\x30\x60\x30\x60\x30\x60\x3f\xe0\x3f\xe0\x30\x60'\ +b'\x30\x60\x30\x60\x30\x60\xfd\xf8\xfd\xf8\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\xff\xc0\xff\xc0\x0c\x00'\ +b'\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00'\ +b'\xff\xc0\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00'\ +b'\x00\x00\x00\x00\x3f\xf8\x3f\xf8\x01\x80\x01\x80\x01\x80\x01\x80'\ +b'\x81\x80\xc1\x80\xc1\x80\xc1\x80\xc3\x80\x7f\x00\x3e\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\xfc\xf0'\ +b'\xfc\xf0\x30\x40\x31\x80\x33\x00\x34\x00\x3f\x00\x31\x80\x30\x80'\ +b'\x30\xc0\x30\x40\xfc\x70\xfc\x30\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x0e\x00\x00\x00\x00\x00\x7f\x00\xff\x00\x18\x00\x18\x00'\ +b'\x18\x00\x18\x00\x18\x00\x18\x30\x18\x30\x18\x30\x18\x30\x7f\xf0'\ +b'\xff\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00'\ +b'\x00\x00\xf0\xf0\xf0\xf0\x70\xe0\x79\xe0\x69\x60\x69\x60\x6f\x60'\ +b'\x66\x60\x66\x60\x66\x60\x60\x60\xf9\xf0\xf9\xf0\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\xf0\xf8\xf8\xf8'\ +b'\x3c\x30\x34\x30\x32\x30\x32\x30\x31\x30\x31\x30\x30\xb0\x30\x70'\ +b'\x30\x70\x7c\x30\x7c\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x0e\x00\x00\x00\x00\x00\x1f\x80\x3f\xc0\x70\xe0\x60\x60\xc0\x30'\ +b'\xc0\x30\xc0\x30\xc0\x30\xc0\x30\x60\x60\x70\xe0\x3f\xc0\x1f\x80'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00'\ +b'\x7f\xc0\xff\xe0\x18\x70\x18\x30\x18\x30\x18\x70\x1f\xe0\x1f\xc0'\ +b'\x18\x00\x18\x00\x18\x00\x7f\x00\xff\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x1f\x80\x3f\xc0\x70\xe0'\ +b'\x60\x60\xc0\x30\xc0\x30\xc0\x30\xc0\x30\xc0\x30\x60\x60\x70\xe0'\ +b'\x3f\xc0\x0f\x80\x18\x20\x3f\xe0\x3f\xc0\x20\x00\x00\x00\x0e\x00'\ +b'\x00\x00\x00\x00\xff\x80\xff\xc0\x30\xe0\x30\x60\x30\xe0\x3f\xc0'\ +b'\x3f\x00\x31\x80\x30\xc0\x30\xc0\x30\x60\xfc\x78\xfc\x38\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x3c\xc0'\ +b'\x7f\xc0\xe1\xc0\xc0\xc0\xc0\x00\x70\x00\x1f\x00\x01\x80\x00\xc0'\ +b'\xc0\xc0\xe1\xc0\xff\x80\xdf\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x0e\x00\x00\x00\x00\x00\xff\xc0\xff\xc0\xcc\xc0\xcc\xc0'\ +b'\xcc\xc0\xcc\xc0\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x7f\x80'\ +b'\x7f\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00'\ +b'\x00\x00\xfc\xfc\xfc\xfc\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30'\ +b'\x30\x30\x30\x30\x30\x30\x38\x70\x1f\xe0\x0f\xc0\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\xfc\x7c\xfc\x7c'\ +b'\x30\x30\x30\x30\x10\x20\x18\x60\x18\x40\x0c\xc0\x0c\xc0\x04\x80'\ +b'\x07\x80\x07\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x0e\x00\x00\x00\x00\x00\xf8\x7c\xf8\x7c\x60\x18\x63\x18\x23\x10'\ +b'\x23\x90\x37\x90\x37\xb0\x34\xb0\x3c\xf0\x1c\xe0\x18\x60\x18\x60'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00'\ +b'\xf9\xf0\xf9\xf0\x30\xc0\x19\x80\x1f\x80\x0f\x00\x06\x00\x0f\x00'\ +b'\x19\x80\x30\xc0\x60\x60\xf9\xf0\xf9\xf0\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\xf9\xf0\xf9\xf0\x30\xc0'\ +b'\x30\xc0\x19\x80\x0f\x00\x0f\x00\x06\x00\x06\x00\x06\x00\x06\x00'\ +b'\x3f\xc0\x3f\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00'\ +b'\x00\x00\x00\x00\xff\xc0\xff\xc0\xc1\x80\xc3\x00\x02\x00\x04\x00'\ +b'\x0c\x00\x18\x00\x10\xc0\x20\xc0\x40\xc0\xff\xc0\xff\xc0\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\xfc\x00\xfc\x00\xc0\x00'\ +b'\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00'\ +b'\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xfc\x00\xfc\x00'\ +b'\x00\x00\x0e\x00\xc0\x00\x40\x00\x60\x00\x20\x00\x30\x00\x30\x00'\ +b'\x18\x00\x18\x00\x0c\x00\x0c\x00\x04\x00\x06\x00\x02\x00\x03\x00'\ +b'\x03\x00\x01\x80\x01\x80\x00\xc0\x00\x00\x00\x00\x0e\x00\xfc\x00'\ +b'\xfc\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00'\ +b'\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00'\ +b'\xfc\x00\xfc\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x18\x00\x18\x00'\ +b'\x3c\x00\x24\x00\x66\x00\xc6\x00\xc3\x00\x83\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x0e\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'\xff\xfc\xff\xfc\x00\x00\x00\x00\x00\x00\x0e\x00\xe0\x00\xf8\x00'\ +b'\x1c\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\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x3e\x00\xff\x00\xc1\x80\x3f\x80\x7f\x80\xc1\x80\xc1\x80\xc3\x80'\ +b'\xff\x80\x7c\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00'\ +b'\xf0\x00\xf0\x00\x30\x00\x30\x00\x30\x00\x37\xc0\x3f\xe0\x38\x60'\ +b'\x30\x30\x30\x30\x30\x30\x30\x30\x38\x60\xff\xe0\xf3\xc0\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x1f\x60\x7f\xe0\x70\xe0\xc0\x60\xc0\x00\xc0\x00'\ +b'\xc0\x00\x70\x60\x7f\xc0\x1f\x80\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x0e\x00\x03\xc0\x03\xc0\x00\xc0\x00\xc0\x00\xc0\x3e\xc0'\ +b'\x7f\xc0\x61\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x61\xc0\x7f\xf0'\ +b'\x3c\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x1f\x00\x7f\x80\x60\xc0\xff\xc0'\ +b'\xff\xc0\xc0\x00\xc0\x00\x60\xc0\x7f\xc0\x1f\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0e\x00\x0f\x80\x1f\xc0\x38\x40\x30\x00'\ +b'\x30\x00\xff\x80\xff\x80\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00'\ +b'\x30\x00\xff\x80\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1e\x78\x7f\xf8'\ +b'\x60\xe0\xc0\x60\xc0\x60\xc0\x60\xc0\x60\x60\xe0\x7f\xe0\x1e\x60'\ +b'\x00\x60\x20\xe0\x3f\xc0\x1f\x80\x00\x00\x0e\x00\xf0\x00\xf0\x00'\ +b'\x30\x00\x30\x00\x30\x00\x37\xc0\x3f\xe0\x3c\x60\x38\x60\x30\x60'\ +b'\x30\x60\x30\x60\x30\x60\xfd\xf8\xfd\xf8\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x0e\x00\x0c\x00\x0c\x00\x0c\x00\x00\x00\x00\x00'\ +b'\x7c\x00\x7c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00'\ +b'\xff\xc0\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00'\ +b'\x03\x00\x03\x00\x03\x00\x00\x00\x00\x00\x7f\x00\x7f\x00\x03\x00'\ +b'\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00'\ +b'\xc3\x00\xfe\x00\x3c\x00\x00\x00\x0e\x00\xf0\x00\xf0\x00\x30\x00'\ +b'\x30\x00\x30\x00\x33\xe0\x33\xe0\x31\x80\x33\x00\x34\x00\x3a\x00'\ +b'\x31\x80\x30\xc0\xfd\xf0\xfd\xf0\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x0e\x00\x7c\x00\x7c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00'\ +b'\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\xff\xc0'\ +b'\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\xf6\x70\xff\xf8\x3b\x98\x33\x18'\ +b'\x33\x18\x33\x18\x33\x18\x33\x18\xfb\x9c\xfb\x9c\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\xf3\xc0\xf7\xe0\x38\x60\x38\x60\x30\x60\x30\x60\x30\x60'\ +b'\x30\x60\xfd\xf8\xfd\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f\x80\x3f\xc0'\ +b'\x70\xe0\xc0\x30\xc0\x30\xc0\x30\xc0\x30\x70\xe0\x3f\xc0\x1f\x80'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\xf7\xc0\xff\xe0\x38\x60\x30\x30\x30\x30'\ +b'\x30\x30\x30\x30\x38\x60\x3f\xe0\x37\xc0\x30\x00\x30\x00\xfc\x00'\ +b'\xfc\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x3e\xf0\x7f\xf0\x61\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x61\xc0'\ +b'\x7f\xc0\x3c\xc0\x00\xc0\x00\xc0\x03\xf0\x03\xf0\x00\x00\x0e\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\xc0\xf7\xe0\x3e\x40'\ +b'\x38\x00\x30\x00\x30\x00\x30\x00\x30\x00\xff\x00\xff\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x7d\x80\xff\x80\xc1\x80\xc0\x80\x7c\x00\x03\x80'\ +b'\xc0\xc0\xe1\xc0\xff\xc0\xdf\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x0e\x00\x00\x00\x30\x00\x30\x00\x30\x00\x30\x00\xff\x80'\ +b'\xff\x80\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\xc0\x1f\xc0'\ +b'\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\xf1\xe0\xf1\xe0\x30\x60\x30\x60'\ +b'\x30\x60\x30\x60\x30\xe0\x30\xe0\x3f\xf8\x1e\x78\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\xf8\xf0\xf9\xf0\x20\x60\x30\xc0\x10\xc0\x18\x80\x09\x80'\ +b'\x0d\x00\x0f\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8\x3c\xf8\x3c'\ +b'\x63\x18\x63\x18\x27\x90\x37\xb0\x34\xb0\x1c\xe0\x1c\xe0\x18\x60'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\xf9\xf0\xf9\xf0\x30\xc0\x19\x80\x0f\x00'\ +b'\x0f\x00\x19\x80\x30\xc0\xf9\xf0\xf9\xf0\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\xf8\xf0\xf9\xf0\x30\x60\x30\xc0\x18\xc0\x19\x80\x0d\x80\x07\x00'\ +b'\x07\x00\x06\x00\x0c\x00\x1c\x00\x78\x00\x70\x00\x00\x00\x0e\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc0\xff\x80\xc1\x00'\ +b'\xc2\x00\x04\x00\x18\x00\x30\xc0\x60\xc0\xff\xc0\xff\xc0\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x06\x00\x0e\x00\x18\x00'\ +b'\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x38\x00\xf0\x00\xf0\x00'\ +b'\x38\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x1e\x00\x0e\x00'\ +b'\x00\x00\x0e\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00'\ +b'\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00'\ +b'\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\x00\x00\x0e\x00\xc0\x00'\ +b'\xe0\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x38\x00'\ +b'\x1e\x00\x1e\x00\x38\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00'\ +b'\xf0\x00\xe0\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x78\x40\xff\xc0\x87\x80\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ + +_index =\ +b'\x00\x00\x2a\x00\x54\x00\x7e\x00\xa8\x00\xd2\x00\xfc\x00\x26\x01'\ +b'\x50\x01\x7a\x01\xa4\x01\xce\x01\xf8\x01\x22\x02\x4c\x02\x76\x02'\ +b'\xa0\x02\xca\x02\xf4\x02\x1e\x03\x48\x03\x72\x03\x9c\x03\xc6\x03'\ +b'\xf0\x03\x1a\x04\x44\x04\x6e\x04\x98\x04\xc2\x04\xec\x04\x16\x05'\ +b'\x40\x05\x6a\x05\x94\x05\xbe\x05\xe8\x05\x12\x06\x3c\x06\x66\x06'\ +b'\x90\x06\xba\x06\xe4\x06\x0e\x07\x38\x07\x62\x07\x8c\x07\xb6\x07'\ +b'\xe0\x07\x0a\x08\x34\x08\x5e\x08\x88\x08\xb2\x08\xdc\x08\x06\x09'\ +b'\x30\x09\x5a\x09\x84\x09\xae\x09\xd8\x09\x02\x0a\x2c\x0a\x56\x0a'\ +b'\x80\x0a\xaa\x0a\xd4\x0a\xfe\x0a\x28\x0b\x52\x0b\x7c\x0b\xa6\x0b'\ +b'\xd0\x0b\xfa\x0b\x24\x0c\x4e\x0c\x78\x0c\xa2\x0c\xcc\x0c\xf6\x0c'\ +b'\x20\x0d\x4a\x0d\x74\x0d\x9e\x0d\xc8\x0d\xf2\x0d\x1c\x0e\x46\x0e'\ +b'\x70\x0e\x9a\x0e\xc4\x0e\xee\x0e\x18\x0f\x42\x0f\x6c\x0f\x96\x0f'\ +b'\xc0\x0f' + +_mvfont = memoryview(_font) + +def _chr_addr(ordch): + offset = 2 * (ordch - 32) + return int.from_bytes(_index[offset:offset + 2], 'little') + +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], 20, width + diff --git a/writer/font10.py b/writer/font10.py new file mode 100644 index 0000000..4e82368 --- /dev/null +++ b/writer/font10.py @@ -0,0 +1,229 @@ +# Code generated by font-to-py.py. +# Font: FreeSans.ttf +version = '0.1' + +def height(): + return 17 + +def max_width(): + return 17 + +def hmap(): + return True + +def reverse(): + return False + +def monospaced(): + return False + +_font =\ +b'\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x06\x00\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x80'\ +b'\x00\xc0\x00\x00\x00\x00\x06\x00\x00\xf0\xf0\xf0\xa0\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x19'\ +b'\x00\x19\x00\x13\x00\x7f\x80\x12\x00\x32\x00\x32\x00\xff\x80\x26'\ +b'\x00\x24\x00\x64\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x10'\ +b'\x00\x3c\x00\x56\x00\xd3\x00\xd3\x00\xd0\x00\xd0\x00\x3c\x00\x17'\ +b'\x00\x13\x00\xd3\x00\xd6\x00\x7c\x00\x10\x00\x00\x00\x00\x00\x00'\ +b'\x00\x0f\x00\x00\x00\x78\x20\xcc\x40\xcc\x80\xcc\x80\xc9\x00\x31'\ +b'\x00\x02\x78\x04\xcc\x04\xcc\x08\xcc\x08\xcc\x10\x78\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x1e\x00\x33\x00\x33\x00\x33'\ +b'\x00\x1e\x00\x18\x00\x74\xc0\xe6\xc0\xc3\x80\xc1\x80\xe3\x80\x3c'\ +b'\x40\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\xc0\xc0\xc0\x80'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x10\x20'\ +b'\x20\x60\x40\xc0\xc0\xc0\xc0\xc0\xc0\x40\x60\x20\x30\x10\x00\x06'\ +b'\x00\x80\xc0\x40\x60\x20\x30\x30\x30\x30\x30\x30\x20\x60\x40\xc0'\ +b'\x80\x00\x07\x00\x20\xa8\x70\x50\x50\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x30\x00\x30\x00\x30\x00\xfc\x00\x30\x00\x30\x00\x30'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\xc0\x40\x40\x80\x00\x06\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x04'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\x00\x00'\ +b'\x00\x00\x05\x00\x08\x08\x10\x10\x10\x20\x20\x20\x40\x40\x40\x80'\ +b'\x80\x00\x00\x00\x00\x09\x00\x00\x00\x3c\x00\x66\x00\x42\x00\xc3'\ +b'\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\x42\x00\x66\x00\x3c'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x10\x00\x30'\ +b'\x00\xf0\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30'\ +b'\x00\x30\x00\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00'\ +b'\x00\x3c\x00\x66\x00\xc3\x00\xc3\x00\x03\x00\x06\x00\x0c\x00\x38'\ +b'\x00\x60\x00\x40\x00\xc0\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x09\x00\x00\x00\x7c\x00\xe7\x00\xc3\x00\x03\x00\x02\x00\x1c'\ +b'\x00\x07\x00\x03\x00\x03\x00\xc3\x00\xe6\x00\x3c\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x09\x00\x00\x00\x0c\x00\x0c\x00\x1c\x00\x2c'\ +b'\x00\x2c\x00\x4c\x00\x8c\x00\x8c\x00\xfe\x00\x0c\x00\x0c\x00\x0c'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x7e\x00\x40'\ +b'\x00\x40\x00\x80\x00\xbc\x00\xe6\x00\x03\x00\x03\x00\x03\x00\xc3'\ +b'\x00\x66\x00\x3c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00'\ +b'\x00\x3c\x00\x66\x00\x43\x00\xc0\x00\xc0\x00\xfc\x00\xe6\x00\xc3'\ +b'\x00\xc3\x00\xc3\x00\x66\x00\x3c\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x09\x00\x00\x00\xff\x00\x03\x00\x02\x00\x06\x00\x04\x00\x0c'\ +b'\x00\x08\x00\x18\x00\x18\x00\x10\x00\x30\x00\x30\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x09\x00\x00\x00\x3c\x00\x66\x00\xc3\x00\xc3'\ +b'\x00\x66\x00\x3c\x00\x66\x00\xc3\x00\xc3\x00\xc3\x00\x66\x00\x3c'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x3c\x00\x66'\ +b'\x00\xc3\x00\xc3\x00\xc3\x00\x67\x00\x3b\x00\x03\x00\x03\x00\xc2'\ +b'\x00\x66\x00\x3c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00'\ +b'\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00'\ +b'\x04\x00\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00\xc0\x40'\ +b'\x40\x80\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03'\ +b'\x00\x0e\x00\x38\x00\xc0\x00\xe0\x00\x38\x00\x07\x00\x01\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\xff\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\xc0\x00\x70\x00\x1c\x00\x03\x00\x07'\ +b'\x00\x1c\x00\xe0\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09'\ +b'\x00\x3c\x00\xc7\x00\xc3\x00\x03\x00\x03\x00\x06\x00\x0c\x00\x08'\ +b'\x00\x18\x00\x18\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x11\x00\x07\xe0\x00\x0c\x38\x00\x30\x0c\x00\x20\x06'\ +b'\x00\x63\xb7\x00\x4c\x73\x00\xcc\x63\x00\xd8\x63\x00\xd8\x63\x00'\ +b'\xd8\x46\x00\xdc\xce\x00\x6f\x78\x00\x30\x00\x00\x18\x00\x00\x0f'\ +b'\xe0\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x06\x00\x0e\x00\x0b\x00'\ +b'\x1b\x00\x1b\x00\x11\x80\x31\x80\x31\x80\x3f\xc0\x60\xc0\x60\x40'\ +b'\x40\x60\xc0\x60\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\xfe\x00'\ +b'\xc3\x80\xc1\x80\xc1\x80\xc1\x80\xc3\x00\xfe\x00\xc1\x80\xc0\xc0'\ +b'\xc0\xc0\xc0\xc0\xc1\x80\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x0c\x00\x1f\x80\x30\xc0\x60\x60\x40\x60\xc0\x00\xc0\x00\xc0\x00'\ +b'\xc0\x00\xc0\x00\x40\x60\x60\x60\x30\xc0\x1f\x80\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x0c\x00\xff\x00\xc1\x80\xc0\xc0\xc0\x60\xc0\x60'\ +b'\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\xc0\xc1\x80\xff\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\xff\x00\xc0\x00\xc0\x00'\ +b'\xc0\x00\xc0\x00\xc0\x00\xff\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00'\ +b'\xc0\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\xff\x00'\ +b'\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xfe\x00\xc0\x00\xc0\x00'\ +b'\xc0\x00\xc0\x00\xc0\x00\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x0d\x00\x0f\xc0\x30\x60\x60\x30\x60\x00\xc0\x00\xc0\x00\xc1\xf0'\ +b'\xc0\x30\xc0\x30\x60\x30\x60\x70\x30\xf0\x0f\x10\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x0c\x00\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\ +b'\xc0\xc0\xff\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\xc0\xc0\xc0\xc0\xc0\xc0'\ +b'\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x00\x00\x00\x00\x09\x00\x06\x00\x06'\ +b'\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\xc6'\ +b'\x00\xc6\x00\xc4\x00\x78\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b'\ +b'\x00\xc0\xc0\xc1\x80\xc3\x00\xc6\x00\xcc\x00\xd8\x00\xfc\x00\xe6'\ +b'\x00\xc6\x00\xc3\x00\xc1\x80\xc1\x80\xc0\xc0\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x0a\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0'\ +b'\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xfe\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x0e\x00\xe0\x38\xe0\x38\xf0\x78\xf0'\ +b'\x78\xd0\x58\xd8\xd8\xd8\xd8\xc8\x98\xcd\x98\xcd\x98\xc5\x18\xc7'\ +b'\x18\xc7\x18\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\xe0\x60\xe0'\ +b'\x60\xf0\x60\xd0\x60\xd8\x60\xcc\x60\xc4\x60\xc6\x60\xc3\x60\xc3'\ +b'\x60\xc1\xe0\xc0\xe0\xc0\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x0d'\ +b'\x00\x1f\x80\x30\xc0\x60\x60\xe0\x60\xc0\x30\xc0\x30\xc0\x30\xc0'\ +b'\x30\xc0\x30\xe0\x60\x60\x60\x30\xc0\x1f\x80\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x0b\x00\xff\x00\xc1\x80\xc0\xc0\xc0\xc0\xc0\xc0\xc1'\ +b'\x80\xff\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x0d\x00\x1f\x80\x30\xc0\x60\x60\xe0'\ +b'\x60\xc0\x30\xc0\x30\xc0\x30\xc0\x30\xc0\x30\xe1\x60\x61\xe0\x30'\ +b'\xc0\x1f\xe0\x00\x20\x00\x00\x00\x00\x00\x00\x0c\x00\xff\x00\xc1'\ +b'\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc1\x80\xff\x00\xc1\xc0\xc0\xc0\xc0'\ +b'\xc0\xc0\xc0\xc0\xc0\xc0\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x0b'\ +b'\x00\x3f\x00\x61\x80\xc0\xc0\xc0\x00\xc0\x00\x60\x00\x3e\x00\x07'\ +b'\x80\x01\xc0\xc0\xc0\xc0\xc0\x61\x80\x3f\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x0b\x00\xff\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18'\ +b'\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x0c\x00\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\ +b'\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x61'\ +b'\x80\x3f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\xc0\x60\x40'\ +b'\x40\x60\xc0\x60\xc0\x20\x80\x31\x80\x31\x80\x11\x00\x1b\x00\x0b'\ +b'\x00\x0a\x00\x0e\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10'\ +b'\x00\xc1\x83\xc1\x82\x42\x86\x62\xc6\x62\xc6\x62\x44\x24\x44\x24'\ +b'\x6c\x34\x2c\x3c\x28\x18\x38\x18\x38\x18\x18\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x0b\x00\x60\x40\x20\xc0\x31\x80\x19\x00\x1b\x00\x0e'\ +b'\x00\x06\x00\x0e\x00\x1b\x00\x11\x80\x31\x80\x60\xc0\x40\x60\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x40\x60\x60\x60\x30\xc0\x30'\ +b'\xc0\x19\x80\x0d\x00\x0f\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06'\ +b'\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\xff\x80\x01'\ +b'\x80\x03\x00\x06\x00\x06\x00\x0c\x00\x18\x00\x18\x00\x30\x00\x60'\ +b'\x00\x60\x00\xc0\x00\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00\x05'\ +b'\x00\xe0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\ +b'\xc0\xe0\x05\x00\x80\x80\x40\x40\x40\x20\x20\x20\x10\x10\x10\x08'\ +b'\x08\x00\x00\x00\x00\x05\x00\xe0\x60\x60\x60\x60\x60\x60\x60\x60'\ +b'\x60\x60\x60\x60\x60\x60\x60\xe0\x08\x00\x00\x30\x30\x78\x48\x48'\ +b'\x84\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\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\x00\x00\x00\xff\xc0\x00\x00\x00\x00\x00\x00\x04'\ +b'\x00\xc0\x40\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7c\x00\xc6\x00'\ +b'\x06\x00\x06\x00\x7e\x00\xc6\x00\xc6\x00\xce\x00\x77\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x09\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00'\ +b'\xde\x00\xe3\x00\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xe3\x00'\ +b'\xde\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x3c\x00\x66\x00\xc3\x00\xc0\x00\xc0\x00\xc0\x00'\ +b'\xc3\x00\x66\x00\x3c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00'\ +b'\x03\x00\x03\x00\x03\x00\x03\x00\x3b\x00\x67\x00\xc3\x00\xc3\x00'\ +b'\xc3\x00\xc3\x00\xc3\x00\x67\x00\x3b\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3c\x00\x66\x00'\ +b'\xc3\x00\xc3\x00\xff\x00\xc0\x00\xc3\x00\x66\x00\x3c\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x05\x00\x30\x60\x60\x60\xf0\x60\x60\x60'\ +b'\x60\x60\x60\x60\x60\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x3b\x00\x67\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\xc3'\ +b'\x00\x67\x00\x3b\x00\x03\x00\x03\x00\xc6\x00\x7c\x00\x09\x00\xc0'\ +b'\x00\xc0\x00\xc0\x00\xc0\x00\xde\x00\xe3\x00\xc3\x00\xc3\x00\xc3'\ +b'\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x04\x00\xc0\x00\x00\x00\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\ +b'\x00\x00\x00\x00\x04\x00\x60\x00\x00\x00\x60\x60\x60\x60\x60\x60'\ +b'\x60\x60\x60\x60\x60\x60\xc0\x09\x00\xc0\x00\xc0\x00\xc0\x00\xc0'\ +b'\x00\xc6\x00\xcc\x00\xd8\x00\xf8\x00\xe8\x00\xcc\x00\xc6\x00\xc6'\ +b'\x00\xc3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\xc0\xc0\xc0'\ +b'\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x00\x00\x00\x00\x0e\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\xdd\xe0\xe7\x30\xc6\x30\xc6\x30'\ +b'\xc6\x30\xc6\x30\xc6\x30\xc6\x30\xc6\x30\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\x00\xe3\x00'\ +b'\xc3\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x3c\x00\x66\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\x66\x00'\ +b'\x3c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\xde\x00\xe3\x00\xc1\x80\xc1\x80\xc1\x80\xc1\x80'\ +b'\xc1\x80\xe3\x00\xde\x00\xc0\x00\xc0\x00\xc0\x00\x00\x00\x0a\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x3b\x00\x67\x00\xc3\x00\xc3\x00'\ +b'\xc3\x00\xc3\x00\xc3\x00\x67\x00\x3b\x00\x03\x00\x03\x00\x03\x00'\ +b'\x00\x00\x06\x00\x00\x00\x00\x00\xd8\xe0\xc0\xc0\xc0\xc0\xc0\xc0'\ +b'\xc0\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x7c\xc6\xc0\xc0\x70'\ +b'\x0e\xc6\xc6\x7c\x00\x00\x00\x00\x05\x00\x00\x00\x60\x60\xf0\x60'\ +b'\x60\x60\x60\x60\x60\x60\x70\x00\x00\x00\x00\x09\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\xc3'\ +b'\x00\xc3\x00\xc7\x00\x7b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08'\ +b'\x00\x00\x00\x00\x00\xc3\x43\x62\x66\x26\x34\x3c\x18\x18\x00\x00'\ +b'\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc6\x30\x46\x30'\ +b'\x47\x20\x6f\x20\x69\x60\x29\x60\x29\xc0\x39\xc0\x10\xc0\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x42\x66\x34\x18'\ +b'\x18\x1c\x24\x66\x43\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\xc3'\ +b'\x42\x42\x66\x24\x24\x3c\x18\x18\x18\x10\x30\x60\x08\x00\x00\x00'\ +b'\x00\x00\xfe\x0c\x08\x18\x30\x60\x40\xc0\xfe\x00\x00\x00\x00\x06'\ +b'\x00\x30\x60\x60\x60\x60\x60\x60\xe0\xc0\xe0\x60\x60\x60\x60\x60'\ +b'\x60\x30\x04\x00\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\ +b'\xc0\xc0\xc0\xc0\x00\x06\x00\xc0\x60\x60\x60\x60\x60\x60\x70\x30'\ +b'\x70\x60\x60\x60\x60\x60\x60\xc0\x09\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x62\x00\x9e\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + +_index =\ +b'\x00\x00\x13\x00\x26\x00\x39\x00\x5d\x00\x81\x00\xa5\x00\xc9\x00'\ +b'\xdc\x00\xef\x00\x02\x01\x15\x01\x39\x01\x4c\x01\x5f\x01\x72\x01'\ +b'\x85\x01\xa9\x01\xcd\x01\xf1\x01\x15\x02\x39\x02\x5d\x02\x81\x02'\ +b'\xa5\x02\xc9\x02\xed\x02\x00\x03\x13\x03\x37\x03\x5b\x03\x7f\x03'\ +b'\xa3\x03\xd8\x03\xfc\x03\x20\x04\x44\x04\x68\x04\x8c\x04\xb0\x04'\ +b'\xd4\x04\xf8\x04\x0b\x05\x2f\x05\x53\x05\x77\x05\x9b\x05\xbf\x05'\ +b'\xe3\x05\x07\x06\x2b\x06\x4f\x06\x73\x06\x97\x06\xbb\x06\xdf\x06'\ +b'\x03\x07\x27\x07\x4b\x07\x6f\x07\x82\x07\x95\x07\xa8\x07\xbb\x07'\ +b'\xdf\x07\xf2\x07\x16\x08\x3a\x08\x5e\x08\x82\x08\xa6\x08\xb9\x08'\ +b'\xdd\x08\x01\x09\x14\x09\x27\x09\x4b\x09\x5e\x09\x82\x09\xa6\x09'\ +b'\xca\x09\xee\x09\x12\x0a\x25\x0a\x38\x0a\x4b\x0a\x6f\x0a\x82\x0a'\ +b'\xa6\x0a\xb9\x0a\xcc\x0a\xdf\x0a\xf2\x0a\x05\x0b\x18\x0b\x3c\x0b'\ + +_mvfont = memoryview(_font) + +def _chr_addr(ordch): + offset = 2 * (ordch - 32) + return int.from_bytes(_index[offset:offset + 2], 'little') + +def get_ch(ch): + ordch = ord(ch) + ordch = ordch if ordch >= 32 and ordch <= 126 else ord('?') + 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], 17, width + diff --git a/writer/font6.py b/writer/font6.py new file mode 100644 index 0000000..0adc524 --- /dev/null +++ b/writer/font6.py @@ -0,0 +1,176 @@ +# Code generated by font-to-py.py. +# Font: FreeSans.ttf +version = '0.2' + +def height(): + return 14 + +def max_width(): + return 14 + +def hmap(): + return True + +def reverse(): + return False + +def monospaced(): + return False + +def min_ch(): + return 32 + +def max_ch(): + return 126 + +_font =\ +b'\x08\x00\x00\x78\x8c\x84\x04\x18\x30\x20\x20\x00\x20\x00\x00\x00'\ +b'\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x05\x00\x00\x80\x80\x80\x80\x80\x80\x80\x80\x00\x80\x00\x00\x00'\ +b'\x05\x00\x00\xa0\xa0\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x08\x00\x00\x00\x12\x14\x7f\x24\x24\xfe\x28\x48\x48\x00\x00\x00'\ +b'\x08\x00\x20\x78\xac\xa4\xa0\xa0\x78\x2c\xa4\xac\x78\x20\x00\x00'\ +b'\x0c\x00\x00\x00\x70\x80\x89\x00\x89\x00\x8a\x00\x72\x00\x04\xe0'\ +b'\x05\x10\x09\x10\x09\x10\x10\xe0\x00\x00\x00\x00\x00\x00\x09\x00'\ +b'\x00\x00\x30\x00\x48\x00\x48\x00\x78\x00\x20\x00\x52\x00\x9e\x00'\ +b'\x8c\x00\x8e\x00\x73\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x80'\ +b'\x80\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x20'\ +b'\x40\x40\x80\x80\x80\x80\x80\x80\x80\x40\x40\x20\x05\x00\x00\x80'\ +b'\x40\x40\x20\x20\x20\x20\x20\x20\x20\x40\x40\x80\x05\x00\x00\x20'\ +b'\xf8\x20\x50\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00'\ +b'\x00\x00\x00\x20\x20\xf8\x20\x20\x20\x00\x00\x00\x04\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x80\x80\x80\x00\x05\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x04\x00\x00\x10'\ +b'\x10\x20\x20\x20\x40\x40\x40\x80\x80\x00\x00\x00\x08\x00\x00\x78'\ +b'\x48\x84\x84\x84\x84\x84\x84\x48\x78\x00\x00\x00\x08\x00\x00\x20'\ +b'\x60\xe0\x20\x20\x20\x20\x20\x20\x20\x00\x00\x00\x08\x00\x00\x78'\ +b'\xcc\x84\x04\x0c\x18\x60\x40\x80\xfc\x00\x00\x00\x08\x00\x00\x78'\ +b'\xc4\x84\x04\x38\x04\x04\x84\xcc\x78\x00\x00\x00\x08\x00\x00\x08'\ +b'\x18\x38\x28\x48\x88\xfc\x08\x08\x08\x00\x00\x00\x08\x00\x00\x7c'\ +b'\x80\x80\xb8\xcc\x04\x04\x04\x88\x78\x00\x00\x00\x08\x00\x00\x38'\ +b'\x48\x84\x80\xf8\xcc\x84\x84\x4c\x78\x00\x00\x00\x08\x00\x00\xfc'\ +b'\x0c\x08\x10\x10\x20\x20\x20\x40\x40\x00\x00\x00\x08\x00\x00\x78'\ +b'\x84\x84\x84\x78\xcc\x84\x84\xcc\x78\x00\x00\x00\x08\x00\x00\x78'\ +b'\xc8\x84\x84\xcc\x74\x04\x04\x88\x70\x00\x00\x00\x04\x00\x00\x00'\ +b'\x00\x80\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x04\x00\x00\x00'\ +b'\x00\x00\x80\x00\x00\x00\x00\x00\x80\x80\x80\x00\x08\x00\x00\x00'\ +b'\x00\x00\x00\x1c\x70\x80\x60\x1c\x04\x00\x00\x00\x08\x00\x00\x00'\ +b'\x00\x00\x00\x00\xfc\x00\xfc\x00\x00\x00\x00\x00\x08\x00\x00\x00'\ +b'\x00\x00\x00\xe0\x38\x06\x1c\x60\x80\x00\x00\x00\x08\x00\x00\x78'\ +b'\x8c\x84\x04\x18\x30\x20\x20\x00\x20\x00\x00\x00\x0e\x00\x00\x00'\ +b'\x07\xc0\x18\x60\x20\x10\x43\x48\x84\xc8\x88\xc8\x88\x88\x89\x90'\ +b'\xc6\xe0\x60\x00\x30\x00\x0f\xc0\x00\x00\x09\x00\x00\x00\x0c\x00'\ +b'\x1c\x00\x14\x00\x16\x00\x32\x00\x22\x00\x7f\x00\x41\x00\x41\x80'\ +b'\xc1\x80\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\xfc\x00\x82\x00'\ +b'\x82\x00\x82\x00\xfc\x00\x86\x00\x82\x00\x82\x00\x86\x00\xfc\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x3c\x00\x42\x00\x41\x00'\ +b'\x80\x00\x80\x00\x80\x00\x81\x00\xc1\x00\x62\x00\x3c\x00\x00\x00'\ +b'\x00\x00\x00\x00\x0a\x00\x00\x00\xfc\x00\x82\x00\x83\x00\x81\x00'\ +b'\x81\x00\x81\x00\x81\x00\x83\x00\x82\x00\xfc\x00\x00\x00\x00\x00'\ +b'\x00\x00\x09\x00\x00\x00\xfe\x00\x80\x00\x80\x00\x80\x00\xfc\x00'\ +b'\x80\x00\x80\x00\x80\x00\x80\x00\xfe\x00\x00\x00\x00\x00\x00\x00'\ +b'\x08\x00\x00\xfc\x80\x80\x80\xfc\x80\x80\x80\x80\x80\x00\x00\x00'\ +b'\x0b\x00\x00\x00\x1e\x00\x61\x00\x40\x80\x80\x00\x80\x00\x87\x80'\ +b'\x80\x80\xc0\x80\x61\x80\x3e\x80\x00\x00\x00\x00\x00\x00\x0a\x00'\ +b'\x00\x00\x81\x00\x81\x00\x81\x00\x81\x00\xff\x00\x81\x00\x81\x00'\ +b'\x81\x00\x81\x00\x81\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x80'\ +b'\x80\x80\x80\x80\x80\x80\x80\x80\x80\x00\x00\x00\x07\x00\x00\x04'\ +b'\x04\x04\x04\x04\x04\x04\x84\x84\x78\x00\x00\x00\x09\x00\x00\x00'\ +b'\x82\x00\x84\x00\x88\x00\x90\x00\xb0\x00\xd8\x00\x88\x00\x84\x00'\ +b'\x86\x00\x82\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x80\x80\x80'\ +b'\x80\x80\x80\x80\x80\x80\xfc\x00\x00\x00\x0c\x00\x00\x00\xc1\x80'\ +b'\xc1\x80\xc1\x80\xa2\x80\xa2\x80\xa2\x80\x94\x80\x94\x80\x94\x80'\ +b'\x88\x80\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\xc1\x00\xc1\x00'\ +b'\xe1\x00\xb1\x00\x91\x00\x89\x00\x8d\x00\x87\x00\x83\x00\x83\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x3e\x00\x63\x00\xc1\x00'\ +b'\x80\x80\x80\x80\x80\x80\x80\x80\xc1\x00\x63\x00\x3e\x00\x00\x00'\ +b'\x00\x00\x00\x00\x09\x00\x00\x00\xfc\x00\x86\x00\x82\x00\x82\x00'\ +b'\x86\x00\xfc\x00\x80\x00\x80\x00\x80\x00\x80\x00\x00\x00\x00\x00'\ +b'\x00\x00\x0b\x00\x00\x00\x3e\x00\x63\x00\xc1\x00\x80\x80\x80\x80'\ +b'\x80\x80\x80\x80\xc5\x80\x63\x00\x3f\x00\x00\x80\x00\x00\x00\x00'\ +b'\x0a\x00\x00\x00\xfc\x00\x82\x00\x82\x00\x82\x00\x82\x00\xfc\x00'\ +b'\x82\x00\x82\x00\x82\x00\x83\x00\x00\x00\x00\x00\x00\x00\x09\x00'\ +b'\x00\x00\x7c\x00\xc6\x00\x82\x00\xc0\x00\x78\x00\x0e\x00\x02\x00'\ +b'\x82\x00\xc6\x00\x7c\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00'\ +b'\xfe\x00\x10\x00\x10\x00\x10\x00\x10\x00\x10\x00\x10\x00\x10\x00'\ +b'\x10\x00\x10\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x81\x00'\ +b'\x81\x00\x81\x00\x81\x00\x81\x00\x81\x00\x81\x00\x81\x00\xc3\x00'\ +b'\x3c\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\xc1\x80\x41\x00'\ +b'\x41\x00\x63\x00\x22\x00\x32\x00\x16\x00\x14\x00\x1c\x00\x08\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0d\x00\x00\x00\xc2\x18\x45\x18\x45\x10'\ +b'\x65\x10\x65\xb0\x28\xa0\x28\xa0\x38\xa0\x38\xe0\x10\x40\x00\x00'\ +b'\x00\x00\x00\x00\x09\x00\x00\x00\x41\x00\x63\x00\x32\x00\x14\x00'\ +b'\x0c\x00\x1c\x00\x16\x00\x22\x00\x63\x00\x41\x80\x00\x00\x00\x00'\ +b'\x00\x00\x09\x00\x00\x00\xc1\x80\x63\x00\x22\x00\x36\x00\x14\x00'\ +b'\x08\x00\x08\x00\x08\x00\x08\x00\x08\x00\x00\x00\x00\x00\x00\x00'\ +b'\x09\x00\x00\x00\x7f\x00\x03\x00\x06\x00\x04\x00\x0c\x00\x18\x00'\ +b'\x30\x00\x20\x00\x40\x00\xff\x00\x00\x00\x00\x00\x00\x00\x04\x00'\ +b'\x00\xc0\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\xc0\x04\x00'\ +b'\x00\x80\x80\x40\x40\x40\x20\x20\x20\x10\x10\x00\x00\x00\x04\x00'\ +b'\x00\xc0\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\xc0\x07\x00'\ +b'\x00\x20\x60\x50\x90\x88\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x04\x00'\ +b'\x00\x40\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00'\ +b'\x00\x00\x00\x78\x84\x04\x04\x7c\x84\x8c\x76\x00\x00\x00\x08\x00'\ +b'\x00\x80\x80\xb8\xcc\x84\x84\x84\x84\xc8\xb8\x00\x00\x00\x07\x00'\ +b'\x00\x00\x00\x78\x44\x80\x80\x80\x80\x44\x78\x00\x00\x00\x08\x00'\ +b'\x00\x02\x02\x3a\x46\x82\x82\x82\x82\x46\x3a\x00\x00\x00\x07\x00'\ +b'\x00\x00\x00\x3c\x44\x82\xfe\x80\x80\x46\x3c\x00\x00\x00\x04\x00'\ +b'\x00\x60\x40\xe0\x40\x40\x40\x40\x40\x40\x40\x00\x00\x00\x08\x00'\ +b'\x00\x00\x00\x3a\x46\x82\x82\x82\x82\x46\x7a\x02\x84\x7c\x08\x00'\ +b'\x00\x80\x80\xb0\xc8\x88\x88\x88\x88\x88\x88\x00\x00\x00\x03\x00'\ +b'\x00\x80\x00\x80\x80\x80\x80\x80\x80\x80\x80\x00\x00\x00\x03\x00'\ +b'\x00\x40\x00\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\xc0\x07\x00'\ +b'\x00\x80\x80\x88\x90\xa0\xe0\x90\x98\x88\x8c\x00\x00\x00\x03\x00'\ +b'\x00\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x00\x00\x00\x0b\x00'\ +b'\x00\x00\x00\x00\x00\x00\xb7\x00\xcc\x80\x88\x80\x88\x80\x88\x80'\ +b'\x88\x80\x88\x80\x88\x80\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00'\ +b'\x00\xb8\xc4\x84\x84\x84\x84\x84\x84\x00\x00\x00\x07\x00\x00\x00'\ +b'\x00\x38\x44\x82\x82\x82\x82\x44\x38\x00\x00\x00\x08\x00\x00\x00'\ +b'\x00\xb8\xc8\x84\x84\x84\x84\xc8\xb8\x80\x80\x00\x08\x00\x00\x00'\ +b'\x00\x3a\x46\x82\x82\x82\x82\x46\x7a\x02\x02\x00\x05\x00\x00\x00'\ +b'\x00\xa0\xc0\x80\x80\x80\x80\x80\x80\x00\x00\x00\x07\x00\x00\x00'\ +b'\x00\x70\x88\x80\xc0\x70\x08\x88\x70\x00\x00\x00\x04\x00\x00\x00'\ +b'\x40\xe0\x40\x40\x40\x40\x40\x40\x60\x00\x00\x00\x08\x00\x00\x00'\ +b'\x00\x84\x84\x84\x84\x84\x84\x8c\x74\x00\x00\x00\x07\x00\x00\x00'\ +b'\x00\xc6\x44\x44\x6c\x28\x28\x38\x10\x00\x00\x00\x0a\x00\x00\x00'\ +b'\x00\x00\x00\x00\x8c\x40\xcc\xc0\x4c\x80\x5c\x80\x52\x80\x73\x80'\ +b'\x33\x00\x33\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x44'\ +b'\x68\x28\x30\x30\x28\x4c\xc4\x00\x00\x00\x07\x00\x00\x00\x00\xc6'\ +b'\x44\x44\x6c\x28\x28\x30\x10\x10\x20\x60\x07\x00\x00\x00\x00\x7c'\ +b'\x0c\x08\x10\x30\x60\x40\xfc\x00\x00\x00\x05\x00\x00\x60\x40\x40'\ +b'\x40\x40\x40\x80\x40\x40\x40\x40\x40\x60\x04\x00\x00\x80\x80\x80'\ +b'\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x05\x00\x00\xc0\x40\x40'\ +b'\x40\x40\x40\x20\x40\x40\x40\x40\x40\xc0\x07\x00\x00\x00\x00\x00'\ +b'\x00\x62\x9e\x00\x00\x00\x00\x00\x00\x00' + +_index =\ +b'\x00\x00\x10\x00\x20\x00\x30\x00\x40\x00\x50\x00\x60\x00\x7e\x00'\ +b'\x9c\x00\xac\x00\xbc\x00\xcc\x00\xdc\x00\xec\x00\xfc\x00\x0c\x01'\ +b'\x1c\x01\x2c\x01\x3c\x01\x4c\x01\x5c\x01\x6c\x01\x7c\x01\x8c\x01'\ +b'\x9c\x01\xac\x01\xbc\x01\xcc\x01\xdc\x01\xec\x01\xfc\x01\x0c\x02'\ +b'\x1c\x02\x2c\x02\x4a\x02\x68\x02\x86\x02\xa4\x02\xc2\x02\xe0\x02'\ +b'\xf0\x02\x0e\x03\x2c\x03\x3c\x03\x4c\x03\x6a\x03\x7a\x03\x98\x03'\ +b'\xb6\x03\xd4\x03\xf2\x03\x10\x04\x2e\x04\x4c\x04\x6a\x04\x88\x04'\ +b'\xa6\x04\xc4\x04\xe2\x04\x00\x05\x1e\x05\x2e\x05\x3e\x05\x4e\x05'\ +b'\x5e\x05\x6e\x05\x7e\x05\x8e\x05\x9e\x05\xae\x05\xbe\x05\xce\x05'\ +b'\xde\x05\xee\x05\xfe\x05\x0e\x06\x1e\x06\x2e\x06\x3e\x06\x5c\x06'\ +b'\x6c\x06\x7c\x06\x8c\x06\x9c\x06\xac\x06\xbc\x06\xcc\x06\xdc\x06'\ +b'\xec\x06\x0a\x07\x1a\x07\x2a\x07\x3a\x07\x4a\x07\x5a\x07\x6a\x07'\ +b'\x7a\x07' + +_mvfont = memoryview(_font) + +def _chr_addr(ordch): + offset = 2 * (ordch - 32) + return int.from_bytes(_index[offset:offset + 2], 'little') + +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], 14, width + diff --git a/writer/freesans20.py b/writer/freesans20.py new file mode 100644 index 0000000..a35eb84 --- /dev/null +++ b/writer/freesans20.py @@ -0,0 +1,280 @@ +# Code generated by font-to-py.py. +# Font: FreeSans.ttf +version = '0.2' + +def height(): + return 20 + +def max_width(): + return 20 + +def hmap(): + return True + +def reverse(): + return False + +def monospaced(): + return False + +def min_ch(): + return 32 + +def max_ch(): + return 126 + +_font =\ +b'\x0b\x00\x00\x00\x3c\x00\x7e\x00\xc7\x00\xc3\x00\x03\x00\x03\x00'\ +b'\x06\x00\x0c\x00\x08\x00\x18\x00\x18\x00\x00\x00\x00\x00\x18\x00'\ +b'\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x07\x00\x00\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x00\x00'\ +b'\xc0\xc0\x00\x00\x00\x00\x07\x00\x00\x00\xd8\xd8\xd8\xd8\x90\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\ +b'\x00\x00\x0c\xc0\x08\x80\x08\x80\x7f\xe0\x7f\xe0\x19\x80\x11\x00'\ +b'\x11\x00\xff\xc0\xff\xc0\x33\x00\x33\x00\x22\x00\x22\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0b\x00\x08\x00\x3e\x00\x7f\x80\xe9\xc0'\ +b'\xc8\xc0\xc8\xc0\xc8\x00\xe8\x00\x7c\x00\x1f\x80\x09\xc0\x08\xc0'\ +b'\xc8\xc0\xe9\xc0\x7f\x80\x3e\x00\x08\x00\x00\x00\x00\x00\x00\x00'\ +b'\x12\x00\x00\x00\x00\x00\x00\x00\x38\x10\x00\x7c\x10\x00\xc6\x20'\ +b'\x00\xc6\x20\x00\xc6\x40\x00\x7c\xc0\x00\x38\x80\x00\x01\x1e\x00'\ +b'\x01\x3f\x00\x02\x73\x80\x02\x61\x80\x04\x73\x80\x04\x3f\x00\x08'\ +b'\x1e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x00'\ +b'\x00\x00\x00\x00\x0e\x00\x1f\x00\x31\x80\x31\x80\x31\x80\x1f\x00'\ +b'\x1c\x00\x76\x60\xe3\x60\xc1\xc0\xc0\xc0\xe1\xc0\x7f\x60\x3e\x30'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\xc0\xc0\xc0\xc0'\ +b'\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00'\ +b'\x00\x10\x10\x20\x20\x60\x40\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x40\x60'\ +b'\x20\x30\x10\x18\x07\x00\x00\x40\x40\x20\x20\x30\x10\x18\x18\x18'\ +b'\x18\x18\x18\x18\x10\x30\x20\x60\x40\xc0\x08\x00\x00\x20\x20\xf8'\ +b'\x20\x50\x50\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x18\x00\x18\x00\x18\x00\xff\x00\xff\x00\x18\x00\x18\x00\x18\x00'\ +b'\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\xc0\x40\x40\x80\x00'\ +b'\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8\xf8\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\xc0\xc0\x00\x00\x00\x00\x06\x00\x00\x04'\ +b'\x0c\x08\x08\x18\x10\x10\x30\x20\x20\x60\x40\x40\xc0\x80\x00\x00'\ +b'\x00\x00\x0b\x00\x00\x00\x00\x00\x3e\x00\x7f\x00\x63\x00\xe3\x80'\ +b'\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xe3\x80\x63\x00'\ +b'\x7f\x00\x3e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\ +b'\x00\x00\x10\x00\x30\x00\xf0\x00\xf0\x00\x30\x00\x30\x00\x30\x00'\ +b'\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x3e\x00\x7f\x00'\ +b'\xe3\x80\xc1\x80\x01\x80\x01\x80\x03\x00\x0e\x00\x1c\x00\x30\x00'\ +b'\x60\x00\xc0\x00\xff\x80\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x0b\x00\x00\x00\x00\x00\x3e\x00\x7f\x00\xe3\x80\xc1\x80\x01\x80'\ +b'\x0f\x00\x0f\x00\x03\x80\x01\x80\x01\x80\xc1\x80\xe3\x80\x7f\x00'\ +b'\x3e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00'\ +b'\x06\x00\x06\x00\x0e\x00\x1e\x00\x16\x00\x26\x00\x46\x00\x46\x00'\ +b'\x86\x00\xff\x00\xff\x00\x06\x00\x06\x00\x06\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x7f\x00\x7f\x00\x60\x00'\ +b'\x60\x00\xde\x00\xff\x00\xe3\x80\x01\x80\x01\x80\x01\x80\x01\x80'\ +b'\xc3\x00\x7f\x00\x3e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00'\ +b'\x00\x00\x00\x00\x1e\x00\x3f\x00\x63\x00\x61\x80\xc0\x00\xde\x00'\ +b'\xff\x00\xe3\x80\xc1\x80\xc1\x80\xc1\x80\x63\x80\x7f\x00\x3e\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\xff\x80'\ +b'\xff\x80\x01\x00\x03\x00\x02\x00\x06\x00\x04\x00\x0c\x00\x08\x00'\ +b'\x18\x00\x18\x00\x10\x00\x30\x00\x30\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x0b\x00\x00\x00\x00\x00\x1c\x00\x3e\x00\x63\x00\x63\x00'\ +b'\x63\x00\x3e\x00\x3e\x00\x63\x00\xc1\x80\xc1\x80\xc1\x80\x63\x00'\ +b'\x7f\x00\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\ +b'\x00\x00\x3e\x00\x7f\x00\xe3\x00\xc1\x80\xc1\x80\xc1\x80\xe3\x80'\ +b'\x7f\x80\x3d\x80\x01\x80\x03\x00\xe3\x00\x7e\x00\x3c\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\xc0\xc0\x00'\ +b'\x00\x00\x00\x00\x00\x00\xc0\xc0\x00\x00\x00\x00\x05\x00\x00\x00'\ +b'\x00\x00\x00\x00\xc0\xc0\x00\x00\x00\x00\x00\x00\xc0\xc0\x40\x40'\ +b'\x80\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x40\x01\xc0\x07\x00\x3c\x00\xe0\x00\xe0\x00\x78\x00\x0f\x00'\ +b'\x03\xc0\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc0'\ +b'\xff\xc0\x00\x00\x00\x00\xff\xc0\xff\xc0\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\xe0\x00\x78\x00\x0e\x00\x03\xc0\x01\xc0'\ +b'\x07\x00\x3c\x00\xf0\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x0b\x00\x00\x00\x3c\x00\x7e\x00\xc7\x00\xc3\x00\x03\x00\x03\x00'\ +b'\x06\x00\x0c\x00\x08\x00\x18\x00\x18\x00\x00\x00\x00\x00\x18\x00'\ +b'\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\x00\x03'\ +b'\xf0\x00\x0f\xfc\x00\x1e\x0f\x00\x38\x03\x80\x71\xe1\x80\x63\xe9'\ +b'\xc0\x67\x18\xc0\xce\x18\xc0\xcc\x18\xc0\xcc\x10\xc0\xcc\x31\x80'\ +b'\xce\x73\x80\x67\xff\x00\x63\x9e\x00\x30\x00\x00\x3c\x00\x00\x0f'\ +b'\xf8\x00\x03\xf0\x00\x00\x00\x00\x0d\x00\x00\x00\x07\x00\x07\x00'\ +b'\x07\x80\x0d\x80\x0d\x80\x08\xc0\x18\xc0\x18\xc0\x10\x60\x3f\xe0'\ +b'\x3f\xe0\x30\x30\x60\x30\x60\x38\xc0\x18\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x0d\x00\x00\x00\xff\x00\xff\x80\xc1\xc0\xc0\xc0\xc0\xc0'\ +b'\xc1\xc0\xff\x00\xff\x80\xc0\xc0\xc0\x60\xc0\x60\xc0\x60\xc0\xe0'\ +b'\xff\xc0\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00'\ +b'\x0f\x80\x3f\xe0\x70\x60\x60\x30\xe0\x00\xc0\x00\xc0\x00\xc0\x00'\ +b'\xc0\x00\xc0\x00\xe0\x30\x60\x70\x70\x60\x3f\xe0\x0f\x80\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\xff\x00\xff\x80\xc1\xc0'\ +b'\xc0\xc0\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60'\ +b'\xc0\xc0\xc1\xc0\xff\x80\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x0d\x00\x00\x00\xff\xc0\xff\xc0\xc0\x00\xc0\x00\xc0\x00\xc0\x00'\ +b'\xff\x80\xff\x80\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xff\xc0'\ +b'\xff\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\xff\x80'\ +b'\xff\x80\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xff\x00\xff\x00\xc0\x00'\ +b'\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x0f\x00\x00\x00\x0f\xc0\x3f\xf0\x38\x30\x60\x18'\ +b'\x60\x00\xc0\x00\xc0\x00\xc1\xf8\xc1\xf8\xc0\x18\xe0\x18\x60\x38'\ +b'\x78\x78\x3f\xd8\x0f\x88\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00'\ +b'\x00\x00\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xff\xe0'\ +b'\xff\xe0\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\xc0\xc0\xc0\xc0\xc0'\ +b'\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x00\x00\x00\x00\x0b\x00'\ +b'\x00\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00'\ +b'\x03\x00\x03\x00\x03\x00\xc3\x00\xc3\x00\xe7\x00\x7e\x00\x3c\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x00\x00\x00\xc0\x60\xc0\xc0'\ +b'\xc1\x80\xc3\x00\xc6\x00\xcc\x00\xdc\x00\xf6\x00\xe6\x00\xc3\x00'\ +b'\xc1\x80\xc1\x80\xc0\xc0\xc0\x60\xc0\x60\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x0b\x00\x00\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00'\ +b'\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00'\ +b'\xff\x80\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00'\ +b'\x00\xe0\x1c\x00\xe0\x1c\x00\xf0\x3c\x00\xf0\x3c\x00\xd0\x2c\x00'\ +b'\xd8\x6c\x00\xd8\x6c\x00\xc8\x4c\x00\xcc\xcc\x00\xcc\xcc\x00\xc4'\ +b'\x8c\x00\xc6\x8c\x00\xc7\x8c\x00\xc3\x0c\x00\xc3\x0c\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x00\xe0\x60'\ +b'\xe0\x60\xf0\x60\xf0\x60\xd8\x60\xd8\x60\xcc\x60\xc4\x60\xc6\x60'\ +b'\xc2\x60\xc3\x60\xc1\xe0\xc1\xe0\xc0\xe0\xc0\xe0\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x10\x00\x00\x00\x0f\xc0\x1f\xe0\x38\x70\x60\x18'\ +b'\x60\x1c\xc0\x0c\xc0\x0c\xc0\x0c\xc0\x0c\xc0\x0c\x60\x1c\x60\x18'\ +b'\x38\x70\x1f\xe0\x0f\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x00'\ +b'\x00\x00\xff\x00\xff\x80\xc1\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc1\xc0'\ +b'\xff\x80\xff\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x0f\xc0\x1f\xe0'\ +b'\x38\x70\x60\x18\x60\x1c\xc0\x0c\xc0\x0c\xc0\x0c\xc0\x0c\xc0\x0c'\ +b'\x60\x18\x60\xd8\x38\x70\x1f\xf8\x0f\x98\x00\x08\x00\x00\x00\x00'\ +b'\x00\x00\x0e\x00\x00\x00\xff\x80\xff\xc0\xc0\xe0\xc0\x60\xc0\x60'\ +b'\xc0\x60\xc0\xc0\xff\x80\xff\xc0\xc0\xe0\xc0\x60\xc0\x60\xc0\x60'\ +b'\xc0\x60\xc0\x70\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x00\x00\x00'\ +b'\x1f\x80\x7f\xe0\xe0\x70\xc0\x30\xc0\x00\xe0\x00\x78\x00\x3f\x80'\ +b'\x03\xe0\x00\x70\xc0\x30\xc0\x30\x70\x60\x7f\xe0\x1f\x80\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0d\x00\x00\x00\xff\xc0\xff\xc0\x0c\x00'\ +b'\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00'\ +b'\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x0e\x00\x00\x00\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60'\ +b'\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60\x60\xc0\x7f\xc0'\ +b'\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x00\x00\x00\xc0\x30'\ +b'\x60\x30\x60\x30\x20\x20\x30\x60\x30\x60\x10\x40\x18\xc0\x18\xc0'\ +b'\x08\x80\x0d\x80\x0d\x80\x07\x00\x07\x00\x07\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x13\x00\x00\x00\x00\xc0\xc0\xc0\x60\xe0\xc0\x60'\ +b'\xe0\xc0\x61\xe0\xc0\x61\xb1\x80\x31\xb1\x80\x31\xb1\x80\x33\x11'\ +b'\x80\x33\x19\x00\x13\x1b\x00\x1f\x1b\x00\x1e\x0b\x00\x1e\x0e\x00'\ +b'\x0e\x0e\x00\x0c\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x0d\x00\x00\x00\x60\x30\x30\x70\x30\x60\x18\xc0\x0c\xc0'\ +b'\x0d\x80\x07\x00\x07\x00\x07\x00\x0d\x80\x18\xc0\x18\xe0\x30\x60'\ +b'\x70\x30\x60\x38\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00'\ +b'\x60\x18\x70\x38\x30\x30\x18\x60\x18\x60\x0c\xc0\x0f\xc0\x07\x80'\ +b'\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\xff\xe0\xff\xe0\x00\xc0'\ +b'\x01\x80\x03\x80\x03\x00\x06\x00\x0c\x00\x1c\x00\x38\x00\x30\x00'\ +b'\x60\x00\xc0\x00\xff\xe0\xff\xe0\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x06\x00\x00\xe0\xe0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\ +b'\xc0\xc0\xc0\xc0\xe0\xe0\x06\x00\x00\x80\xc0\x40\x40\x60\x20\x20'\ +b'\x30\x10\x10\x18\x08\x08\x0c\x04\x00\x00\x00\x00\x06\x00\x00\xe0'\ +b'\xe0\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60'\ +b'\xe0\xe0\x09\x00\x00\x00\x00\x00\x18\x00\x38\x00\x28\x00\x2c\x00'\ +b'\x64\x00\x46\x00\xc2\x00\x82\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xf0'\ +b'\x00\x00\x00\x00\x00\x00\x05\x00\x00\xc0\x60\x30\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x3e\x00\xff\x80\xc1\x80\x01\x80'\ +b'\x01\x80\x3f\x80\xf1\x80\xc1\x80\xc3\x80\xff\xc0\x78\xc0\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\xc0\x00\xc0\x00\xc0\x00'\ +b'\xc0\x00\xdf\x00\xff\x80\xe1\x80\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\ +b'\xc0\xc0\xe1\x80\xff\x80\xde\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1e\x00\x7f\x00'\ +b'\x61\x80\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc1\x80\x63\x80\x7f\x00'\ +b'\x3e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x01\x80'\ +b'\x01\x80\x01\x80\x01\x80\x3d\x80\x7f\x80\x63\x80\xc1\x80\xc1\x80'\ +b'\xc1\x80\xc1\x80\xc1\x80\x63\x80\x7f\x80\x3d\x80\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x3e\x00\x7f\x00\x63\x00\xc1\x80\xff\x80\xff\x80\xc0\x00\xc0\x00'\ +b'\x63\x80\x7f\x00\x3e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00'\ +b'\x00\x30\x70\x60\x60\xf0\xf0\x60\x60\x60\x60\x60\x60\x60\x60\x60'\ +b'\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x3d\x80\x7f\x80\x63\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80'\ +b'\x63\x80\x7f\x80\x3d\x80\x01\x80\xc3\x80\x7f\x00\x3e\x00\x0b\x00'\ +b'\x00\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xdf\x00\xdf\x80\xe3\x80'\ +b'\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\xc0\xc0\x00\x00\xc0'\ +b'\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x00\x00\x00\x00\x05\x00'\ +b'\x00\x30\x30\x00\x00\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30'\ +b'\x30\x30\xf0\xe0\x0a\x00\x00\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00'\ +b'\xc3\x00\xc6\x00\xcc\x00\xd8\x00\xf8\x00\xec\x00\xce\x00\xc6\x00'\ +b'\xc3\x00\xc3\x00\xc1\x80\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00'\ +b'\x00\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\ +b'\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\xde\x78\xfe\xfc\xe3\x8c\xc3\x0c\xc3\x0c\xc3\x0c\xc3\x0c\xc3\x0c'\ +b'\xc3\x0c\xc3\x0c\xc3\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xcf\x00\xdf\x80\xe3\x80'\ +b'\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x3e\x00\x7f\x00\x63\x00\xc1\x80\xc1\x80\xc1\x80'\ +b'\xc1\x80\xc1\x80\x63\x00\x7f\x00\x3e\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\x00'\ +b'\xff\x80\xe1\x80\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xe1\x80'\ +b'\xff\x80\xde\x00\xc0\x00\xc0\x00\xc0\x00\x00\x00\x0b\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x3d\x80\x7f\x80\x63\x80\xc1\x80'\ +b'\xc1\x80\xc1\x80\xc1\x80\xc1\x80\x63\x80\x7f\x80\x3d\x80\x01\x80'\ +b'\x01\x80\x01\x80\x00\x00\x07\x00\x00\x00\x00\x00\x00\xd8\xf8\xe0'\ +b'\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x00\x00\x00\x00\x0a\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x3c\x00\x7f\x00\xc3\x00\xc0\x00'\ +b'\xf0\x00\x7e\x00\x0f\x00\x03\x00\xc3\x00\xfe\x00\x7c\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x60\x60\xf0\xf0\x60'\ +b'\x60\x60\x60\x60\x60\x60\x70\x70\x00\x00\x00\x00\x0b\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\xc1\x80\xc1\x80\xc1\x80\xc1\x80'\ +b'\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xe3\x80\xfd\x80\x79\x80\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\xc0\xc0\x61\x80\x61\x80\x61\x00\x23\x00\x33\x00\x32\x00'\ +b'\x16\x00\x1e\x00\x1c\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc3\x0c\xc3\x8c'\ +b'\x63\x8c\x67\x88\x66\x98\x24\xd8\x34\xd0\x3c\xd0\x3c\x70\x18\x70'\ +b'\x18\x60\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x61\x80\x63\x00\x33\x00\x1e\x00\x1c\x00'\ +b'\x0c\x00\x1c\x00\x16\x00\x33\x00\x63\x00\x41\x80\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\xc0\x80\x41\x80\x61\x80\x61\x00\x23\x00\x33\x00\x32\x00\x16\x00'\ +b'\x1c\x00\x1c\x00\x0c\x00\x08\x00\x18\x00\x78\x00\x70\x00\x0a\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\xff\x00\x06\x00'\ +b'\x06\x00\x0c\x00\x18\x00\x30\x00\x60\x00\xc0\x00\xff\x00\xff\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x18\x38\x30\x30\x30'\ +b'\x30\x30\x30\x70\xc0\x70\x30\x30\x30\x30\x30\x30\x38\x18\x05\x00'\ +b'\x00\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\ +b'\xc0\xc0\xc0\xc0\x07\x00\x00\xc0\xe0\x60\x60\x60\x60\x60\x60\x70'\ +b'\x18\x70\x60\x60\x60\x60\x60\x60\xe0\xc0\x0a\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x60\x00\xf1\x00\x9f\x00'\ +b'\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ +b'\x00\x00\x00\x00' + +_index =\ +b'\x00\x00\x2a\x00\x40\x00\x56\x00\x6c\x00\x96\x00\xc0\x00\xfe\x00'\ +b'\x28\x01\x3e\x01\x54\x01\x6a\x01\x80\x01\xaa\x01\xc0\x01\xd6\x01'\ +b'\xec\x01\x02\x02\x2c\x02\x56\x02\x80\x02\xaa\x02\xd4\x02\xfe\x02'\ +b'\x28\x03\x52\x03\x7c\x03\xa6\x03\xbc\x03\xd2\x03\xfc\x03\x26\x04'\ +b'\x50\x04\x7a\x04\xb8\x04\xe2\x04\x0c\x05\x36\x05\x60\x05\x8a\x05'\ +b'\xb4\x05\xde\x05\x08\x06\x1e\x06\x48\x06\x72\x06\x9c\x06\xda\x06'\ +b'\x04\x07\x2e\x07\x58\x07\x82\x07\xac\x07\xd6\x07\x00\x08\x2a\x08'\ +b'\x54\x08\x92\x08\xbc\x08\xe6\x08\x10\x09\x26\x09\x3c\x09\x52\x09'\ +b'\x7c\x09\xa6\x09\xbc\x09\xe6\x09\x10\x0a\x3a\x0a\x64\x0a\x8e\x0a'\ +b'\xa4\x0a\xce\x0a\xf8\x0a\x0e\x0b\x24\x0b\x4e\x0b\x64\x0b\x8e\x0b'\ +b'\xb8\x0b\xe2\x0b\x0c\x0c\x36\x0c\x4c\x0c\x76\x0c\x8c\x0c\xb6\x0c'\ +b'\xe0\x0c\x0a\x0d\x34\x0d\x5e\x0d\x88\x0d\x9e\x0d\xb4\x0d\xca\x0d'\ +b'\xf4\x0d' + +_mvfont = memoryview(_font) + +def _chr_addr(ordch): + offset = 2 * (ordch - 32) + return int.from_bytes(_index[offset:offset + 2], 'little') + +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], 20, width + diff --git a/writer/images/IMG_2861.JPG b/writer/images/IMG_2861.JPG new file mode 100644 index 0000000000000000000000000000000000000000..98ba7310b48e0a80783cd8271a48a9c10c5d2609 GIT binary patch literal 19274 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 z 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 L!#Usi?a}`OO26!~ literal 0 HcmV?d00001 diff --git a/writer/images/fields.JPG b/writer/images/fields.JPG new file mode 100644 index 0000000000000000000000000000000000000000..624d373751951e543c3078897a3b7f42a90d9da1 GIT binary patch literal 19337 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 IRqT)bFUfp7mjD0& literal 0 HcmV?d00001 diff --git a/writer/images/mixed.JPG b/writer/images/mixed.JPG new file mode 100644 index 0000000000000000000000000000000000000000..48863aa59731c781e1b54f6884eb8e80a29c9073 GIT binary patch literal 18552 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 I$alv64^XH?3;+NC literal 0 HcmV?d00001 diff --git a/writer/images/rjust.JPG b/writer/images/rjust.JPG new file mode 100644 index 0000000000000000000000000000000000000000..5ce723cb8832740463f27d5213c395cb4e62a911 GIT binary patch literal 18007 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 literal 0 HcmV?d00001 diff --git a/writer/ssd1306_setup.py b/writer/ssd1306_setup.py new file mode 100644 index 0000000..61ba35d --- /dev/null +++ b/writer/ssd1306_setup.py @@ -0,0 +1,69 @@ +# ssd1306_setup.py Demo pogram for rendering arbitrary fonts to an SSD1306 OLED display. +# Device initialisation + +# 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. + + +# https://learn.adafruit.com/monochrome-oled-breakouts/wiring-128x32-spi-oled-display +# https://www.proto-pic.co.uk/monochrome-128x32-oled-graphic-display.html + +# V0.3 12th Aug 2018 + +import machine +from ssd1306 import SSD1306_SPI, SSD1306_I2C + +WIDTH = const(128) +HEIGHT = const(64) + +def setup(use_spi=False, soft=True): + if use_spi: + # Pyb SSD + # 3v3 Vin + # Gnd Gnd + # X1 DC + # X2 CS + # X3 Rst + # X6 CLK + # X8 DATA + pdc = machine.Pin('X1', machine.Pin.OUT_PP) + pcs = machine.Pin('X2', machine.Pin.OUT_PP) + prst = machine.Pin('X3', machine.Pin.OUT_PP) + if soft: + spi = machine.SPI(sck=machine.Pin('X6'), mosi=machine.Pin('X8'), miso=machine.Pin('X7')) + else: + spi = machine.SPI(1) + ssd = SSD1306_SPI(WIDTH, HEIGHT, spi, pdc, prst, pcs) + else: # I2C + # Pyb SSD + # 3v3 Vin + # Gnd Gnd + # Y9 CLK + # Y10 DATA + if soft: + pscl = machine.Pin('Y9', machine.Pin.OPEN_DRAIN) + psda = machine.Pin('Y10', machine.Pin.OPEN_DRAIN) + i2c = machine.I2C(scl=pscl, sda=psda) + else: + i2c = machine.I2C(2) + ssd = SSD1306_I2C(WIDTH, HEIGHT, i2c) + return ssd diff --git a/writer/writer.py b/writer/writer.py new file mode 100644 index 0000000..116f4e3 --- /dev/null +++ b/writer/writer.py @@ -0,0 +1,343 @@ +# writer.py Implements the Writer class. +# V0.3 Peter Hinch 11th Aug 2018 +# Handles colour, upside down diplays, word wrap and tab stops + +# 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. + +# A Writer supports rendering text to a Display instance in a given font. +# Multiple Writer instances may be created, each rendering a font to the +# same Display object. + +# Timings based on a 20 pixel high proportional font, run on a pyboard V1.0. +# Using CWriter's slow rendering: _printchar 9.5ms typ, 13.5ms max. +# Using Writer's fast rendering: _printchar 115μs min 480μs typ 950μs max. + +import framebuf + +class DisplayState(): + def __init__(self): + self.text_row = 0 + self.text_col = 0 + self.usd = False + +def _get_id(device): + if not isinstance(device, framebuf.FrameBuffer): + raise ValueError('Device must be derived from FrameBuffer.') + return id(device) + +# Basic Writer class for monochrome displays +class Writer(): + + state = {} # Holds a display state for each device + + @staticmethod + def set_textpos(device, row=None, col=None): + devid = _get_id(device) + if devid not in Writer.state: + Writer.state[devid] = DisplayState() + s = Writer.state[devid] # Current state + if row is not None: + if row < 0 or row >= device.height: + raise ValueError('row is out of range') + s.text_row = device.height - 1 - row if s.usd else row + if col is not None: + if col < 0 or col >= device.width: + raise ValueError('col is out of range') + s.text_col = device.width -1 - col if s.usd else col + return s.text_row, s.text_col + + def __init__(self, device, font, verbose=True): + self.devid = _get_id(device) + self.device = device + if self.devid not in Writer.state: + Writer.state[self.devid] = DisplayState() + self.font = font + self.usd = Writer.state[self.devid].usd + + # Allow to work with reverse or normal font mapping + if font.hmap(): + self.map = framebuf.MONO_HMSB if font.reverse() else framebuf.MONO_HLSB + else: + raise ValueError('Font must be horizontally mapped.') + if verbose: + fstr = 'Orientation: Horizontal. Reversal: {}. Width: {}. Height: {}.' + print(fstr.format(font.reverse(), device.width, device.height)) + print('Start row = {} col = {}'.format(self._getstate().text_row, self._getstate().text_col)) + self.screenwidth = device.width # In pixels + self.screenheight = device.height + self.bgcolor = 0 # Monochrome background and foreground colors + self.fgcolor = 1 + self.row_clip = False # Clip or scroll when screen full + self.col_clip = False # Clip or new line when row is full + self.wrap = True # Word wrap + self.cpos = 0 + self.tab = 4 + + self.glyph = None # Current char + self.char_height = 0 + self.char_width = 0 + + def _getstate(self): + return Writer.state[self.devid] + + def _newline(self): + s = self._getstate() + height = self.height() + if self.usd: + s.text_row -= height + s.text_col = self.screenwidth - 1 + margin = s.text_row - height + y = 0 + else: + s.text_row += height + s.text_col = 0 + margin = self.screenheight - (s.text_row + height) + y = self.screenheight + margin + if margin < 0: + if not self.row_clip: + if self.usd: + margin = -margin + self.device.scroll(0, margin) + self.device.fill_rect(0, y, self.screenwidth, abs(margin), self.bgcolor) + s.text_row += margin + + def set_clip(self, row_clip=None, col_clip=None, wrap=None): + if row_clip is not None: + self.row_clip = row_clip + if col_clip is not None: + self.col_clip = col_clip + if wrap is not None: + self.wrap = wrap + return self.row_clip, self.col_clip, self.wrap + + def height(self): + return self.font.height() + + def printstring(self, string, invert=False): + # word wrapping. Assumes words separated by single space. + while True: + lines = string.split('\n', 1) + s = lines[0] + if s: + self._printline(s, invert) + if len(lines) == 1: + break + else: + self._printchar('\n') + string = lines[1] + + def _printline(self, string, invert): + rstr = None + if self.wrap and self.stringlen(string) > self.screenwidth: + pos = 0 + lstr = string[:] + while self.stringlen(lstr) > self.screenwidth: + pos = lstr.rfind(' ') + lstr = lstr[:pos].rstrip() + if pos > 0: + rstr = string[pos + 1:] + string = lstr + + for char in string: + self._printchar(char, invert) + if rstr is not None: + self._printchar('\n') + self._printline(rstr, invert) # Recurse + + def stringlen(self, string): + l = 0 + for char in string: + l += self._charlen(char) + return l + + def _charlen(self, char): + if char == '\n': + char_width = 0 + else: + _, _, char_width = self.font.get_ch(char) + return char_width + + def _get_char(self, char, recurse): + if not recurse: # Handle tabs + if char == '\n': + self.cpos = 0 + elif char == '\t': + nspaces = self.tab - (self.cpos % self.tab) + if nspaces == 0: + nspaces = self.tab + while nspaces: + nspaces -= 1 + self._printchar(' ', recurse=True) + self.glyph = None # All done + return + + self.glyph = None # Assume all done + if char == '\n': + self._newline() + return + glyph, char_height, char_width = self.font.get_ch(char) + s = self._getstate() + if self.usd: + if s.text_row - char_height < 0: + if self.row_clip: + return + self._newline() + if s.text_col - char_width < 0: + if self.col_clip: + return + else: + self._newline() + else: + if s.text_row + char_height > self.screenheight: + if self.row_clip: + return + self._newline() + if s.text_col + char_width > self.screenwidth: + if self.col_clip: + return + else: + self._newline() + self.glyph = glyph + self.char_height = char_height + self.char_width = char_width + + # Method using blitting. Efficient rendering for monochrome displays. + # Tested on SSD1306. Invert is for black-on-white rendering. + def _printchar(self, char, invert=False, recurse=False): + s = self._getstate() + self._get_char(char, recurse) + if self.glyph is None: + return # All done + buf = bytearray(self.glyph) + if invert: + for i, v in enumerate(buf): + buf[i] = 0xFF & ~ v + fbc = framebuf.FrameBuffer(buf, self.char_width, self.char_height, self.map) + self.device.blit(fbc, s.text_col, s.text_row) + s.text_col += self.char_width + self.cpos += 1 + + def tabsize(self, value=None): + if value is not None: + self.tab = value + return self.tab + + +# Writer for colour displays or upside down rendering +class CWriter(Writer): + + @staticmethod + def invert_display(device, value=True): + devid = id(device) + if devid not in Writer.state: + Writer.state[devid] = DisplayState() + Writer.state[devid].usd = value + + def __init__(self,device, font, verbose=True): + super().__init__(device, font, verbose) + + def setcolor(self, fgcolor=None, bgcolor=None): + if fgcolor is not None: + self.fgcolor = fgcolor + if bgcolor is not None: + self.bgcolor = bgcolor + return self.fgcolor, self.bgcolor + + def _printchar(self, char, invert=False, recurse=False): + s = self._getstate() + self._get_char(char, recurse) + if self.glyph is None: + return # All done + char_height = self.char_height + char_width = self.char_width + + div, mod = divmod(char_width, 8) + gbytes = div + 1 if mod else div # No. of bytes per row of glyph + device = self.device + fgcolor = self.bgcolor if invert else self.fgcolor + bgcolor = self.fgcolor if invert else self.bgcolor + usd = self.usd + drow = s.text_row # Destination row + wcol = s.text_col # Destination column of character start + for srow in range(char_height): # Source row + for scol in range(char_width): # Source column + # Destination column: add/subtract writer column + if usd: + dcol = wcol - scol + else: + dcol = wcol + scol + gbyte, gbit = divmod(scol, 8) + if gbit == 0: # Next glyph byte + data = self.glyph[srow * gbytes + gbyte] + pixel = fgcolor if data & (1 << (7 - gbit)) else bgcolor + device.pixel(dcol, drow, pixel) + drow += -1 if usd else 1 + if drow >= self.screenheight or drow < 0: + break + s.text_col += -char_width if usd else char_width + self.cpos += 1 + +class Label(): + def __init__(self, writer, row, col, text, invert=False): + self.writer = writer + self.row = row + self.col = col + self.invert = invert + if isinstance(text, int): + self.length = text + self.text = '' + else: + self.length = writer.stringlen(text) + self.text = text + if not isinstance(self, Field): + self.show() + + def show(self): + wri = self.writer + dev = wri.device + Writer.set_textpos(dev, self.row, self.col) + wri.printstring(self.text, self.invert) + +class Field(Label): + def __init__(self, writer, row, col, max_text, border=False): + super().__init__(writer, row, col, max_text, False) + self.border = border + + def value(self, text, invert=False): + self.text = text + self.invert = invert + self.show() + + def show(self): + wri = self.writer + dev = wri.device + if wri._getstate().usd: + row = dev.height - self.row - wri.height() + col = dev.width - self.col - self.length + else: + row = self.row + col = self.col + dev.fill_rect(col, row, self.length, wri.height(), wri.bgcolor) + if self.border: + dev.rect(col - 2, row - 2, self.length + 4, wri.height() + 4, wri.fgcolor) + super().show() diff --git a/writer/writer_demo.py b/writer/writer_demo.py new file mode 100644 index 0000000..4ee79e5 --- /dev/null +++ b/writer/writer_demo.py @@ -0,0 +1,56 @@ +# ssd1306_test.py Demo pogram for rendering arbitrary fonts to an SSD1306 OLED display. + +# 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. + + +# https://learn.adafruit.com/monochrome-oled-breakouts/wiring-128x32-spi-oled-display +# https://www.proto-pic.co.uk/monochrome-128x32-oled-graphic-display.html + +# V0.3 13th Aug 2018 + +import machine +from ssd1306_setup import WIDTH, HEIGHT, setup +from writer import Writer + +# Font +import freesans20 + +def test(use_spi=False): + ssd = setup(use_spi) # Create a display instance + rhs = WIDTH -1 + ssd.line(rhs - 20, 0, rhs, 20, 1) + square_side = 10 + ssd.fill_rect(rhs - square_side, 0, square_side, square_side, 1) + + wri = Writer(ssd, freesans20) + Writer.set_textpos(ssd, 0, 0) # verbose = False to suppress console output + wri.printstring('Sunday\n') + wri.printstring('12 Aug 2018\n') + wri.printstring('10.30am') + ssd.show() + +print('Test assumes a 128*64 (w*h) display. Edit WIDTH and HEIGHT in ssd1306_setup.py for others.') +print('Device pinouts are comments in ssd1306_setup.py.') +print('Issue:') +print('writer_demo.test() for an I2C connected device.') +print('writer_demo.test(True) for an SPI connected device.') diff --git a/writer.py b/writer/writer_minimal.py similarity index 73% rename from writer.py rename to writer/writer_minimal.py index 2a03c82..ff3fc99 100644 --- a/writer.py +++ b/writer/writer_minimal.py @@ -1,5 +1,6 @@ -# writer.py Implements the Writer class. -# V0.2 Peter Hinch Dec 2016 +# writer_minimal.py Implements the Writer class. +# Minimal version for SSD1306 +# V0.22 Peter Hinch 3rd Jan 2018: Supports new SSD1306 driver. # The MIT License (MIT) # @@ -27,8 +28,9 @@ # Multiple Writer instances may be created, each rendering a font to the # same Display object. +import framebuf -class Writer(object): +class Writer(): text_row = 0 # attributes common to all Writer instances text_col = 0 row_clip = False # Clip or scroll when screen full @@ -44,12 +46,16 @@ class Writer(object): cls.row_clip = row_clip cls.col_clip = col_clip - def __init__(self, device, font): - super().__init__() + def __init__(self, device, font, verbose=True): self.device = device self.font = font + # Allow to work with any font mapping if font.hmap(): - raise OSError('Font must be vertically mapped') + self.map = framebuf.MONO_HMSB if font.reverse() else framebuf.MONO_HLSB + else: + raise ValueError('Font must be horizontally mapped.') + if verbose: + print('Orientation: {} Reversal: {}'.format('horiz' if font.hmap() else 'vert', font.reverse())) self.screenwidth = device.width # In pixels self.screenheight = device.height @@ -67,7 +73,9 @@ class Writer(object): for char in string: self._printchar(char) - def _printchar(self, char): + # Method using blitting. Efficient rendering for monochrome displays. + # Tested on SSD1306. Invert is for black-on-white rendering. + def _printchar(self, char, invert=False): if char == '\n': self._newline() return @@ -81,19 +89,10 @@ class Writer(object): return else: self._newline() - - div, mod = divmod(char_height, 8) - gbytes = div + 1 if mod else div # No. of bytes per column of glyph - device = self.device - for scol in range(char_width): # Source column - dcol = scol + Writer.text_col # Destination column - drow = Writer.text_row # Destination row - for srow in range(char_height): # Source row - gbyte, gbit = divmod(srow, 8) - if drow >= self.screenheight: - break - if gbit == 0: # Next glyph byte - data = glyph[scol * gbytes + gbyte] - device.pixel(dcol, drow, data & (1 << gbit)) - drow += 1 + buf = bytearray(glyph) + if invert: + for i, v in enumerate(buf): + buf[i] = 0xFF & ~ v + fbc = framebuf.FrameBuffer(buf, char_width, char_height, self.map) + self.device.blit(fbc, Writer.text_col, Writer.text_row) Writer.text_col += char_width diff --git a/writer/writer_tests.py b/writer/writer_tests.py new file mode 100644 index 0000000..0144c68 --- /dev/null +++ b/writer/writer_tests.py @@ -0,0 +1,292 @@ +# ssd1306_test.py Demo pogram for rendering arbitrary fonts to an SSD1306 OLED display. + +# 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. + + +# https://learn.adafruit.com/monochrome-oled-breakouts/wiring-128x32-spi-oled-display +# https://www.proto-pic.co.uk/monochrome-128x32-oled-graphic-display.html + +# V0.3 12th Aug 2018 + +import machine +import utime +import uos +from ssd1306_setup import WIDTH, HEIGHT, setup +from writer import Writer, CWriter, Field, Label + +# Fonts +import freesans20 +import courier20 as fixed +import font6 as small + +def inverse(use_spi=False, soft=True): + ssd = setup(use_spi, soft) # Create a display instance + rhs = WIDTH -1 + ssd.line(rhs - 20, 0, rhs, 20, 1) + square_side = 10 + ssd.fill_rect(rhs - square_side, 0, square_side, square_side, 1) + + Writer.set_textpos(ssd, 0, 0) # In case previous tests have altered it + wri = Writer(ssd, freesans20, verbose=False) + wri.set_clip(False, False, False) # Char wrap + wri.printstring('Sunday\n') + wri.printstring('12 Aug 2018\n') + wri.printstring('10.30am', True) # Inverse text + ssd.show() + +def scroll(use_spi=False, soft=True): + ssd = setup(use_spi, soft) # Create a display instance + rhs = WIDTH -1 + ssd.line(rhs - 20, 0, rhs, 20, 1) + square_side = 10 + ssd.fill_rect(rhs - square_side, 0, square_side, square_side, 1) + + Writer.set_textpos(ssd, 0, 0) # In case previous tests have altered it + wri = Writer(ssd, freesans20, verbose=False) + wri.set_clip(False, False, False) # Char wrap + wri.printstring('Sunday\n') + wri.printstring('12 Aug 2018\n') + wri.printstring('10.30am') + for x in range(5): + ssd.show() + utime.sleep(2) + wri.printstring('\nCount = {:2d}'.format(x)) + ssd.show() + utime.sleep(2) + wri.printstring('\nDone.') + ssd.show() + +def usd_scroll(use_spi=False, soft=True): + ssd = setup(use_spi, soft) # Create a display instance + # Only CWriter can do usd + CWriter.invert_display(ssd) + CWriter.set_textpos(ssd, 0, 0) + wri = CWriter(ssd, freesans20, verbose=False) + wri.set_clip(False, False, False) # Char wrap + + wri.printstring('Sunday\n') + wri.printstring('12 Aug 2018\n') + wri.printstring('10.30am') + for x in range(5): + ssd.show() + utime.sleep(2) + wri.printstring('\nCount = {:2d}'.format(x)) + ssd.show() + utime.sleep(2) + wri.printstring('\nDone.') + ssd.show() + CWriter.invert_display(ssd, False) # For subsequent tests + +def usd(use_spi=False, soft=True): + ssd = setup(use_spi, soft) # Create a display instance + # Only CWriter can do usd + CWriter.invert_display(ssd) + CWriter.set_textpos(ssd, 0, 0) + wri = CWriter(ssd, freesans20, verbose=False) + wri.set_clip(False, False, False) # Char wrap + wri.printstring('Sunday\n') + wri.printstring('12 Aug 2018\n') + wri.printstring('10.30am') + ssd.show() + CWriter.invert_display(ssd, False) # For subsequent tests + +def rjust(use_spi=False, soft=True): + ssd = setup(use_spi, soft) # Create a display instance + Writer.set_textpos(ssd, 0, 0) # Previous tests may have altered it + wri = Writer(ssd, freesans20, verbose=False) + wri.set_clip(False, False, False) # Char wrap + + my_str = 'Sunday\n' + l = wri.stringlen(my_str) + Writer.set_textpos(ssd, col = WIDTH - l) + wri.printstring(my_str) + + my_str = '12 Aug 2018\n' + l = wri.stringlen(my_str) + Writer.set_textpos(ssd, col = WIDTH - l) + wri.printstring(my_str) + + my_str = '10.30am' + l = wri.stringlen(my_str) + Writer.set_textpos(ssd, col = WIDTH - l) + wri.printstring(my_str) + ssd.show() + +def fonts(use_spi=False, soft=True): + ssd = setup(use_spi, soft) # Create a display instance + Writer.set_textpos(ssd, 0, 0) # In case previous tests have altered it + wri = Writer(ssd, freesans20, verbose=False) + wri.set_clip(False, False, False) # Char wrap + wri_f = Writer(ssd, small, verbose=False) + wri_f.set_clip(False, False, False) # Char wrap + wri_f.printstring('Sunday\n') + wri.printstring('12 Aug 2018\n') + wri.printstring('10.30am') + ssd.show() + +def tabs(use_spi=False, soft=True): + ssd = setup(use_spi, soft) # Create a display instance + Writer.set_textpos(ssd, 0, 0) # In case previous tests have altered it + wri = Writer(ssd, fixed, verbose=False) + wri.set_clip(False, False, False) # Char wrap + wri.printstring('1\t2\n') + wri.printstring('111\t22\n') + wri.printstring('1111\t1') + ssd.show() + +def usd_tabs(use_spi=False, soft=True): + ssd = setup(use_spi, soft) # Create a display instance + CWriter.invert_display(ssd) + CWriter.set_textpos(ssd, 0, 0) + wri = CWriter(ssd, fixed, verbose=False) + wri.set_clip(False, False, False) # Char wrap + wri.printstring('1\t2\n') + wri.printstring('111\t22\n') + wri.printstring('1111\t1') + ssd.show() + CWriter.invert_display(ssd, False) # For subsequent tests + +def wrap(use_spi=False, soft=True): + ssd = setup(use_spi, soft) # Create a display instance + Writer.set_textpos(ssd, 0, 0) # In case previous tests have altered it + wri = Writer(ssd, freesans20, verbose=False) + wri.set_clip(False, False, True) # Word wrap + wri.printstring('the quick brown fox jumps over') + ssd.show() + +def fields(use_spi=False, soft=True): + ssd = setup(use_spi, soft) # Create a display instance + Writer.set_textpos(ssd, 0, 0) # In case previous tests have altered it + wri = Writer(ssd, fixed, verbose=False) + wri.set_clip(False, False, False) + textfield = Field(wri, 0, 2, 'longer', False) + numfield = Field(wri, 25, 2, '99.99', True) + countfield = Field(wri, 0, 90, '1', False) + n = 1 + for s in ('short', 'longer', '1', ''): + textfield.value(s) + numfield.value('{:5.2f}'.format(int.from_bytes(uos.urandom(2),'little')/1000)) + countfield.value('{:1d}'.format(n)) + n += 1 + ssd.show() + utime.sleep(2) + textfield.value('Done', True) + ssd.show() + +def usd_fields(use_spi=False, soft=True): + ssd = setup(use_spi, soft) # Create a display instance + CWriter.invert_display(ssd) + CWriter.set_textpos(ssd, 0, 0) # In case previous tests have altered it + wri = CWriter(ssd, fixed, verbose=False) + wri.set_clip(False, False, False) + textfield = Field(wri, 25, 2, 'longer', False) + numfield = Field(wri, 2, 2, '99.99', True) + countfield = Field(wri, 25, 100, '1', False) + n = 1 + for s in ('short', 'longer', '1', ''): + textfield.value(s) + numfield.value('{:5.2f}'.format(int.from_bytes(uos.urandom(2),'little')/1000)) + countfield.value('{:1d}'.format(n)) + n += 1 + ssd.show() + utime.sleep(2) + textfield.value('Done', True) + ssd.show() + +def multi_fields(use_spi=False, soft=True): + ssd = setup(use_spi, soft) # Create a display instance + Writer.set_textpos(ssd, 0, 0) # In case previous tests have altered it + wri = Writer(ssd, small, verbose=False) + wri.set_clip(False, False, False) + + nfields = [] + dy = small.height() + 6 + y = 2 + col = 15 + for txt in ('X:', 'Y:', 'Z:'): + Label(wri, y, 0, txt) + nfields.append(Field(wri, y, col, '99.99', True)) + y += dy + + for _ in range(10): + for field in nfields: + field.value('{:5.2f}'.format(int.from_bytes(uos.urandom(2),'little')/1000)) + ssd.show() + utime.sleep(1) + Label(wri, 0, 64, ' DONE ', True) + ssd.show() + +def dual(use_spi=False, soft=True): + ssd0 = setup(False, soft) # I2C display + ssd1 = setup(True, False) # SPI instance + Writer.set_textpos(ssd0, 0, 0) # In case previous tests have altered it + wri0 = Writer(ssd0, small, verbose=False) + wri0.set_clip(False, False, False) + Writer.set_textpos(ssd1, 0, 0) # In case previous tests have altered it + wri1 = Writer(ssd1, small, verbose=False) + wri1.set_clip(False, False, False) + + nfields = [] + dy = small.height() + 6 + col = 15 + for n, wri in enumerate((wri0, wri1)): + nfields.append([]) + y = 2 + for txt in ('X:', 'Y:', 'Z:'): + Label(wri, y, 0, txt) + nfields[n].append(Field(wri, y, col, '99.99', True)) + y += dy + + for _ in range(10): + for n, wri in enumerate((wri0, wri1)): + for field in nfields[n]: + field.value('{:5.2f}'.format(int.from_bytes(uos.urandom(2),'little')/1000)) + wri.device.show() + utime.sleep(1) + for wri in (wri0, wri1): + Label(wri, 0, 64, ' DONE ', True) + wri.device.show() + + +tstr = '''Test assumes a 128*64 (w*h) display. Edit WIDTH and HEIGHT in ssd1306_setup.py for others. +Device pinouts are comments in ssd1306_setup.py. +All tests take two boolean args: +use_spi = False. Set True for SPI connected device +soft=True set False to use hardware I2C/SPI. Hardware I2C option currently fails with official SSD1306 driver. + +Available tests: +inverse() Show black on white text. +scroll() Illustrate scrolling +usd() Upside-down display. +usd_scroll() Upside-down scroll test. +rjust() Right justification. +fonts() Two fonts. +tabs() Tab stops. +usd_tabs() Upside-down tabs. +wrap() Word wrapping +fields() Field test with dynamic data. +usd_fields() Upside down fields. +multi_fields() Fields and labels. +dual() Test two displays on one host.''' + +print(tstr)