Initial commit.

pull/8/head
Peter Hinch 2021-06-09 17:11:48 +01:00
rodzic 7828c0513c
commit 64d75fe273
68 zmienionych plików z 9822 dodań i 4 usunięć

470
README.md
Wyświetl plik

@ -1,8 +1,470 @@
# micropython-micro-gui
Under development: will be released soon after further testing.
A lightweight MicroPython GUI library for display drivers based on framebuf, allows input via pushbuttons or via a switch joystick.
This is a lightweight, portable, MicroPython GUI library for displays with
drivers subclassed from `framebuf`. It allows input via pushbuttons or via a
switch joystick.
It is larger and more complex than nano-gui owing to the support for input. It enables switching between screens and launching modal windows. In addition to nano-gui widgets it supports listboxes, dropdown lists, various means of entering or displaying floating point values, and other widgets.
It is larger and more complex than `nano-gui` owing to the support for input.
It enables switching between screens and launching modal windows. In addition
to `nano-gui` widgets it supports listboxes, dropdown lists, various means of
entering or displaying floating point values, and other widgets.
It uses display drivers for [nano-gui](https://github.com/peterhinch/micropython-nano-gui) providing portability to a wide range of displays. It is also portable between hosts. Currently running on RP2, but I intend to test on Pyboard and ESP32/TTGo TDisplay.
It is compatible with all display drivers for
[nano-gui](https://github.com/peterhinch/micropython-nano-gui) so is portable
to a wide range of displays. It is also portable between hosts.
## This document is seriously incomplete.
## It may be several weeks before it is usable.
## Code is in a better state.
# 0. Contents
**TODO**
# 1. Basic concepts
Internally `micro-gui` uses `uasyncio`. It presents a conventional callback
based interface; knowledge of `uasyncio` is not required for its use. Display
refresh is handled automatically. As in nano-gui, widgets are drawn using
graphics primitives rather than icons. This makes them efficiently scalable and
minimises RAM usage compared to icon-based graphics. It also facilitates the
provision of extra visual information. For example the color of all or part of
a widget may be changed programmatically, for example to highlight an overrange
condition.
## 1.1 Coordinates
These are defined as `row` and `col` values where `row==0` and `col==0`
corresponds to the top left most pixel. Rows increase downwards and columns
increase to the right. The graph plotting widget uses normal mathematical
conventions within graphs.
## 1.2 Screen, Window and Widget objects
A `Screen` is a window which occupies the entire display. A `Screen` can
overlay another, replacing all its contents. When closed, the `Screen` below is
re-displayed.
A `Window` is a subclass of `Screen` but is smaller, with size and location
attributes. It can overlay part of an underlying `Screen` and is typically used
for modal dialog boxes.
A `Widget` is an object capable of displaying data. Some are also capable of
data input. The latter can be capable of accepting focus, see
[navigation](./README.md#13-navigation). `Widget` objects have dimensions
defined as `height` and `width`. The space requred by them exceeds these by two
pixels all round, as a white border is drawn to show which object currently has
focus. Thus to place a `Widget` at the extreme top left, `row` and `col` values
should be 2.
## 1.3 Fonts
Python font files are in the `gui/fonts` directory. The easiest way to conserve
RAM is to freeze them which is highly recommended. In doing so the directory
structure must be maintained.
To create alternatives, Python fonts may be generated from industry standard
font files with
[font_to_py.py](https://github.com/peterhinch/micropython-font-to-py.git). The
`-x` option for horizontal mapping must be specified. If fixed pitch rendering
is required `-f` is also required. Supplied examples are:
* `arial10.py` Variable pitch Arial. 10 pixels high.
* `arial35.py` Arial 35 high.
* `arial_50.py` Arial 50 high.
* `courier20.py` Fixed pitch Courier, 20 high.
* `font6.py` FreeSans 14 high.
* `font10.py` FreeSans 17 high.
* `freesans20.py` FreeSans 20 high.
## 1.4 Navigation
The GUI requires from 2 to 5 pushbuttons for control. These are:
1. `Next` Move to the next widget.
2. `Select` Operate the currently selected widget.
3. `Prev` Move to the previous widget.
4. `Increase` Move within the widget.
5. `Decrease` Move within the widget.
Many widgets such as `Pushbutton` or `Checkbox` objects require only the
`Select` button to operate: it is possible to design an interface using only
the first two buttons.
Widgets such as `Listbox` objects, dropdown lists (`Dropdown`), and those for
floating point data entry require the `Increase` and `Decrease` buttons to move
within the widget or to adjust the linear value.
A `LinearIO` is a `Widget` that responds to the `increase` and `decrease`
buttons by running an `asyncio` task. These typically output floating point
values using an accelerating algorithm responding to the duration of the button
press. This enables floats with a wide dynamic range to be adjusted with
precision.
The currently selected `Widget` is identified by a white border: the focus
moves between widgets via `Next` and `Prev`. Only `Widget` instances that can
accept input can receive the focus; such widgets are defined as `active`. Some
widgets can be declared as `active` or not in the constructor. An `active`
widget can be disabled and re-enabled at runtime. While disabled a widget is
shown "greyed-out" and cannot accept the focus.
## 1.5 Hardware definition
A file `hardware_setup.py` must exist in the GUI root directory. This defines
the connections to the display, the display driver, and pins used for the
pushbuttons. Example files may be found in the `setup_examples` directory.
Display drivers are documented
[here](https://github.com/peterhinch/micropython-nano-gui/blob/master/DRIVERS.md).
## 1.6 Installation
The easy way to start is to use `mpremote` which allows a directory on your PC
to be mounted on the host. In this way the filesystem on the host is left
unchanged. This is at some cost in loading speed, especially on ESP32. If
adopting this approach, you will need to ensure the `hardware_setup.py` file on
the PC matches your hardware. Install `mpremote` with
```bash
$ pip3 install mpremote
```
Clone the repo to your PC with
```bash
$ git clone https://github.com/peterhinch/micropython-micro-gui
$ cd micropython-micro-gui
```
Edit `hardware_setup.py` then run:
```bash
$ mpremote mount .
```
This should provide a REPL. Run the minimal demo:
```python
>>> import gui.demos.simple
```
If installing to the device's filesystem it is necessary to maintain the
directory structure. The `drivers` and `gui` directories (with subdirectories
and contents) should be copied, along with `hardware_setup.py`. Filesystem
space may be conserved by copying only the display driver in use. Unused
widgets, fonts and demos can also be trimmed, but directory structure must be
kept.
There is scope for speeding loading and saving RAM by using frozen bytecode.
Once again, directory structure must be maintained.
## 1.7 Quick hardware check
The following may be pasted at the REPL to verify correct connection to the
display. It also confirms that `hardware_setup.py` is specifying a suitable
display driver.
```python
from hardware_setup import ssd # Create a display instance
from gui.core.colors import *
ssd.fill(0)
ssd.line(0, 0, ssd.width - 1, ssd.height - 1, GREEN) # Green diagonal corner-to-corner
ssd.rect(0, 0, 15, 15, RED) # Red square at top left
ssd.rect(ssd.width -15, ssd.height -15, 15, 15, BLUE) # Blue square at bottom right
ssd.show()
```
## 1.8 Performance and hardware notes
The largest supported display is a 320x240 ILI9341 unit. On a Pi Pico with no
use of frozen bytecode the demos run with over 74K of free RAM. Substantial
improvements could be achieved using frozen bytecode.
Snappy navigation benefits from several approaches:
1. Clocking the SPI bus as fast as possible.
2. Clocking the host fast (`machine.freq`).
3. Device driver support for `uasyncio`. Currently this exists on ILI9341 and
ST7789 (e.g. TTGO T-Display). I intend to extend this to other drivers.
On ESP32 I found it necessary to use physical pullup resistors on the
pushbutton GPIO lines.
## 1.9 Firmware and dependencies
Firmware should be V1.15 or later.
The source tree includes all dependencies. These are listed to enable users to
check for newer versions:
* [writer.py](https://github.com/peterhinch/micropython-font-to-py/blob/master/writer/writer.py)
Provides text rendering of Python font files.
A copy of the official driver for OLED displays using the SSD1306 chip is
provided. The official file is here:
* [SSD1306 driver](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py).
Displays based on the Nokia 5110 (PCD8544 chip) require this driver. It is not
in this repo but may be found here:
* [PCD8544/Nokia 5110](https://github.com/mcauser/micropython-pcd8544.git)
Synchronisation primitives for `uasyncio` may be found here:
* [My async repo](https://github.com/peterhinch/micropython-async/tree/master/v3/primitives).
## 1.10 Supported hosts and displays
Development was done using a Raspberry Pi Pico connected to a cheap ILI9341
320x240 display. I have also tested a TTGO T-Display (an ESP32 host) and a
Pyboard. Code is written with portability as an aim, but MicroPython configs
vary between platforms and I can't guarantee that every widget will work on
every platform. For example, some use the `cmath` module which may be absent on
some builds.
Supported displays are as per
[the nano-gui list](https://github.com/peterhinch/micropython-nano-gui/blob/master/README.md#12-description).
In practice usage with ePaper displays is questionable because of their slow
refresh times. I haven't tested these, or the Sharp displays.
Display drivers are documented [here](https://github.com/peterhinch/micropython-nano-gui/blob/master/DRIVERS.md).
## 1.11 Files
Display drivers may be found in the `drivers` directory. These are copies of
those in `nano-gui`, included for convenience.
The system is organised as a Python package with the root being `gui`. Core
files in `gui/core` are:
* `colors.py` Constants including colors and shapes.
* `ugui.py` The main GUI code.
* `writer.py` Supports the `Writer` and `CWriter` classes.
The `gui/primitives` directory contains the following files:
* `switch.py` Interface to physical pushbuttons.
* `delay_ms.py` A software triggerable timer.
The `gui/demos` directory contains a variety of demos and tests, some of which
require a large (320x240) display. Demos are run by issuing (fo example):
```python
>>> import gui.demos.simple
```
* `simple.py` Minimal demo discussed below.
* `active.py` Demonstrates `active` controls providing floating point input.
* `plot.py` Graph plotting.
* `screens.py` Listbox, dropdown and dialog boxes.
* `tbox.py` Text boxes and user-controlled scrolling.
* `various.py` Assorted widgets including the different types of pushbutton.
* `vtest.py` Clock and compass styles of vector display.
# 2. Usage
## 2.1 Program structure and operation
The following is a minimal script (found in `gui.demos.simple.py`) which will
run on a minimal system with a small display and two pushbuttons. It provides
two `Button` widgets with "Yes" and "No" legends.
It may be run by issuing
```python
>>> import gui.demos.simple
```
at the REPL.
Note that the import of `hardware_setup.py` is the first line of code. This is
because the frame buffer is created here, with a need for a substantial block
of contiguous RAM.
```python
from hardware_setup import ssd # Create a display instance
from gui.core.ugui import Screen
from gui.widgets.label import Label
from gui.widgets.buttons import Button, CloseButton
from gui.core.writer import CWriter
# Font for CWriter
import gui.fonts.arial10 as arial10
from gui.core.colors import *
class BaseScreen(Screen):
def __init__(self):
def my_callback(button, arg):
print('Button pressed', arg)
super().__init__()
wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False)
col = 2
row = 2
Label(wri, row, col, 'Simple Demo')
row = 20
Button(wri, row, col, text='Yes', callback=my_callback, args=('Yes',))
col += 60
Button(wri, row, col, text='No', callback=my_callback, args=('No',))
CloseButton(wri) # Quit the application
def test():
print('Testing micro-gui...')
Screen.change(BaseScreen)
test()
```
Note how the `Next` pushbutton moves the focus between the two buttons and the
"X" close button. The focus does not move to the "Simple Demo" widget because
it is not `active`: a `Label` cannot accept user input. Pushing the `Select`
pushbutton while the focus is on a `Pushbutton` causes the callback to run.
Applications start by performing `Screen.change()` to a user-defined Screen
object. This must be subclassed from the GUI's `Screen` class. Note that
`Screen.change` accepts a class name, not a class instance.
The user defined `BaseScreen` class constructor instantiates all widgets to be
displayed and typically associates them with callback functions - which may be
bound methods. Screens typically have a `CloseButton` widget. This is a special
`Pushbutton` subclass which displays as an "X" at the top right corner of the
physical display and closes the current screen, showing the one below. If used
on the bottom level `Screen` (as above) it closes the application.
The `wri` `CWriter` instance associates a widget with a font. All widgets use
a `CWriter` instance followed by `row` and `col` as positional constructor args
followed typically by a number of optional keyword args. These have (hopefully)
sensible defaults enabling you to get started easily.
## 2.2 Callbacks
The interface is event driven. Widgets may have optional callbacks which will
be executed when a given event occurs. A callback function receives positional
arguments. The first is a reference to the object raising the callback.
Subsequent arguments are user defined, and are specified as a tuple or list of
items. Callbacks are optional, as are the argument lists - a default null
function and empty list are provided. Callbacks may optionally be written as
bound methods - see Screens below for a reason why this can be useful.
When writing callbacks take care to ensure that the number of arguments passed
is correct, bearing in mind the first arg listed above. Failure to do this will
result in tracebacks which implicate the GUI code rather than the buggy user
code: this is because the GUI runs the callbacks.
## 2.3 Colors
The file `gui/core/colors.py` defines standard color constants which may be
used with any display driver. This section describes how to change these or
to create additional colors.
Most of the color display drivers define colors as 8-bit or larger values.
In such cases colors may be created and assigned to variables as follows:
```python
from hardware_setup import ssd
PALE_YELLOW = ssd.rgb(150, 150, 0)
```
The GUI also provides drivers with 4-bit color to minimise RAM use. Colors are
assigned to a lookup table having 16 entries. The frame buffer stores 4-bit
color values, which are converted to the correct color depth for the hardware
when the display is refreshed.
Of the possible 16 colors 13 are assigned in `gui/core/colors.py`, leaving
color numbers 12, 13 and 14 free. Any color can be assigned as follows:
```python
from gui.core.colors import * # Imports the create_color function
PALE_YELLOW = create_color(12, 150, 150, 0)
```
This creates a color `rgb(150, 150, 0)` assigns it to "spare" color number 12
then sets `PALE_YELLOW` to 12. Any color number in range `0 <= n <= 15` may be
used (implying that predefined colors may be reassigned). It is recommended
that `BLACK` (0) and `WHITE` (15) are not changed. If code is to be ported
between 4-bit and other drivers, use `create_color()` for all custom colors:
it will produce appropriate behaviour. For an example see the `nano-gui` demo
`color15.py` - in particular the `vari_fields` function.
### 2.3.1 Monochrome displays
Most widgets work on monochrome displays if color settings are left at default
values. If a color is specified, drivers in this repo will convert it to black
or white depending on its level of saturation. A low level will produce the
background color, a high level the foreground.
At the bit level `1` represents the foreground. This is white on an emitting
display such as an OLED. On a Sharp display it indicates reflection.
There is an issue regarding ePaper displays discussed
[here](https://github.com/peterhinch/micropython-nano-gui/blob/master/README.md#312-monochrome-displays).
I don't consider ePaper displays as suitable for I/O because of their slow
refresh time.
# 3. Class details
# 4. Screen class
The `Screen` class presents a full-screen canvas onto which displayable
objects are rendered. Before instantiating widgets a `Screen` instance must be
created. This will be current until another is instantiated. When a widget is
instantiated it is associated with the current screen.
All applications require the creation of at least one user screen. This is done
by subclassing the `Screen` class. Widgets are instantiated in the constructor.
Widgets may be assigned to bound variable: this facilitates communication
between them.
## 4.1 Class methods
In normal use the following methods only are required:
* `change` Change screen, refreshing the display. Mandatory positional
argument: the new screen class name. This must be a class subclassed from
`Screen`. The class will be instantiated and displayed. Optional keyword
arguments: `args`, `kwargs`. These enable passing positional and keyword
arguments to the constructor of the new screen.
* `back` Restore previous screen.
These are uncommon:__
* `shutdown` Clear the screen and shut down the GUI. Normally done by a
`CloseButton` instance.
* `show(cls, force)`. This causes the screen to be redrawn. If `force` is
`False` unchanged widgets are not refreshed. If `True`, all visible widgets
are re-drawn. Explicit calls to this should never be needed.
See `demos/plot.py` for an example of multi-screen design.
## 4.2 Constructor
This takes no arguments.
## 4.3 Callback methods
These are null functions which may be redefined in user subclasses.
* `on_open` Called when a screen is instantiated but prior to display.
* `after_open` Called after a screen has been displayed.
* `on_hide` Called when a screen ceases to be current.
See `demos/plot.py` for examples of usage of `after_open`.
## 4.4 Method
* `reg_task` args `task`, `on_change=False`. The first arg may be a `Task`
instance or a coroutine. It is a convenience method which provides for the
automatic cancellation of tasks. If a screen runs independent coros it can opt
to register these. On shudown, any registered tasks of the base screen are
cancelled. On screen change, registered tasks with `on_change` `True` are
cancelled. For finer control applications can ignore this method and handle
cancellation explicitly in code.
# 5. Window class
This is a `Screen` subclass providing for modal windows. As such it has
positional and dimension information. Usage consists of writing a user class
subclassed from `Window`. Example code is in `demos/screens.py`.
## 5.2 Constructor
This takes the following positional args:
* `row`
* `col`
* `height`
* `width`
Followed by keyword-only args
* `draw_border=True`
* `bgcolor=None` Background color, default black.
* `fgcolor=None` Foreground color, default white.
## 5.3 Class method
* `value` This accepts a single arg which cane be any Python type. It allows
widgets on a `Window` to store information in a way which can be accessed from
the calling screen. This typically occurs after the window has closed and no
longer exists as an instance.
Another approach, demonstrated in `demos/screens.py`, is to pass one or more
callbacks to the user window constructor args. These may be called by widgets
to send data to the calling screen. Note that widgets on the screen below will
not be updated until the window has closed.

Wyświetl plik

@ -0,0 +1,303 @@
# epaper2in7_fb.py nanogui driver for ePpaper 2.7" display
# Tested with Pyboard linked to Raspberry Pi 2.7" E-Ink Display HAT
# EPD is subclassed from framebuf.FrameBuffer for use with Writer class and nanogui.
# Optimisations to reduce allocations and RAM use.
# Copyright (c) Peter Hinch 2020
# Released under the MIT license see LICENSE
# Based on the following sources:
# https://www.waveshare.com/wiki/2.7inch_e-Paper_HAT
# MicroPython Waveshare 2.7" Black/White GDEW027W3 e-paper display driver
# https://github.com/mcauser/micropython-waveshare-epaper referred to as "mcauser"
# https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epd2in7.py ("official")
import framebuf
import uasyncio as asyncio
from time import sleep_ms, ticks_ms, ticks_us, ticks_diff
class EPD(framebuf.FrameBuffer):
# A monochrome approach should be used for coding this. The rgb method ensures
# nothing breaks if users specify colors.
@staticmethod
def rgb(r, g, b):
return int((r > 127) or (g > 127) or (b > 127))
def __init__(self, spi, cs, dc, rst, busy, landscape=False, asyn=False):
self._spi = spi
self._cs = cs # Pins
self._dc = dc
self._rst = rst
self._busy = busy
self._lsc = landscape
self._asyn = asyn
self._as_busy = False # Set immediately on start of task. Cleared when busy pin is logically false (physically 1).
self._updated = asyncio.Event()
# Dimensions in pixels. Waveshare code is portrait mode.
# Public bound variables required by nanogui.
self.width = 264 if landscape else 176
self.height = 176 if landscape else 264
self.demo_mode = False # Special mode enables demos to run
self._buffer = bytearray(self.height * self.width // 8)
self._mvb = memoryview(self._buffer)
mode = framebuf.MONO_VLSB if landscape else framebuf.MONO_HLSB
super().__init__(self._buffer, self.width, self.height, mode)
self.init()
def _command(self, command, data=None):
self._dc(0)
self._cs(0)
self._spi.write(command)
self._cs(1)
if data is not None:
self._dc(1)
self._cs(0)
self._spi.write(data)
self._cs(1)
def init(self):
# Hardware reset
self._rst(1)
sleep_ms(200)
self._rst(0)
sleep_ms(200) # 5ms in Waveshare code
self._rst(1)
sleep_ms(200)
# Initialisation
cmd = self._command
cmd(b'\x01', b'\x03\x00\x2B\x2B\x09') # POWER_SETTING: VDS_EN VDG_EN, VCOM_HV VGHL_LV[1] VGHL_LV[0], VDH, VDL, VDHR
cmd(b'\x06', b'\x07\x07\x17') # BOOSTER_SOFT_START
cmd(b'\xf8', b'\x60\xA5') # POWER_OPTIMIZATION
cmd(b'\xf8', b'\x89\xA5')
cmd(b'\xf8', b'\x90\x00')
cmd(b'\xf8', b'\x93\x2A')
cmd(b'\xf8', b'\xA0\xA5')
cmd(b'\xf8', b'\xA1\x00')
cmd(b'\xf8', b'\x73\x41')
cmd(b'\x16', b'\x00') # PARTIAL_DISPLAY_REFRESH
cmd(b'\x04') # POWER_ON
self.wait_until_ready()
cmd(b'\x00', b'\xAF') # PANEL_SETTING: KW-BF, KWR-AF, BWROTP 0f
cmd(b'\x30', b'\x3A') # PLL_CONTROL: 3A 100HZ, 29 150Hz, 39 200HZ 31 171HZ
cmd(b'\x50', b'\x57') # Vcom and data interval setting (PGH)
cmd(b'\x82', b'\x12') # VCM_DC_SETTING_REGISTER
sleep_ms(2) # No delay in official code
# Set LUT. Local bytes objects reduce RAM usage.
# Values used by mcauser
#lut_vcom_dc =\
#b'\x00\x00\x00\x0F\x0F\x00\x00\x05\x00\x32\x32\x00\x00\x02\x00'\
#b'\x0F\x0F\x00\x00\x05\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'
#lut_ww =\
#b'\x50\x0F\x0F\x00\x00\x05\x60\x32\x32\x00\x00\x02\xA0\x0F\x0F'\
#b'\x00\x00\x05\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' # R21H
#lut_bb =\
#b'\xA0\x0F\x0F\x00\x00\x05\x60\x32\x32\x00\x00\x02\x50\x0F\x0F'\
#b'\x00\x00\x05\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' # R24H b
# Values from official code:
lut_vcom_dc =\
b'\x00\x00\x00\x08\x00\x00\x00\x02\x60\x28\x28\x00\x00\x01\x00'\
b'\x14\x00\x00\x00\x01\x00\x12\x12\x00\x00\x01\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
lut_ww =\
b'\x40\x08\x00\x00\x00\x02\x90\x28\x28\x00\x00\x01\x40\x14\x00'\
b'\x00\x00\x01\xA0\x12\x12\x00\x00\x01\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
lut_bb =\
b'\x80\x08\x00\x00\x00\x02\x90\x28\x28\x00\x00\x01\x80\x14\x00'\
b'\x00\x00\x01\x50\x12\x12\x00\x00\x01\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
# Both agree on this:
lut_bw = lut_ww # R22H r
lut_wb = lut_bb # R23H w
cmd(b'\x20', lut_vcom_dc) # LUT_FOR_VCOM vcom
cmd(b'\x21', lut_ww) # LUT_WHITE_TO_WHITE ww --
cmd(b'\x22', lut_bw) # LUT_BLACK_TO_WHITE bw r
cmd(b'\x23', lut_bb) # LUT_WHITE_TO_BLACK wb w
cmd(b'\x24', lut_wb) # LUT_BLACK_TO_BLACK bb b
print('Init Done.')
def wait_until_ready(self):
sleep_ms(50)
t = ticks_ms()
while not self.ready():
sleep_ms(100)
dt = ticks_diff(ticks_ms(), t)
print('wait_until_ready {}ms {:5.1f}mins'.format(dt, dt/60_000))
async def wait(self):
await asyncio.sleep_ms(0) # Ensure tasks run that might make it unready
while not self.ready():
await asyncio.sleep_ms(100)
# Pause until framebuf has been copied to device.
async def updated(self):
await self._updated.wait()
# For polling in asynchronous code. Just checks pin state.
# 0 == busy. Comment in official code is wrong. Code is correct.
def ready(self):
return not(self._as_busy or (self._busy() == 0)) # 0 == busy
async def _as_show(self, buf1=bytearray(1)):
mvb = self._mvb
send = self._spi.write
cmd = self._command
cmd(b'\x10') # DATA_START_TRANSMISSION_1
self._dc(1) # For some reason don't need to deassert CS here
buf1[0] = 0xff
t = ticks_ms()
for i in range(len(mvb)):
self._cs(0) # but do when copying the framebuf
send(buf1)
if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20):
await asyncio.sleep_ms(0)
t = ticks_ms()
self._cs(1)
cmd(b'\x13') # DATA_START_TRANSMISSION_2 not in datasheet
self._dc(1)
# Necessary to deassert CS after each byte otherwise display does not
# clear down correctly
t = ticks_ms()
if self._lsc: # Landscape mode
wid = self.width
tbc = self.height // 8 # Vertical bytes per column
iidx = wid * (tbc - 1) # Initial index
idx = iidx # Index into framebuf
vbc = 0 # Current vertical byte count
hpc = 0 # Horizontal pixel count
for i in range(len(mvb)):
self._cs(0)
buf1[0] = mvb[idx] # INVERSION HACK ~data
send(buf1)
self._cs(1)
idx -= self.width
vbc += 1
vbc %= tbc
if not vbc:
hpc += 1
idx = iidx + hpc
if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20):
await asyncio.sleep_ms(0)
t = ticks_ms()
else:
for i, b in enumerate(mvb):
self._cs(0)
buf1[0] = b # INVERSION HACK ~data
send(buf1)
self._cs(1)
if not(i & 0x1f) and (ticks_diff(ticks_ms(), t) > 20):
await asyncio.sleep_ms(0)
t = ticks_ms()
self._updated.set() # framebuf has now been copied to the device
self._updated.clear()
cmd(b'\x12') # DISPLAY_REFRESH
await asyncio.sleep(1)
while self._busy() == 0:
await asyncio.sleep_ms(200) # Don't release lock until update is complete
self._as_busy = False
# draw the current frame memory. Blocking time ~180ms
def show(self, buf1=bytearray(1)):
if self._asyn:
if self._as_busy:
raise RuntimeError('Cannot refresh: display is busy.')
self._as_busy = True
asyncio.create_task(self._as_show())
return
t = ticks_us()
mvb = self._mvb
send = self._spi.write
cmd = self._command
cmd(b'\x10') # DATA_START_TRANSMISSION_1
self._dc(1) # For some reason don't need to deassert CS here
buf1[0] = 0xff
for i in range(len(mvb)):
self._cs(0) # but do when copying the framebuf
send(buf1)
self._cs(1)
cmd(b'\x13') # DATA_START_TRANSMISSION_2 not in datasheet
self._dc(1)
# Necessary to deassert CS after each byte otherwise display does not
# clear down correctly
if self._lsc: # Landscape mode
wid = self.width
tbc = self.height // 8 # Vertical bytes per column
iidx = wid * (tbc - 1) # Initial index
idx = iidx # Index into framebuf
vbc = 0 # Current vertical byte count
hpc = 0 # Horizontal pixel count
for _ in range(len(mvb)):
self._cs(0)
buf1[0] = mvb[idx] # INVERSION HACK ~data
send(buf1)
self._cs(1)
idx -= self.width
vbc += 1
vbc %= tbc
if not vbc:
hpc += 1
idx = iidx + hpc
else:
for b in mvb:
self._cs(0)
buf1[0] = b # INVERSION HACK ~data
send(buf1)
self._cs(1)
cmd(b'\x12') # DISPLAY_REFRESH
te = ticks_us()
print('show time', ticks_diff(te, t)//1000, 'ms')
if not self.demo_mode:
# Immediate return to avoid blocking the whole application.
# User should wait for ready before calling refresh()
return
self.wait_until_ready()
sleep_ms(2000) # Give time for user to see result
# to wake call init()
def sleep(self):
self._as_busy = False
self.wait_until_ready()
cmd = self._command
cmd(b'\x50', b'\xf7') # From Waveshare code
cmd(b'\x02') # POWER_OFF
cmd(b'\x07', b'\xA5') # DEEP_SLEEP (Waveshare and mcauser)
self._rst(0) # According to schematic this turns off the power
# Testing connections by toggling pins connected to 40-way connector and checking volts on small connector
# All OK except rst: a 1 level produced only about 1.6V as against 3.3V for all other I/O.
# Further the level on the 40-way connector read 2.9V as agains 3.3V for others. Suspect hardware problem,
# ordered a second unit from Amazon.
#import machine
#import gc
#pdc = machine.Pin('Y1', machine.Pin.OUT_PP, value=0)
#pcs = machine.Pin('Y2', machine.Pin.OUT_PP, value=1)
#prst = machine.Pin('Y3', machine.Pin.OUT_PP, value=1)
#pbusy = machine.Pin('Y4', machine.Pin.IN)
## baudrate
## From https://github.com/mcauser/micropython-waveshare-epaper/blob/master/examples/2in9-hello-world/test.py 2MHz
## From https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/python/lib/waveshare_epd/epd2in7.py 4MHz
#spi = machine.SPI(2, baudrate=2_000_000)
#gc.collect() # Precaution before instantiating framebuf
#epd = EPD(spi, pcs, pdc, prst, pbusy) # Create a display instance
#sleep_ms(100)
#epd.init()
#print('Initialised')
#epd.fill(1) # 1 seems to be white
#epd.show()
#sleep_ms(1000)
#epd.fill(0)
#epd.show()
#epd._rst(0)
#epd._dc(0) # Turn off power according to RPI code

Wyświetl plik

@ -0,0 +1,234 @@
# epd29.py nanogui driver for Adafruit Flexible 2.9" Black and White ePaper display.
# [Interface breakout](https://www.adafruit.com/product/4224)
# [Display](https://www.adafruit.com/product/4262)
# EPD is subclassed from framebuf.FrameBuffer for use with Writer class and nanogui.
# Copyright (c) Peter Hinch 2020
# Released under the MIT license see LICENSE
# Based on the following sources:
# [CircuitPython code](https://github.com/adafruit/Adafruit_CircuitPython_IL0373) Author: Scott Shawcroft
# [Adafruit setup guide](https://learn.adafruit.com/adafruit-eink-display-breakouts/circuitpython-code-2)
# [IL0373 datasheet](https://www.smart-prototyping.com/image/data/9_Modules/EinkDisplay/GDEW0154T8/IL0373.pdf)
# [Adafruit demo](https://github.com/adafruit/Adafruit_CircuitPython_IL0373/blob/3f4f52eb3a65173165da1908f93a95383b45a726/examples/il0373_flexible_2.9_monochrome.py)
# [eInk breakout schematic](https://learn.adafruit.com/assets/57645)
# Physical pixels are 296w 128h. However the driver views the display as 128w * 296h with the
# Adfruit code transposing the axes.
import framebuf
import uasyncio as asyncio
from micropython import const
from time import sleep_ms, sleep_us, ticks_ms, ticks_us, ticks_diff
_MAX_BLOCK = const(20) # Maximum blocking time (ms) for asynchronous show.
class EPD(framebuf.FrameBuffer):
# A monochrome approach should be used for coding this. The rgb method ensures
# nothing breaks if users specify colors.
@staticmethod
def rgb(r, g, b):
return int((r > 127) or (g > 127) or (b > 127))
def __init__(self, spi, cs, dc, rst, busy, landscape=True, asyn=False):
self._spi = spi
self._cs = cs # Pins
self._dc = dc
self._rst = rst # Active low.
self._busy = busy # Active low on IL0373
self._lsc = landscape
self._asyn = asyn
# ._as_busy is set immediately on start of task. Cleared
# when busy pin is logically false (physically 1).
self._as_busy = False
self._updated = asyncio.Event()
# Public bound variables required by nanogui.
# Dimensions in pixels as seen by nanogui (landscape mode).
self.width = 296 if landscape else 128
self.height = 128 if landscape else 296
# Other public bound variable.
# Special mode enables demos written for generic displays to run.
self.demo_mode = False
self._buffer = bytearray(self.height * self.width // 8)
self._mvb = memoryview(self._buffer)
mode = framebuf.MONO_VLSB if landscape else framebuf.MONO_HLSB
super().__init__(self._buffer, self.width, self.height, mode)
self.init()
def _command(self, command, data=None):
self._dc(0)
self._cs(0)
self._spi.write(command)
self._cs(1)
if data is not None:
self._data(data)
# Datasheet P26 seems to mandate CS False after each byte. Ugh.
def _data(self, data, buf1=bytearray(1)):
self._dc(1)
for b in data:
self._cs(0)
buf1[0] = b
self._spi.write(buf1)
self._cs(1)
def init(self):
# Hardware reset
self._rst(1)
sleep_ms(200)
self._rst(0)
sleep_ms(200)
self._rst(1)
sleep_ms(200)
# Initialisation
cmd = self._command
# Power setting. Data from Adafruit.
# Datasheet default \x03\x00\x26\x26\x03 - slightly different voltages.
cmd(b'\x01', b'\x03\x00\x2b\x2b\x09')
# Booster soft start. Matches datasheet.
cmd(b'\x06', b'\x17\x17\x17')
cmd(b'\x04') # Power on
sleep_ms(200)
# Iss https://github.com/adafruit/Adafruit_CircuitPython_IL0373/issues/16
cmd(b'\x00', b'\x9f')
# CDI: As used by Adafruit. Datasheet is confusing on this.
# See https://github.com/adafruit/Adafruit_CircuitPython_IL0373/issues/11
# With 0x37 got white border on flexible display, black on FeatherWing
# 0xf7 still produced black border on FeatherWing
cmd(b'\x50', b'\x37')
# PLL: correct for 150Hz as specified in Adafruit code
cmd(b'\x30', b'\x29')
# Resolution 128w * 296h as required by IL0373
cmd(b'\x61', b'\x80\x01\x28') # Note hex(296) == 0x128
# Set VCM_DC. Now clarified with Adafruit.
# https://github.com/adafruit/Adafruit_CircuitPython_IL0373/issues/17
cmd(b'\x82', b'\x12') # Set Vcom to -1.0V
sleep_ms(50)
print('Init Done.')
# For use in synchronous code: blocking wait on ready state.
def wait_until_ready(self):
sleep_ms(50)
while not self.ready():
sleep_ms(100)
# Asynchronous wait on ready state. Pause (4.9s) for physical refresh.
async def wait(self):
await asyncio.sleep_ms(0) # Ensure tasks run that might make it unready
while not self.ready():
await asyncio.sleep_ms(100)
# Pause until framebuf has been copied to device.
async def updated(self):
await self._updated.wait()
# Return immediate status. Pin state: 0 == busy.
def ready(self):
return not(self._as_busy or (self._busy() == 0))
async def _as_show(self, buf1=bytearray(1)):
mvb = self._mvb
cmd = self._command
dat = self._data
cmd(b'\x13')
t = ticks_ms()
if self._lsc: # Landscape mode
wid = self.width
tbc = self.height // 8 # Vertical bytes per column
iidx = wid * (tbc - 1) # Initial index
idx = iidx # Index into framebuf
vbc = 0 # Current vertical byte count
hpc = 0 # Horizontal pixel count
for i in range(len(mvb)):
buf1[0] = ~mvb[idx]
dat(buf1)
idx -= wid
vbc += 1
vbc %= tbc
if not vbc:
hpc += 1
idx = iidx + hpc
if not(i & 0x0f) and (ticks_diff(ticks_ms(), t) > _MAX_BLOCK):
await asyncio.sleep_ms(0)
t = ticks_ms()
else:
for i, b in enumerate(mvb):
buf1[0] = ~b
dat(buf1)
if not(i & 0x0f) and (ticks_diff(ticks_ms(), t) > _MAX_BLOCK):
await asyncio.sleep_ms(0)
t = ticks_ms()
cmd(b'\x11') # Data stop
self._updated.set()
self._updated.clear()
sleep_us(20) # Allow for data coming back: currently ignore this
cmd(b'\x12') # DISPLAY_REFRESH
# busy goes low now, for ~4.9 seconds.
await asyncio.sleep(1)
while self._busy() == 0:
await asyncio.sleep_ms(200)
self._as_busy = False
# draw the current frame memory.
def show(self, buf1=bytearray(1)):
if self._asyn:
if self._as_busy:
raise RuntimeError('Cannot refresh: display is busy.')
self._as_busy = True # Immediate busy flag. Pin goes low much later.
asyncio.create_task(self._as_show())
return
mvb = self._mvb
cmd = self._command
dat = self._data
# DATA_START_TRANSMISSION_2 Datasheet P31 indicates this sets
# busy pin low (True) and that it stays logically True until
# refresh is complete. In my testing this doesn't happen.
cmd(b'\x13')
if self._lsc: # Landscape mode
wid = self.width
tbc = self.height // 8 # Vertical bytes per column
iidx = wid * (tbc - 1) # Initial index
idx = iidx # Index into framebuf
vbc = 0 # Current vertical byte count
hpc = 0 # Horizontal pixel count
for _ in range(len(mvb)):
buf1[0] = ~mvb[idx]
dat(buf1)
idx -= wid
vbc += 1
vbc %= tbc
if not vbc:
hpc += 1
idx = iidx + hpc
else:
for b in mvb:
buf1[0] = ~b
dat(buf1)
cmd(b'\x11') # Data stop
sleep_us(20) # Allow for data coming back: currently ignore this
cmd(b'\x12') # DISPLAY_REFRESH
# 258ms to get here on Pyboard D
# Checking with scope, busy goes low now. For 4.9s.
if not self.demo_mode:
# Immediate return to avoid blocking the whole application.
# User should wait for ready before calling refresh()
return
self.wait_until_ready()
sleep_ms(2000) # Give time for user to see result
# to wake call init()
def sleep(self):
self._as_busy = False
self.wait_until_ready()
cmd = self._command
# CDI: not sure about value or why we set this here. Copying Adafruit.
cmd(b'\x50', b'\x17')
# Set VCM_DC. 0 is datasheet default.
cmd(b'\x82', b'\x00')
# POWER_OFF. User code should pull ENA low to power down the display.
cmd(b'\x02')

Wyświetl plik

@ -0,0 +1,166 @@
# ILI9341 nano-gui driver for ili9341 displays
# As with all nano-gui displays, touch is not supported.
# Copyright (c) Peter Hinch 2020-2021
# Released under the MIT license see LICENSE
# This work is based on the following sources.
# https://github.com/rdagger/micropython-ili9341
# Also this forum thread with ideas from @minyiky:
# https://forum.micropython.org/viewtopic.php?f=18&t=9368
from time import sleep_ms
import gc
import framebuf
import uasyncio as asyncio
@micropython.viper
def _lcopy(dest:ptr16, source:ptr8, lut:ptr16, length:int):
# rgb565 - 16bit/pixel
n = 0
for x in range(length):
c = source[x]
dest[n] = lut[c >> 4] # current pixel
n += 1
dest[n] = lut[c & 0x0f] # next pixel
n += 1
class ILI9341(framebuf.FrameBuffer):
lut = bytearray(32)
# Convert r, g, b in range 0-255 to a 16 bit colour value
# LS byte goes into LUT offset 0, MS byte into offset 1
# Same mapping in linebuf so LS byte is shifted out 1st
# ILI9341 expects RGB order
@staticmethod
def rgb(r, g, b):
return (r & 0xf8) | (g & 0xe0) >> 5 | (g & 0x1c) << 11 | (b & 0xf8) << 5
# Transpose width & height for landscape mode
def __init__(self, spi, cs, dc, rst, height=240, width=320,
usd=False, init_spi=False):
self._spi = spi
self._cs = cs
self._dc = dc
self._rst = rst
self.height = height
self.width = width
self._spi_init = init_spi
mode = framebuf.GS4_HMSB
gc.collect()
buf = bytearray(self.height * self.width // 2)
self._mvb = memoryview(buf)
super().__init__(buf, self.width, self.height, mode)
self._linebuf = bytearray(self.width * 2)
# Hardware reset
self._rst(0)
sleep_ms(50)
self._rst(1)
sleep_ms(50)
if self._spi_init: # A callback was passed
self._spi_init(spi) # Bus may be shared
self._lock = asyncio.Lock()
# Send initialization commands
self._wcmd(b'\x01') # SWRESET Software reset
sleep_ms(100)
self._wcd(b'\xcf', b'\x00\xC1\x30') # PWCTRB Pwr ctrl B
self._wcd(b'\xed', b'\x64\x03\x12\x81') # POSC Pwr on seq. ctrl
self._wcd(b'\xe8', b'\x85\x00\x78') # DTCA Driver timing ctrl A
self._wcd(b'\xcb', b'\x39\x2C\x00\x34\x02') # PWCTRA Pwr ctrl A
self._wcd(b'\xf7', b'\x20') # PUMPRC Pump ratio control
self._wcd(b'\xea', b'\x00\x00') # DTCB Driver timing ctrl B
self._wcd(b'\xc0', b'\x23') # PWCTR1 Pwr ctrl 1
self._wcd(b'\xc1', b'\x10') # PWCTR2 Pwr ctrl 2
self._wcd(b'\xc5', b'\x3E\x28') # VMCTR1 VCOM ctrl 1
self._wcd(b'\xc7', b'\x86') # VMCTR2 VCOM ctrl 2
# (b'\x88', b'\xe8', b'\x48', b'\x28')[rotation // 90]
if self.height > self.width:
self._wcd(b'\x36', b'\x48' if usd else b'\x88') # MADCTL: RGB portrait mode
else:
self._wcd(b'\x36', b'\x28' if usd else b'\xe8') # MADCTL: RGB landscape mode
self._wcd(b'\x37', b'\x00') # VSCRSADD Vertical scrolling start address
self._wcd(b'\x3a', b'\x55') # PIXFMT COLMOD: Pixel format 16 bits (MCU & interface)
self._wcd(b'\xb1', b'\x00\x18') # FRMCTR1 Frame rate ctrl
self._wcd(b'\xb6', b'\x08\x82\x27') # DFUNCTR
self._wcd(b'\xf2', b'\x00') # ENABLE3G Enable 3 gamma ctrl
self._wcd(b'\x26', b'\x01') # GAMMASET Gamma curve selected
self._wcd(b'\xe0', b'\x0F\x31\x2B\x0C\x0E\x08\x4E\xF1\x37\x07\x10\x03\x0E\x09\x00') # GMCTRP1
self._wcd(b'\xe1', b'\x00\x0E\x14\x03\x11\x07\x31\xC1\x48\x08\x0F\x0C\x31\x36\x0F') # GMCTRN1
self._wcmd(b'\x11') # SLPOUT Exit sleep
sleep_ms(100)
self._wcmd(b'\x29') # DISPLAY_ON
sleep_ms(100)
# Write a command.
def _wcmd(self, buf):
self._dc(0)
self._cs(0)
self._spi.write(buf)
self._cs(1)
# Write a command followed by a data arg.
def _wcd(self, command, data):
self._dc(0)
self._cs(0)
self._spi.write(command)
self._cs(1)
self._dc(1)
self._cs(0)
self._spi.write(data)
self._cs(1)
# Time (ESP32 stock freq) 196ms portrait, 185ms landscape.
# mem free on ESP32 43472 bytes (vs 110192)
@micropython.native
def show(self):
clut = ILI9341.lut
wd = self.width // 2
ht = self.height
lb = self._linebuf
buf = self._mvb
if self._spi_init: # A callback was passed
self._spi_init(self._spi) # Bus may be shared
# Commands needed to start data write
self._wcd(b'\x2a', int.to_bytes(self.width, 4, 'big')) # SET_COLUMN
self._wcd(b'\x2b', int.to_bytes(ht, 4, 'big')) # SET_PAGE
self._wcmd(b'\x2c') # WRITE_RAM
self._dc(1)
self._cs(0)
for start in range(0, wd*ht, wd): # For each line
_lcopy(lb, buf[start :], clut, wd) # Copy and map colors
self._spi.write(lb)
self._cs(1)
def busy(self):
return self._lock.locked()
async def do_refresh(self, split=4):
if self.busy():
print('Warning: refresh paused until prior refresh completed.')
async with self._lock:
lines, mod = divmod(self.height, split) # Lines per segment
if mod:
raise ValueError('Invalid do_refresh arg.')
clut = ILI9341.lut
wd = self.width // 2
ht = self.height
lb = self._linebuf
buf = self._mvb
# Commands needed to start data write
self._wcd(b'\x2a', int.to_bytes(self.width, 4, 'big')) # SET_COLUMN
self._wcd(b'\x2b', int.to_bytes(ht, 4, 'big')) # SET_PAGE
self._wcmd(b'\x2c') # WRITE_RAM
self._dc(1)
line = 0
for _ in range(split): # For each segment
if self._spi_init: # A callback was passed
self._spi_init(self._spi) # Bus may be shared
self._cs(0)
for start in range(wd * line, wd * (line + lines), wd): # For each line
_lcopy(lb, buf[start :], clut, wd) # Copy and map colors
self._spi.write(lb)
line += lines
self._cs(1) # Allow other tasks to use bus
await asyncio.sleep_ms(0)

Wyświetl plik

Wyświetl plik

@ -0,0 +1,70 @@
# sharp.py Device driver for monochrome sharp displays
# Tested on
# https://www.adafruit.com/product/4694 2.7 inch 400x240 Monochrome
# Should also work on
# https://www.adafruit.com/product/3502 1.3 inch 144x168
# https://www.adafruit.com/product/1393 1.3 inch 96x96 Monochrome
# Copyright (c) Peter Hinch 2020-2021
# Released under the MIT license see LICENSE
# Code checked against https://github.com/adafruit/Adafruit_CircuitPython_SharpMemoryDisplay
# Current draw on 2.7" Adafruit display ~90uA.
# 2.7" schematic https://learn.adafruit.com/assets/94077
# Datasheet 2.7" https://cdn-learn.adafruit.com/assets/assets/000/094/215/original/LS027B7DH01_Rev_Jun_2010.pdf?1597872422
# Datasheet 1.3" http://www.adafruit.com/datasheets/LS013B4DN04-3V_FPC-204284.pdf
import framebuf
import machine
from micropython import const
_WRITECMD = const(1) # Command bits
_VCOM = const(2)
class SHARP(framebuf.FrameBuffer):
@staticmethod
def rgb(r, g, b):
return int((r > 127) or (g > 127) or (b > 127))
def __init__(self, spi, pincs, height=240, width=400, vcom=False):
spi.init(baudrate=2_000_000, firstbit=machine.SPI.LSB) # Data sheet: should support 2MHz
self._spi = spi
self._pincs = pincs
self.height = height # Required by Writer class and nanogui
self.width = width
self._buffer = bytearray(self.height * self.width // 8)
self._mvb = memoryview(self._buffer)
super().__init__(self._buffer, self.width, self.height, framebuf.MONO_HMSB)
self._cmd = bytearray(1) # Buffer for command. Holds current VCOM bit
self._cmd[0] = _WRITECMD | _VCOM if vcom else _WRITECMD
self._lno = bytearray(1) # Line no.
self._dummy = bytearray(1) # Dummy (0)
# .show should be called periodically to avoid frame inversion flag
# (VCOM) retaining the same value for long periods
def show(self):
spi = self._spi
bpl = self.width // 8 # Bytes per line
self._pincs(1) # CS is active high
spi.write(self._cmd)
start = 0
lno = self._lno
lno[0] = 1 # Gate line address (starts at 1)
for _ in range(self.height):
spi.write(lno)
spi.write(self._mvb[start : start + bpl])
spi.write(self._dummy)
start += bpl
lno[0] += 1 # Gate line address
spi.write(self._dummy)
self._pincs(0)
self._cmd[0] ^= _VCOM # Toggle frame inversion flag
# Toggle the VCOM bit without changing the display. Power saving method.
def update(self):
self._pincs(1)
self._lno[0] = self._cmd[0] & _VCOM
self._spi.write(self._lno)
self._cmd[0] ^= _VCOM # Toggle frame inversion flag
self._pincs(0)

Wyświetl plik

@ -0,0 +1,159 @@
# MicroPython SSD1306 OLED driver, I2C and SPI interfaces
from micropython import const
import framebuf
# register definitions
SET_CONTRAST = const(0x81)
SET_ENTIRE_ON = const(0xA4)
SET_NORM_INV = const(0xA6)
SET_DISP = const(0xAE)
SET_MEM_ADDR = const(0x20)
SET_COL_ADDR = const(0x21)
SET_PAGE_ADDR = const(0x22)
SET_DISP_START_LINE = const(0x40)
SET_SEG_REMAP = const(0xA0)
SET_MUX_RATIO = const(0xA8)
SET_COM_OUT_DIR = const(0xC0)
SET_DISP_OFFSET = const(0xD3)
SET_COM_PIN_CFG = const(0xDA)
SET_DISP_CLK_DIV = const(0xD5)
SET_PRECHARGE = const(0xD9)
SET_VCOM_DESEL = const(0xDB)
SET_CHARGE_PUMP = const(0x8D)
# Subclassing FrameBuffer provides support for graphics primitives
# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html
class SSD1306(framebuf.FrameBuffer):
@staticmethod
def rgb(r, g, b):
return int((r > 127) or (g > 127) or (b > 127))
def __init__(self, width, height, external_vcc):
self.width = width
self.height = height
self.external_vcc = external_vcc
self.pages = self.height // 8
self.buffer = bytearray(self.pages * self.width)
super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
self.init_display()
def init_display(self):
for cmd in (
SET_DISP | 0x00, # off
# address setting
SET_MEM_ADDR,
0x00, # horizontal
# resolution and layout
SET_DISP_START_LINE | 0x00,
SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0
SET_MUX_RATIO,
self.height - 1,
SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0
SET_DISP_OFFSET,
0x00,
SET_COM_PIN_CFG,
0x02 if self.width > 2 * self.height else 0x12,
# timing and driving scheme
SET_DISP_CLK_DIV,
0x80,
SET_PRECHARGE,
0x22 if self.external_vcc else 0xF1,
SET_VCOM_DESEL,
0x30, # 0.83*Vcc
# display
SET_CONTRAST,
0xFF, # maximum
SET_ENTIRE_ON, # output follows RAM contents
SET_NORM_INV, # not inverted
# charge pump
SET_CHARGE_PUMP,
0x10 if self.external_vcc else 0x14,
SET_DISP | 0x01,
): # on
self.write_cmd(cmd)
self.fill(0)
self.show()
def poweroff(self):
self.write_cmd(SET_DISP | 0x00)
def poweron(self):
self.write_cmd(SET_DISP | 0x01)
def contrast(self, contrast):
self.write_cmd(SET_CONTRAST)
self.write_cmd(contrast)
def invert(self, invert):
self.write_cmd(SET_NORM_INV | (invert & 1))
def show(self):
x0 = 0
x1 = self.width - 1
if self.width == 64:
# displays with width of 64 pixels are shifted by 32
x0 += 32
x1 += 32
self.write_cmd(SET_COL_ADDR)
self.write_cmd(x0)
self.write_cmd(x1)
self.write_cmd(SET_PAGE_ADDR)
self.write_cmd(0)
self.write_cmd(self.pages - 1)
self.write_data(self.buffer)
class SSD1306_I2C(SSD1306):
def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False):
self.i2c = i2c
self.addr = addr
self.temp = bytearray(2)
self.write_list = [b"\x40", None] # Co=0, D/C#=1
super().__init__(width, height, external_vcc)
def write_cmd(self, cmd):
self.temp[0] = 0x80 # Co=1, D/C#=0
self.temp[1] = cmd
self.i2c.writeto(self.addr, self.temp)
def write_data(self, buf):
self.write_list[1] = buf
self.i2c.writevto(self.addr, self.write_list)
class SSD1306_SPI(SSD1306):
def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
self.rate = 10 * 1024 * 1024
dc.init(dc.OUT, value=0)
res.init(res.OUT, value=0)
cs.init(cs.OUT, value=1)
self.spi = spi
self.dc = dc
self.res = res
self.cs = cs
import time
self.res(1)
time.sleep_ms(1)
self.res(0)
time.sleep_ms(10)
self.res(1)
super().__init__(width, height, external_vcc)
def write_cmd(self, cmd):
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
self.cs(1)
self.dc(0)
self.cs(0)
self.spi.write(bytearray([cmd]))
self.cs(1)
def write_data(self, buf):
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
self.cs(1)
self.dc(1)
self.cs(0)
self.spi.write(buf)
self.cs(1)

Wyświetl plik

Wyświetl plik

@ -0,0 +1,81 @@
# SSD1331.py MicroPython driver for Adafruit 0.96" OLED display
# https://www.adafruit.com/product/684
# The MIT License (MIT)
# Copyright (c) Peter Hinch 2018-2020
# Released under the MIT license see LICENSE
# Show command
# 0x15, 0, 0x5f, 0x75, 0, 0x3f Col 0-95 row 0-63
# Initialisation command
# 0xae display off (sleep mode)
# 0xa0, 0x32 256 color RGB, horizontal RAM increment
# 0xa1, 0x00 Startline row 0
# 0xa2, 0x00 Vertical offset 0
# 0xa4 Normal display
# 0xa8, 0x3f Set multiplex ratio
# 0xad, 0x8e Ext supply
# 0xb0, 0x0b Disable power save mode
# 0xb1, 0x31 Phase period
# 0xb3, 0xf0 Oscillator frequency
# 0x8a, 0x64, 0x8b, 0x78, 0x8c, 0x64, # Precharge
# 0xbb, 0x3a Precharge voltge
# 0xbe, 0x3e COM deselect level
# 0x87, 0x06 master current attenuation factor
# 0x81, 0x91 contrast for all color "A" segment
# 0x82, 0x50 contrast for all color "B" segment
# 0x83, 0x7d contrast for all color "C" segment
# 0xaf Display on
import framebuf
import utime
import gc
import sys
# https://github.com/peterhinch/micropython-nano-gui/issues/2
# The ESP32 does not work reliably in SPI mode 1,1. Waveforms look correct.
# Mode 0, 0 works on ESP and STM
# Data sheet SPI spec: 150ns min clock period 6.66MHz
class SSD1331(framebuf.FrameBuffer):
# Convert r, g, b in range 0-255 to an 8 bit colour value
# acceptable to hardware: rrrgggbb
@staticmethod
def rgb(r, g, b):
return (r & 0xe0) | ((g >> 3) & 0x1c) | (b >> 6)
def __init__(self, spi, pincs, pindc, pinrs, height=64, width=96, init_spi=False):
self._spi = spi
self._pincs = pincs
self._pindc = pindc # 1 = data 0 = cmd
self.height = height # Required by Writer class
self.width = width
self._spi_init = init_spi
mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color.
gc.collect()
self.buffer = bytearray(self.height * self.width)
super().__init__(self.buffer, self.width, self.height, mode)
pinrs(0) # Pulse the reset line
utime.sleep_ms(1)
pinrs(1)
utime.sleep_ms(1)
self._write(b'\xae\xa0\x32\xa1\x00\xa2\x00\xa4\xa8\x3f\xad\x8e\xb0'\
b'\x0b\xb1\x31\xb3\xf0\x8a\x64\x8b\x78\x8c\x64\xbb\x3a\xbe\x3e\x87'\
b'\x06\x81\x91\x82\x50\x83\x7d\xaf', 0)
gc.collect()
self.show()
def _write(self, buf, dc):
self._pincs(1)
self._pindc(dc)
self._pincs(0)
self._spi.write(buf)
self._pincs(1)
def show(self, _cmd=b'\x15\x00\x5f\x75\x00\x3f'): # Pre-allocate
if self._spi_init: # A callback was passed
self._spi_init(spi) # Bus may be shared
self._write(_cmd, 0)
self._write(self.buffer, 1)

Wyświetl plik

@ -0,0 +1,81 @@
# SSD1331.py MicroPython driver for Adafruit 0.96" OLED display
# https://www.adafruit.com/product/684
# Copyright (c) Peter Hinch 2019-2020
# Released under the MIT license see LICENSE
# Show command
# 0x15, 0, 0x5f, 0x75, 0, 0x3f Col 0-95 row 0-63
# Initialisation command
# 0xae display off (sleep mode)
# 0xa0, 0x72 16 bit RGB, horizontal RAM increment
# 0xa1, 0x00 Startline row 0
# 0xa2, 0x00 Vertical offset 0
# 0xa4 Normal display
# 0xa8, 0x3f Set multiplex ratio
# 0xad, 0x8e Ext supply
# 0xb0, 0x0b Disable power save mode
# 0xb1, 0x31 Phase period
# 0xb3, 0xf0 Oscillator frequency
# 0x8a, 0x64, 0x8b, 0x78, 0x8c, 0x64, # Precharge
# 0xbb, 0x3a Precharge voltge
# 0xbe, 0x3e COM deselect level
# 0x87, 0x06 master current attenuation factor
# 0x81, 0x91 contrast for all color "A" segment
# 0x82, 0x50 contrast for all color "B" segment
# 0x83, 0x7d contrast for all color "C" segment
# 0xaf Display on
import framebuf
import utime
import gc
# https://github.com/peterhinch/micropython-nano-gui/issues/2
# The ESP32 does not work reliably in SPI mode 1,1. Waveforms look correct.
# Mode 0, 0 works on ESP and STM
# Data sheet SPI spec: 150ns min clock period 6.66MHz
class SSD1331(framebuf.FrameBuffer):
# Convert r, g, b in range 0-255 to a 16 bit colour value RGB565
# acceptable to hardware: rrrrrggggggbbbbb
# LS byte of 16 bit result is shifted out 1st
@staticmethod
def rgb(r, g, b):
return ((b & 0xf8) << 5) | ((g & 0x1c) << 11) | (r & 0xf8) | ((g & 0xe0) >> 5)
def __init__(self, spi, pincs, pindc, pinrs, height=64, width=96, init_spi=False):
self._spi = spi
self._pincs = pincs
self._pindc = pindc # 1 = data 0 = cmd
self.height = height # Required by Writer class
self.width = width
self._spi_init = init_spi
mode = framebuf.RGB565
gc.collect()
self.buffer = bytearray(self.height * self.width * 2)
super().__init__(self.buffer, self.width, self.height, mode)
pinrs(0) # Pulse the reset line
utime.sleep_ms(1)
pinrs(1)
utime.sleep_ms(1)
if self._spi_init: # A callback was passed
self._spi_init(spi) # Bus may be shared
self._write(b'\xae\xa0\x72\xa1\x00\xa2\x00\xa4\xa8\x3f\xad\x8e\xb0'\
b'\x0b\xb1\x31\xb3\xf0\x8a\x64\x8b\x78\x8c\x64\xbb\x3a\xbe\x3e\x87'\
b'\x06\x81\x91\x82\x50\x83\x7d\xaf', 0)
gc.collect()
self.show()
def _write(self, buf, dc):
self._pincs(1)
self._pindc(dc)
self._pincs(0)
self._spi.write(buf)
self._pincs(1)
def show(self, _cmd=b'\x15\x00\x5f\x75\x00\x3f'): # Pre-allocate
if self._spi_init: # A callback was passed
self._spi_init(spi) # Bus may be shared
self._write(_cmd, 0)
self._write(self.buffer, 1)

Wyświetl plik

@ -0,0 +1,31 @@
# test_colors.py Test color rendition on SSD1331 (Adafruit 0.96" OLED).
# Author Peter Hinch
# Released under the MIT licence.
import machine
# Can test either driver
# from ssd1331 import SSD1331 as SSD
from ssd1331_16bit import SSD1331 as SSD
# Initialise hardware
def setup():
pdc = machine.Pin('X1', machine.Pin.OUT_PP, value=0)
pcs = machine.Pin('X2', machine.Pin.OUT_PP, value=1)
prst = machine.Pin('X3', machine.Pin.OUT_PP, value=1)
spi = machine.SPI(1)
ssd = SSD(spi, pcs, pdc, prst) # Create a display instance
return ssd
ssd = setup()
ssd.fill(0)
# Operate in landscape mode
x = 0
for y in range(96):
ssd.line(y, x, y, x+20, ssd.rgb(round(255*y/96), 0, 0))
x += 20
for y in range(96):
ssd.line(y, x, y, x+20, ssd.rgb(0, round(255*y/96), 0))
x += 20
for y in range(96):
ssd.line(y, x, y, x+20, ssd.rgb(0, 0, round(255*y/96)))
ssd.show()

Wyświetl plik

Wyświetl plik

@ -0,0 +1,143 @@
# SSD1351.py MicroPython driver for Adafruit color OLED displays.
# STM (Pyboard etc) version. Display refresh takes 41ms on Pyboard V1.0
# Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431
# Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673
# For wiring details see drivers/ADAFRUIT.md in this repo.
# This driver is based on the Adafruit C++ library for Arduino
# https://github.com/adafruit/Adafruit-SSD1351-library.git
# Copyright (c) Peter Hinch 2018-2020
# Released under the MIT license see LICENSE
import framebuf
import utime
import gc
import micropython
from uctypes import addressof
# Timings with standard emitter
# 1.86ms * 128 lines = 240ms. copy dominates: show() took 272ms
# Buffer transfer time = 272-240 = 32ms which accords with expected:
# 128*128*2/10500000 = 31.2ms (2 bytes/pixel, baudrate = 10.5MHz)
# With assembler .show() takes 41ms
# Copy a buffer with 8 bit rrrgggbb pixels to a buffer of 16 bit pixels.
@micropython.asm_thumb
def _lcopy(r0, r1, r2): # r0 dest, r1 source, r2 no. of bytes
label(LOOP)
ldrb(r3, [r1, 0]) # Get source byte to r3, r5, r6
mov(r5, r3)
mov(r6, r3)
mov(r4, 3)
and_(r3, r4)
mov(r4, 6)
lsl(r3, r4)
mov(r4, 0x1c)
and_(r5, r4)
mov(r4, 2)
lsr(r5, r4)
orr(r3, r5)
strb(r3, [r0, 0])
mov(r4, 0xe0)
and_(r6, r4)
mov(r4, 3)
lsr(r6, r4)
strb(r6, [r0, 1])
add(r0, 2)
add(r1, 1)
sub(r2, 1)
bne(LOOP)
# Initialisation commands in cmd_init:
# 0xfd, 0x12, 0xfd, 0xb1, # Unlock command mode
# 0xae, # display off (sleep mode)
# 0xb3, 0xf1, # clock div
# 0xca, 0x7f, # mux ratio
# 0xa0, 0x74, # setremap 0x74
# 0x15, 0, 0x7f, # setcolumn
# 0x75, 0, 0x7f, # setrow
# 0xa1, 0, # set display start line
# 0xa2, 0, # displayoffset
# 0xb5, 0, # setgpio
# 0xab, 1, # functionselect: serial interface, internal Vdd regulator
# 0xb1, 0x32, # Precharge
# 0xbe, 0x05, # vcommh
# 0xa6, # normaldisplay
# 0xc1, 0xc8, 0x80, 0xc8, # contrast abc
# 0xc7, 0x0f, # Master contrast
# 0xb4, 0xa0, 0xb5, 0x55, # set vsl (see datasheet re ext circuit)
# 0xb6, 1, # Precharge 2
# 0xaf, # Display on
# SPI baudrate: Pyboard can produce 10.5MHz or 21MHz. Datasheet gives max of 20MHz.
# Attempt to use 21MHz failed but might work on a PCB or with very short leads.
class SSD1351(framebuf.FrameBuffer):
# Convert r, g, b in range 0-255 to an 8 bit colour value
# acceptable to hardware: rrrgggbb
@staticmethod
def rgb(r, g, b):
return (r & 0xe0) | ((g >> 3) & 0x1c) | (b >> 6)
def __init__(self, spi, pincs, pindc, pinrs, height=128, width=128, init_spi=False):
if height not in (96, 128):
raise ValueError('Unsupported height {}'.format(height))
self.spi = spi
self.spi_init = init_spi
self.pincs = pincs
self.pindc = pindc # 1 = data 0 = cmd
self.height = height # Required by Writer class
self.width = width
mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color.
gc.collect()
self.buffer = bytearray(self.height * self.width)
super().__init__(self.buffer, self.width, self.height, mode)
self.linebuf = bytearray(self.width * 2)
pinrs(0) # Pulse the reset line
utime.sleep_ms(1)
pinrs(1)
utime.sleep_ms(1)
if self.spi_init: # A callback was passed
self.spi_init(spi) # Bus may be shared
# See above comment to explain this allocation-saving gibberish.
self._write(b'\xfd\x12\xfd\xb1\xae\xb3\xf1\xca\x7f\xa0\x74'\
b'\x15\x00\x7f\x75\x00\x7f\xa1\x00\xa2\x00\xb5\x00\xab\x01'\
b'\xb1\x32\xbe\x05\xa6\xc1\xc8\x80\xc8\xc7\x0f'\
b'\xb4\xa0\xb5\x55\xb6\x01\xaf', 0)
self.show()
gc.collect()
def _write(self, buf, dc):
self.pincs(1)
self.pindc(dc)
self.pincs(0)
self.spi.write(buf)
self.pincs(1)
# Write lines from the framebuf out of order to match the mapping of the
# SSD1351 RAM to the OLED device.
def show(self):
lb = self.linebuf
buf = self.buffer
if self.spi_init: # A callback was passed
self.spi_init(self.spi) # Bus may be shared
self._write(b'\x5c', 0) # Enable data write
if self.height == 128:
for l in range(128):
l0 = (95 - l) % 128 # 95 94 .. 1 0 127 126 .. 96
start = l0 * self.width
_lcopy(lb, addressof(buf) + start, self.width)
self._write(lb, 1) # Send a line
else:
for l in range(128):
if l < 64:
start = (63 -l) * self.width # 63 62 .. 1 0
_lcopy(lb, addressof(buf) + start, self.width)
self._write(lb, 1) # Send a line
elif l < 96: # This is daft but I can't get setrow to work
self._write(lb, 1) # Let RAM counter increase
else:
start = (191 - l) * self.width # 127 126 .. 95
_lcopy(lb, addressof(buf) + start, self.width)
self._write(lb, 1) # Send a line

Wyświetl plik

@ -0,0 +1,108 @@
# SSD1351_16bit.py MicroPython driver for Adafruit color OLED displays.
# Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431
# Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673
# For wiring details see drivers/ADAFRUIT.md in this repo.
# This driver is based on the Adafruit C++ library for Arduino
# https://github.com/adafruit/Adafruit-SSD1351-library.git
# Copyright (c) Peter Hinch 2019-2020
# Released under the MIT license see LICENSE
import framebuf
import utime
import gc
import micropython
from uctypes import addressof
# https://github.com/peterhinch/micropython-nano-gui/issues/2
# The ESP32 does not work reliably in SPI mode 1,1. Waveforms look correct.
# Now using 0,0 on STM and ESP32
# Initialisation commands in cmd_init:
# 0xfd, 0x12, 0xfd, 0xb1, # Unlock command mode
# 0xae, # display off (sleep mode)
# 0xb3, 0xf1, # clock div
# 0xca, 0x7f, # mux ratio
# 0xa0, 0x74, # setremap 0x74
# 0x15, 0, 0x7f, # setcolumn
# 0x75, 0, 0x7f, # setrow
# 0xa1, 0, # set display start line
# 0xa2, 0, # displayoffset
# 0xb5, 0, # setgpio
# 0xab, 1, # functionselect: serial interface, internal Vdd regulator
# 0xb1, 0x32, # Precharge
# 0xbe, 0x05, # vcommh
# 0xa6, # normaldisplay
# 0xc1, 0xc8, 0x80, 0xc8, # contrast abc
# 0xc7, 0x0f, # Master contrast
# 0xb4, 0xa0, 0xb5, 0x55, # set vsl (see datasheet re ext circuit)
# 0xb6, 1, # Precharge 2
# 0xaf, # Display on
class SSD1351(framebuf.FrameBuffer):
# Convert r, g, b in range 0-255 to a 16 bit colour value RGB565
# acceptable to hardware: rrrrrggggggbbbbb
@staticmethod
def rgb(r, g, b):
return ((r & 0xf8) << 5) | ((g & 0x1c) << 11) | (b & 0xf8) | ((g & 0xe0) >> 5)
def __init__(self, spi, pincs, pindc, pinrs, height=128, width=128, init_spi=False):
if height not in (96, 128):
raise ValueError('Unsupported height {}'.format(height))
self.spi = spi
self.spi_init = init_spi
self.pincs = pincs
self.pindc = pindc # 1 = data 0 = cmd
self.height = height # Required by Writer class
self.width = width
mode = framebuf.RGB565
gc.collect()
self.buffer = bytearray(self.height * self.width * 2)
super().__init__(self.buffer, self.width, self.height, mode)
self.mvb = memoryview(self.buffer)
pinrs(0) # Pulse the reset line
utime.sleep_ms(1)
pinrs(1)
utime.sleep_ms(1)
if self.spi_init: # A callback was passed
self.spi_init(spi) # Bus may be shared
# See above comment to explain this allocation-saving gibberish.
self._write(b'\xfd\x12\xfd\xb1\xae\xb3\xf1\xca\x7f\xa0\x74'\
b'\x15\x00\x7f\x75\x00\x7f\xa1\x00\xa2\x00\xb5\x00\xab\x01'\
b'\xb1\x32\xbe\x05\xa6\xc1\xc8\x80\xc8\xc7\x0f'\
b'\xb4\xa0\xb5\x55\xb6\x01\xaf', 0)
self.show()
gc.collect()
def _write(self, mv, dc):
self.pincs(1)
self.pindc(dc)
self.pincs(0)
self.spi.write(bytes(mv))
self.pincs(1)
# Write lines from the framebuf out of order to match the mapping of the
# SSD1351 RAM to the OLED device.
def show(self):
mvb = self.mvb
bw = self.width * 2 # Width in bytes
if self.spi_init: # A callback was passed
self.spi_init(self.spi) # Bus may be shared
self._write(b'\x5c', 0) # Enable data write
if self.height == 128:
for l in range(128):
l0 = (95 - l) % 128 # 95 94 .. 1 0 127 126 .. 96
start = l0 * self.width * 2
self._write(mvb[start : start + bw], 1) # Send a line
else:
for l in range(128):
if l < 64:
start = (63 -l) * self.width * 2 # 63 62 .. 1 0
elif l < 96:
start = 0
else:
start = (191 - l) * self.width * 2 # 127 126 .. 95
self._write(mvb[start : start + bw], 1) # Send a line

Wyświetl plik

@ -0,0 +1,142 @@
# SSD1351_4bit.py MicroPython driver for Adafruit color OLED displays.
# This is cross-platform and uses 4 bit color for minimum RAM usage.
# Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431
# Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673
# For wiring details see drivers/ADAFRUIT.md in this repo.
# This driver is based on the Adafruit C++ library for Arduino
# https://github.com/adafruit/Adafruit-SSD1351-library.git
# Copyright (c) Peter Hinch 2020
# Released under the MIT license see LICENSE
import framebuf
import utime
import gc
import micropython
from uctypes import addressof
# https://github.com/peterhinch/micropython-nano-gui/issues/2
# The ESP32 does not work reliably in SPI mode 1,1. Waveforms look correct.
# Now using 0,0 on STM and ESP32
# ESP32 produces 20MHz, Pyboard D SF2W: 15MHz, SF6W: 18MHz, Pyboard 1.1: 10.5MHz
# OLED datasheet: should support 20MHz
def spi_init(spi):
spi.init(baudrate=20_000_000) # Data sheet: should support 20MHz
@micropython.viper
def _lcopy(dest:ptr8, source:ptr8, lut:ptr8, length:int):
n = 0
for x in range(length):
c = source[x]
d = (c & 0xf0) >> 3 # 2* LUT indices (LUT is 16 bit color)
e = (c & 0x0f) << 1
dest[n] = lut[d]
n += 1
dest[n] = lut[d + 1]
n += 1
dest[n] = lut[e]
n += 1
dest[n] = lut[e + 1]
n += 1
# Initialisation commands in cmd_init:
# 0xfd, 0x12, 0xfd, 0xb1, # Unlock command mode
# 0xae, # display off (sleep mode)
# 0xb3, 0xf1, # clock div
# 0xca, 0x7f, # mux ratio
# 0xa0, 0x74, # setremap 0x74
# 0x15, 0, 0x7f, # setcolumn
# 0x75, 0, 0x7f, # setrow
# 0xa1, 0, # set display start line
# 0xa2, 0, # displayoffset
# 0xb5, 0, # setgpio
# 0xab, 1, # functionselect: serial interface, internal Vdd regulator
# 0xb1, 0x32, # Precharge
# 0xbe, 0x05, # vcommh
# 0xa6, # normaldisplay
# 0xc1, 0xc8, 0x80, 0xc8, # contrast abc
# 0xc7, 0x0f, # Master contrast
# 0xb4, 0xa0, 0xb5, 0x55, # set vsl (see datasheet re ext circuit)
# 0xb6, 1, # Precharge 2
# 0xaf, # Display on
class SSD1351(framebuf.FrameBuffer):
lut = bytearray(32)
# Convert r, g, b in range 0-255 to a 16 bit colour value
# LS byte goes into LUT offset 0, MS byte into offset 1
# Same mapping in linebuf so LS byte is shifted out 1st
# Note pretty colors in datasheet don't match actual colors. See doc.
@staticmethod
def rgb(r, g, b):
return (r & 0xf8) << 5 | (g & 0x1c) << 11 | (g & 0xe0) >> 5 | (b & 0xf8)
def __init__(self, spi, pincs, pindc, pinrs, height=128, width=128, init_spi=False):
if height not in (96, 128):
raise ValueError('Unsupported height {}'.format(height))
self.spi = spi
self.pincs = pincs
self.pindc = pindc # 1 = data 0 = cmd
self.height = height # Required by Writer class
self.width = width
self.spi_init = init_spi
mode = framebuf.GS4_HMSB # Use 4bit greyscale.
gc.collect()
self.buffer = bytearray(self.height * self.width // 2)
super().__init__(self.buffer, self.width, self.height, mode)
self.linebuf = bytearray(self.width * 2)
pinrs(0) # Pulse the reset line
utime.sleep_ms(1)
pinrs(1)
utime.sleep_ms(1)
if self.spi_init: # A callback was passed
self.spi_init(spi) # Bus may be shared
# See above comment to explain this allocation-saving gibberish.
self._write(b'\xfd\x12\xfd\xb1\xae\xb3\xf1\xca\x7f\xa0\x74'\
b'\x15\x00\x7f\x75\x00\x7f\xa1\x00\xa2\x00\xb5\x00\xab\x01'\
b'\xb1\x32\xbe\x05\xa6\xc1\xc8\x80\xc8\xc7\x0f'\
b'\xb4\xa0\xb5\x55\xb6\x01\xaf', 0)
gc.collect()
self.show()
def _write(self, buf, dc):
self.pincs(1)
self.pindc(dc)
self.pincs(0)
self.spi.write(buf)
self.pincs(1)
# Write lines from the framebuf out of order to match the mapping of the
# SSD1351 RAM to the OLED device.
def show(self): # 44ms on Pyboard 1.x
clut = SSD1351.lut
lb = self.linebuf
wd = self.width // 2
buf = memoryview(self.buffer)
if self.spi_init: # A callback was passed
self.spi_init(self.spi) # Bus may be shared
self._write(b'\x5c', 0) # Enable data write
if self.height == 128:
for l in range(128):
l0 = (95 - l) % 128 # 95 94 .. 1 0 127 126...
start = l0 * wd
_lcopy(lb, buf[start : start + wd], clut, wd)
self._write(lb, 1) # Send a line
else:
for l in range(128):
if l < 64:
start = (63 -l) * wd
_lcopy(lb, buf[start : start + wd], clut, wd)
elif l < 96: # This is daft but I can't get setrow to work
pass # Let RAM counter increase
else:
start = (191 - l) * wd
_lcopy(lb, buf[start : start + wd], clut, wd)
self._write(lb, 1) # Send a line

Wyświetl plik

@ -0,0 +1,131 @@
# SSD1351_generic.py MicroPython driver for Adafruit color OLED displays.
# This is cross-platform. It lacks STM optimisations and is slower than the
# standard version.
# Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431
# Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673
# For wiring details see drivers/ADAFRUIT.md in this repo.
# This driver is based on the Adafruit C++ library for Arduino
# https://github.com/adafruit/Adafruit-SSD1351-library.git
# Copyright (c) Peter Hinch 2018-2020
# Released under the MIT license see LICENSE
import framebuf
import utime
import gc
import micropython
from uctypes import addressof
import sys
# https://github.com/peterhinch/micropython-nano-gui/issues/2
# The ESP32 does not work reliably in SPI mode 1,1. Waveforms look correct.
# Now using 0,0 on STM and ESP32
# Timings with standard emitter
# 1.86ms * 128 lines = 240ms. copy dominates: show() took 272ms
# Buffer transfer time = 272-240 = 32ms which accords with expected:
# 128*128*2/10500000 = 31.2ms (2 bytes/pixel, baudrate = 10.5MHz)
# With viper emitter show() takes 47ms vs 41ms for assembler.
@micropython.viper
def _lcopy(dest:ptr8, source:ptr8, length:int):
n = 0
for x in range(length):
c = source[x]
dest[n] = ((c & 3) << 6) | ((c & 0x1c) >> 2) # Blue green
n += 1
dest[n] = (c & 0xe0) >> 3 # Red
n += 1
# Initialisation commands in cmd_init:
# 0xfd, 0x12, 0xfd, 0xb1, # Unlock command mode
# 0xae, # display off (sleep mode)
# 0xb3, 0xf1, # clock div
# 0xca, 0x7f, # mux ratio
# 0xa0, 0x74, # setremap 0x74
# 0x15, 0, 0x7f, # setcolumn
# 0x75, 0, 0x7f, # setrow
# 0xa1, 0, # set display start line
# 0xa2, 0, # displayoffset
# 0xb5, 0, # setgpio
# 0xab, 1, # functionselect: serial interface, internal Vdd regulator
# 0xb1, 0x32, # Precharge
# 0xbe, 0x05, # vcommh
# 0xa6, # normaldisplay
# 0xc1, 0xc8, 0x80, 0xc8, # contrast abc
# 0xc7, 0x0f, # Master contrast
# 0xb4, 0xa0, 0xb5, 0x55, # set vsl (see datasheet re ext circuit)
# 0xb6, 1, # Precharge 2
# 0xaf, # Display on
class SSD1351(framebuf.FrameBuffer):
# Convert r, g, b in range 0-255 to an 8 bit colour value
# acceptable to hardware: rrrgggbb
@staticmethod
def rgb(r, g, b):
return (r & 0xe0) | ((g >> 3) & 0x1c) | (b >> 6)
def __init__(self, spi, pincs, pindc, pinrs, height=128, width=128, init_spi=False):
if height not in (96, 128):
raise ValueError('Unsupported height {}'.format(height))
self.spi = spi
self.spi_init = init_spi
self.pincs = pincs
self.pindc = pindc # 1 = data 0 = cmd
self.height = height # Required by Writer class
self.width = width
mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color.
gc.collect()
self.buffer = bytearray(self.height * self.width)
super().__init__(self.buffer, self.width, self.height, mode)
self.linebuf = bytearray(self.width * 2)
pinrs(0) # Pulse the reset line
utime.sleep_ms(1)
pinrs(1)
utime.sleep_ms(1)
if self.spi_init: # A callback was passed
self.spi_init(spi) # Bus may be shared
# See above comment to explain this allocation-saving gibberish.
self._write(b'\xfd\x12\xfd\xb1\xae\xb3\xf1\xca\x7f\xa0\x74'\
b'\x15\x00\x7f\x75\x00\x7f\xa1\x00\xa2\x00\xb5\x00\xab\x01'\
b'\xb1\x32\xbe\x05\xa6\xc1\xc8\x80\xc8\xc7\x0f'\
b'\xb4\xa0\xb5\x55\xb6\x01\xaf', 0)
gc.collect()
self.show()
def _write(self, buf, dc):
self.pincs(1)
self.pindc(dc)
self.pincs(0)
self.spi.write(buf)
self.pincs(1)
# Write lines from the framebuf out of order to match the mapping of the
# SSD1351 RAM to the OLED device.
def show(self):
lb = self.linebuf
buf = memoryview(self.buffer)
if self.spi_init: # A callback was passed
self.spi_init(self.spi) # Bus may be shared
self._write(b'\x5c', 0) # Enable data write
if self.height == 128:
for l in range(128):
l0 = (95 - l) % 128 # 95 94 .. 1 0 127 126...
start = l0 * self.width
_lcopy(lb, buf[start : start + self.width], self.width)
self._write(lb, 1) # Send a line
else:
for l in range(128):
if l < 64:
start = (63 -l) * self.width
_lcopy(lb, buf[start : start + self.width], self.width)
self._write(lb, 1) # Send a line
elif l < 96: # This is daft but I can't get setrow to work
self._write(lb, 1) # Let RAM counter increase
else:
start = (191 - l) * self.width
_lcopy(lb, buf[start : start + self.width], self.width)
self._write(lb, 1) # Send a line

Wyświetl plik

@ -0,0 +1,18 @@
# test128_row.py Test for device driver on 96 row display
import machine
from ssd1351 import SSD1351 as SSD
# Initialise hardware
def setup():
pdc = machine.Pin('X1', machine.Pin.OUT_PP, value=0)
pcs = machine.Pin('X2', machine.Pin.OUT_PP, value=1)
prst = machine.Pin('X3', machine.Pin.OUT_PP, value=1)
spi = machine.SPI(1)
ssd = SSD(spi, pcs, pdc, prst) # Create a display instance
return ssd
ssd = setup()
ssd.fill(0)
ssd.line(0, 0, 127, 127, ssd.rgb(0, 255, 0))
ssd.rect(0, 0, 15, 15, ssd.rgb(255, 0, 0))
ssd.show()

Wyświetl plik

@ -0,0 +1,18 @@
# test96.py Test for device driver on 96 row display
import machine
from ssd1351 import SSD1351 as SSD
# Initialise hardware
def setup():
pdc = machine.Pin('X1', machine.Pin.OUT_PP, value=0)
pcs = machine.Pin('X2', machine.Pin.OUT_PP, value=1)
prst = machine.Pin('X3', machine.Pin.OUT_PP, value=1)
spi = machine.SPI(1)
ssd = SSD(spi, pcs, pdc, prst, height=96) # Create a display instance
return ssd
ssd = setup()
ssd.fill(0)
ssd.line(0, 0, 127, 95, ssd.rgb(0, 255, 0))
ssd.rect(0, 0, 15, 15, ssd.rgb(255, 0, 0))
ssd.show()

Wyświetl plik

@ -0,0 +1,27 @@
# test_colors_96.py
# Check color mapping. Runs on 96 row display (change height for 128 row)
import machine
from ssd1351_16bit import SSD1351 as SSD
# Initialise hardware
def setup():
pdc = machine.Pin('X1', machine.Pin.OUT_PP, value=0)
pcs = machine.Pin('X2', machine.Pin.OUT_PP, value=1)
prst = machine.Pin('X3', machine.Pin.OUT_PP, value=1)
spi = machine.SPI(1)
ssd = SSD(spi, pcs, pdc, prst, height=96) # Create a display instance
return ssd
ssd = setup()
ssd.fill(0)
x = 0
for y in range(128):
ssd.line(y, x, y, x+20, ssd.rgb(round(255*y/128), 0, 0))
x += 20
for y in range(128):
ssd.line(y, x, y, x+20, ssd.rgb(0, round(255*y/128), 0))
x += 20
for y in range(128):
ssd.line(y, x, y, x+20, ssd.rgb(0, 0, round(255*y/128)))
ssd.show()

Wyświetl plik

@ -0,0 +1,53 @@
https://learn.adafruit.com/adafruit-1-44-color-tft-with-micro-sd-socket/python-usage
# disp = st7735.ST7735R(spi, rotation=90, # 1.8" ST7735R
# disp = st7735.ST7735R(spi, rotation=270, height=128, x_offset=2, y_offset=3, # 1.44" ST7735R
# disp = st7735.ST7735R(spi, rotation=90, bgr=True, # 0.96" MiniTFT ST7735R
# disp = ssd1351.SSD1351(spi, rotation=180,
import machine
import gc
from time import sleep_ms
from drivers.st7735r.st7735r144 import ST7735R as SSD
height = 128
width = 128 # 160
pdc = machine.Pin('Y1', machine.Pin.OUT_PP, value=0)
pcs = machine.Pin('Y2', machine.Pin.OUT_PP, value=1)
prst = machine.Pin('Y3', machine.Pin.OUT_PP, value=1)
spi = machine.SPI(2, baudrate=12_000_000)
gc.collect() # Precaution before instantiating framebuf
ssd = SSD(spi, pcs, pdc, prst, height, width) # Create a display instance
ssd.fill(0)
ssd.show()
sleep_ms(1000)
ssd.line(0, 0, width - 1, height - 1, ssd.rgb(0, 255, 0)) # Green diagonal corner-to-corner
ssd.rect(0, 0, 15, 15, ssd.rgb(255, 0, 0)) # Red square at top left
ssd.show()
sleep_ms(2000)
ssd.fill(0)
ssd.show()
ssd.line(0, 0, width - 1, height - 1, ssd.rgb(0, 255, 255))
ssd.rect(0, 0, 40, 40, ssd.rgb(0, 0, 255))
ssd.rect(width - 41, height - 41, 40, 40, ssd.rgb(0, 0, 255))
ssd.show()
Attempt to rotate 1.44" unit fell foul of x and y offsets with 1-2 pixel errors.
Rotation passed to ctor
def __init__(self, spi, cs, dc, rst, height=128, width=128, init_spi=False, rotation=0):
...
quad, mod = divmod(rotation, 90) # Get quadrant
if mod:
raise ValueError('Rotation should be divisible by 90')
...
self._init(quad)
def _init(self, quad):
...
rval = (b'\x20', b'\x40', b'\xe0', b'\x80')[quad]
wcd(b'\x36', rval)

Wyświetl plik

@ -0,0 +1,154 @@
# st7735r.py Driver for 1.8" 128*160 ST7735R LCD displays for nano-gui
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2018-2020 Peter Hinch
# Supported display
# Adfruit 1.8' Color TFT LCD display with MicroSD Card Breakout:
# https://www.adafruit.com/product/358
# Based on
# https://github.com/adafruit/Adafruit_CircuitPython_ST7735R/blob/master/adafruit_st7735r.py
# https://github.com/GuyCarver/MicroPython/blob/master/lib/ST7735.py
# https://github.com/boochow/MicroPython-ST7735
# https://learn.adafruit.com/adafruit-1-44-color-tft-with-micro-sd-socket/python-usage
# disp = st7735.ST7735R(spi, rotation=90, # 1.8" ST7735R
# disp = st7735.ST7735R(spi, rotation=270, height=128, x_offset=2, y_offset=3, # 1.44" ST7735R
from time import sleep_ms
import framebuf
import gc
import micropython
# Datasheet para 8.4 scl write cycle 66ns == 15MHz
# _lcopy: copy a line in 8 bit format to one in 12 bit RGB444. para 9.8.20.
# 2 bytes become 3 in destination. Source format:
# < D7 D6 D5 D4 D3 D2 D1 D0>
# <R02 R01 R00 G02 G01 G00 B01 B00> <R12 R11 R10 G12 G11 G10 B11 B10>
# dest:
# <R02 R01 R00 0 G02 G01 G00 0> <B01 B00 0 0 R12 R11 R10 0> <G12 G11 G10 0 B11 B10 0 0>
@micropython.viper
def _lcopy(dest:ptr8, source:ptr8, length:int):
n = 0
for x in range(0, length, 2):
c = source[x]
d = source[x + 1]
dest[n] = (c & 0xe0) | ((c & 0x1c) >> 1) # R0 G0
n += 1
dest[n] = ((c & 3) << 6) | ((d & 0xe0) >> 4) # B0 R1
n += 1
dest[n] = ((d & 0x1c) << 3) | ((d & 3) << 2) # G1 B1
n += 1
class ST7735R(framebuf.FrameBuffer):
# Convert r, g, b in range 0-255 to an 8 bit colour value
# rrrgggbb. Converted to 12 bit on the fly.
@staticmethod
def rgb(r, g, b):
return (r & 0xe0) | ((g >> 3) & 0x1c) | (b >> 6)
# rst and cs are active low, SPI is mode 0
def __init__(self, spi, cs, dc, rst, height=128, width=160, usd=False, init_spi=False):
self._spi = spi
self._rst = rst # Pins
self._dc = dc
self._cs = cs
self.height = height # Required by Writer class
self.width = width
self._spi_init = init_spi
mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color.
gc.collect()
buf = bytearray(height * width)
self._mvb = memoryview(buf)
super().__init__(buf, width, height, mode)
self._linebuf = bytearray(int(width * 3 // 2)) # 12 bit color out
self._init(usd)
self.show()
# Hardware reset
def _hwreset(self):
self._dc(0)
self._rst(1)
sleep_ms(1)
self._rst(0)
sleep_ms(1)
self._rst(1)
sleep_ms(1)
# Write a command, a bytes instance (in practice 1 byte).
def _wcmd(self, buf):
self._dc(0)
self._cs(0)
self._spi.write(buf)
self._cs(1)
# Write a command followed by a data arg.
def _wcd(self, c, d):
self._dc(0)
self._cs(0)
self._spi.write(c)
self._cs(1)
self._dc(1)
self._cs(0)
self._spi.write(d)
self._cs(1)
# Initialise the hardware. Blocks 500ms.
def _init(self, usd):
self._hwreset() # Hardware reset. Blocks 3ms
if self._spi_init: # A callback was passed
self._spi_init(self._spi) # Bus may be shared
cmd = self._wcmd
wcd = self._wcd
cmd(b'\x01') # SW reset datasheet specifies > 120ms
sleep_ms(150)
cmd(b'\x11') # SLPOUT
sleep_ms(256) # Adafruit delay (datsheet 120ms)
wcd(b'\xb1', b'\x01\x2C\x2D') # FRMCTRL1
wcd(b'\xb2', b'\x01\x2C\x2D') # FRMCTRL2
wcd(b'\xb3', b'\x01\x2C\x2D\x01\x2C\x2D') # FRMCTRL3
wcd(b'\xb4', b'\x07') # INVCTR line inversion
wcd(b'\xc0', b'\xa2\x02\x84') # PWCTR1 GVDD = 4.7V, 1.0uA
wcd(b'\xc1', b'\xc5') # PWCTR2 VGH=14.7V, VGL=-7.35V
wcd(b'\xc2', b'\x0a\x00') # PWCTR3 Opamp current small, Boost frequency
wcd(b'\xc3', b'\x8a\x2a') # PWCTR4
wcd(b'\xc4', b'\x8a\xee') # PWCTR5
wcd(b'\xc5', b'\x0e') # VMCTR1 VCOMH = 4V, VOML = -1.1V NOTE I make VCOM == -0.775V
cmd(b'\x20') # INVOFF
# d7..d5 of MADCTL determine rotation/orientation
if self.height > self.width:
wcd(b'\x36', b'\x80' if usd else b'\x40') # MADCTL: RGB portrait mode
else:
wcd(b'\x36', b'\xe0' if usd else b'\x20') # MADCTL: RGB landscape mode
wcd(b'\x3a', b'\x03') # COLMOD 12 bit
wcd(b'\xe0', b'\x02\x1c\x07\x12\x37\x32\x29\x2d\x29\x25\x2B\x39\x00\x01\x03\x10') # GMCTRP1 Gamma
wcd(b'\xe1', b'\x03\x1d\x07\x06\x2E\x2C\x29\x2D\x2E\x2E\x37\x3F\x00\x00\x02\x10') # GMCTRN1
wcd(b'\x2a', int.to_bytes(self.width, 4, 'big')) # CASET column address 0 start, 160 end
wcd(b'\x2b', int.to_bytes(self.height, 4, 'big')) # RASET
cmd(b'\x13') # NORON
sleep_ms(10)
cmd(b'\x29') # DISPON
sleep_ms(100)
def show(self): # Blocks 36ms on Pyboard D at stock frequency (160*128)
wd = self.width
ht = self.height
lb = self._linebuf
buf = self._mvb
self._dc(0)
self._cs(0)
if self._spi_init: # A callback was passed
self._spi_init(self._spi) # Bus may be shared
self._spi.write(b'\x2c') # RAMWR
self._dc(1)
for start in range(wd * (ht - 1), -1, - wd): # For each line
_lcopy(lb, buf[start :], wd) # Copy and map colors (68us)
self._spi.write(lb)
self._cs(1)

Wyświetl plik

@ -0,0 +1,158 @@
# st7735r144.py Driver for ST7735R 1.44" LCD display for nano-gui
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2018-2020 Peter Hinch
# Supported display
# Adafruit 1.44' Color TFT LCD Display with MicroSD Card breakout:
# https://www.adafruit.com/product/2088
# Based on
# https://github.com/adafruit/Adafruit_CircuitPython_ST7735R/blob/master/adafruit_st7735r.py
# https://github.com/GuyCarver/MicroPython/blob/master/lib/ST7735.py
# https://github.com/boochow/MicroPython-ST7735
# https://learn.adafruit.com/adafruit-1-44-color-tft-with-micro-sd-socket/python-usage
# disp = st7735.ST7735R(spi, rotation=90, # 1.8" ST7735R
# disp = st7735.ST7735R(spi, rotation=270, height=128, x_offset=2, y_offset=3, # 1.44" ST7735R
from time import sleep_ms
import framebuf
import gc
import micropython
# Datasheet para 8.4 scl write cycle 66ns == 15MHz
# _lcopy: copy a line in 8 bit format to one in 16 bit RGB565.
# 1 bytes becomes 2 in destination. Source format:
# < D7 D6 D5 D4 D3 D2 D1 D0>
# <R02 R01 R00 G02 G01 G00 B01 B00> <R12 R11 R10 G12 G11 G10 B11 B10>
# dest:
# <B01 B00 0 0 0 G02 G01 G00> <0 0 0 R02 R01 R00 0 0>
@micropython.viper
def _lcopy(dest:ptr8, source:ptr8, length:int):
n = 0
for x in range(length):
c = source[x]
dest[n] = ((c & 3) << 6) | ((c & 0x1c) >> 2) # Blue green
n += 1
dest[n] = (c & 0xe0) >> 3 # Red
n += 1
class ST7735R(framebuf.FrameBuffer):
# Convert r, g, b in range 0-255 to an 8 bit colour value
# rrrgggbb. Converted to 16 bit on the fly.
@staticmethod
def rgb(r, g, b):
return (r & 0xe0) | ((g >> 3) & 0x1c) | (b >> 6)
# rst and cs are active low, SPI is mode 0
def __init__(self, spi, cs, dc, rst, height=128, width=128, rotation=0, init_spi=False):
self._spi = spi
self._rst = rst # Pins
self._dc = dc
self._cs = cs
self.height = height # Required by Writer class
self.width = width
self._spi_init = init_spi
mode = framebuf.GS8 # Use 8bit greyscale for 8 bit color.
gc.collect()
buf = bytearray(self.height * self.width)
self._mvb = memoryview(buf)
super().__init__(buf, self.width, self.height, mode)
self._linebuf = bytearray(self.width * 2) # 16 bit color out
quad, mod = divmod(rotation, 90) # Get quadrant
if mod or quad > 3:
quad %= 4
print('Warning: rotation adjusted to', quad * 90)
self._init(quad)
self.show()
# Hardware reset
def _hwreset(self):
self._dc(0)
self._rst(1)
sleep_ms(1)
self._rst(0)
sleep_ms(1)
self._rst(1)
sleep_ms(1)
# Write a command, a bytes instance (in practice 1 byte).
def _wcmd(self, buf):
self._dc(0)
self._cs(0)
self._spi.write(buf)
self._cs(1)
# Write a command followed by a data arg.
def _wcd(self, c, d):
self._dc(0)
self._cs(0)
self._spi.write(c)
self._cs(1)
self._dc(1)
self._cs(0)
self._spi.write(d)
self._cs(1)
# Initialise the hardware. Blocks 500ms.
def _init(self, quad):
self._hwreset() # Hardware reset. Blocks 3ms
if self._spi_init: # A callback was passed
self._spi_init(self._spi) # Bus may be shared
cmd = self._wcmd
wcd = self._wcd
cmd(b'\x01') # SW reset datasheet specifies > 120ms
sleep_ms(150)
cmd(b'\x11') # SLPOUT
sleep_ms(256) # Adafruit delay (datsheet 120ms)
wcd(b'\xb1', b'\x01\x2C\x2D') # FRMCTRL1
wcd(b'\xb2', b'\x01\x2C\x2D') # FRMCTRL2
wcd(b'\xb3', b'\x01\x2C\x2D\x01\x2C\x2D') # FRMCTRL3
wcd(b'\xb4', b'\x07') # INVCTR line inversion
wcd(b'\xc0', b'\xa2\x02\x84') # PWCTR1 GVDD = 4.7V, 1.0uA
wcd(b'\xc1', b'\xc5') # PWCTR2 VGH=14.7V, VGL=-7.35V
wcd(b'\xc2', b'\x0a\x00') # PWCTR3 Opamp current small, Boost frequency
wcd(b'\xc3', b'\x8a\x2a') # PWCTR4
wcd(b'\xc4', b'\x8a\xee') # PWCTR5
wcd(b'\xc5', b'\x0e') # VMCTR1 VCOMH = 4V, VOML = -1.1V NOTE I make VCOM == -0.775V
cmd(b'\x20') # INVOFF
# d7..d5 of MADCTL determine rotation/orientation
# (MADCTL_DATA, ColumnOffset, RowOffset)
rval, co, ro = ((b'\x20', 1, 2),
(b'\x40', 2, 1),
(b'\xe0', 3, 2),
(b'\x80', 2, 3))[quad]
wcd(b'\x36', rval) # MADCTL: rotation mode for 1.44" display
wcd(b'\x3a', b'\x05') # COLMOD 16 bit
wcd(b'\xe0', b'\x02\x1c\x07\x12\x37\x32\x29\x2d\x29\x25\x2B\x39\x00\x01\x03\x10') # GMCTRP1 Gamma
wcd(b'\xe1', b'\x03\x1d\x07\x06\x2E\x2C\x29\x2D\x2E\x2E\x37\x3F\x00\x00\x02\x10') # GMCTRN1
wcd(b'\x2a', int.to_bytes((co << 16) + self.width + co - 1, 4, 'big')) # CASET
wcd(b'\x2b', int.to_bytes((ro << 16) + self.height + ro - 1, 4, 'big')) # RASET
cmd(b'\x13') # NORON
sleep_ms(10)
cmd(b'\x29') # DISPON
sleep_ms(100)
def show(self): # Blocks 38.6ms on Pyboard D at stock frequency
wd = self.width
ht = self.height
lb = self._linebuf
buf = self._mvb
self._dc(0)
self._cs(0)
if self._spi_init: # A callback was passed
self._spi_init(self._spi) # Bus may be shared
self._spi.write(b'\x2c') # RAMWR
self._dc(1)
for start in range(wd * (ht - 1), -1, - wd): # For each line
_lcopy(lb, buf[start :], wd) # Copy and map colors (68us)
self._spi.write(lb)
self._cs(1)

Wyświetl plik

@ -0,0 +1,162 @@
# st7735r144.py Driver for ST7735R 1.44" LCD display for nano-gui
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2018-2020 Peter Hinch
# Supported display
# Adafruit 1.44' Color TFT LCD Display with MicroSD Card breakout:
# https://www.adafruit.com/product/2088
# Based on
# https://github.com/adafruit/Adafruit_CircuitPython_ST7735R/blob/master/adafruit_st7735r.py
# https://github.com/GuyCarver/MicroPython/blob/master/lib/ST7735.py
# https://github.com/boochow/MicroPython-ST7735
# https://learn.adafruit.com/adafruit-1-44-color-tft-with-micro-sd-socket/python-usage
# disp = st7735.ST7735R(spi, rotation=90, # 1.8" ST7735R
# disp = st7735.ST7735R(spi, rotation=270, height=128, x_offset=2, y_offset=3, # 1.44" ST7735R
from time import sleep_ms
import framebuf
import gc
import micropython
# Datasheet para 8.4 scl write cycle 66ns == 15MHz
@micropython.viper
def _lcopy(dest:ptr8, source:ptr8, lut:ptr8, length:int):
n = 0
for x in range(length):
c = source[x]
d = (c & 0xf0) >> 3 # 2* LUT indices (LUT is 16 bit color)
e = (c & 0x0f) << 1
dest[n] = lut[d]
n += 1
dest[n] = lut[d + 1]
n += 1
dest[n] = lut[e]
n += 1
dest[n] = lut[e + 1]
n += 1
class ST7735R(framebuf.FrameBuffer):
lut = bytearray(32)
# Convert r, g, b in range 0-255 to a 16 bit colour value
# LS byte goes into LUT offset 0, MS byte into offset 1
# Same mapping in linebuf so LS byte is shifted out 1st
@staticmethod
def rgb(r, g, b):
return (r & 0xf8) << 5 | (g & 0x1c) << 11 | (g & 0xe0) >> 5 | (b & 0xf8)
# rst and cs are active low, SPI is mode 0
def __init__(self, spi, cs, dc, rst, height=128, width=128, rotation=0, init_spi=False):
self._spi = spi
self._rst = rst # Pins
self._dc = dc
self._cs = cs
self.height = height # Required by Writer class
self.width = width
self._spi_init = init_spi
mode = framebuf.GS4_HMSB # Use 4bit greyscale.
gc.collect()
buf = bytearray(self.height * self.width // 2)
self._mvb = memoryview(buf)
super().__init__(buf, self.width, self.height, mode)
self._linebuf = bytearray(self.width * 2) # 16 bit color out
quad, mod = divmod(rotation, 90) # Get quadrant
if mod or quad > 3:
quad %= 4
print('Warning: rotation adjusted to', quad * 90)
self._init(quad)
self.show()
# Hardware reset
def _hwreset(self):
self._dc(0)
self._rst(1)
sleep_ms(1)
self._rst(0)
sleep_ms(1)
self._rst(1)
sleep_ms(1)
# Write a command, a bytes instance (in practice 1 byte).
def _wcmd(self, buf):
self._dc(0)
self._cs(0)
self._spi.write(buf)
self._cs(1)
# Write a command followed by a data arg.
def _wcd(self, c, d):
self._dc(0)
self._cs(0)
self._spi.write(c)
self._cs(1)
self._dc(1)
self._cs(0)
self._spi.write(d)
self._cs(1)
# Initialise the hardware. Blocks 500ms.
def _init(self, quad):
self._hwreset() # Hardware reset. Blocks 3ms
if self._spi_init: # A callback was passed
self._spi_init(self._spi) # Bus may be shared
cmd = self._wcmd
wcd = self._wcd
cmd(b'\x01') # SW reset datasheet specifies > 120ms
sleep_ms(150)
cmd(b'\x11') # SLPOUT
sleep_ms(256) # Adafruit delay (datsheet 120ms)
wcd(b'\xb1', b'\x01\x2C\x2D') # FRMCTRL1
wcd(b'\xb2', b'\x01\x2C\x2D') # FRMCTRL2
wcd(b'\xb3', b'\x01\x2C\x2D\x01\x2C\x2D') # FRMCTRL3
wcd(b'\xb4', b'\x07') # INVCTR line inversion
wcd(b'\xc0', b'\xa2\x02\x84') # PWCTR1 GVDD = 4.7V, 1.0uA
wcd(b'\xc1', b'\xc5') # PWCTR2 VGH=14.7V, VGL=-7.35V
wcd(b'\xc2', b'\x0a\x00') # PWCTR3 Opamp current small, Boost frequency
wcd(b'\xc3', b'\x8a\x2a') # PWCTR4
wcd(b'\xc4', b'\x8a\xee') # PWCTR5
wcd(b'\xc5', b'\x0e') # VMCTR1 VCOMH = 4V, VOML = -1.1V NOTE I make VCOM == -0.775V
cmd(b'\x20') # INVOFF
# d7..d5 of MADCTL determine rotation/orientation
# (MADCTL_DATA, ColumnOffset, RowOffset)
rval, co, ro = ((b'\x20', 1, 2),
(b'\x40', 2, 1),
(b'\xe0', 3, 2),
(b'\x80', 2, 3))[quad]
wcd(b'\x36', rval) # MADCTL: rotation mode for 1.44" display
wcd(b'\x3a', b'\x05') # COLMOD 16 bit
wcd(b'\xe0', b'\x02\x1c\x07\x12\x37\x32\x29\x2d\x29\x25\x2B\x39\x00\x01\x03\x10') # GMCTRP1 Gamma
wcd(b'\xe1', b'\x03\x1d\x07\x06\x2E\x2C\x29\x2D\x2E\x2E\x37\x3F\x00\x00\x02\x10') # GMCTRN1
wcd(b'\x2a', int.to_bytes((co << 16) + self.width + co - 1, 4, 'big')) # CASET
wcd(b'\x2b', int.to_bytes((ro << 16) + self.height + ro - 1, 4, 'big')) # RASET
cmd(b'\x13') # NORON
sleep_ms(10)
cmd(b'\x29') # DISPON
sleep_ms(100)
def show(self): # Blocks 38.6ms on Pyboard D at stock frequency
clut = ST7735R.lut
wd = self.width // 2
ht = self.height
lb = self._linebuf
buf = self._mvb
self._dc(0)
self._cs(0)
if self._spi_init: # A callback was passed
self._spi_init(self._spi) # Bus may be shared
self._spi.write(b'\x2c') # RAMWR
self._dc(1)
for start in range(wd * (ht - 1), -1, - wd): # For each line
_lcopy(lb, buf[start :], clut, wd) # Copy and map colors (68us)
self._spi.write(lb)
self._cs(1)

Wyświetl plik

@ -0,0 +1,156 @@
# st7735r.py Driver for 1.8" 128*160 ST7735R LCD displays for nano-gui
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2018-2020 Peter Hinch
# Supported display
# Adfruit 1.8' Color TFT LCD display with MicroSD Card Breakout:
# https://www.adafruit.com/product/358
# Based on
# https://github.com/adafruit/Adafruit_CircuitPython_ST7735R/blob/master/adafruit_st7735r.py
# https://github.com/GuyCarver/MicroPython/blob/master/lib/ST7735.py
# https://github.com/boochow/MicroPython-ST7735
# https://learn.adafruit.com/adafruit-1-44-color-tft-with-micro-sd-socket/python-usage
# disp = st7735.ST7735R(spi, rotation=90, # 1.8" ST7735R
# disp = st7735.ST7735R(spi, rotation=270, height=128, x_offset=2, y_offset=3, # 1.44" ST7735R
from time import sleep_ms
import framebuf
import gc
import micropython
# Datasheet para 8.4 scl write cycle 66ns == 15MHz
@micropython.viper
def _lcopy(dest:ptr8, source:ptr8, lut:ptr8, length:int):
n = 0
for x in range(length):
c = source[x]
d = (c & 0xf0) >> 3 # 2* LUT indices (LUT is 16 bit color)
e = (c & 0x0f) << 1
dest[n] = lut[d]
n += 1
dest[n] = lut[d + 1]
n += 1
dest[n] = lut[e]
n += 1
dest[n] = lut[e + 1]
n += 1
class ST7735R(framebuf.FrameBuffer):
lut = bytearray(32)
# Convert r, g, b in range 0-255 to a 16 bit colour value
# LS byte goes into LUT offset 0, MS byte into offset 1
# Same mapping in linebuf so LS byte is shifted out 1st
@staticmethod
def rgb(r, g, b):
return (b & 0xf8) << 5 | (g & 0x1c) << 11 | (g & 0xe0) >> 5 | (r & 0xf8)
# rst and cs are active low, SPI is mode 0
def __init__(self, spi, cs, dc, rst, height=128, width=160, usd=False, init_spi=False):
self._spi = spi
self._rst = rst # Pins
self._dc = dc
self._cs = cs
self.height = height # Required by Writer class
self.width = width
self._spi_init = init_spi
mode = framebuf.GS4_HMSB # Use 4bit greyscale.
gc.collect()
buf = bytearray(height * width // 2)
self._mvb = memoryview(buf)
super().__init__(buf, width, height, mode)
self._linebuf = bytearray(self.width * 2) # 16 bit color out
self._init(usd)
self.show()
# Hardware reset
def _hwreset(self):
self._dc(0)
self._rst(1)
sleep_ms(1)
self._rst(0)
sleep_ms(1)
self._rst(1)
sleep_ms(1)
# Write a command, a bytes instance (in practice 1 byte).
def _wcmd(self, buf):
self._dc(0)
self._cs(0)
self._spi.write(buf)
self._cs(1)
# Write a command followed by a data arg.
def _wcd(self, c, d):
self._dc(0)
self._cs(0)
self._spi.write(c)
self._cs(1)
self._dc(1)
self._cs(0)
self._spi.write(d)
self._cs(1)
# Initialise the hardware. Blocks 500ms.
def _init(self, usd):
self._hwreset() # Hardware reset. Blocks 3ms
if self._spi_init: # A callback was passed
self._spi_init(self._spi) # Bus may be shared
cmd = self._wcmd
wcd = self._wcd
cmd(b'\x01') # SW reset datasheet specifies > 120ms
sleep_ms(150)
cmd(b'\x11') # SLPOUT
sleep_ms(256) # Adafruit delay (datsheet 120ms)
wcd(b'\xb1', b'\x01\x2C\x2D') # FRMCTRL1
wcd(b'\xb2', b'\x01\x2C\x2D') # FRMCTRL2
wcd(b'\xb3', b'\x01\x2C\x2D\x01\x2C\x2D') # FRMCTRL3
wcd(b'\xb4', b'\x07') # INVCTR line inversion
wcd(b'\xc0', b'\xa2\x02\x84') # PWCTR1 GVDD = 4.7V, 1.0uA
wcd(b'\xc1', b'\xc5') # PWCTR2 VGH=14.7V, VGL=-7.35V
wcd(b'\xc2', b'\x0a\x00') # PWCTR3 Opamp current small, Boost frequency
wcd(b'\xc3', b'\x8a\x2a') # PWCTR4
wcd(b'\xc4', b'\x8a\xee') # PWCTR5
wcd(b'\xc5', b'\x0e') # VMCTR1 VCOMH = 4V, VOML = -1.1V NOTE I make VCOM == -0.775V
cmd(b'\x20') # INVOFF
# d7..d5 of MADCTL determine rotation/orientation
if self.height > self.width:
wcd(b'\x36', b'\x80' if usd else b'\x40') # MADCTL: RGB portrait mode
else:
wcd(b'\x36', b'\xe0' if usd else b'\x20') # MADCTL: RGB landscape mode
wcd(b'\x3a', b'\x05') # COLMOD 16 bit
wcd(b'\xe0', b'\x02\x1c\x07\x12\x37\x32\x29\x2d\x29\x25\x2B\x39\x00\x01\x03\x10') # GMCTRP1 Gamma
wcd(b'\xe1', b'\x03\x1d\x07\x06\x2E\x2C\x29\x2D\x2E\x2E\x37\x3F\x00\x00\x02\x10') # GMCTRN1
wcd(b'\x2a', int.to_bytes(self.width, 4, 'big')) # CASET column address 0 start, 160 end
wcd(b'\x2b', int.to_bytes(self.height, 4, 'big')) # RASET
cmd(b'\x13') # NORON
sleep_ms(10)
cmd(b'\x29') # DISPON
sleep_ms(100)
def show(self): # Blocks 36ms on Pyboard D at stock frequency (160*128)
clut = ST7735R.lut
wd = self.width // 2
ht = self.height
lb = self._linebuf
buf = self._mvb
self._dc(0)
self._cs(0)
if self._spi_init: # A callback was passed
self._spi_init(self._spi) # Bus may be shared
self._spi.write(b'\x2c') # RAMWR
self._dc(1)
for start in range(wd * (ht - 1), -1, - wd): # For each line
_lcopy(lb, buf[start :], clut, wd) # Copy and map colors
self._spi.write(lb)
self._cs(1)

Wyświetl plik

@ -0,0 +1,237 @@
# st7789.py Driver for ST7789 LCD displays for nano-gui
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch, Ihor Nehrutsa
# Tested displays:
# Adafruit 1.3" 240x240 Wide Angle TFT LCD Display with MicroSD - ST7789
# https://www.adafruit.com/product/4313
# TTGO T-Display
# http://www.lilygo.cn/prod_view.aspx?TypeId=50044&Id=1126
# Based on
# Adfruit https://github.com/adafruit/Adafruit_CircuitPython_ST7789/blob/master/adafruit_st7789.py
# Also see st7735r_4bit.py for other source acknowledgements
# SPI bus: default mode. Driver performs no read cycles.
# Datasheet table 6 p44 scl write cycle 16ns == 62.5MHz
from time import sleep_ms #, ticks_us, ticks_diff
import framebuf
import gc
import micropython
import uasyncio as asyncio
# User orientation constants
LANDSCAPE = 0 # Default
REFLECT = 1
USD = 2
PORTRAIT = 4
# Display types
GENERIC = (0, 0, 0)
TDISPLAY = (52, 40, 1)
@micropython.viper
def _lcopy(dest:ptr16, source:ptr8, lut:ptr16, length:int):
# rgb565 - 16bit/pixel
n = 0
for x in range(length):
c = source[x]
dest[n] = lut[c >> 4] # current pixel
n += 1
dest[n] = lut[c & 0x0f] # next pixel
n += 1
class ST7789(framebuf.FrameBuffer):
lut = bytearray(0xFF for _ in range(32)) # set all colors to BLACK
# Convert r, g, b in range 0-255 to a 16 bit colour value rgb565.
# LS byte goes into LUT offset 0, MS byte into offset 1
# Same mapping in linebuf so LS byte is shifted out 1st
# For some reason color must be inverted on this controller.
@staticmethod
def rgb(r, g, b):
return ((b & 0xf8) << 5 | (g & 0x1c) << 11 | (g & 0xe0) >> 5 | (r & 0xf8)) ^ 0xffff
# rst and cs are active low, SPI is mode 0
def __init__(self, spi, cs, dc, rst, height=240, width=240,
disp_mode=LANDSCAPE, init_spi=False, display=GENERIC):
if not 0 <= disp_mode <= 7:
raise ValueError('Invalid display mode:', disp_mode)
if not display in (GENERIC, TDISPLAY):
raise ValueError('Invalid display type.')
self._spi = spi # Clock cycle time for write 16ns 62.5MHz max (read is 150ns)
self._rst = rst # Pins
self._dc = dc
self._cs = cs
self.height = height # Required by Writer class
self.width = width
self._offset = display[:2] # display arg is (x, y, orientation)
orientation = display[2] # where x, y is the RAM offset
self._spi_init = init_spi # Possible user callback
self._lock = asyncio.Lock()
mode = framebuf.GS4_HMSB # Use 4bit greyscale.
gc.collect()
buf = bytearray(height * -(-width // 2)) # Ceiling division for odd widths
self._mvb = memoryview(buf)
super().__init__(buf, width, height, mode)
self._linebuf = bytearray(self.width * 2) # 16 bit color out
self._init(disp_mode, orientation)
self.show()
# Hardware reset
def _hwreset(self):
self._dc(0)
self._rst(1)
sleep_ms(1)
self._rst(0)
sleep_ms(1)
self._rst(1)
sleep_ms(1)
# Write a command, a bytes instance (in practice 1 byte).
def _wcmd(self, buf):
self._dc(0)
self._cs(0)
self._spi.write(buf)
self._cs(1)
# Write a command followed by a data arg.
def _wcd(self, c, d):
self._dc(0)
self._cs(0)
self._spi.write(c)
self._cs(1)
self._dc(1)
self._cs(0)
self._spi.write(d)
self._cs(1)
# Initialise the hardware. Blocks 163ms. Adafruit have various sleep delays
# where I can find no requirement in the datasheet. I removed them with
# other redundant code.
def _init(self, user_mode, orientation):
self._hwreset() # Hardware reset. Blocks 3ms
if self._spi_init: # A callback was passed
self._spi_init(self._spi) # Bus may be shared
cmd = self._wcmd
wcd = self._wcd
cmd(b'\x01') # SW reset datasheet specifies 120ms before SLPOUT
sleep_ms(150)
cmd(b'\x11') # SLPOUT: exit sleep mode
sleep_ms(10) # Adafruit delay 500ms (datsheet 5ms)
wcd(b'\x3a', b'\x55') # _COLMOD 16 bit/pixel, 65Kbit color space
cmd(b'\x20') # INVOFF Adafruit turn inversion on. This driver fixes .rgb
cmd(b'\x13') # NORON Normal display mode
# Table maps user request onto hardware values. index values:
# 0 Normal
# 1 Reflect
# 2 USD
# 3 USD reflect
# Followed by same for LANDSCAPE
if not orientation:
user_mode ^= PORTRAIT
# Hardware mappings
# d7..d5 of MADCTL determine rotation/orientation datasheet P124, P231
# d5 = MV row/col exchange
# d6 = MX col addr order
# d7 = MY page addr order
# LANDSCAPE = 0
# PORTRAIT = 0x20
# REFLECT = 0x40
# USD = 0x80
mode = (0x60, 0xe0, 0xa0, 0x20, 0, 0x40, 0xc0, 0x80)[user_mode]
# Set display window depending on mode, .height and .width.
self.set_window(mode)
wcd(b'\x36', int.to_bytes(mode, 1, 'little'))
cmd(b'\x29') # DISPON. Adafruit then delay 500ms.
# Define the mapping between RAM and the display.
# Datasheet section 8.12 p124.
def set_window(self, mode):
portrait, reflect, usd = 0x20, 0x40, 0x80
rht = 320
rwd = 240 # RAM ht and width
wht = self.height # Window (framebuf) dimensions.
wwd = self.width # In portrait mode wht > wwd
if mode & portrait:
xoff = self._offset[1] # x and y transposed
yoff = self._offset[0]
xs = xoff
xe = wwd + xoff - 1
ys = yoff # y start
ye = wht + yoff - 1 # y end
if mode & reflect:
ys = rwd - wht - yoff
ye = rwd - yoff - 1
if mode & usd:
xs = rht - wwd - xoff
xe = rht - xoff - 1
else: # LANDSCAPE
xoff = self._offset[0]
yoff = self._offset[1]
xs = xoff
xe = wwd + xoff - 1
ys = yoff # y start
ye = wht + yoff - 1 # y end
if mode & usd:
ys = rht - wht - yoff
ye = rht - yoff - 1
if mode & reflect:
xs = rwd - wwd - xoff
xe = rwd - xoff - 1
# Col address set.
self._wcd(b'\x2a', int.to_bytes((xs << 16) + xe, 4, 'big'))
# Row address set
self._wcd(b'\x2b', int.to_bytes((ys << 16) + ye, 4, 'big'))
#@micropython.native # Made virtually no difference to timing.
def show(self): # Blocks for 83ms @60MHz SPI
# Blocks for 60ms @30MHz SPI on TTGO in PORTRAIT mode
# Blocks for 46ms @30MHz SPI on TTGO in LANDSCAPE mode
#ts = ticks_us()
clut = ST7789.lut
wd = -(-self.width // 2) # Ceiling division for odd number widths
end = self.height * wd
lb = memoryview(self._linebuf)
buf = self._mvb
if self._spi_init: # A callback was passed
self._spi_init(self._spi) # Bus may be shared
self._dc(0)
self._cs(0)
self._spi.write(b'\x2c') # RAMWR
self._dc(1)
for start in range(0, end, wd):
_lcopy(lb, buf[start:], clut, wd) # Copy and map colors
self._spi.write(lb)
self._cs(1)
#print(ticks_diff(ticks_us(), ts))
# Asynchronous refresh with support for reducing blocking time.
async def do_refresh(self, split=5):
async with self._lock:
lines, mod = divmod(self.height, split) # Lines per segment
if mod:
raise ValueError('Invalid do_refresh arg.')
clut = ST7789.lut
wd = -(-self.width // 2)
lb = memoryview(self._linebuf)
buf = self._mvb
line = 0
for n in range(split):
if self._spi_init: # A callback was passed
self._spi_init(self._spi) # Bus may be shared
self._dc(0)
self._cs(0)
self._spi.write(b'\x3c' if n else b'\x2c') # RAMWR/Write memory continue
self._dc(1)
for start in range(wd * line, wd * (line + lines), wd):
_lcopy(lb, buf[start :], clut, wd) # Copy and map colors
self._spi.write(lb)
line += lines
self._cs(1)
await asyncio.sleep(0)

56
gui/core/colors.py 100644
Wyświetl plik

@ -0,0 +1,56 @@
# colors.py Micropython GUI library for TFT displays: colors and shapes
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2019 Peter Hinch
from hardware_setup import SSD
# Code can be portable between 4-bit and other drivers by calling create_color
def create_color(idx, r, g, b):
c = SSD.rgb(r, g, b)
if not hasattr(SSD, 'lut'):
return c
if not 0 <= idx <= 15:
raise ValueError('Color nos must be 0..15')
x = idx << 1
SSD.lut[x] = c & 0xff
SSD.lut[x + 1] = c >> 8
return idx
if hasattr(SSD, 'lut'): # Colors defined by LUT
BLACK = create_color(0, 0, 0, 0)
GREEN = create_color(1, 0, 255, 0)
RED = create_color(2, 255, 0, 0)
LIGHTRED = create_color(3, 140, 0, 0)
BLUE = create_color(4, 0, 0, 255)
YELLOW = create_color(5, 255, 255, 0)
GREY = create_color(6, 100, 100, 100)
MAGENTA = create_color(7, 255, 0, 255)
CYAN = create_color(8, 0, 255, 255)
LIGHTGREEN = create_color(9, 0, 100, 0)
DARKGREEN = create_color(10, 0, 80, 0)
DARKBLUE = create_color(11, 0, 0, 90)
# 12, 13, 14 free for user definition
WHITE = create_color(15, 255, 255, 255)
else:
BLACK = SSD.rgb(0, 0, 0)
GREEN = SSD.rgb(0, 255, 0)
RED = SSD.rgb(255, 0, 0)
LIGHTRED = SSD.rgb(140, 0, 0)
BLUE = SSD.rgb(0, 0, 255)
YELLOW = SSD.rgb(255, 255, 0)
GREY = SSD.rgb(100, 100, 100)
MAGENTA = SSD.rgb(255, 0, 255)
CYAN = SSD.rgb(0, 255, 255)
LIGHTGREEN = SSD.rgb(0, 100, 0)
DARKGREEN = SSD.rgb(0, 80, 0)
DARKBLUE = SSD.rgb(0, 0, 90)
WHITE = SSD.rgb(255, 255, 255)
# Color used when clearing the screen
BGCOLOR = BLACK
# RA8875 defines colors as 3-tuples with greying-out operating on those.
# Should we assign GREY to any color > 0?
CIRCLE = 1
RECTANGLE = 2
CLIPPED_RECT = 3

687
gui/core/ugui.py 100644
Wyświetl plik

@ -0,0 +1,687 @@
# ugui.py Micropython GUI library
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2019-2021 Peter Hinch
# Requires uasyncio V3
import uasyncio as asyncio
from uasyncio import Event
from time import ticks_diff, ticks_ms
import gc
from gui.core.colors import *
from hardware_setup import ssd
from gui.primitives.delay_ms import Delay_ms
from gui.primitives.switch import Switch
gc.collect()
__version__ = (0, 1, 0)
# Null function
dolittle = lambda *_ : None
async def _g():
pass
type_coro = type(_g())
def setup(d):
global display
display = d
_FIRST = const(0)
_NEXT = const(1)
_PREV = const(2)
_LAST = const(3)
# Wrapper for ssd providing buttons and framebuf compatible methods
class Display:
def __init__(self, ssd, nxt, sel, prev=None, up=None, down=None):
self._next = Switch(nxt)
self._sel = Switch(sel)
# Mandatory buttons
self._next.close_func(Screen.next_ctrl) # Call current screen bound method
self._sel.close_func(Screen.sel_ctrl)
self._sel.open_func(Screen.unsel)
self.height = ssd.height
self.width = ssd.width
# Optional buttons
self._prev = None
if prev is not None:
self._prev = Switch(prev)
self._prev.close_func(Screen.prev_ctrl)
# Up and down methods get the button as an arg.
self._up = None
if up is not None:
self._up = Switch(up)
self._up.close_func(self.do_up)
self._down = None
if down is not None:
self._down = Switch(down)
self._down.close_func(self.do_down)
self._is_grey = False # Not greyed-out
def print_centred(self, writer, x, y, text, fgcolor=None, bgcolor=None):
sl = writer.stringlen(text)
writer.set_textpos(ssd, y - writer.height // 2, x - sl // 2)
writer.setcolor(fgcolor, bgcolor)
writer.printstring(text)
writer.setcolor() # Restore defaults
def print_left(self, writer, x, y, txt, fgcolor=None, bgcolor=None, invert=False):
writer.set_textpos(ssd, y, x)
writer.setcolor(fgcolor, bgcolor)
writer.printstring(txt, invert)
writer.setcolor() # Restore defaults
def do_up(self):
if self._down is not None and self._down(): # Interlock. TODO More?
Screen.up_ctrl(self._up)
def do_down(self):
if self._up is not None and self._up():
Screen.down_ctrl(self._down)
# Greying out has only one option given limitation of 4-bit display driver
# It would be possible to do better with RGB565 but would need inverse transformation
# to (r, g, b), scale and re-convert to integer.
def _getcolor(self, color): # Takes in an integer color, bit size dependent on driver
return GREY if self._is_grey and color != BGCOLOR else color
def usegrey(self, val): # display.usegrey(True) sets greyed-out
self._is_grey = val
return self
# Graphics primitives: despatch to device (i.e. framebuf) or
# local function for methods not implemented by framebuf.
# These methods support greying out color overrides.
# Clear screen.
def clr_scr(self):
ssd.fill_rect(0, 0, self.width - 1, self.height - 1, BGCOLOR)
def rect(self, x1, y1, w, h, color):
ssd.rect(x1, y1, w, h, self._getcolor(color))
def fill_rect(self, x1, y1, w, h, color):
ssd.fill_rect(x1, y1, w, h, self._getcolor(color))
def vline(self, x, y, l, color):
ssd.vline(x, y, l, self._getcolor(color))
def hline(self, x, y, l, color):
ssd.hline(x, y, l, self._getcolor(color))
def line(self, x1, y1, x2, y2, color):
ssd.line(x1, y1, x2, y2, self._getcolor(color))
# Private method uses physical color
def _circle(self, x0, y0, r, color): # Single pixel circle
x = -r
y = 0
err = 2 -2*r
while x <= 0:
ssd.pixel(x0 -x, y0 +y, color)
ssd.pixel(x0 +x, y0 +y, color)
ssd.pixel(x0 +x, y0 -y, color)
ssd.pixel(x0 -x, y0 -y, color)
e2 = err
if (e2 <= y):
y += 1
err += y*2 +1
if (-x == y and e2 <= x):
e2 = 0
if (e2 > x):
x += 1
err += x*2 +1
def circle(self, x0, y0, r, color, width =1): # Draw circle (maybe grey)
color = self._getcolor(color)
x0, y0, r = int(x0), int(y0), int(r)
for r in range(r, r -width, -1):
self._circle(x0, y0, r, color)
def fillcircle(self, x0, y0, r, color): # Draw filled circle
color = self._getcolor(color)
x0, y0, r = int(x0), int(y0), int(r)
x = -r
y = 0
err = 2 -2*r
while x <= 0:
ssd.line(x0 -x, y0 -y, x0 -x, y0 +y, color)
ssd.line(x0 +x, y0 -y, x0 +x, y0 +y, color)
e2 = err
if (e2 <= y):
y +=1
err += y*2 +1
if (-x == y and e2 <= x):
e2 = 0
if (e2 > x):
x += 1
err += x*2 +1
def clip_rect(self, x, y, w, h, color):
color = self._getcolor(color)
c = 4
ssd.hline(x + c, y, w - 2 * c, color)
ssd.hline(x + c, y + h, w - 2 * c, color)
ssd.vline(x, y + c, h - 2 * c, color)
ssd.vline(x + w - 1, y + c, h - 2 * c, color)
ssd.line(x + c, y, x, y + c, color)
ssd.line(x + w - c - 1, y, x + w - 1, y + c, color)
ssd.line(x, y + h - c - 1, x + c, y + h - 1, color)
ssd.line(x + w - 1, y + h - c - 1, x + w - c - 1, y + h, color)
def fill_clip_rect(self, x, y, w, h, color):
color = self._getcolor(color)
c = 4
ssd.fill_rect(x, y + c, w, h - 2 * c, color)
for z in range(c):
l = w - 2 * (c - z) # Line length
ssd.hline(x + c - z, y + z, l, color)
ssd.hline(x + c - z, y + h - z - 1, l, color)
class Screen:
current_screen = None
is_shutdown = Event()
@classmethod
def next_ctrl(cls):
if cls.current_screen is not None:
cls.current_screen.move(_NEXT)
@classmethod
def prev_ctrl(cls):
if cls.current_screen is not None:
cls.current_screen.move(_PREV)
@classmethod
def sel_ctrl(cls):
if cls.current_screen is not None:
cls.current_screen.do_sel()
@classmethod
def unsel(cls):
if cls.current_screen is not None:
cls.current_screen.unsel_i()
@classmethod
def up_ctrl(cls, button):
if cls.current_screen is not None:
cls.current_screen.do_up(button)
@classmethod
def down_ctrl(cls, button):
if cls.current_screen is not None:
cls.current_screen.do_down(button)
# Move currency to a specific widget (e.g. ButtonList)
@classmethod
def select(cls, obj):
if cls.current_screen is not None:
return cls.current_screen.move_to(obj)
@classmethod
def show(cls, force):
for obj in cls.current_screen.displaylist:
if obj.visible: # In a buttonlist only show visible button
# pend signals an obect with changed contents
if force or obj.draw:
obj.draw_border()
obj.show()
obj.draw = False
@classmethod
def change(cls, cls_new_screen, *, forward=True, args=[], kwargs={}):
cs_old = cls.current_screen
if cs_old is not None: # Leaving an existing screen
for entry in cls.current_screen.tasklist:
if entry[1]: # To be cancelled on screen change
entry[0].cancel()
cs_old.on_hide() # Optional method in subclass
if forward:
if isinstance(cls_new_screen, type):
# Instantiate new screen. __init__ must terminate
new_screen = cls_new_screen(*args, **kwargs)
else:
raise ValueError('Must pass Screen class or subclass (not instance)')
new_screen.parent = cs_old
cs_new = new_screen
else:
cs_new = cls_new_screen # An object, not a class
cls.current_screen = cs_new
cs_new.on_open() # Optional subclass method
cs_new._do_open(cs_old) # Clear and redraw
cs_new.after_open() # Optional subclass method
if cs_old is None: # Initialising
try:
asyncio.run(Screen.monitor()) # Starts and ends uasyncio
finally:
asyncio.new_event_loop()
@classmethod
async def monitor(cls):
ar = asyncio.create_task(cls.auto_refresh())
await cls.is_shutdown.wait()
cls.is_shutdown.clear()
# Task cancellation and shutdown
ar.cancel() # Refresh task
for entry in cls.current_screen.tasklist:
entry[0].cancel()
await asyncio.sleep_ms(0) # Allow subclass to cancel tasks
display.clr_scr()
ssd.show()
cls.current_screen = None # Ensure another demo can run
@staticmethod
async def auto_refresh():
arfsh = hasattr(ssd, 'do_refresh') # Refresh can be asynchronous
while True:
Screen.show(False) # Update stale controls. No physical refresh.
# Now perform physical refresh.
if arfsh:
await ssd.do_refresh()
else:
ssd.show() # Synchronous (blocking) refresh.
await asyncio.sleep_ms(0)
@classmethod
def back(cls):
parent = cls.current_screen.parent
if parent is None: # Closing base screen. Quit.
cls.shutdown()
else:
cls.change(parent, forward = False)
@classmethod
def addobject(cls, obj):
cs = cls.current_screen
if cs is None:
raise OSError('You must create a Screen instance')
# Populate list of active widgets (i.e. ones that can acquire focus).
if obj.active:
# Append to active list regrdless of disabled state which may
# change at runtime.
al = cs.lstactive
empty = al == [] or all(o.greyed_out() for o in al)
al.append(obj)
if empty and not obj.greyed_out():
cs.selected_obj = len(al) - 1 # Index into lstactive
cs.displaylist.append(obj) # All displayable objects
@classmethod
def shutdown(cls):
cls.is_shutdown.set() # Tell monitor() to shutdown
def __init__(self):
self.lstactive = [] # Controls which respond to Select button
self.selected_obj = None # Index of currently selected object
self.displaylist = [] # All displayable objects
self.tasklist = [] # Allow instance to register tasks for shutdown
self.modal = False
self.height = ssd.height # Occupies entire display
self.width = ssd.width
self.row = 0
self.col = 0
if Screen.current_screen is None: # Initialising class and task
# Here we create singleton tasks
asyncio.create_task(self._garbage_collect())
Screen.current_screen = self
self.parent = None
def _do_open(self, old_screen): # Window overrides
dev = display.usegrey(False)
# If opening a Screen from an Window just blank and redraw covered area
if old_screen is not None and old_screen.modal:
x0, y0, x1, y1, w, h = old_screen._list_dims()
dev.fill_rect(x0, y0, w, h, BGCOLOR) # Blank to screen BG
for obj in [z for z in self.displaylist if z.overlaps(x0, y0, x1, y1)]:
if obj.visible:
obj.draw_border()
obj.show()
# Normally clear the screen and redraw everything
else:
dev.clr_scr() # Clear framebuf but don't update display
Screen.show(True) # Force full redraw
# Return an active control or None
# By default returns the selected control
# else checks a given control by index into lstactive
def get_obj(self, idx=None):
so = self.selected_obj if idx is None else idx
if so is not None:
co = self.lstactive[so]
if co.visible and not co.greyed_out():
return co
return None
# Move currency to next enabled control. Arg is direction of move.
def move(self, to):
if to == _FIRST:
idx = -1
up = 1
elif to == _LAST:
idx = len(self.lstactive)
up = -1
else:
idx = self.selected_obj
up = 1 if to == _NEXT else -1
lo = self.get_obj() # Old current object
done = False
while not done:
idx += up
idx %= len(self.lstactive)
co = self.get_obj(idx)
if co is not None:
if co is not lo:
self.selected_obj = idx
if lo is not None:
lo.leave() # Tell object it's losing currency.
lo.show() # Re-display with new status
co.enter() # Tell object it has currency
co.show()
elif isinstance(self, Window):
# Special case of Window with one object: leave
# without making changes (Dropdown in particular)
Screen.back()
done = True
# Move currency to a specific control.
def move_to(self, obj):
lo = self.get_obj() # Old current object
for idx in range(len(self.lstactive)) :
co = self.get_obj(idx)
if co is obj:
self.selected_obj = idx
if lo is not None:
lo.leave() # Tell object it's losing currency.
lo.show() # Re-display with new status
co.enter() # Tell object it has currency
co.show()
return True # Success
return False
def do_sel(self): # Direct to current control
co = self.get_obj()
if co is not None:
co.do_sel()
def unsel_i(self):
co = self.get_obj()
if co is not None:
co.unsel()
def do_up(self, button):
co = self.get_obj()
if co is not None and hasattr(co, 'do_up'):
co.do_up(button) # Widget handles up/down
else:
Screen.current_screen.move(_FIRST)
def do_down(self, button):
co = self.get_obj()
if co is not None and hasattr(co, 'do_down'):
co.do_down(button)
else:
Screen.current_screen.move(_LAST)
# Methods optionally implemented in subclass
def on_open(self):
return
def after_open(self):
return
def on_hide(self):
return
def locn(self, row, col):
return self.row + row, self.col + col
# Housekeeping methods
def reg_task(self, task, on_change=False): # May be passed a coro or a Task
if isinstance(task, type_coro):
task = asyncio.create_task(task)
self.tasklist.append([task, on_change])
async def _garbage_collect(self):
while True:
await asyncio.sleep_ms(500)
gc.collect()
gc.threshold(gc.mem_free() // 4 + gc.mem_alloc())
#print(gc.mem_free())
# Very basic window class. Cuts a rectangular hole in a screen on which content may be drawn
class Window(Screen):
_value = None
def __init__(self, row, col, height, width, *, draw_border=True, bgcolor=None, fgcolor=None):
Screen.__init__(self)
self.row = row
self.col = col
self.height = height
self.width = width
self.draw_border = draw_border
self.modal = True
self.fgcolor = fgcolor if fgcolor is not None else WHITE
self.bgcolor = bgcolor if bgcolor is not None else BGCOLOR
def _do_open(self, old_screen):
dev = display.usegrey(False)
x, y = self.col, self.row
dev.fill_rect(x, y, self.width, self.height, self.bgcolor)
if self.draw_border:
dev.rect(x, y, self.width, self.height, self.fgcolor)
Screen.show(True)
def _list_dims(self):
w = self.width
h = self.height
x = self.col
y = self.row
return x, y, x + w, y + h, w, h
@classmethod
def value(cls, val=None): # Mechanism for testing the outcome of a dialog box
if val is not None:
cls._value = val
return cls._value
# Base class for all displayable objects
class Widget:
def __init__(self, writer, row, col, height, width,
fgcolor, bgcolor, bdcolor,
value=None, active=False):
self.active = active
self._greyed_out = False
Screen.addobject(self)
self.screen = Screen.current_screen
writer.set_clip(True, True, False) # Disable scrolling text
self.writer = writer
# The following assumes that the widget is mal-positioned, not oversize.
if row < 0:
row = 0
self.warning()
elif row + height >= ssd.height:
row = ssd.height - height - 1
self.warning()
if col < 0:
col = 0
self.warning()
elif col + width >= ssd.width:
col = ssd.width - width - 1
self.warning()
self.row = row
self.col = col
self.height = height
self.width = width
self.visible = True # Used by ButtonList class for invisible buttons
self.draw = True # Signals that obect must be redrawn
self._value = value
# Current colors
if fgcolor is None:
fgcolor = writer.fgcolor
if bgcolor is None:
bgcolor = BGCOLOR
if bdcolor is None:
bdcolor = fgcolor
self.fgcolor = fgcolor
self.bgcolor = bgcolor
# bdcolor is False if no border is to be drawn
self.bdcolor = bdcolor
# Default colors allow restoration after dynamic change
self.def_fgcolor = fgcolor
self.def_bgcolor = bgcolor
self.def_bdcolor = bdcolor
# has_border is True if a border was drawn
self.has_border = False
self.callback = dolittle # Value change callback
self.args = []
def warning(self):
print('Warning: attempt to create {} outside screen dimensions.'.format(self.__class__.__name__))
def greyed_out(self):
return self._greyed_out # Subclass may be greyed out
def value(self, val=None): # User method to get or set value
if val is not None:
if type(val) is float:
val = min(max(val, 0.0), 1.0)
if val != self._value:
self._value = val
self.draw = True # Ensure a redraw on next refresh
self.callback(self, *self.args)
return self._value
# Some widgets (e.g. Dial) have an associated Label
def text(self, text=None, invert=False, fgcolor=None, bgcolor=None, bdcolor=None):
if hasattr(self, 'label'):
self.label.value(text, invert, fgcolor, bgcolor, bdcolor)
else:
raise ValueError('Method {}.text does not exist.'.format(self.__class__.__name__))
# Called from subclass prior to populating framebuf with control
def show(self, black=True):
if self.screen != Screen.current_screen:
# Can occur if a control's action is to change screen.
return False # Subclass abandons
self.draw = False
self.draw_border()
# Blank controls' space
if self.visible:
dev = display.usegrey(self._greyed_out)
x = self.col
y = self.row
dev.fill_rect(x, y, self.width, self.height, BGCOLOR if black else self.bgcolor)
return True
# Called by Screen.show(). Draw background and bounding box if required.
# Border is always 2 pixels wide, outside control's bounding box
def draw_border(self):
if self.screen is Screen.current_screen:
dev = display.usegrey(self._greyed_out)
x = self.col - 2
y = self.row - 2
w = self.width + 4
h = self.height + 4
if self.has_focus():
dev.rect(x, y, w, h, WHITE)
self.has_border = True
else:
if isinstance(self.bdcolor, bool): # No border
if self.has_border: # Border exists: erase it
dev.rect(x, y, w, h, BGCOLOR)
self.has_border = False
elif self.bdcolor: # Border is required
dev.rect(x, y, w, h, self.bdcolor)
self.has_border = True
def overlaps(self, xa, ya, xb, yb): # Args must be sorted: xb > xa and yb > ya
x0 = self.col
y0 = self.row
x1 = x0 + self.width
y1 = y0 + self.height
if (ya <= y1 and yb >= y0) and (xa <= x1 and xb >= x0):
return True
return False
def _set_callbacks(self, cb, args): # Runs when value changes.
self.callback = cb
self.args = args
def has_focus(self):
if self.active:
cs = Screen.current_screen
if (cso := cs.selected_obj) is not None:
return cs.lstactive[cso] is self
return False
def greyed_out(self, val=None):
if val is not None and self.active and self._greyed_out != val:
self._greyed_out = val
if self.screen is Screen.current_screen:
display.usegrey(val)
self.draw_border()
self.show()
return self._greyed_out
# Button press methods. Called from Screen if object not greyed out.
# For subclassing if specific behaviour is required.
def do_sel(self): # Select button was pushed
pass
def unsel(self): # Select button was released
pass
def enter(self): # Control has acquired focus
pass
def leave(self): # Control has lost focus
pass
# Optional methods. Implement for controls which respond to up and down.
# No dummy methods as these would prevent "first" and "last" focus movement
# when current control has focus but is inactive.
# def do_up(self, button)
# def do_down(self, button)
# A LinearIO widget uses the up and down buttons to vary a float. Such widgets
# have do_up and do_down methods which adjust the control's value in a
# time-dependent manner.
class LinearIO(Widget):
def __init__(self, writer, row, col, height, width,
fgcolor, bgcolor, bdcolor,
value=None, active=True,
min_delta=0.01, max_delta=0.1):
self.min_delta = min_delta
self.max_delta = max_delta
super().__init__(writer, row, col, height, width,
fgcolor, bgcolor, bdcolor,
value, active)
def do_up(self, button):
asyncio.create_task(self.btnhan(button, 1))
def do_down(self, button):
asyncio.create_task(self.btnhan(button, -1))
async def btnhan(self, button, up): # Note: textbox.py, scale_log.py overrides
d = self.min_delta
self.value(self.value() + up * d)
t = ticks_ms()
while not button():
await asyncio.sleep_ms(0) # Quit fast on button release
if ticks_diff(ticks_ms(), t) > 500: # Button was held down
d = min(self.max_delta, d * 2)
self.value(self.value() + up * d)
t = ticks_ms()

336
gui/core/writer.py 100644
Wyświetl plik

@ -0,0 +1,336 @@
# writer.py Implements the Writer class.
# Handles colour, word wrap and tab stops
# V0.40 Jan 2021 Improved handling of word wrap and line clip. Upside-down
# rendering no longer supported: delegate to device driver.
# V0.35 Sept 2020 Fast rendering option for color displays
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2019-2021 Peter Hinch
# 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.
# CWriter on Pyboard D SF2W at standard clock rate
# Fast method 500-600μs typical, up to 1.07ms on larger fonts
# Revised fast method 691μs avg, up to 2.14ms on larger fonts
# Slow method 2700μs typical, up to 11ms on larger fonts
import framebuf
from uctypes import bytearray_at, addressof
from sys import platform
__version__ = (0, 4, 2)
fast_mode = platform == 'pyboard'
if fast_mode:
try:
try:
from framebuf_utils import render
except ImportError: # May be running in GUI. Try relative import.
try:
from .framebuf_utils import render
except ImportError:
fast_mode = False
except ValueError:
fast_mode = False
if not fast_mode:
print('Ignoring missing or invalid framebuf_utils.mpy.')
class DisplayState():
def __init__(self):
self.text_row = 0
self.text_col = 0
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 = row
if col is not None:
if col < 0 or col >= device.width:
raise ValueError('col is out of range')
s.text_col = 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
if font.height() >= device.height or font.max_width() >= device.width:
raise ValueError('Font too large for screen')
# 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 fullt
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
self.clip_width = 0
def _getstate(self):
return Writer.state[self.devid]
def _newline(self):
s = self._getstate()
height = self.font.height()
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:
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
@property
def height(self): # Property for consistency with device
return self.font.height()
def printstring(self, string, invert=False):
# word wrapping. Assumes words separated by single space.
q = string.split('\n')
last = len(q) - 1
for n, s in enumerate(q):
if s:
self._printline(s, invert)
if n != last:
self._printchar('\n')
def _printline(self, string, invert):
rstr = None
if self.wrap and self.stringlen(string, True): # Length > self.screenwidth
pos = 0
lstr = string[:]
while self.stringlen(lstr, True): # Length > 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, oh=False):
sc = self._getstate().text_col # Start column
wd = self.screenwidth
l = 0
for char in string[:-1]:
_, _, char_width = self.font.get_ch(char)
l += char_width
if oh and l + sc > wd:
return True # All done. Save time.
char = string[-1]
_, _, char_width = self.font.get_ch(char)
if oh and l + sc + char_width > wd:
l += self._truelen(char) # Last char might have blank cols on RHS
else:
l += char_width # Public method. Return same value as old code.
return l + sc > wd if oh else l
# Return the printable width of a glyph less any blank columns on RHS
def _truelen(self, char):
glyph, ht, wd = self.font.get_ch(char)
div, mod = divmod(wd, 8)
gbytes = div + 1 if mod else div # No. of bytes per row of glyph
mc = 0 # Max non-blank column
data = glyph[(wd - 1) // 8] # Last byte of row 0
for row in range(ht): # Glyph row
for col in range(wd -1, -1, -1): # Glyph column
gbyte, gbit = divmod(col, 8)
if gbit == 0: # Next glyph byte
data = glyph[row * gbytes + gbyte]
if col <= mc:
break
if data & (1 << (7 - gbit)): # Pixel is lit (1)
mc = col # Eventually gives rightmost lit pixel
break
if mc + 1 == wd:
break # All done: no trailing space
print('Truelen', char, wd, mc + 1) # TEST
return mc + 1
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()
np = None # Allow restriction on printable columns
if s.text_row + char_height > self.screenheight:
if self.row_clip:
return
self._newline()
oh = s.text_col + char_width - self.screenwidth # Overhang (+ve)
if oh > 0:
if self.col_clip or self.wrap:
np = char_width - oh # No. of printable columns
if np <= 0:
return
else:
self._newline()
self.glyph = glyph
self.char_height = char_height
self.char_width = char_width
self.clip_width = char_width if np is None else np
# 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.clip_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
def setcolor(self, *_):
return self.fgcolor, self.bgcolor
# Writer for colour displays or upside down rendering
class CWriter(Writer):
def __init__(self, device, font, fgcolor=None, bgcolor=None, verbose=True):
super().__init__(device, font, verbose)
if bgcolor is not None: # Assume monochrome.
self.bgcolor = bgcolor
if fgcolor is not None:
self.fgcolor = fgcolor
self.def_bgcolor = self.bgcolor
self.def_fgcolor = self.fgcolor
self._printchar = self._pchfast if fast_mode else self._pchslow
verbose and print('Render {} using fast mode'.format('is' if fast_mode else 'not'))
def _pchfast(self, char, invert=False, recurse=False):
s = self._getstate()
self._get_char(char, recurse)
if self.glyph is None:
return # All done
buf = bytearray_at(addressof(self.glyph), len(self.glyph))
fbc = framebuf.FrameBuffer(buf, self.char_width, self.char_height, self.map)
fgcolor = self.bgcolor if invert else self.fgcolor
bgcolor = self.fgcolor if invert else self.bgcolor
# render clips a glyph if outside bounds of destination
render(self.device, fbc, s.text_col, s.text_row, fgcolor, bgcolor)
s.text_col += self.char_width
self.cpos += 1
def _pchslow(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
clip_width = self.clip_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
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(clip_width): # Source column
# Destination column: add writer column
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 drow >= self.screenheight or drow < 0:
break
s.text_col += char_width
self.cpos += 1
def setcolor(self, fgcolor=None, bgcolor=None):
if fgcolor is None and bgcolor is None:
self.fgcolor = self.def_fgcolor
self.bgcolor = self.def_bgcolor
else:
if fgcolor is not None:
self.fgcolor = fgcolor
if bgcolor is not None:
self.bgcolor = bgcolor
return self.fgcolor, self.bgcolor

Wyświetl plik

@ -0,0 +1,99 @@
# active.py micro-gui demo of widgets that respond to user control
# Import SSD and Display instances. Must be done first because of RAM use.
from hardware_setup import display, ssd # Create a display instance
from gui.core.ugui import Screen
from gui.core.writer import CWriter
import gui.fonts.arial10 as arial10 # Font for CWriter
from gui.core.colors import *
# Widgets
from gui.widgets.label import Label
from gui.widgets.scale import Scale
from gui.widgets.scale_log import ScaleLog
from gui.widgets.buttons import Button, CloseButton
from gui.widgets.sliders import Slider, HorizSlider
from gui.widgets.knob import Knob
from gui.widgets.checkbox import Checkbox
class BaseScreen(Screen):
def __init__(self):
def tickcb(f, c):
if f > 0.8:
return RED
if f < -0.8:
return BLUE
return c
def tick_log_cb(f, c):
if f > 20_000:
return RED
if f < 4:
return BLUE
return c
super().__init__()
wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False)
col = 2
row = 200
Label(wri, row, col, 'Result')
col = 42
self.lbl = Label(wri, row, col, 70, bdcolor=RED)
self.vslider = Slider(wri, 2, 2, callback=self.slider_cb,
bdcolor=RED, slotcolor=BLUE,
legends=('0.0', '0.5', '1.0'), value=0.5)
col = 80
row = 15
self.hslider = HorizSlider(wri, row, col, callback=self.slider_cb,
bdcolor=YELLOW, slotcolor=BLUE,
legends=('0.0', '0.5', '1.0'), value=0.7)
row += 30
self.scale = Scale(wri, row, col, width = 150, tickcb = tickcb,
pointercolor=RED, fontcolor=YELLOW, bdcolor=CYAN,
callback=self.cb, active=True)
row += 40
self.scale_log = ScaleLog(wri, row, col, width = 150, tickcb = tick_log_cb,
pointercolor=RED, fontcolor=YELLOW, bdcolor=CYAN,
callback=self.cb, value=10, active=True)
row = 120
self.knob = Knob(wri, row, 2, callback = self.cb, bgcolor=DARKGREEN, color=LIGHTRED)
col = 150
row = 185
Checkbox(wri, row, col, callback=self.cbcb)
col += 35
Label(wri, row, col, 'Enable/disable')
CloseButton(wri)
def cb(self, obj):
self.lbl.value('{:4.2f}'.format(obj.value()))
def cbcb(self, cb):
val = cb.value()
self.vslider.greyed_out(val)
self.hslider.greyed_out(val)
self.scale.greyed_out(val)
self.scale_log.greyed_out(val)
self.knob.greyed_out(val)
def slider_cb(self, s):
self.cb(s)
v = s.value()
if v < 0.2:
s.color(BLUE)
elif v > 0.8:
s.color(RED)
else:
s.color(GREEN)
def test():
if display.height < 240 or display.width < 320:
print(' This test requires a display of at least 320x240 pixels.')
else:
print('Testing micro-gui...')
Screen.change(BaseScreen)
test()

250
gui/demos/plot.py 100644
Wyświetl plik

@ -0,0 +1,250 @@
# plot.py Test/demo program for micro-gui plot. Cross-patform,
# but requires a large enough display.
# Tested on Adafruit ssd1351-based OLED displays:
# Adafruit 1.5" 128*128 OLED display: https://www.adafruit.com/product/1431
# Adafruit 1.27" 128*96 display https://www.adafruit.com/product/1673
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# Initialise hardware and framebuf before importing modules.
from hardware_setup import ssd, display # Create a display instance
import cmath
import math
import uasyncio as asyncio
from collections import OrderedDict
from gui.core.writer import Writer, CWriter
from gui.core.ugui import Screen
from gui.widgets.graph import PolarGraph, PolarCurve, CartesianGraph, Curve, TSequence
from gui.widgets.label import Label
from gui.widgets.buttons import Button, CloseButton
from gui.widgets.listbox import Listbox
# Fonts & colors
import gui.fonts.arial10 as arial10
from gui.core.colors import *
wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False)
def fwdbutton(writer, row, col, cls_screen, text, color, *args, **kwargs):
def fwd(button):
Screen.change(cls_screen, args = args, kwargs = kwargs)
Button(writer, row, col, callback = fwd, bgcolor = color,
text = text, textcolor = BLACK, height = 20, width = 60)
class EmptyScreen(Screen):
def __init__(self):
super().__init__()
Label(wri, 2, 2, 'Test of overlay.')
Label(wri, 20, 2, 'Check redraw of underlying screen.')
CloseButton(wri)
class CartesianScreen(Screen):
def __init__(self):
super().__init__()
self.g = CartesianGraph(wri, 2, 2, yorigin = 2, fgcolor=GREEN,
gridcolor=LIGHTGREEN) # Asymmetric y axis
Label(wri, 100, 2, 'Asymmetric axes.')
fwdbutton(wri, 2, 130, EmptyScreen, 'Forward', GREEN)
CloseButton(wri)
# At this point in time the Screen and graph have been constructed but
# not rendered. If we drew the curves now they would be overwritten by
# the graph
# Now the graph has been drawn and we can overlay it with graphics primitives.
# This is not a uasyncio issue, but is the way Screen.change() is coded.
def after_open(self):
def populate_1(func):
x = -1
while x < 1.01:
yield x, func(x) # x, y
x += 0.1
def populate_2():
x = -1
while x < 1.01:
yield x, x**2 # x, y
x += 0.1
Curve(self.g, YELLOW, populate_1(lambda x : x**3 + x**2 -x,)) # args demo
Curve(self.g, RED, populate_2())
class PolarScreen(Screen):
def __init__(self):
super().__init__()
self.g = PolarGraph(wri, 2, 2, fgcolor=GREEN, gridcolor=LIGHTGREEN)
fwdbutton(wri, 2, 130, EmptyScreen, 'Forward', GREEN)
CloseButton(wri)
def after_open(self): # After graph has been drawn
def populate():
def f(theta):
return cmath.rect(math.sin(3 * theta), theta) # complex
nmax = 150
for n in range(nmax + 1):
yield f(2 * cmath.pi * n / nmax) # complex z
PolarCurve(self.g, YELLOW, populate())
class Lissajous(Screen):
def __init__(self):
super().__init__()
self.g = CartesianGraph(wri, 2, 2, fgcolor=GREEN, gridcolor=LIGHTGREEN)
Label(wri, 100, 2, 'Lissajous figure.')
fwdbutton(wri, 2, 130, EmptyScreen, 'Forward', GREEN)
CloseButton(wri)
def after_open(self): # After graph has been drawn
def populate():
t = -math.pi
while t <= math.pi:
yield math.sin(t), math.cos(3*t) # x, y
t += 0.1
Curve(self.g, YELLOW, populate())
class Lemniscate(Screen):
def __init__(self):
super().__init__()
self.g = CartesianGraph(wri, 2, 2, height = 75, fgcolor=GREEN, gridcolor=LIGHTGREEN)
Label(wri, 82, 2, 'To infinity and beyond...')
fwdbutton(wri, 2, 130, EmptyScreen, 'Forward', GREEN)
CloseButton(wri)
def after_open(self): # After graph has been drawn
def populate():
t = -math.pi
while t <= math.pi + 0.1:
x = 0.5*math.sqrt(2)*math.cos(t)/(math.sin(t)**2 + 1)
y = math.sqrt(2)*math.cos(t)*math.sin(t)/(math.sin(t)**2 + 1)
yield x, y
t += 0.1
Curve(self.g, YELLOW, populate())
class PolarClip(Screen):
def __init__(self):
super().__init__()
self.g = PolarGraph(wri, 2, 2, fgcolor=GREEN, gridcolor=LIGHTGREEN)
Label(wri, 100, 2, 'Clipping of polar data.')
fwdbutton(wri, 2, 130, EmptyScreen, 'Forward', GREEN)
CloseButton(wri)
def after_open(self): # After graph has been drawn
def populate(rot):
f = lambda theta : cmath.rect(1.15 * math.sin(5 * theta), theta) * rot # complex
nmax = 150
for n in range(nmax + 1):
yield f(2 * cmath.pi * n / nmax) # complex z
PolarCurve(self.g, YELLOW, populate(1))
PolarCurve(self.g, RED, populate(cmath.rect(1, cmath.pi/5),))
class RTPolar(Screen):
def __init__(self):
super().__init__()
self.g = PolarGraph(wri, 2, 2, fgcolor=GREEN, gridcolor=LIGHTGREEN)
Label(wri, 100, 2, 'Realtime polar data.')
fwdbutton(wri, 2, 130, EmptyScreen, 'Forward', GREEN)
CloseButton(wri)
def after_open(self): # After graph has been drawn
self.reg_task(self.run(self.g), True) # Cancel on screen change
async def run(self, g):
await asyncio.sleep_ms(0)
curvey = PolarCurve(g, YELLOW)
curver = PolarCurve(g, RED)
for x in range(100):
curvey.point(cmath.rect(x/100, -x * cmath.pi/30))
curver.point(cmath.rect((100 - x)/100, -x * cmath.pi/30))
await asyncio.sleep_ms(60)
class RTRect(Screen):
def __init__(self):
super().__init__()
self.g = CartesianGraph(wri, 2, 2, fgcolor=GREEN, gridcolor=LIGHTGREEN)
Label(wri, 100, 2, 'Realtime discontinuous data.')
fwdbutton(wri, 2, 130, EmptyScreen, 'Forward', GREEN)
CloseButton(wri)
def after_open(self): # After graph has been drawn
self.reg_task(self.run(self.g), True) # Cancel on screen change
async def run(self, g):
await asyncio.sleep_ms(0)
curve = Curve(g, RED)
x = -1
for _ in range(40):
y = 0.1/x if abs(x) > 0.05 else None # Discontinuity
curve.point(x, y)
await asyncio.sleep_ms(100)
x += 0.05
g.clear()
curve = Curve(g, YELLOW)
x = -1
for _ in range(40):
y = -0.1/x if abs(x) > 0.05 else None # Discontinuity
curve.point(x, y)
await asyncio.sleep_ms(100)
x += 0.05
class TSeq(Screen):
def __init__(self):
super().__init__()
self.g = CartesianGraph(wri, 2, 2, xorigin = 10, fgcolor=GREEN,
gridcolor=LIGHTGREEN, bdcolor=False)
Label(wri, 100, 2, 'Time sequence.')
fwdbutton(wri, 2, 130, EmptyScreen, 'Forward', GREEN)
CloseButton(wri)
def after_open(self): # After graph has been drawn
self.reg_task(self.run(self.g), True) # Cancel on screen change
async def run(self, g):
await asyncio.sleep_ms(0)
tsy = TSequence(g, YELLOW, 50)
tsr = TSequence(g, RED, 50)
t = 0
while True:
g.show() # Redraw the empty graph
tsy.add(0.9*math.sin(t/10))
tsr.add(0.4*math.cos(t/10)) # Plot the new curves
await asyncio.sleep_ms(400)
t += 1
class BaseScreen(Screen):
def __init__(self):
super().__init__()
d = OrderedDict()
d['Cartesian'] = CartesianScreen
d['Polar'] = PolarScreen
d['Lissajous'] = Lissajous
d['Lemniscate'] = Lemniscate
d['Polar clipping'] = PolarClip
d['Realtime polar'] = RTPolar
d['Realtime rect'] = RTRect
d['Time sequence'] = TSeq
row = 2
col = 2
Listbox(wri, row, col, callback=self.lbcb, args=(d,),
elements = tuple(d.keys()),
bdcolor = GREEN, bgcolor = DARKGREEN)
CloseButton(wri)
def lbcb(self, lb, d):
Screen.change(d[lb.textvalue()])
def test():
if display.height < 240 or display.width < 320:
print(' This test requires a display of at least 320x240 pixels.')
else:
print('Testing micro-gui...')
Screen.change(BaseScreen)
test()

Wyświetl plik

@ -0,0 +1,109 @@
# screens.py micro-gui demo of multiple screens, dropdowns etc
# Initialise hardware and framebuf before importing modules.
# Import SSD and Display instances. Must be done first because of RAM use.
from hardware_setup import display, ssd # Create a display instance
from gui.core.ugui import Screen, Window
from gui.widgets.label import Label
from gui.widgets.buttons import Button, RadioButtons, CloseButton
from gui.widgets.listbox import Listbox, ON_LEAVE
from gui.widgets.dropdown import Dropdown
from gui.widgets.dialog import DialogBox
from gui.core.writer import CWriter
# Font for CWriter
import gui.fonts.arial10 as arial10
from gui.core.colors import *
# Note that litcolor is defeated by design, because the callback's action
# is to change the screen currency. This tests the bugfix.
def fwdbutton(writer, row, col, cls_screen, text, color, *args, **kwargs):
def fwd(button):
Screen.change(cls_screen, args = args, kwargs = kwargs)
Button(writer, row, col, callback = fwd, bgcolor = color, litcolor = YELLOW,
text = text, textcolor = WHITE, shape = CLIPPED_RECT)
# Demo of creating a dialog manually
class UserDialogBox(Window):
def __init__(self, writer, callback, args):
def back(button, text):
Window.value(text)
callback(Window, *args)
Screen.back()
def close_cb(button, text):
Window.value(text)
callback(Window, *args)
height = 80
width = 150
super().__init__(20, 20, height, width, bgcolor = DARKGREEN)
row = self.height - 30
# .locn converts Window relative row, col to absolute row, col
Button(writer, *self.locn(row, 20), height = 20, width = 50, textcolor = BLACK, bgcolor = RED,
text = 'Cat', callback = back, args = ('Cat',))
Button(writer, *self.locn(row, 80), height = 20, width = 50, textcolor = BLACK, bgcolor = GREEN,
text = 'Dog', callback = back, args = ('Dog',))
CloseButton(writer, callback=close_cb, args = ('Close',))
# Minimal screen change example
class Overlay(Screen):
def __init__(self):
super().__init__()
wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False)
Label(wri, 20, 20, 'Screen overlays base')
CloseButton(wri)
class BaseScreen(Screen):
def __init__(self):
def lbcb(lb):
print('Listbox', lb.textvalue())
def ddcb(dd):
print('Dropdown', tv := dd.textvalue())
if tv == 'new screen':
Screen.change(Overlay)
def dbcb(window, label):
label.value('Auto Dialog: {}'.format(window.value()))
def udbcb(window, label):
label.value('User Dialog: {}'.format(window.value()))
super().__init__()
wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False)
col = 2
row = 2
Listbox(wri, row, col, callback=lbcb,
elements = ('cat', 'dog', 'aardvark', 'goat', 'pig', 'mouse'),
bdcolor = GREEN, bgcolor = DARKGREEN, also = ON_LEAVE)
col = 70
Dropdown(wri, row, col, callback=ddcb,
elements = ('hydrogen', 'helium', 'neon', 'xenon', 'new screen'),
bdcolor = GREEN, bgcolor = DARKGREEN)
row += 30
lbl = Label(wri, row, col, 'Result from dialog box.')
row += 20
dialog_elements = (('Yes', GREEN), ('No', RED), ('Foo', YELLOW))
# 1st 6 args are for fwdbutton
fwdbutton(wri, row, col, DialogBox, 'Dialog', RED,
# Args for DialogBox constructor
wri, elements = dialog_elements, label = 'Test dialog', callback = dbcb, args = (lbl,))
col += 60
fwdbutton(wri, row, col, UserDialogBox, 'User', BLUE,
# Args for UserDialogBox constructor
wri, udbcb, (lbl,))
CloseButton(wri) # Quit the application
def test():
if display.height < 128 or display.width < 240:
print('This test requires a display of at least 240x128 pixels.')
else:
print('Testing micro-gui...')
Screen.change(BaseScreen)
test()

Wyświetl plik

@ -0,0 +1,39 @@
# simple.py Minimal micro-gui demo.
# Initialise hardware and framebuf before importing modules.
# Import SSD and Display instances. Must be done first because of RAM use.
from hardware_setup import ssd # Create a display instance
from gui.core.ugui import Screen
from gui.widgets.label import Label
from gui.widgets.buttons import Button, CloseButton
from gui.core.writer import CWriter
# Font for CWriter
import gui.fonts.arial10 as arial10
from gui.core.colors import *
class BaseScreen(Screen):
def __init__(self):
def my_callback(button, arg):
print('Button pressed', arg)
super().__init__()
wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False)
col = 2
row = 2
Label(wri, row, col, 'Simple Demo')
row = 20
Button(wri, row, col, text='Yes', callback=my_callback, args=('Yes',))
col += 60
Button(wri, row, col, text='No', callback=my_callback, args=('No',))
CloseButton(wri) # Quit the application
def test():
print('Testing micro-gui...')
Screen.change(BaseScreen)
test()

120
gui/demos/tbox.py 100644
Wyświetl plik

@ -0,0 +1,120 @@
# tbox.py Test/demo of Textbox widget for micro-gui
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# Usage:
# import gui.demos.tbox
# Initialise hardware and framebuf before importing modules.
from hardware_setup import display, ssd # Create a display instance
from gui.core.ugui import Screen
from gui.core.writer import CWriter
import uasyncio as asyncio
from gui.core.colors import *
import gui.fonts.arial10 as arial10
from gui.widgets.label import Label
from gui.widgets.textbox import Textbox
from gui.widgets.buttons import Button, CloseButton
wri = CWriter(ssd, arial10, verbose=False)
def fwdbutton(wri, row, col, cls_screen, width, text='Next'):
def fwd(button):
Screen.change(cls_screen)
Button(wri, row, col, height = 20, width = width,
callback = fwd, fgcolor = BLACK, bgcolor = GREEN,
text = text, shape = RECTANGLE)
return width
async def wrap(tb):
s = '''The textbox displays multiple lines of text in a field of fixed dimensions. \
Text may be clipped to the width of the control or may be word-wrapped. If the number \
of lines of text exceeds the height available, scrolling may be performed \
by calling a method.
'''
tb.clear()
tb.append(s, ntrim = 100, line = 0)
while True:
await asyncio.sleep(1)
if not tb.scroll(1):
break
async def clip(tb):
ss = ('clip demo', 'short', 'longer line', 'much longer line with spaces',
'antidisestablishmentarianism', 'line with\nline break', 'Done')
tb.clear()
for s in ss:
tb.append(s, ntrim = 100) # Default line=None scrolls to show most recent
await asyncio.sleep(1)
# Args for textboxes
# Positional
pargs = (2, 2, 124, 7) # Row, Col, Width, nlines
# Keyword
tbargs = {'fgcolor' : YELLOW,
'bdcolor' : RED,
'bgcolor' : DARKGREEN,
}
class TBCScreen(Screen):
def __init__(self):
super().__init__()
self.tb = Textbox(wri, *pargs, clip=True, **tbargs)
CloseButton(wri)
asyncio.create_task(self.main())
async def main(self):
await clip(self.tb)
class TBWScreen(Screen):
def __init__(self):
super().__init__()
self.tb = Textbox(wri, *pargs, clip=False, **tbargs)
CloseButton(wri)
asyncio.create_task(self.main())
async def main(self):
await wrap(self.tb)
user_str = '''The textbox displays multiple lines of text in a field of fixed dimensions. \
Text may be clipped to the width of the control or may be word-wrapped. If the number \
of lines of text exceeds the height available, scrolling may be performed \
by calling a method.
Please use the increase and decrease buttons to scroll this text.
'''
class TBUScreen(Screen):
def __init__(self):
super().__init__()
tb = Textbox(wri, *pargs, clip=False, active=True, **tbargs)
tb.append(user_str, ntrim=100)
CloseButton(wri)
class MainScreen(Screen):
def __init__(self):
super().__init__()
Label(wri, 2, 2, 'Select test to run')
col = 2
row = 60
col += fwdbutton(wri, row, col, TBWScreen, 50, 'Wrap') + 10
col += fwdbutton(wri, row, col, TBCScreen, 50, 'Clip') + 10
fwdbutton(wri, row, col, TBUScreen, 50, 'Scroll')
CloseButton(wri)
def test():
if display.height < 128 or display.width < 128:
print(' This test requires a display of at least 128x128 pixels.')
else:
print('Testing micro-gui...')
Screen.change(MainScreen)
test()

Wyświetl plik

@ -0,0 +1,160 @@
# various.py micro-gui demo of multiple controls on a large display
# Initialise hardware and framebuf before importing modules.
# Import SSD and Display instances. Must be done first because of RAM use.
from hardware_setup import display, ssd # Create a display instance
from gui.core.ugui import Screen
from gui.core.writer import CWriter
import gui.fonts.arial10 as arial10 # Font for CWriter
from gui.core.colors import *
# Widgets
from gui.widgets.label import Label
from gui.widgets.dial import Dial, Pointer
from gui.widgets.meter import Meter
from gui.widgets.scale import Scale
from gui.widgets.buttons import Button, ButtonList, RadioButtons, CloseButton
from gui.widgets.checkbox import Checkbox
from gui.widgets.led import LED
import cmath
import uasyncio as asyncio
import utime
import gc
class FooScreen(Screen):
def __init__(self):
buttons = []
# A ButtonList with two entries
table_buttonset = (
{'fgcolor' : RED, 'text' : 'Disable', 'args' : (buttons, True)},
{'fgcolor' : GREEN, 'text' : 'Enable', 'args' : (buttons, False)},
)
table_radiobuttons = (
{'text' : '1', 'args' : ('1',)},
{'text' : '2', 'args' : ('2',)},
{'text' : '3', 'args' : ('3',)},
{'text' : '4', 'args' : ('4',)},
)
def tickcb(f, c):
if f > 0.8:
return RED
if f < -0.8:
return BLUE
return c
def bcb(b):
print('Button pressed', b)
super().__init__()
self.rb0 = None
self.bs0 = None
wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False)
lbltim = Label(wri, 50, 85, 'this is a test', bdcolor=RED)
m0 = Meter(wri, 20, 240, divisions = 4, ptcolor=YELLOW,
label='left', style=Meter.LINE, legends=('0.0', '0.5', '1.0'))
# Instantiate displayable objects. bgcolor forces complete redraw.
dial = Dial(wri, 2, 2, height = 75, ticks = 12, bgcolor=BLACK, bdcolor=None, label=120) # Border in fg color
scale = Scale(wri, 2, 100, width = 124, tickcb = tickcb,
pointercolor=RED, fontcolor=YELLOW, bdcolor=CYAN)
# Four Button instances
row = 120
ht = 30
for i, s in enumerate(('a', 'b', 'c', 'd')):
col= 2 + i * (ht + 5)
buttons.append(Button(wri, row, col, height=ht, callback=bcb, text=s, litcolor=RED, shape=CIRCLE, bgcolor=DARKGREEN))
# ButtonList
self.bs = ButtonList(self.callback)
self.bs0 = None
col+= 50
for t in table_buttonset: # Buttons overlay each other at same location
button = self.bs.add_button(wri, 120, col, shape=RECTANGLE, textcolor=BLUE, height=30, **t)
if self.bs0 is None: # Save for reset button callback
self.bs0 = button
# Reset button
col+= 60
btn = Button(wri, row, col, height=30, callback=self.rstcb, text='reset', litcolor=RED, fgcolor=GREEN, bgcolor=DARKGREEN)
# Radio buttons
col= 2
self.rb = RadioButtons(BLUE, self.rbcb) # color of selected button
self.rb0 = None
for t in table_radiobuttons:
button = self.rb.add_button(wri, 160, col, textcolor = WHITE,
fgcolor = BLUE, bgcolor = DARKBLUE, shape=CIRCLE, height = 30, **t)
if self.rb0 is None: # Save for reset button callback
self.rb0 = button
col+= 35
# Checkbox
col+= 35
Checkbox(wri, 160, col, callback=self.cbcb)
col+= 40
self.led = LED(wri, 160, col, color=YELLOW, bdcolor=GREEN)
CloseButton(wri)
asyncio.create_task(run(dial, lbltim, m0, scale))
def callback(self, button, buttons, val):
buttons[2].greyed_out(val)
def rbcb(self, button, val):
print('RadioButtons callback', val)
def rstcb(self, button):
print('Reset button: init ButtonList and RadioButtons')
self.bs.value(self.bs0) # BUG This is calling ButtonList.value which calls ._update -> Screen.select which changes currency
self.rb.value(self.rb0)
def cbcb(self, cb):
self.led.value(cb.value())
gc.collect()
print('Free RAM:', gc.mem_free())
async def run(dial, lbltim, m0, scale):
days = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',
'Sunday')
months = ('Jan', 'Feb', 'March', 'April', 'May', 'June', 'July',
'Aug', 'Sept', 'Oct', 'Nov', 'Dec')
uv = lambda phi : cmath.rect(1, phi) # Return a unit vector of phase phi
pi = cmath.pi
hrs = Pointer(dial)
mins = Pointer(dial)
secs = Pointer(dial)
hstart = 0 + 0.7j # Pointer lengths and position at top
mstart = 0 + 0.92j
sstart = 0 + 0.92j
cv = -1.0 # Scale
dv = 0.005
while True:
t = utime.localtime()
hrs.value(hstart * uv(-t[3]*pi/6 - t[4]*pi/360), YELLOW)
mins.value(mstart * uv(-t[4] * pi/30), YELLOW)
secs.value(sstart * uv(-t[5] * pi/30), RED)
lbltim.value('{:02d}.{:02d}.{:02d}'.format(t[3], t[4], t[5]))
dial.text('{} {} {} {}'.format(days[t[6]], t[2], months[t[1] - 1], t[0]))
m0.value(t[5]/60)
scale.value(cv)
await asyncio.sleep_ms(200)
cv += dv
if abs(cv) > 1.0:
dv = -dv
cv += dv
def test():
if display.height < 240 or display.width < 320:
print(' This test requires a display of at least 320x240 pixels.')
else:
print('Testing micro-gui...')
Screen.change(FooScreen)
test()

113
gui/demos/vtest.py 100644
Wyświetl plik

@ -0,0 +1,113 @@
# vtest.py Test/demo of VectorDial for micro-gui
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# Initialise hardware and framebuf before importing modules.
from hardware_setup import display, ssd # Create a display instance
import urandom
import time
from cmath import rect, pi
import uasyncio as asyncio
from gui.core.ugui import Screen
from gui.core.writer import CWriter
from gui.fonts import font10
from gui.core.colors import *
# Widgets
from gui.widgets.label import Label
from gui.widgets.buttons import Button, CloseButton
from gui.widgets.vectors import Pointer, VectorDial
def fwdbutton(wri, row, col, cls_screen, text='Next'):
def fwd(button):
Screen.change(cls_screen)
Button(wri, row, col, height = 30, callback = fwd, fgcolor = BLACK, bgcolor = GREEN,
text = text, shape = RECTANGLE, width = 100)
class BackScreen(Screen):
def __init__(self):
super().__init__()
wri = CWriter(ssd, font10, GREEN, BLACK, verbose=False)
Label(wri, 2, 2, 'Ensure back refreshes properly')
CloseButton(wri)
# Create a random vector. Interpolate between current vector and the new one.
# Change pointer color dependent on magnitude.
async def ptr_test(dial):
ptr = Pointer(dial)
v = 0j
steps = 20 # No. of interpolation steps
grv = lambda : urandom.getrandbits(16) / 2**15 - 1 # Random: range -1.0 to +1.0
while True:
v1 = grv() + 1j * grv() # Random vector
dv = (v1 - v) / steps # Interpolation vector
for _ in range(steps):
v += dv
mag = abs(v)
if mag < 0.3:
ptr.value(v, BLUE)
elif mag < 0.7:
ptr.value(v, GREEN)
else:
ptr.value(v, RED)
await asyncio.sleep_ms(200)
# Analog clock demo. Note this could also be achieved using the Dial class.
async def aclock(dial, lbldate, lbltim):
uv = lambda phi : rect(1, phi) # Return a unit vector of phase phi
days = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',
'Sunday')
months = ('January', 'February', 'March', 'April', 'May', 'June', 'July',
'August', 'September', 'October', 'November', 'December')
hrs = Pointer(dial)
mins = Pointer(dial)
secs = Pointer(dial)
hstart = 0 + 0.7j # Pointer lengths. Position at top.
mstart = 0 + 1j
sstart = 0 + 1j
while True:
t = time.localtime()
hrs.value(hstart * uv(-t[3] * pi/6 - t[4] * pi / 360), CYAN)
mins.value(mstart * uv(-t[4] * pi/30), CYAN)
secs.value(sstart * uv(-t[5] * pi/30), RED)
lbltim.value('{:02d}.{:02d}.{:02d}'.format(t[3], t[4], t[5]))
lbldate.value('{} {} {} {}'.format(days[t[6]], t[2], months[t[1] - 1], t[0]))
await asyncio.sleep(1)
class VScreen(Screen):
def __init__(self):
super().__init__()
labels = {'bdcolor' : RED,
'fgcolor' : WHITE,
'bgcolor' : DARKGREEN,
}
wri = CWriter(ssd, font10, GREEN, BLACK, verbose=False)
fwdbutton(wri, 200, 2, BackScreen, 'Forward')
CloseButton(wri)
# Set up random vector display with two pointers
dial = VectorDial(wri, 2, 2, height = 100, ticks = 12, fgcolor = YELLOW, arrow = True)
self.reg_task(ptr_test(dial))
self.reg_task(ptr_test(dial))
# Set up clock display: instantiate labels
lbldate = Label(wri, 110, 2, 200, **labels)
lbltim = Label(wri, 150, 2, 80, **labels)
dial = VectorDial(wri, 2, 120, height = 100, ticks = 12, fgcolor = GREEN, pip = GREEN)
self.reg_task(aclock(dial, lbldate, lbltim))
def test():
if display.height < 240 or display.width < 320:
print(' This test requires a display of at least 320x240 pixels.')
else:
print('Testing micro-gui...')
Screen.change(VScreen)
test()

Wyświetl plik

Wyświetl plik

@ -0,0 +1,139 @@
# Code generated by font-to-py.py.
# Font: Arial.ttf
version = '0.25'
def height():
return 10
def max_width():
return 11
def hmap():
return True
def reverse():
return False
def monospaced():
return False
def min_ch():
return 32
def max_ch():
return 126
_font =\
b'\x06\x00\x70\x88\x08\x10\x20\x20\x00\x20\x00\x00\x03\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x80\x80\x80\x80\x80\x80'\
b'\x00\x80\x00\x00\x04\x00\xa0\xa0\xa0\x00\x00\x00\x00\x00\x00\x00'\
b'\x06\x00\x28\x28\xf8\x50\x50\xf8\xa0\xa0\x00\x00\x06\x00\x70\xa8'\
b'\xa0\x70\x28\x28\xa8\x70\x20\x00\x0a\x00\x62\x00\x94\x00\x94\x00'\
b'\x68\x00\x0b\x00\x14\x80\x14\x80\x23\x00\x00\x00\x00\x00\x07\x00'\
b'\x30\x48\x48\x30\x50\x8c\x88\x74\x00\x00\x02\x00\x80\x80\x80\x00'\
b'\x00\x00\x00\x00\x00\x00\x04\x00\x20\x40\x80\x80\x80\x80\x80\x80'\
b'\x40\x20\x04\x00\x80\x40\x20\x20\x20\x20\x20\x20\x40\x80\x04\x00'\
b'\x40\xe0\x40\xa0\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x20\x20'\
b'\xf8\x20\x20\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x80'\
b'\x80\x80\x04\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\x00\x03\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x03\x00\x20\x20\x40\x40'\
b'\x40\x40\x80\x80\x00\x00\x06\x00\x70\x88\x88\x88\x88\x88\x88\x70'\
b'\x00\x00\x06\x00\x20\x60\xa0\x20\x20\x20\x20\x20\x00\x00\x06\x00'\
b'\x70\x88\x08\x08\x10\x20\x40\xf8\x00\x00\x06\x00\x70\x88\x08\x30'\
b'\x08\x08\x88\x70\x00\x00\x06\x00\x10\x30\x50\x50\x90\xf8\x10\x10'\
b'\x00\x00\x06\x00\x78\x40\x80\xf0\x08\x08\x88\x70\x00\x00\x06\x00'\
b'\x70\x88\x80\xf0\x88\x88\x88\x70\x00\x00\x06\x00\xf8\x10\x10\x20'\
b'\x20\x40\x40\x40\x00\x00\x06\x00\x70\x88\x88\x70\x88\x88\x88\x70'\
b'\x00\x00\x06\x00\x70\x88\x88\x88\x78\x08\x88\x70\x00\x00\x03\x00'\
b'\x00\x00\x80\x00\x00\x00\x00\x80\x00\x00\x03\x00\x00\x00\x80\x00'\
b'\x00\x00\x00\x80\x80\x80\x06\x00\x00\x00\x08\x70\x80\x70\x08\x00'\
b'\x00\x00\x06\x00\x00\x00\x00\xf8\x00\xf8\x00\x00\x00\x00\x06\x00'\
b'\x00\x00\x80\x70\x08\x70\x80\x00\x00\x00\x06\x00\x70\x88\x08\x10'\
b'\x20\x20\x00\x20\x00\x00\x0b\x00\x1f\x00\x60\x80\x4d\x40\x93\x40'\
b'\xa2\x40\xa2\x40\xa6\x80\x9b\x00\x40\x40\x3f\x80\x08\x00\x10\x28'\
b'\x28\x28\x44\x7c\x82\x82\x00\x00\x07\x00\xf8\x84\x84\xfc\x84\x84'\
b'\x84\xf8\x00\x00\x07\x00\x38\x44\x80\x80\x80\x80\x44\x38\x00\x00'\
b'\x07\x00\xf0\x88\x84\x84\x84\x84\x88\xf0\x00\x00\x06\x00\xf8\x80'\
b'\x80\xf8\x80\x80\x80\xf8\x00\x00\x06\x00\xf8\x80\x80\xf0\x80\x80'\
b'\x80\x80\x00\x00\x08\x00\x38\x44\x82\x80\x8e\x82\x44\x38\x00\x00'\
b'\x07\x00\x84\x84\x84\xfc\x84\x84\x84\x84\x00\x00\x02\x00\x80\x80'\
b'\x80\x80\x80\x80\x80\x80\x00\x00\x05\x00\x10\x10\x10\x10\x10\x90'\
b'\x90\x60\x00\x00\x07\x00\x84\x88\x90\xb0\xd0\x88\x88\x84\x00\x00'\
b'\x06\x00\x80\x80\x80\x80\x80\x80\x80\xf8\x00\x00\x08\x00\x82\xc6'\
b'\xc6\xaa\xaa\xaa\x92\x92\x00\x00\x07\x00\x84\xc4\xa4\xa4\x94\x94'\
b'\x8c\x84\x00\x00\x08\x00\x38\x44\x82\x82\x82\x82\x44\x38\x00\x00'\
b'\x06\x00\xf0\x88\x88\x88\xf0\x80\x80\x80\x00\x00\x08\x00\x38\x44'\
b'\x82\x82\x82\x9a\x44\x3e\x00\x00\x07\x00\xf8\x84\x84\xf8\x90\x88'\
b'\x88\x84\x00\x00\x07\x00\x78\x84\x80\x60\x18\x04\x84\x78\x00\x00'\
b'\x06\x00\xf8\x20\x20\x20\x20\x20\x20\x20\x00\x00\x07\x00\x84\x84'\
b'\x84\x84\x84\x84\x84\x78\x00\x00\x08\x00\x82\x82\x44\x44\x28\x28'\
b'\x10\x10\x00\x00\x0b\x00\x84\x20\x8a\x20\x4a\x40\x4a\x40\x51\x40'\
b'\x51\x40\x20\x80\x20\x80\x00\x00\x00\x00\x07\x00\x84\x48\x48\x30'\
b'\x30\x48\x48\x84\x00\x00\x08\x00\x82\x44\x44\x28\x10\x10\x10\x10'\
b'\x00\x00\x07\x00\x7c\x08\x10\x10\x20\x20\x40\xfc\x00\x00\x03\x00'\
b'\xc0\x80\x80\x80\x80\x80\x80\x80\x80\xc0\x03\x00\x80\x80\x40\x40'\
b'\x40\x40\x20\x20\x00\x00\x03\x00\xc0\x40\x40\x40\x40\x40\x40\x40'\
b'\x40\xc0\x05\x00\x20\x50\x50\x88\x00\x00\x00\x00\x00\x00\x06\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\xfc\x00\x04\x00\x80\x40\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x70\x88\x78\x88\x98\xe8'\
b'\x00\x00\x06\x00\x80\x80\xb0\xc8\x88\x88\xc8\xb0\x00\x00\x06\x00'\
b'\x00\x00\x70\x88\x80\x80\x88\x70\x00\x00\x06\x00\x08\x08\x68\x98'\
b'\x88\x88\x98\x68\x00\x00\x06\x00\x00\x00\x70\x88\xf8\x80\x88\x70'\
b'\x00\x00\x04\x00\x20\x40\xe0\x40\x40\x40\x40\x40\x00\x00\x06\x00'\
b'\x00\x00\x68\x98\x88\x88\x98\x68\x08\xf0\x06\x00\x80\x80\xb0\xc8'\
b'\x88\x88\x88\x88\x00\x00\x02\x00\x80\x00\x80\x80\x80\x80\x80\x80'\
b'\x00\x00\x02\x00\x40\x00\x40\x40\x40\x40\x40\x40\x40\x80\x05\x00'\
b'\x80\x80\x90\xa0\xc0\xe0\xa0\x90\x00\x00\x02\x00\x80\x80\x80\x80'\
b'\x80\x80\x80\x80\x00\x00\x08\x00\x00\x00\xbc\xd2\x92\x92\x92\x92'\
b'\x00\x00\x06\x00\x00\x00\xf0\x88\x88\x88\x88\x88\x00\x00\x06\x00'\
b'\x00\x00\x70\x88\x88\x88\x88\x70\x00\x00\x06\x00\x00\x00\xb0\xc8'\
b'\x88\x88\xc8\xb0\x80\x80\x06\x00\x00\x00\x68\x98\x88\x88\x98\x68'\
b'\x08\x08\x04\x00\x00\x00\xa0\xc0\x80\x80\x80\x80\x00\x00\x06\x00'\
b'\x00\x00\x70\x88\x60\x10\x88\x70\x00\x00\x03\x00\x40\x40\xe0\x40'\
b'\x40\x40\x40\x60\x00\x00\x06\x00\x00\x00\x88\x88\x88\x88\x98\x68'\
b'\x00\x00\x06\x00\x00\x00\x88\x88\x50\x50\x20\x20\x00\x00\x0a\x00'\
b'\x00\x00\x00\x00\x88\x80\x94\x80\x55\x00\x55\x00\x22\x00\x22\x00'\
b'\x00\x00\x00\x00\x06\x00\x00\x00\x88\x50\x20\x20\x50\x88\x00\x00'\
b'\x06\x00\x00\x00\x88\x88\x50\x50\x20\x20\x20\x40\x06\x00\x00\x00'\
b'\xf8\x10\x20\x20\x40\xf8\x00\x00\x04\x00\x20\x40\x40\x40\x80\x40'\
b'\x40\x40\x40\x20\x02\x00\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80'\
b'\x04\x00\x80\x40\x40\x40\x20\x40\x40\x40\x40\x80\x06\x00\x00\x00'\
b'\x00\xe8\xb0\x00\x00\x00\x00\x00'
_index =\
b'\x00\x00\x0c\x00\x0c\x00\x18\x00\x18\x00\x24\x00\x24\x00\x30\x00'\
b'\x30\x00\x3c\x00\x3c\x00\x48\x00\x48\x00\x5e\x00\x5e\x00\x6a\x00'\
b'\x6a\x00\x76\x00\x76\x00\x82\x00\x82\x00\x8e\x00\x8e\x00\x9a\x00'\
b'\x9a\x00\xa6\x00\xa6\x00\xb2\x00\xb2\x00\xbe\x00\xbe\x00\xca\x00'\
b'\xca\x00\xd6\x00\xd6\x00\xe2\x00\xe2\x00\xee\x00\xee\x00\xfa\x00'\
b'\xfa\x00\x06\x01\x06\x01\x12\x01\x12\x01\x1e\x01\x1e\x01\x2a\x01'\
b'\x2a\x01\x36\x01\x36\x01\x42\x01\x42\x01\x4e\x01\x4e\x01\x5a\x01'\
b'\x5a\x01\x66\x01\x66\x01\x72\x01\x72\x01\x7e\x01\x7e\x01\x8a\x01'\
b'\x8a\x01\x96\x01\x96\x01\xac\x01\xac\x01\xb8\x01\xb8\x01\xc4\x01'\
b'\xc4\x01\xd0\x01\xd0\x01\xdc\x01\xdc\x01\xe8\x01\xe8\x01\xf4\x01'\
b'\xf4\x01\x00\x02\x00\x02\x0c\x02\x0c\x02\x18\x02\x18\x02\x24\x02'\
b'\x24\x02\x30\x02\x30\x02\x3c\x02\x3c\x02\x48\x02\x48\x02\x54\x02'\
b'\x54\x02\x60\x02\x60\x02\x6c\x02\x6c\x02\x78\x02\x78\x02\x84\x02'\
b'\x84\x02\x90\x02\x90\x02\x9c\x02\x9c\x02\xa8\x02\xa8\x02\xb4\x02'\
b'\xb4\x02\xca\x02\xca\x02\xd6\x02\xd6\x02\xe2\x02\xe2\x02\xee\x02'\
b'\xee\x02\xfa\x02\xfa\x02\x06\x03\x06\x03\x12\x03\x12\x03\x1e\x03'\
b'\x1e\x03\x2a\x03\x2a\x03\x36\x03\x36\x03\x42\x03\x42\x03\x4e\x03'\
b'\x4e\x03\x5a\x03\x5a\x03\x66\x03\x66\x03\x72\x03\x72\x03\x7e\x03'\
b'\x7e\x03\x8a\x03\x8a\x03\x96\x03\x96\x03\xa2\x03\xa2\x03\xae\x03'\
b'\xae\x03\xba\x03\xba\x03\xc6\x03\xc6\x03\xd2\x03\xd2\x03\xde\x03'\
b'\xde\x03\xea\x03\xea\x03\xf6\x03\xf6\x03\x02\x04\x02\x04\x0e\x04'\
b'\x0e\x04\x1a\x04\x1a\x04\x26\x04\x26\x04\x32\x04\x32\x04\x3e\x04'\
b'\x3e\x04\x54\x04\x54\x04\x60\x04\x60\x04\x6c\x04\x6c\x04\x78\x04'\
b'\x78\x04\x84\x04\x84\x04\x90\x04\x90\x04\x9c\x04\x9c\x04\xa8\x04'\
_mvfont = memoryview(_font)
def get_ch(ch):
ordch = ord(ch)
ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 32
idx_offs = 4 * (ordch - 32)
offset = int.from_bytes(_index[idx_offs : idx_offs + 2], 'little')
next_offs = int.from_bytes(_index[idx_offs + 2 : idx_offs + 4], 'little')
width = int.from_bytes(_font[offset:offset + 2], 'little')
return _mvfont[offset + 2:next_offs], 10, width

Wyświetl plik

@ -0,0 +1,671 @@
# Code generated by font_to_py.py.
# Font: Arial.ttf
# Cmd: ./font_to_py.py Arial.ttf 35 arial35.py -x
version = '0.33'
def height():
return 35
def baseline():
return 27
def max_width():
return 37
def hmap():
return True
def reverse():
return False
def monospaced():
return False
def min_ch():
return 32
def max_ch():
return 126
_font =\
b'\x14\x00\x00\x00\x00\x01\xf8\x00\x07\xfe\x00\x0f\xff\x00\x1f\x0f'\
b'\x80\x1c\x03\xc0\x38\x01\xc0\x38\x01\xc0\x00\x01\xc0\x00\x01\xc0'\
b'\x00\x03\x80\x00\x07\x80\x00\x0f\x00\x00\x1e\x00\x00\x3c\x00\x00'\
b'\x78\x00\x00\x70\x00\x00\xe0\x00\x00\xe0\x00\x00\xe0\x00\x00\xe0'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\xe0\x00'\
b'\x00\xe0\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\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\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x0b\x00\x00\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e'\
b'\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e'\
b'\x00\x04\x00\x04\x00\x04\x00\x04\x00\x04\x00\x04\x00\x04\x00\x00'\
b'\x00\x00\x00\x00\x00\x0e\x00\x0e\x00\x0e\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x00\x00\x00\x70'\
b'\xe0\x70\xe0\x70\xe0\x70\xe0\x70\xe0\x70\xe0\x70\xe0\x70\xe0\x20'\
b'\x40\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x14\x00\x00\x00\x00\x01\xc1\xc0\x01\xc1\xc0\x01\xc3'\
b'\xc0\x03\x83\x80\x03\x83\x80\x03\x83\x80\x03\x83\x80\xff\xff\xf0'\
b'\xff\xff\xf0\xff\xff\xf0\x07\x07\x00\x07\x07\x00\x07\x07\x00\x0e'\
b'\x0e\x00\x0e\x0e\x00\x0e\x0e\x00\xff\xff\xf0\xff\xff\xf0\xff\xff'\
b'\xf0\x1c\x1c\x00\x1c\x1c\x00\x1c\x1c\x00\x1c\x1c\x00\x3c\x38\x00'\
b'\x38\x38\x00\x38\x38\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\x14\x00'\
b'\x00\x60\x00\x03\xf8\x00\x0f\xfe\x00\x1f\xff\x00\x1e\x67\x00\x3c'\
b'\x63\x80\x38\x63\x80\x38\x60\x00\x38\x60\x00\x38\x60\x00\x1c\x60'\
b'\x00\x1f\xe0\x00\x0f\xf8\x00\x03\xfe\x00\x00\xff\x00\x00\x6f\x80'\
b'\x00\x63\x80\x00\x61\xc0\x00\x61\xc0\x70\x61\xc0\x70\x61\xc0\x78'\
b'\x63\xc0\x3c\x63\x80\x3e\x67\x80\x1f\xff\x00\x0f\xfe\x00\x01\xf8'\
b'\x00\x00\x60\x00\x00\x60\x00\x00\x60\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x00\x00\x07'\
b'\xc0\x03\x80\x0f\xe0\x07\x80\x1c\x70\x07\x00\x1c\x70\x0f\x00\x38'\
b'\x38\x0e\x00\x38\x38\x1c\x00\x38\x38\x3c\x00\x38\x38\x38\x00\x38'\
b'\x38\x78\x00\x38\x38\x70\x00\x18\x70\xf0\x00\x1c\x70\xe0\x00\x0f'\
b'\xe1\xc3\xe0\x07\xc1\xc7\xf0\x00\x03\x8e\x38\x00\x07\x8e\x38\x00'\
b'\x07\x1c\x1c\x00\x0f\x1c\x1c\x00\x0e\x1c\x1c\x00\x1e\x1c\x1c\x00'\
b'\x1c\x1c\x1c\x00\x38\x1c\x1c\x00\x78\x0c\x38\x00\x70\x0e\x38\x00'\
b'\xf0\x07\xf0\x00\xe0\x03\xe0\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\x00\x00\x00\x18\x00\x00\x00\x00\x00\x7c\x00\x01'\
b'\xfe\x00\x03\xff\x00\x07\x87\x80\x07\x03\x80\x07\x03\x80\x07\x03'\
b'\x80\x07\x83\x80\x03\x87\x00\x03\xde\x00\x01\xfc\x00\x01\xf8\x00'\
b'\x03\xf0\x00\x0f\xb8\x00\x1e\x3c\x20\x1c\x1e\x38\x3c\x0f\x78\x38'\
b'\x07\x70\x38\x03\xf0\x38\x03\xe0\x3c\x01\xe0\x1c\x03\xf0\x1f\x0f'\
b'\xf8\x0f\xff\xbe\x07\xfe\x1c\x01\xf8\x08\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\x07\x00\x00\x38\x38\x38\x38\x38\x38\x38\x38\x10\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\x0c\x00\x00\x00\x00\xc0\x01\x80\x03'\
b'\x80\x03\x00\x07\x00\x06\x00\x0e\x00\x0c\x00\x1c\x00\x1c\x00\x1c'\
b'\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38'\
b'\x00\x38\x00\x38\x00\x18\x00\x1c\x00\x1c\x00\x1c\x00\x0c\x00\x0e'\
b'\x00\x06\x00\x07\x00\x03\x00\x03\x80\x01\x80\x00\xc0\x00\x00\x0c'\
b'\x00\x00\x00\x30\x00\x18\x00\x1c\x00\x0c\x00\x0e\x00\x06\x00\x07'\
b'\x00\x03\x00\x03\x80\x03\x80\x03\x80\x01\x80\x01\xc0\x01\xc0\x01'\
b'\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\xc0\x01\x80\x03'\
b'\x80\x03\x80\x03\x80\x07\x00\x07\x00\x06\x00\x0e\x00\x0c\x00\x1c'\
b'\x00\x18\x00\x30\x00\x00\x00\x0e\x00\x00\x00\x03\x80\x03\x80\x03'\
b'\x80\x3b\xb8\x7f\xfc\x1f\xf0\x07\xc0\x0e\xe0\x1e\xf0\x1c\x70\x08'\
b'\x20\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x15'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x70\x00\x00\x70\x00\x00\x70\x00\x00\x70\x00\x00'\
b'\x70\x00\x00\x70\x00\x00\x70\x00\x3f\xff\xe0\x3f\xff\xe0\x3f\xff'\
b'\xe0\x00\x70\x00\x00\x70\x00\x00\x70\x00\x00\x70\x00\x00\x70\x00'\
b'\x00\x70\x00\x00\x70\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\x00\x00\x00\x00\x00\x00\x0a\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\x00\x00\x00\x00\x00\x00\x00\x00\x1c\x00\x1c\x00'\
b'\x1c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x18\x00\x18\x00\x00\x00'\
b'\x00\x00\x0c\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\x7f\xe0\x7f\xe0\x7f\xe0\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\x00\x00\x00\x00\x00\x00\x0a\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\x00\x00\x00\x00\x00\x00\x00\x00\x1c\x00\x1c\x00'\
b'\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x0a\x00\x00\x00\x01\xc0\x01\xc0\x03\x80\x03\x80\x03\x80'\
b'\x03\x80\x07\x00\x07\x00\x07\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00'\
b'\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x38\x00\x38\x00\x38\x00\x78\x00'\
b'\x70\x00\x70\x00\x70\x00\xe0\x00\xe0\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\x00\x01'\
b'\xf8\x00\x07\xfe\x00\x0f\xff\x00\x0f\x0f\x00\x1e\x07\x80\x1c\x03'\
b'\x80\x1c\x03\x80\x38\x01\xc0\x38\x01\xc0\x38\x01\xc0\x38\x01\xc0'\
b'\x38\x01\xc0\x38\x01\xc0\x38\x01\xc0\x38\x01\xc0\x38\x01\xc0\x38'\
b'\x01\xc0\x38\x01\xc0\x38\x01\xc0\x1c\x03\x80\x1c\x03\x80\x1e\x07'\
b'\x80\x0f\x0f\x00\x0f\xff\x00\x07\xfe\x00\x01\xf8\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\x14\x00\x00\x00\x00\x00\x18\x00\x00\x38\x00'\
b'\x00\x78\x00\x00\xf8\x00\x01\xf8\x00\x03\xf8\x00\x0f\xb8\x00\x0f'\
b'\x38\x00\x0c\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38'\
b'\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00'\
b'\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00'\
b'\x38\x00\x00\x38\x00\x00\x38\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'\x14\x00\x00\x00\x00\x03\xf8\x00\x0f\xfe\x00\x1f\xff\x00\x3e\x0f'\
b'\x80\x38\x03\x80\x70\x03\xc0\x70\x01\xc0\x00\x01\xc0\x00\x01\xc0'\
b'\x00\x01\xc0\x00\x03\x80\x00\x07\x80\x00\x07\x00\x00\x0e\x00\x00'\
b'\x1c\x00\x00\x38\x00\x00\xf0\x00\x01\xe0\x00\x03\xc0\x00\x07\x80'\
b'\x00\x0f\x00\x00\x1c\x00\x00\x38\x00\x00\x3f\xff\xc0\x7f\xff\xc0'\
b'\x7f\xff\xc0\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\x14\x00\x00\x00\x00'\
b'\x01\xf8\x00\x07\xfe\x00\x0f\xff\x00\x1e\x0f\x00\x1c\x07\x80\x38'\
b'\x03\x80\x38\x03\x80\x00\x03\x80\x00\x07\x00\x00\x0f\x00\x00\xfe'\
b'\x00\x00\xfe\x00\x00\xff\x00\x00\x07\x80\x00\x03\x80\x00\x01\xc0'\
b'\x00\x01\xc0\x00\x01\xc0\x00\x01\xc0\x38\x01\xc0\x38\x03\xc0\x1c'\
b'\x03\x80\x1e\x0f\x80\x0f\xff\x00\x07\xfe\x00\x01\xf8\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\x14\x00\x00\x00\x00\x00\x0e\x00\x00\x1e'\
b'\x00\x00\x3e\x00\x00\x3e\x00\x00\x7e\x00\x00\xfe\x00\x00\xee\x00'\
b'\x01\xee\x00\x03\xce\x00\x03\x8e\x00\x07\x8e\x00\x07\x0e\x00\x0e'\
b'\x0e\x00\x1e\x0e\x00\x1c\x0e\x00\x38\x0e\x00\x78\x0e\x00\x7f\xff'\
b'\xc0\x7f\xff\xc0\x7f\xff\xc0\x00\x0e\x00\x00\x0e\x00\x00\x0e\x00'\
b'\x00\x0e\x00\x00\x0e\x00\x00\x0e\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\x14\x00\x00\x00\x00\x07\xff\x80\x07\xff\x80\x0f\xff\x80\x0e'\
b'\x00\x00\x0e\x00\x00\x0e\x00\x00\x1e\x00\x00\x1c\x00\x00\x1c\xf8'\
b'\x00\x1f\xfe\x00\x1f\xff\x00\x3e\x0f\x80\x38\x03\x80\x00\x03\xc0'\
b'\x00\x01\xc0\x00\x01\xc0\x00\x01\xc0\x00\x01\xc0\x00\x01\xc0\x38'\
b'\x01\xc0\x38\x03\x80\x1c\x03\x80\x1e\x0f\x00\x0f\xff\x00\x07\xfc'\
b'\x00\x01\xf8\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\x14\x00\x00\x00'\
b'\x00\x01\xf8\x00\x07\xfe\x00\x0f\xff\x00\x1f\x0f\x80\x1c\x03\x80'\
b'\x3c\x03\xc0\x38\x01\xc0\x38\x00\x00\x70\x00\x00\x70\xf8\x00\x73'\
b'\xfe\x00\x77\xff\x00\x7e\x0f\x80\x7c\x03\x80\x78\x03\xc0\x70\x01'\
b'\xc0\x70\x01\xc0\x70\x01\xc0\x70\x01\xc0\x30\x01\xc0\x38\x03\x80'\
b'\x3c\x03\x80\x1e\x0f\x80\x0f\xff\x00\x07\xfe\x00\x01\xf8\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\x14\x00\x00\x00\x00\x7f\xff\xc0\x7f'\
b'\xff\xc0\x7f\xff\xc0\x00\x01\x80\x00\x03\x00\x00\x07\x00\x00\x0e'\
b'\x00\x00\x1c\x00\x00\x1c\x00\x00\x38\x00\x00\x38\x00\x00\x70\x00'\
b'\x00\x70\x00\x00\xe0\x00\x00\xe0\x00\x01\xc0\x00\x01\xc0\x00\x01'\
b'\xc0\x00\x03\x80\x00\x03\x80\x00\x03\x80\x00\x03\x80\x00\x07\x00'\
b'\x00\x07\x00\x00\x07\x00\x00\x07\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\x14\x00\x00\x00\x00\x01\xf8\x00\x07\xfe\x00\x0f\xff\x00'\
b'\x0f\x0f\x00\x1e\x07\x80\x1c\x03\x80\x1c\x03\x80\x1c\x03\x80\x1e'\
b'\x07\x80\x0f\x0f\x00\x07\xfe\x00\x03\xfc\x00\x0f\xff\x00\x1e\x07'\
b'\x80\x1c\x03\x80\x38\x03\xc0\x38\x01\xc0\x38\x01\xc0\x38\x01\xc0'\
b'\x38\x01\xc0\x38\x03\xc0\x1c\x03\x80\x1f\x07\x80\x0f\xff\x00\x07'\
b'\xfe\x00\x01\xf8\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\x14\x00\x00'\
b'\x00\x00\x01\xf0\x00\x07\xfc\x00\x0f\xfe\x00\x1f\x07\x00\x1c\x03'\
b'\x80\x3c\x03\x80\x38\x01\x80\x38\x01\xc0\x38\x01\xc0\x38\x01\xc0'\
b'\x38\x01\xc0\x3c\x03\xc0\x1c\x03\xc0\x1e\x0f\xc0\x0f\xfd\xc0\x07'\
b'\xf9\xc0\x01\xf1\xc0\x00\x01\xc0\x00\x03\x80\x38\x03\x80\x38\x03'\
b'\x80\x1c\x07\x00\x1e\x0f\x00\x0f\xfe\x00\x07\xfc\x00\x03\xf0\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\x0a\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1c\x00\x1c\x00\x1c\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\x1c\x00\x1c\x00\x1c\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x1c\x00\x1c\x00\x1c\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\x1c\x00\x1c\x00\x1c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00'\
b'\x18\x00\x10\x00\x00\x00\x00\x00\x15\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00'\
b'\x01\xe0\x00\x07\xe0\x00\x3f\xc0\x00\xfe\x00\x03\xf8\x00\x1f\xc0'\
b'\x00\x3f\x00\x00\x38\x00\x00\x3f\x00\x00\x1f\xc0\x00\x03\xf8\x00'\
b'\x00\xfe\x00\x00\x3f\xc0\x00\x07\xe0\x00\x01\xe0\x00\x00\x20\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\x15\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'\x3f\xff\xe0\x3f\xff\xe0\x3f\xff\xe0\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x3f\xff\xe0\x3f\xff\xe0\x3f\xff'\
b'\xe0\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x15\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x20\x00\x00\x3c\x00\x00\x3f\x00\x00\x1f\xe0\x00\x03\xf8'\
b'\x00\x00\xfe\x00\x00\x1f\xc0\x00\x07\xe0\x00\x00\xe0\x00\x07\xe0'\
b'\x00\x1f\xc0\x00\xfe\x00\x03\xf8\x00\x1f\xe0\x00\x3f\x00\x00\x3c'\
b'\x00\x00\x20\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\x00\x00\x00\x00\x00\x14\x00\x00\x00\x00\x01\xf8'\
b'\x00\x07\xfe\x00\x0f\xff\x00\x1f\x0f\x80\x1c\x03\xc0\x38\x01\xc0'\
b'\x38\x01\xc0\x00\x01\xc0\x00\x01\xc0\x00\x03\x80\x00\x07\x80\x00'\
b'\x0f\x00\x00\x1e\x00\x00\x3c\x00\x00\x78\x00\x00\x70\x00\x00\xe0'\
b'\x00\x00\xe0\x00\x00\xe0\x00\x00\xe0\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\xe0\x00\x00\xe0\x00\x00\xe0\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\x25\x00\x00\x00\x00\x00\x00\x00\x01\xff\x00\x00'\
b'\x00\x0f\xff\xc0\x00\x00\x3f\xff\xf0\x00\x00\xfe\x01\xf8\x00\x01'\
b'\xf8\x00\x7c\x00\x03\xe0\x00\x1e\x00\x03\xc0\x00\x0f\x00\x07\x80'\
b'\xf8\xe7\x80\x0f\x03\xfc\xe3\x80\x0e\x07\xfe\xc3\x80\x1e\x0f\x07'\
b'\xc3\xc0\x1c\x1e\x03\xc1\xc0\x1c\x1c\x01\xc1\xc0\x3c\x38\x01\xc1'\
b'\xc0\x38\x38\x01\x81\xc0\x38\x70\x01\x81\xc0\x38\x70\x01\x81\xc0'\
b'\x38\x70\x01\x83\x80\x38\x70\x03\x83\x80\x38\x70\x03\x07\x80\x38'\
b'\x70\x07\x07\x00\x38\x38\x0f\x0e\x00\x1c\x3c\x3f\x3e\x00\x1c\x1f'\
b'\xff\xfc\x00\x1e\x0f\xe7\xf8\x00\x0e\x07\xc3\xe0\x00\x0f\x00\x00'\
b'\x00\xe0\x07\x80\x00\x01\xc0\x07\xc0\x00\x07\x80\x03\xf0\x00\x0f'\
b'\x00\x00\xfe\x00\x7e\x00\x00\x7f\xff\xfc\x00\x00\x1f\xff\xf0\x00'\
b'\x00\x01\xff\x80\x00\x17\x00\x00\x00\x00\x00\x7c\x00\x00\x7c\x00'\
b'\x00\x7c\x00\x00\xee\x00\x00\xee\x00\x01\xef\x00\x01\xc7\x00\x01'\
b'\xc7\x00\x03\x83\x80\x03\x83\x80\x03\x83\x80\x07\x01\xc0\x07\x01'\
b'\xc0\x0f\x01\xe0\x0e\x00\xe0\x0f\xff\xe0\x1f\xff\xf0\x1f\xff\xf0'\
b'\x1c\x00\x70\x38\x00\x38\x38\x00\x38\x78\x00\x3c\x70\x00\x1c\x70'\
b'\x00\x1c\xf0\x00\x1e\xe0\x00\x0e\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'\x18\x00\x00\x00\x00\x1f\xff\x00\x1f\xff\xc0\x1f\xff\xf0\x1c\x00'\
b'\xf0\x1c\x00\x78\x1c\x00\x38\x1c\x00\x38\x1c\x00\x38\x1c\x00\x38'\
b'\x1c\x00\x70\x1c\x00\xf0\x1f\xff\xe0\x1f\xff\xe0\x1f\xff\xf0\x1c'\
b'\x00\xf8\x1c\x00\x38\x1c\x00\x1c\x1c\x00\x1c\x1c\x00\x1c\x1c\x00'\
b'\x1c\x1c\x00\x1c\x1c\x00\x38\x1c\x00\xf8\x1f\xff\xf0\x1f\xff\xe0'\
b'\x1f\xff\x80\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\x1a\x00\x00\x00\x00'\
b'\x00\x00\x3f\x80\x00\x00\xff\xe0\x00\x03\xff\xf0\x00\x07\xc0\xf8'\
b'\x00\x0f\x00\x3c\x00\x0e\x00\x1c\x00\x1c\x00\x1e\x00\x1c\x00\x0e'\
b'\x00\x3c\x00\x00\x00\x38\x00\x00\x00\x38\x00\x00\x00\x38\x00\x00'\
b'\x00\x38\x00\x00\x00\x38\x00\x00\x00\x38\x00\x00\x00\x38\x00\x00'\
b'\x00\x38\x00\x00\x00\x1c\x00\x07\x00\x1c\x00\x0f\x00\x1e\x00\x0e'\
b'\x00\x0e\x00\x1e\x00\x0f\x00\x3c\x00\x07\xc0\xfc\x00\x03\xff\xf8'\
b'\x00\x01\xff\xe0\x00\x00\x3f\x80\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\x00\x00\x00\x00\x00\x1a\x00\x00\x00\x00\x00\x1f'\
b'\xff\x80\x00\x1f\xff\xe0\x00\x1f\xff\xf0\x00\x1c\x00\xf8\x00\x1c'\
b'\x00\x3c\x00\x1c\x00\x1c\x00\x1c\x00\x0e\x00\x1c\x00\x0e\x00\x1c'\
b'\x00\x0f\x00\x1c\x00\x07\x00\x1c\x00\x07\x00\x1c\x00\x07\x00\x1c'\
b'\x00\x07\x00\x1c\x00\x07\x00\x1c\x00\x07\x00\x1c\x00\x07\x00\x1c'\
b'\x00\x07\x00\x1c\x00\x0e\x00\x1c\x00\x0e\x00\x1c\x00\x0e\x00\x1c'\
b'\x00\x1c\x00\x1c\x00\x3c\x00\x1c\x00\xf8\x00\x1f\xff\xf0\x00\x1f'\
b'\xff\xe0\x00\x1f\xff\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\x00\x00\x00\x18\x00\x00\x00\x00\x1f\xff\xf8\x1f'\
b'\xff\xf8\x1f\xff\xf8\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00'\
b'\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1f\xff\xf0'\
b'\x1f\xff\xf0\x1f\xff\xf0\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c'\
b'\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00'\
b'\x00\x1f\xff\xfc\x1f\xff\xfc\x1f\xff\xfc\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\x16\x00\x00\x00\x00\x1f\xff\xf0\x1f\xff\xf0\x1f\xff\xf0'\
b'\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c'\
b'\x00\x00\x1c\x00\x00\x1c\x00\x00\x1f\xff\xc0\x1f\xff\xc0\x1f\xff'\
b'\xc0\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00'\
b'\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c'\
b'\x00\x00\x1c\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\x1c\x00\x00'\
b'\x00\x00\x00\x00\x1f\xe0\x00\x00\xff\xf8\x00\x01\xff\xfe\x00\x03'\
b'\xe0\x3f\x00\x07\x80\x0f\x00\x0f\x00\x07\x80\x1e\x00\x03\x80\x1c'\
b'\x00\x01\xc0\x1c\x00\x00\x00\x38\x00\x00\x00\x38\x00\x00\x00\x38'\
b'\x00\x00\x00\x38\x00\x00\x00\x38\x01\xff\xc0\x38\x01\xff\xc0\x38'\
b'\x01\xff\xc0\x38\x00\x01\xc0\x1c\x00\x01\xc0\x1c\x00\x01\xc0\x1e'\
b'\x00\x01\xc0\x0f\x00\x01\xc0\x07\x80\x07\xc0\x03\xf0\x1f\xc0\x01'\
b'\xff\xff\x00\x00\xff\xfc\x00\x00\x1f\xe0\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\x00\x00\x00\x00\x00\x00\x00\x1a\x00\x00\x00\x00'\
b'\x00\x1c\x00\x0e\x00\x1c\x00\x0e\x00\x1c\x00\x0e\x00\x1c\x00\x0e'\
b'\x00\x1c\x00\x0e\x00\x1c\x00\x0e\x00\x1c\x00\x0e\x00\x1c\x00\x0e'\
b'\x00\x1c\x00\x0e\x00\x1c\x00\x0e\x00\x1c\x00\x0e\x00\x1f\xff\xfe'\
b'\x00\x1f\xff\xfe\x00\x1f\xff\xfe\x00\x1c\x00\x0e\x00\x1c\x00\x0e'\
b'\x00\x1c\x00\x0e\x00\x1c\x00\x0e\x00\x1c\x00\x0e\x00\x1c\x00\x0e'\
b'\x00\x1c\x00\x0e\x00\x1c\x00\x0e\x00\x1c\x00\x0e\x00\x1c\x00\x0e'\
b'\x00\x1c\x00\x0e\x00\x1c\x00\x0e\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\x00\x00\x00\x00\x00\x09\x00\x00\x00\x1c\x00\x1c'\
b'\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c'\
b'\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c'\
b'\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x12\x00\x00\x00\x00\x00\x0e\x00\x00\x0e\x00\x00\x0e\x00\x00'\
b'\x0e\x00\x00\x0e\x00\x00\x0e\x00\x00\x0e\x00\x00\x0e\x00\x00\x0e'\
b'\x00\x00\x0e\x00\x00\x0e\x00\x00\x0e\x00\x00\x0e\x00\x00\x0e\x00'\
b'\x00\x0e\x00\x00\x0e\x00\x00\x0e\x00\x00\x0e\x00\x70\x0e\x00\x70'\
b'\x0e\x00\x70\x0e\x00\x78\x1e\x00\x3c\x3c\x00\x3f\xfc\x00\x1f\xf8'\
b'\x00\x07\xe0\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\x18\x00\x00\x00'\
b'\x00\x1c\x00\x1e\x1c\x00\x3c\x1c\x00\x78\x1c\x00\xf0\x1c\x01\xe0'\
b'\x1c\x03\xc0\x1c\x07\x80\x1c\x0f\x00\x1c\x1e\x00\x1c\x3c\x00\x1c'\
b'\x78\x00\x1c\xf8\x00\x1d\xfc\x00\x1f\xde\x00\x1f\x8e\x00\x1f\x07'\
b'\x00\x1e\x07\x80\x1c\x03\xc0\x1c\x01\xc0\x1c\x00\xe0\x1c\x00\xf0'\
b'\x1c\x00\x78\x1c\x00\x38\x1c\x00\x1c\x1c\x00\x1e\x1c\x00\x0f\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\x14\x00\x00\x00\x00\x1c\x00\x00\x1c'\
b'\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00'\
b'\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00'\
b'\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c'\
b'\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00'\
b'\x00\x1f\xff\xe0\x1f\xff\xe0\x1f\xff\xe0\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\x1d\x00\x00\x00\x00\x00\x1f\x00\x07\xc0\x1f\x80\x0f\xc0'\
b'\x1f\x80\x0f\xc0\x1f\x80\x0f\xc0\x1f\xc0\x0f\xc0\x1d\xc0\x1d\xc0'\
b'\x1d\xc0\x1d\xc0\x1d\xc0\x1d\xc0\x1c\xe0\x39\xc0\x1c\xe0\x39\xc0'\
b'\x1c\xe0\x39\xc0\x1c\xf0\x39\xc0\x1c\x70\x71\xc0\x1c\x70\x71\xc0'\
b'\x1c\x70\x71\xc0\x1c\x38\xe1\xc0\x1c\x38\xe1\xc0\x1c\x38\xe1\xc0'\
b'\x1c\x3d\xc1\xc0\x1c\x1d\xc1\xc0\x1c\x1d\xc1\xc0\x1c\x1d\xc1\xc0'\
b'\x1c\x0f\x81\xc0\x1c\x0f\x81\xc0\x1c\x0f\x81\xc0\x1c\x0f\x01\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x1a\x00\x00\x00\x00\x00\x1e\x00\x0e\x00\x1e\x00\x0e\x00\x1f\x00'\
b'\x0e\x00\x1f\x80\x0e\x00\x1f\x80\x0e\x00\x1f\xc0\x0e\x00\x1d\xc0'\
b'\x0e\x00\x1c\xe0\x0e\x00\x1c\xf0\x0e\x00\x1c\x70\x0e\x00\x1c\x38'\
b'\x0e\x00\x1c\x3c\x0e\x00\x1c\x1c\x0e\x00\x1c\x0e\x0e\x00\x1c\x0f'\
b'\x0e\x00\x1c\x07\x0e\x00\x1c\x03\x8e\x00\x1c\x03\xce\x00\x1c\x01'\
b'\xce\x00\x1c\x00\xee\x00\x1c\x00\xfe\x00\x1c\x00\x7e\x00\x1c\x00'\
b'\x7e\x00\x1c\x00\x3e\x00\x1c\x00\x1e\x00\x1c\x00\x1e\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\x1c\x00'\
b'\x00\x00\x00\x00\x00\x3f\xc0\x00\x00\xff\xf0\x00\x03\xff\xf8\x00'\
b'\x07\xe0\x7e\x00\x07\x80\x1e\x00\x0f\x00\x0f\x00\x1e\x00\x07\x80'\
b'\x1c\x00\x03\x80\x1c\x00\x03\x80\x38\x00\x01\xc0\x38\x00\x01\xc0'\
b'\x38\x00\x01\xc0\x38\x00\x01\xc0\x38\x00\x01\xc0\x38\x00\x01\xc0'\
b'\x38\x00\x01\xc0\x38\x00\x01\xc0\x1c\x00\x03\x80\x1c\x00\x03\x80'\
b'\x1e\x00\x07\x80\x0f\x00\x0f\x00\x07\x80\x1e\x00\x07\xe0\x7e\x00'\
b'\x01\xff\xfc\x00\x00\xff\xf0\x00\x00\x3f\xc0\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\x18\x00\x00\x00'\
b'\x00\x1f\xff\x80\x1f\xff\xe0\x1f\xff\xf0\x1c\x00\x78\x1c\x00\x38'\
b'\x1c\x00\x1c\x1c\x00\x1c\x1c\x00\x1c\x1c\x00\x1c\x1c\x00\x1c\x1c'\
b'\x00\x38\x1c\x00\xf8\x1f\xff\xf0\x1f\xff\xe0\x1f\xff\x80\x1c\x00'\
b'\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00'\
b'\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\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\x1c\x00\x00\x00\x00\x00\x00\x3f\x80'\
b'\x00\x00\xff\xe0\x00\x03\xff\xf8\x00\x07\xe0\xfc\x00\x0f\x80\x3e'\
b'\x00\x0e\x00\x1e\x00\x1e\x00\x0f\x00\x1c\x00\x07\x00\x1c\x00\x07'\
b'\x00\x38\x00\x03\x80\x38\x00\x03\x80\x38\x00\x03\x80\x38\x00\x03'\
b'\x80\x38\x00\x03\x80\x38\x00\x03\x80\x38\x00\x03\x80\x38\x00\x03'\
b'\x80\x1c\x00\x07\x00\x1c\x02\x07\x00\x1e\x03\x8f\x00\x0e\x03\xfe'\
b'\x00\x0f\x81\xfe\x00\x07\xe0\xfc\x00\x03\xff\xfe\x00\x00\xff\xff'\
b'\x80\x00\x3f\xcf\xc0\x00\x00\x03\x80\x00\x00\x00\x80\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\x1a\x00\x00\x00\x00\x00\x1f\xff\xc0\x00\x1f'\
b'\xff\xf0\x00\x1f\xff\xf8\x00\x1c\x00\x7c\x00\x1c\x00\x1e\x00\x1c'\
b'\x00\x0e\x00\x1c\x00\x0e\x00\x1c\x00\x0e\x00\x1c\x00\x0e\x00\x1c'\
b'\x00\x1e\x00\x1c\x00\x7c\x00\x1f\xff\xf8\x00\x1f\xff\xf0\x00\x1f'\
b'\xff\xc0\x00\x1c\x0f\x00\x00\x1c\x07\xc0\x00\x1c\x03\xc0\x00\x1c'\
b'\x01\xe0\x00\x1c\x00\xf0\x00\x1c\x00\xf0\x00\x1c\x00\x78\x00\x1c'\
b'\x00\x3c\x00\x1c\x00\x3c\x00\x1c\x00\x1e\x00\x1c\x00\x1e\x00\x1c'\
b'\x00\x0f\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\x18\x00\x00\x00\x00\x00\xfe\x00\x03\xff\xc0\x07\xff'\
b'\xe0\x0f\x81\xf0\x1e\x00\xf0\x1c\x00\x78\x1c\x00\x38\x1c\x00\x38'\
b'\x1e\x00\x00\x0f\x00\x00\x0f\xe0\x00\x07\xfe\x00\x01\xff\xc0\x00'\
b'\x7f\xf0\x00\x07\xf8\x00\x00\xf8\x00\x00\x3c\x38\x00\x1c\x38\x00'\
b'\x1c\x3c\x00\x1c\x1c\x00\x3c\x1e\x00\x38\x0f\x80\xf8\x07\xff\xf0'\
b'\x03\xff\xe0\x00\x7f\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\x15\x00'\
b'\x00\x00\x00\x7f\xff\xf0\x7f\xff\xf0\x7f\xff\xf0\x00\x70\x00\x00'\
b'\x70\x00\x00\x70\x00\x00\x70\x00\x00\x70\x00\x00\x70\x00\x00\x70'\
b'\x00\x00\x70\x00\x00\x70\x00\x00\x70\x00\x00\x70\x00\x00\x70\x00'\
b'\x00\x70\x00\x00\x70\x00\x00\x70\x00\x00\x70\x00\x00\x70\x00\x00'\
b'\x70\x00\x00\x70\x00\x00\x70\x00\x00\x70\x00\x00\x70\x00\x00\x70'\
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\x1a\x00\x00\x00\x00\x00\x1c'\
b'\x00\x0e\x00\x1c\x00\x0e\x00\x1c\x00\x0e\x00\x1c\x00\x0e\x00\x1c'\
b'\x00\x0e\x00\x1c\x00\x0e\x00\x1c\x00\x0e\x00\x1c\x00\x0e\x00\x1c'\
b'\x00\x0e\x00\x1c\x00\x0e\x00\x1c\x00\x0e\x00\x1c\x00\x0e\x00\x1c'\
b'\x00\x0e\x00\x1c\x00\x0e\x00\x1c\x00\x0e\x00\x1c\x00\x0e\x00\x1c'\
b'\x00\x0e\x00\x1c\x00\x0e\x00\x1c\x00\x0e\x00\x1e\x00\x1e\x00\x0e'\
b'\x00\x1c\x00\x0f\x00\x3c\x00\x07\xc0\xf8\x00\x07\xff\xf0\x00\x01'\
b'\xff\xe0\x00\x00\x7f\x80\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\x00\x00\x00\x18\x00\x00\x00\x00\x70\x00\x07\x78'\
b'\x00\x0f\x38\x00\x0e\x38\x00\x0e\x3c\x00\x1e\x1c\x00\x1c\x1e\x00'\
b'\x3c\x0e\x00\x38\x0e\x00\x38\x0f\x00\x78\x07\x00\x70\x07\x00\x70'\
b'\x03\x80\xe0\x03\x80\xe0\x03\xc1\xe0\x01\xc1\xc0\x01\xc1\xc0\x01'\
b'\xe3\xc0\x00\xe3\x80\x00\xe7\x80\x00\x77\x00\x00\x77\x00\x00\x7f'\
b'\x00\x00\x3e\x00\x00\x3e\x00\x00\x3e\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\x24\x00\x00\x00\x00\x00\x00\xe0\x01\xf0\x00\xe0\xe0\x01'\
b'\xf0\x00\xe0\x70\x01\xf0\x01\xc0\x70\x03\xb8\x01\xc0\x70\x03\xb8'\
b'\x01\xc0\x70\x03\xb8\x01\xc0\x38\x07\x1c\x03\x80\x38\x07\x1c\x03'\
b'\x80\x38\x07\x1c\x03\x80\x38\x0f\x1e\x03\x80\x1c\x0e\x0e\x07\x00'\
b'\x1c\x0e\x0e\x07\x00\x1c\x1e\x0e\x07\x00\x1c\x1c\x07\x07\x00\x0e'\
b'\x1c\x07\x0e\x00\x0e\x1c\x07\x0e\x00\x0e\x38\x03\x8e\x00\x0e\x38'\
b'\x03\x8e\x00\x0f\x38\x03\x9e\x00\x07\x78\x01\xdc\x00\x07\x70\x01'\
b'\xdc\x00\x07\x70\x01\xdc\x00\x07\xf0\x01\xfc\x00\x03\xe0\x00\xf8'\
b'\x00\x03\xe0\x00\xf8\x00\x03\xe0\x00\xf8\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x17\x00\x00\x00\x00\x78\x00\x3c\x3c\x00\x78\x1c\x00'\
b'\x70\x1e\x00\xf0\x0f\x01\xe0\x07\x01\xc0\x03\x83\x80\x03\xc7\x80'\
b'\x01\xef\x00\x00\xee\x00\x00\xfe\x00\x00\x7c\x00\x00\x38\x00\x00'\
b'\x7c\x00\x00\xfe\x00\x01\xef\x00\x01\xc7\x00\x03\xc7\x80\x07\x83'\
b'\xc0\x07\x01\xc0\x0e\x00\xe0\x1e\x00\xf0\x3c\x00\x78\x38\x00\x38'\
b'\x78\x00\x3c\xf0\x00\x1e\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\x17\x00'\
b'\x00\x00\x00\xf0\x00\x1e\x70\x00\x1c\x38\x00\x38\x3c\x00\x78\x1c'\
b'\x00\x70\x0e\x00\xe0\x0f\x01\xe0\x07\x01\xc0\x03\x83\x80\x03\xc7'\
b'\x80\x01\xc7\x00\x00\xee\x00\x00\xfe\x00\x00\x7c\x00\x00\x38\x00'\
b'\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00'\
b'\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x16\x00\x00\x00\x00\x1f\xff'\
b'\xf0\x1f\xff\xf0\x1f\xff\xf0\x00\x00\xf0\x00\x01\xe0\x00\x03\xc0'\
b'\x00\x03\x80\x00\x07\x80\x00\x0f\x00\x00\x1e\x00\x00\x1c\x00\x00'\
b'\x38\x00\x00\x78\x00\x00\xf0\x00\x01\xe0\x00\x01\xc0\x00\x03\xc0'\
b'\x00\x07\x80\x00\x0f\x00\x00\x0e\x00\x00\x1c\x00\x00\x3c\x00\x00'\
b'\x78\x00\x00\x7f\xff\xf8\x7f\xff\xf8\x7f\xff\xf8\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\x0a\x00\x00\x00\x3f\x80\x3f\x80\x3f\x80\x38\x00'\
b'\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00'\
b'\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00'\
b'\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00'\
b'\x38\x00\x38\x00\x3f\x80\x3f\x80\x3f\x80\x00\x00\x0a\x00\x00\x00'\
b'\xe0\x00\xe0\x00\x70\x00\x70\x00\x70\x00\x70\x00\x38\x00\x38\x00'\
b'\x38\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x0e\x00\x0e\x00\x0e\x00'\
b'\x0e\x00\x07\x00\x07\x00\x07\x00\x07\x80\x03\x80\x03\x80\x03\x80'\
b'\x01\xc0\x01\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x0a\x00\x00\x00\x7f\x00\x7f\x00\x7f\x00\x07\x00'\
b'\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00'\
b'\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00'\
b'\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00'\
b'\x07\x00\x07\x00\x7f\x00\x7f\x00\x7f\x00\x00\x00\x11\x00\x00\x00'\
b'\x00\x01\xc0\x00\x03\xe0\x00\x03\xe0\x00\x07\x70\x00\x07\x70\x00'\
b'\x06\x30\x00\x0e\x38\x00\x0e\x38\x00\x1c\x1c\x00\x1c\x1c\x00\x38'\
b'\x0e\x00\x38\x0e\x00\x38\x0e\x00\x70\x07\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\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\x00\x00\x00\x15\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\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xf8\xff\xff\xf8'\
b'\xff\xff\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x0c\x00\x00\x00\x78\x00\x3c\x00\x1c\x00\x0c\x00\x06\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\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\x01\xfc\x00\x07\xff\x00\x0f\xff\x80\x1e\x07\xc0'\
b'\x3c\x01\xc0\x38\x01\xc0\x00\x01\xc0\x00\x07\xc0\x01\xff\xc0\x0f'\
b'\xff\xc0\x1f\xf9\xc0\x1e\x01\xc0\x38\x01\xc0\x38\x03\xc0\x38\x07'\
b'\xc0\x3c\x0f\xc0\x1f\xff\xc0\x0f\xfd\xc0\x07\xf0\xe0\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\x14\x00\x00\x00\x00\x38\x00\x00\x38\x00\x00'\
b'\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38'\
b'\xf8\x00\x3b\xfe\x00\x3f\xff\x00\x3f\x0f\x80\x3e\x07\x80\x3c\x03'\
b'\x80\x38\x01\xc0\x38\x01\xc0\x38\x01\xc0\x38\x01\xc0\x38\x01\xc0'\
b'\x38\x01\xc0\x38\x01\xc0\x3c\x03\x80\x3c\x07\x80\x3f\x0f\x00\x3f'\
b'\xff\x00\x3b\xfe\x00\x38\xf8\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'\x12\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\x01\xf0\x00\x07\xfc\x00'\
b'\x0f\xfe\x00\x1e\x0f\x00\x3c\x07\x80\x38\x03\x80\x70\x00\x00\x70'\
b'\x00\x00\x70\x00\x00\x70\x00\x00\x70\x00\x00\x70\x00\x00\x70\x03'\
b'\x80\x38\x03\x80\x3c\x07\x00\x3e\x0f\x00\x1f\xfe\x00\x0f\xfc\x00'\
b'\x03\xf0\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\x14\x00\x00\x00\x00'\
b'\x00\x01\xc0\x00\x01\xc0\x00\x01\xc0\x00\x01\xc0\x00\x01\xc0\x00'\
b'\x01\xc0\x00\x01\xc0\x01\xf1\xc0\x07\xfd\xc0\x0f\xff\xc0\x1f\x0f'\
b'\xc0\x1c\x07\xc0\x1c\x03\xc0\x38\x01\xc0\x38\x01\xc0\x38\x01\xc0'\
b'\x38\x01\xc0\x38\x01\xc0\x38\x01\xc0\x38\x01\xc0\x1c\x03\xc0\x1e'\
b'\x07\xc0\x0f\x0f\xc0\x0f\xff\xc0\x07\xfd\xc0\x01\xf1\xc0\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\x14\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'\x01\xf8\x00\x07\xfe\x00\x0f\xff\x00\x0f\x0f\x80\x1c\x03\x80\x18'\
b'\x01\x80\x38\x01\xc0\x3f\xff\xc0\x3f\xff\xc0\x3f\xff\xc0\x38\x00'\
b'\x00\x38\x00\x00\x38\x00\x00\x1c\x01\xc0\x1e\x03\x80\x1f\x07\x80'\
b'\x0f\xff\x00\x07\xfe\x00\x01\xf8\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\x0b\x00\x00\x00\x07\xe0\x0f\xe0\x1f\xe0\x1e\x00\x1c\x00\x1c'\
b'\x00\x1c\x00\xff\xc0\xff\xc0\xff\xc0\x1c\x00\x1c\x00\x1c\x00\x1c'\
b'\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c'\
b'\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\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\x01\xf1\xc0\x07\xfd\xc0\x0f\xff\xc0\x0f\x0f\xc0\x1e'\
b'\x07\xc0\x1c\x03\xc0\x38\x01\xc0\x38\x01\xc0\x38\x01\xc0\x38\x01'\
b'\xc0\x38\x01\xc0\x38\x01\xc0\x38\x01\xc0\x1c\x03\xc0\x1e\x07\xc0'\
b'\x0f\x0f\xc0\x0f\xff\xc0\x07\xfd\xc0\x01\xf9\xc0\x00\x01\xc0\x38'\
b'\x01\xc0\x3c\x03\x80\x3e\x07\x80\x1f\xff\x00\x0f\xfe\x00\x03\xf8'\
b'\x00\x00\x00\x00\x13\x00\x00\x00\x00\x38\x00\x00\x38\x00\x00\x38'\
b'\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\xf8'\
b'\x00\x3b\xfe\x00\x3f\xff\x00\x3f\x0f\x80\x3c\x07\x80\x3c\x03\x80'\
b'\x38\x03\x80\x38\x03\x80\x38\x03\x80\x38\x03\x80\x38\x03\x80\x38'\
b'\x03\x80\x38\x03\x80\x38\x03\x80\x38\x03\x80\x38\x03\x80\x38\x03'\
b'\x80\x38\x03\x80\x38\x03\x80\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\x07'\
b'\x00\x00\x38\x38\x38\x00\x00\x00\x00\x38\x38\x38\x38\x38\x38\x38'\
b'\x38\x38\x38\x38\x38\x38\x38\x38\x38\x38\x38\x38\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x0b\x00\x00\x00\x07\x00\x07\x00\x07\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00'\
b'\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00'\
b'\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00'\
b'\x07\x00\x0f\x00\x7e\x00\x7e\x00\xfc\x00\x00\x00\x13\x00\x00\x00'\
b'\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00\x1c\x00\x00'\
b'\x1c\x00\x00\x1c\x00\x00\x1c\x03\xc0\x1c\x07\x80\x1c\x0f\x00\x1c'\
b'\x1e\x00\x1c\x7c\x00\x1c\xf8\x00\x1d\xe0\x00\x1f\xc0\x00\x1f\xe0'\
b'\x00\x1f\xf0\x00\x1e\x78\x00\x1c\x78\x00\x1c\x3c\x00\x1c\x1e\x00'\
b'\x1c\x0f\x00\x1c\x0f\x00\x1c\x07\x80\x1c\x03\xc0\x1c\x01\xe0\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\x07\x00\x00\x38\x38\x38\x38\x38\x38'\
b'\x38\x38\x38\x38\x38\x38\x38\x38\x38\x38\x38\x38\x38\x38\x38\x38'\
b'\x38\x38\x38\x38\x00\x00\x00\x00\x00\x00\x00\x00\x1f\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\x1c\x7c'\
b'\x0f\x80\x1d\xfe\x3f\xc0\x1d\xff\x7f\xe0\x1f\x87\xe0\xf0\x1e\x07'\
b'\xc0\x70\x1e\x03\x80\x70\x1c\x03\x80\x70\x1c\x03\x80\x70\x1c\x03'\
b'\x80\x70\x1c\x03\x80\x70\x1c\x03\x80\x70\x1c\x03\x80\x70\x1c\x03'\
b'\x80\x70\x1c\x03\x80\x70\x1c\x03\x80\x70\x1c\x03\x80\x70\x1c\x03'\
b'\x80\x70\x1c\x03\x80\x70\x1c\x03\x80\x70\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\x00\x00\x00\x00\x00\x00\x13\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\x38\xf8\x00\x3b\xfe\x00\x3f\xff\x00\x3f\x0f\x80'\
b'\x3c\x07\x80\x3c\x03\x80\x38\x03\x80\x38\x03\x80\x38\x03\x80\x38'\
b'\x03\x80\x38\x03\x80\x38\x03\x80\x38\x03\x80\x38\x03\x80\x38\x03'\
b'\x80\x38\x03\x80\x38\x03\x80\x38\x03\x80\x38\x03\x80\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\x14\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\x01'\
b'\xf8\x00\x07\xfe\x00\x0f\xff\x00\x1f\x0f\x80\x1e\x07\x80\x1c\x03'\
b'\x80\x38\x01\xc0\x38\x01\xc0\x38\x01\xc0\x38\x01\xc0\x38\x01\xc0'\
b'\x38\x01\xc0\x38\x01\xc0\x1c\x03\x80\x1e\x07\x80\x1f\x0f\x80\x0f'\
b'\xff\x00\x07\xfe\x00\x01\xf8\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'\x14\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\x38\xf8\x00\x3b\xfe\x00'\
b'\x3b\xff\x00\x3f\x0f\x80\x3e\x07\x80\x3c\x03\x80\x38\x01\xc0\x38'\
b'\x01\xc0\x38\x01\xc0\x38\x01\xc0\x38\x01\xc0\x38\x01\xc0\x38\x01'\
b'\xc0\x3c\x03\x80\x3c\x07\x80\x3f\x0f\x00\x3f\xff\x00\x3b\xfc\x00'\
b'\x38\xf8\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38\x00\x00\x38'\
b'\x00\x00\x38\x00\x00\x38\x00\x00\x00\x00\x00\x14\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\x01\xf1\xc0\x07\xfd\xc0\x0f\xfd\xc0\x1f\x0f'\
b'\xc0\x1e\x07\xc0\x1c\x03\xc0\x38\x03\xc0\x38\x01\xc0\x38\x01\xc0'\
b'\x38\x01\xc0\x38\x01\xc0\x38\x01\xc0\x38\x01\xc0\x1c\x03\xc0\x1e'\
b'\x07\xc0\x0f\x0f\xc0\x0f\xff\xc0\x03\xfd\xc0\x01\xf1\xc0\x00\x01'\
b'\xc0\x00\x01\xc0\x00\x01\xc0\x00\x01\xc0\x00\x01\xc0\x00\x01\xc0'\
b'\x00\x01\xc0\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x39\xf0\x3b\xf0\x3f\xe0\x3e\x00'\
b'\x3c\x00\x3c\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00'\
b'\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x38\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x12\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\x07\xf0\x00\x1f\xfc\x00\x3f\xfe'\
b'\x00\x78\x0f\x00\x70\x07\x00\x70\x00\x00\x78\x00\x00\x3f\x00\x00'\
b'\x3f\xf0\x00\x0f\xfe\x00\x01\xff\x00\x00\x1f\x80\x00\x07\x80\x70'\
b'\x03\x80\x78\x03\x80\x3c\x0f\x00\x3f\xff\x00\x0f\xfe\x00\x03\xf8'\
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\x0a\x00\x00\x00\x00\x00\x0c'\
b'\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\xff\xc0\xff\xc0\xff'\
b'\xc0\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c'\
b'\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1f\xc0\x0f\xc0\x07'\
b'\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x13\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\x38\x03\x80\x38\x03'\
b'\x80\x38\x03\x80\x38\x03\x80\x38\x03\x80\x38\x03\x80\x38\x03\x80'\
b'\x38\x03\x80\x38\x03\x80\x38\x03\x80\x38\x03\x80\x38\x03\x80\x38'\
b'\x03\x80\x38\x07\x80\x3c\x07\x80\x3e\x1f\x80\x1f\xfb\x80\x0f\xf3'\
b'\x80\x03\xe3\x80\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\x11\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\xe0\x03\x80\x70\x07\x00\x70\x07\x00\x70'\
b'\x07\x00\x38\x0e\x00\x38\x0e\x00\x3c\x1e\x00\x1c\x1c\x00\x1c\x1c'\
b'\x00\x0e\x38\x00\x0e\x38\x00\x0e\x38\x00\x07\x70\x00\x07\x70\x00'\
b'\x07\x70\x00\x03\xe0\x00\x03\xe0\x00\x01\xc0\x00\x01\xc0\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\x1b\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\x00\x00\x00\x00\x00\x70\x0e\x01\xc0\x70\x0e\x01'\
b'\xc0\x70\x1f\x01\xc0\x38\x1f\x03\x80\x38\x1b\x03\x80\x38\x1b\x07'\
b'\x80\x1c\x3b\x87\x00\x1c\x39\x87\x00\x0e\x31\x8e\x00\x0e\x31\x8e'\
b'\x00\x0e\x31\x8e\x00\x07\x71\xdc\x00\x07\x60\xdc\x00\x07\x60\xdc'\
b'\x00\x03\x60\xd8\x00\x03\xe0\xf8\x00\x03\xc0\x70\x00\x01\xc0\x70'\
b'\x00\x01\xc0\x70\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\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\xf0\x07\x70\x0e\x38\x1e\x3c\x1c\x1c'\
b'\x38\x0e\x78\x0f\x70\x07\xe0\x03\xe0\x03\xc0\x03\xc0\x07\xe0\x0f'\
b'\xf0\x0e\x70\x1e\x38\x3c\x3c\x38\x1c\x70\x0e\xf0\x0f\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\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\xe0\x03\x80\x70\x07\x80\x70\x07\x00'\
b'\x78\x07\x00\x38\x0f\x00\x38\x0e\x00\x3c\x0e\x00\x1c\x1c\x00\x1c'\
b'\x1c\x00\x0e\x3c\x00\x0e\x38\x00\x0f\x38\x00\x07\x78\x00\x07\x70'\
b'\x00\x07\xf0\x00\x03\xe0\x00\x03\xe0\x00\x01\xe0\x00\x01\xc0\x00'\
b'\x01\xc0\x00\x03\xc0\x00\x03\x80\x00\x07\x80\x00\x3f\x00\x00\x3e'\
b'\x00\x00\x3c\x00\x00\x00\x00\x00\x11\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\x7f\xff\x00\x7f\xff\x00\x7f\xff\x00\x00\x1e\x00\x00\x3c'\
b'\x00\x00\x78\x00\x00\x70\x00\x00\xf0\x00\x01\xe0\x00\x03\xc0\x00'\
b'\x07\x80\x00\x0f\x00\x00\x1e\x00\x00\x3c\x00\x00\x78\x00\x00\x70'\
b'\x00\x00\xff\xff\x00\xff\xff\x00\xff\xff\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\x0c\x00\x00\x00\x00\xf0\x03\xf0\x03\xf0\x07\x80\x07'\
b'\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07'\
b'\x00\x0e\x00\x1e\x00\x7c\x00\x70\x00\x7c\x00\x1e\x00\x0e\x00\x07'\
b'\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07\x00\x07'\
b'\x00\x07\x80\x03\xf0\x03\xf0\x00\xf0\x00\x00\x09\x00\x00\x00\x1c'\
b'\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c'\
b'\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c'\
b'\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c'\
b'\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c\x00\x1c'\
b'\x00\x1c\x00\x0c\x00\x00\x00\xf0\x00\xfc\x00\xfc\x00\x1e\x00\x0e'\
b'\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e'\
b'\x00\x07\x00\x07\x80\x03\xe0\x00\xe0\x03\xe0\x07\x80\x0f\x00\x0e'\
b'\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e\x00\x0e'\
b'\x00\x1e\x00\xfc\x00\xfc\x00\xf0\x00\x00\x00\x15\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\x07\x80'\
b'\x00\x1f\xf0\x10\x3f\xfc\x70\x38\x7f\xf0\x20\x1f\xe0\x00\x07\x80'\
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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00'
_index =\
b'\x00\x00\x6b\x00\xb3\x00\xfb\x00\x43\x01\xae\x01\x19\x02\xa7\x02'\
b'\x12\x03\x37\x03\x7f\x03\xc7\x03\x0f\x04\x7a\x04\xc2\x04\x0a\x05'\
b'\x52\x05\x9a\x05\x05\x06\x70\x06\xdb\x06\x46\x07\xb1\x07\x1c\x08'\
b'\x87\x08\xf2\x08\x5d\x09\xc8\x09\x10\x0a\x58\x0a\xc3\x0a\x2e\x0b'\
b'\x99\x0b\x04\x0c\xb5\x0c\x20\x0d\x8b\x0d\x19\x0e\xa7\x0e\x12\x0f'\
b'\x7d\x0f\x0b\x10\x99\x10\xe1\x10\x4c\x11\xb7\x11\x22\x12\xb0\x12'\
b'\x3e\x13\xcc\x13\x37\x14\xc5\x14\x53\x15\xbe\x15\x29\x16\xb7\x16'\
b'\x22\x17\xd3\x17\x3e\x18\xa9\x18\x14\x19\x5c\x19\xa4\x19\xec\x19'\
b'\x57\x1a\xc2\x1a\x0a\x1b\x75\x1b\xe0\x1b\x4b\x1c\xb6\x1c\x21\x1d'\
b'\x69\x1d\xd4\x1d\x3f\x1e\x64\x1e\xac\x1e\x17\x1f\x3c\x1f\xca\x1f'\
b'\x35\x20\xa0\x20\x0b\x21\x76\x21\xbe\x21\x29\x22\x71\x22\xdc\x22'\
b'\x47\x23\xd5\x23\x1d\x24\x88\x24\xf3\x24\x3b\x25\x83\x25\xcb\x25'\
b'\x36\x26'
_mvfont = memoryview(_font)
_mvi = memoryview(_index)
ifb = lambda l : l[0] | (l[1] << 8)
def get_ch(ch):
oc = ord(ch)
ioff = 2 * (oc - 32 + 1) if oc >= 32 and oc <= 126 else 0
doff = ifb(_mvi[ioff : ])
width = ifb(_mvfont[doff : ])
next_offs = doff + 2 + ((width - 1)//8 + 1) * 35
return _mvfont[doff + 2:next_offs], 35, width

Wyświetl plik

@ -0,0 +1,232 @@
# Code generated by font_to_py.py.
# Font: Arial.ttf Char set: 0123456789:
# Cmd: ./font_to_py.py Arial.ttf 50 arial_50.py -x -c 0123456789:
version = '0.33'
def height():
return 50
def baseline():
return 49
def max_width():
return 37
def hmap():
return True
def reverse():
return False
def monospaced():
return False
def min_ch():
return 48
def max_ch():
return 63
_font =\
b'\x25\x00\x00\x03\xfe\x00\x00\x00\x1f\xff\xc0\x00\x00\x7f\xff\xf0'\
b'\x00\x00\xff\xff\xf8\x00\x01\xff\xff\xfc\x00\x03\xff\xff\xfe\x00'\
b'\x07\xfe\x03\xff\x00\x07\xf8\x00\xff\x80\x0f\xf0\x00\x7f\x80\x0f'\
b'\xe0\x00\x3f\x80\x0f\xc0\x00\x1f\xc0\x1f\xc0\x00\x1f\xc0\x1f\xc0'\
b'\x00\x0f\xc0\x1f\x80\x00\x0f\xc0\x03\x80\x00\x0f\xc0\x00\x00\x00'\
b'\x0f\xc0\x00\x00\x00\x0f\xc0\x00\x00\x00\x1f\x80\x00\x00\x00\x3f'\
b'\x80\x00\x00\x00\x7f\x00\x00\x00\x00\xff\x00\x00\x00\x01\xfe\x00'\
b'\x00\x00\x03\xfc\x00\x00\x00\x07\xf8\x00\x00\x00\x0f\xf0\x00\x00'\
b'\x00\x1f\xe0\x00\x00\x00\x3f\xc0\x00\x00\x00\x7f\x80\x00\x00\x00'\
b'\x7f\x00\x00\x00\x00\xfe\x00\x00\x00\x00\xfc\x00\x00\x00\x01\xfc'\
b'\x00\x00\x00\x01\xfc\x00\x00\x00\x01\xf8\x00\x00\x00\x01\xf8\x00'\
b'\x00\x00\x01\xf8\x00\x00\x00\x01\xf8\x00\x00\x00\x01\xf8\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\x01\xf8\x00\x00\x00\x01'\
b'\xf8\x00\x00\x00\x01\xf8\x00\x00\x00\x01\xf8\x00\x00\x00\x01\xf8'\
b'\x00\x00\x00\x01\xf8\x00\x00\x00\x00\x00\x00\x00\x25\x00\x00\x00'\
b'\x00\x00\x00\x00\x03\xfe\x00\x00\x00\x0f\xff\x80\x00\x00\x3f\xff'\
b'\xe0\x00\x00\x7f\xff\xf0\x00\x00\xff\xff\xf8\x00\x01\xff\xff\xfc'\
b'\x00\x03\xfe\x07\xfc\x00\x03\xfc\x01\xfe\x00\x07\xf0\x00\xfe\x00'\
b'\x07\xf0\x00\x7f\x00\x07\xe0\x00\x3f\x00\x0f\xe0\x00\x3f\x00\x0f'\
b'\xc0\x00\x1f\x80\x0f\xc0\x00\x1f\x80\x0f\xc0\x00\x1f\x80\x0f\xc0'\
b'\x00\x1f\x80\x1f\x80\x00\x0f\xc0\x1f\x80\x00\x0f\xc0\x1f\x80\x00'\
b'\x0f\xc0\x1f\x80\x00\x0f\xc0\x1f\x80\x00\x0f\xc0\x1f\x80\x00\x0f'\
b'\xc0\x1f\x80\x00\x0f\xc0\x1f\x80\x00\x0f\xc0\x1f\x80\x00\x0f\xc0'\
b'\x1f\x80\x00\x0f\xc0\x1f\x80\x00\x0f\xc0\x1f\x80\x00\x0f\xc0\x1f'\
b'\x80\x00\x0f\xc0\x1f\x80\x00\x0f\xc0\x1f\x80\x00\x0f\xc0\x1f\x80'\
b'\x00\x0f\xc0\x1f\x80\x00\x0f\xc0\x0f\xc0\x00\x1f\x80\x0f\xc0\x00'\
b'\x1f\x80\x0f\xc0\x00\x1f\x80\x0f\xc0\x00\x1f\x80\x0f\xe0\x00\x3f'\
b'\x80\x07\xe0\x00\x3f\x00\x07\xf0\x00\x7f\x00\x07\xf8\x00\xff\x00'\
b'\x03\xfc\x01\xfe\x00\x03\xff\x07\xfe\x00\x01\xff\xff\xfc\x00\x00'\
b'\xff\xff\xf8\x00\x00\x7f\xff\xf0\x00\x00\x3f\xff\xe0\x00\x00\x0f'\
b'\xff\x80\x00\x00\x03\xfe\x00\x00\x25\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x07\x80\x00\x00\x00\x0f\x80\x00\x00\x00\x0f\x80\x00\x00\x00'\
b'\x1f\x80\x00\x00\x00\x3f\x80\x00\x00\x00\x7f\x80\x00\x00\x00\xff'\
b'\x80\x00\x00\x03\xff\x80\x00\x00\x07\xff\x80\x00\x00\x0f\xff\x80'\
b'\x00\x00\x3f\xff\x80\x00\x00\xff\xdf\x80\x00\x01\xff\x9f\x80\x00'\
b'\x01\xfe\x1f\x80\x00\x01\xfc\x1f\x80\x00\x01\xf0\x1f\x80\x00\x01'\
b'\xc0\x1f\x80\x00\x00\x00\x1f\x80\x00\x00\x00\x1f\x80\x00\x00\x00'\
b'\x1f\x80\x00\x00\x00\x1f\x80\x00\x00\x00\x1f\x80\x00\x00\x00\x1f'\
b'\x80\x00\x00\x00\x1f\x80\x00\x00\x00\x1f\x80\x00\x00\x00\x1f\x80'\
b'\x00\x00\x00\x1f\x80\x00\x00\x00\x1f\x80\x00\x00\x00\x1f\x80\x00'\
b'\x00\x00\x1f\x80\x00\x00\x00\x1f\x80\x00\x00\x00\x1f\x80\x00\x00'\
b'\x00\x1f\x80\x00\x00\x00\x1f\x80\x00\x00\x00\x1f\x80\x00\x00\x00'\
b'\x1f\x80\x00\x00\x00\x1f\x80\x00\x00\x00\x1f\x80\x00\x00\x00\x1f'\
b'\x80\x00\x00\x00\x1f\x80\x00\x00\x00\x1f\x80\x00\x00\x00\x1f\x80'\
b'\x00\x00\x00\x1f\x80\x00\x00\x00\x1f\x80\x00\x00\x00\x1f\x80\x00'\
b'\x00\x00\x1f\x80\x00\x00\x00\x1f\x80\x00\x00\x00\x1f\x80\x00\x00'\
b'\x00\x00\x00\x00\x25\x00\x00\x00\x00\x00\x00\x00\x03\xfe\x00\x00'\
b'\x00\x1f\xff\xc0\x00\x00\x7f\xff\xe0\x00\x01\xff\xff\xf8\x00\x03'\
b'\xff\xff\xfc\x00\x03\xff\xff\xfc\x00\x07\xfe\x07\xfe\x00\x0f\xf0'\
b'\x00\xff\x00\x0f\xe0\x00\x7f\x00\x0f\xc0\x00\x3f\x00\x1f\xc0\x00'\
b'\x3f\x80\x1f\x80\x00\x1f\x80\x1f\x80\x00\x1f\x80\x03\x80\x00\x1f'\
b'\x80\x00\x00\x00\x1f\x80\x00\x00\x00\x1f\x80\x00\x00\x00\x1f\x80'\
b'\x00\x00\x00\x3f\x00\x00\x00\x00\x3f\x00\x00\x00\x00\x7f\x00\x00'\
b'\x00\x00\xfe\x00\x00\x00\x01\xfe\x00\x00\x00\x03\xfc\x00\x00\x00'\
b'\x07\xf8\x00\x00\x00\x0f\xf0\x00\x00\x00\x1f\xe0\x00\x00\x00\x3f'\
b'\xe0\x00\x00\x00\x7f\xc0\x00\x00\x00\xff\x80\x00\x00\x01\xfe\x00'\
b'\x00\x00\x03\xfc\x00\x00\x00\x07\xf8\x00\x00\x00\x1f\xf0\x00\x00'\
b'\x00\x3f\xe0\x00\x00\x00\x7f\xc0\x00\x00\x00\xff\x80\x00\x00\x01'\
b'\xfe\x00\x00\x00\x03\xfc\x00\x00\x00\x07\xf8\x00\x00\x00\x07\xf0'\
b'\x00\x00\x00\x0f\xe0\x00\x00\x00\x0f\xe0\x00\x00\x00\x1f\xff\xff'\
b'\xff\x80\x1f\xff\xff\xff\x80\x3f\xff\xff\xff\x80\x3f\xff\xff\xff'\
b'\x80\x3f\xff\xff\xff\x80\x3f\xff\xff\xff\x80\x00\x00\x00\x00\x00'\
b'\x25\x00\x00\x00\x00\x00\x00\x00\x07\xfc\x00\x00\x00\x1f\xff\x80'\
b'\x00\x00\x7f\xff\xe0\x00\x00\xff\xff\xf0\x00\x01\xff\xff\xf8\x00'\
b'\x03\xff\xff\xfc\x00\x07\xfc\x07\xfe\x00\x0f\xf0\x01\xfe\x00\x0f'\
b'\xe0\x00\xfe\x00\x0f\xc0\x00\x7f\x00\x1f\xc0\x00\x3f\x00\x1f\x80'\
b'\x00\x3f\x00\x03\x80\x00\x3f\x00\x00\x00\x00\x3f\x00\x00\x00\x00'\
b'\x3f\x00\x00\x00\x00\x7e\x00\x00\x00\x00\xfe\x00\x00\x00\x01\xfc'\
b'\x00\x00\x00\x0f\xf8\x00\x00\x01\xff\xf0\x00\x00\x01\xff\xe0\x00'\
b'\x00\x01\xff\xe0\x00\x00\x01\xff\xf8\x00\x00\x01\xff\xfc\x00\x00'\
b'\x01\x8f\xfe\x00\x00\x00\x01\xff\x00\x00\x00\x00\x7f\x00\x00\x00'\
b'\x00\x3f\x80\x00\x00\x00\x1f\x80\x00\x00\x00\x1f\xc0\x00\x00\x00'\
b'\x0f\xc0\x00\x00\x00\x0f\xc0\x00\x00\x00\x0f\xc0\x00\x00\x00\x0f'\
b'\xc0\x00\x00\x00\x0f\xc0\x03\x80\x00\x0f\xc0\x1f\x80\x00\x0f\xc0'\
b'\x1f\xc0\x00\x1f\x80\x1f\xc0\x00\x1f\x80\x0f\xe0\x00\x3f\x80\x0f'\
b'\xf0\x00\x7f\x00\x07\xf8\x00\xff\x00\x07\xfe\x03\xfe\x00\x03\xff'\
b'\xff\xfc\x00\x01\xff\xff\xf8\x00\x00\xff\xff\xf0\x00\x00\x7f\xff'\
b'\xe0\x00\x00\x1f\xff\x80\x00\x00\x03\xfc\x00\x00\x25\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf0\x00\x00\x00\x03'\
b'\xf0\x00\x00\x00\x07\xf0\x00\x00\x00\x0f\xf0\x00\x00\x00\x0f\xf0'\
b'\x00\x00\x00\x1f\xf0\x00\x00\x00\x3f\xf0\x00\x00\x00\x7f\xf0\x00'\
b'\x00\x00\x7f\xf0\x00\x00\x00\xff\xf0\x00\x00\x01\xff\xf0\x00\x00'\
b'\x01\xff\xf0\x00\x00\x03\xfb\xf0\x00\x00\x07\xf3\xf0\x00\x00\x0f'\
b'\xf3\xf0\x00\x00\x0f\xe3\xf0\x00\x00\x1f\xc3\xf0\x00\x00\x3f\x83'\
b'\xf0\x00\x00\x7f\x83\xf0\x00\x00\x7f\x03\xf0\x00\x00\xfe\x03\xf0'\
b'\x00\x01\xfc\x03\xf0\x00\x03\xfc\x03\xf0\x00\x03\xf8\x03\xf0\x00'\
b'\x07\xf0\x03\xf0\x00\x0f\xf0\x03\xf0\x00\x0f\xe0\x03\xf0\x00\x1f'\
b'\xc0\x03\xf0\x00\x3f\x80\x03\xf0\x00\x7f\x80\x03\xf0\x00\x7f\xff'\
b'\xff\xff\xc0\x7f\xff\xff\xff\xc0\x7f\xff\xff\xff\xc0\x7f\xff\xff'\
b'\xff\xc0\x7f\xff\xff\xff\xc0\x7f\xff\xff\xff\xc0\x00\x00\x03\xf0'\
b'\x00\x00\x00\x03\xf0\x00\x00\x00\x03\xf0\x00\x00\x00\x03\xf0\x00'\
b'\x00\x00\x03\xf0\x00\x00\x00\x03\xf0\x00\x00\x00\x03\xf0\x00\x00'\
b'\x00\x03\xf0\x00\x00\x00\x03\xf0\x00\x00\x00\x03\xf0\x00\x00\x00'\
b'\x03\xf0\x00\x00\x00\x00\x00\x00\x25\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x7f\xff\xff\x00\x00\x7f\xff\xff\x00\x00\xff'\
b'\xff\xff\x00\x00\xff\xff\xff\x00\x00\xff\xff\xff\x00\x00\xff\xff'\
b'\xff\x00\x00\xfc\x00\x00\x00\x01\xfc\x00\x00\x00\x01\xf8\x00\x00'\
b'\x00\x01\xf8\x00\x00\x00\x01\xf8\x00\x00\x00\x01\xf8\x00\x00\x00'\
b'\x03\xf8\x00\x00\x00\x03\xf0\x00\x00\x00\x03\xf0\x00\x00\x00\x03'\
b'\xf0\x7f\x00\x00\x03\xf3\xff\xc0\x00\x07\xf7\xff\xf0\x00\x07\xff'\
b'\xff\xf8\x00\x07\xff\xff\xfc\x00\x07\xff\xff\xfe\x00\x07\xfe\x03'\
b'\xff\x00\x0f\xf8\x00\xff\x00\x0f\xf0\x00\x7f\x80\x0f\xe0\x00\x3f'\
b'\x80\x01\xc0\x00\x1f\x80\x00\x00\x00\x1f\xc0\x00\x00\x00\x0f\xc0'\
b'\x00\x00\x00\x0f\xc0\x00\x00\x00\x0f\xc0\x00\x00\x00\x0f\xc0\x00'\
b'\x00\x00\x0f\xc0\x00\x00\x00\x0f\xc0\x00\x00\x00\x0f\xc0\x00\x00'\
b'\x00\x0f\xc0\x1f\x80\x00\x1f\x80\x1f\x80\x00\x1f\x80\x1f\xc0\x00'\
b'\x1f\x80\x0f\xc0\x00\x3f\x00\x0f\xe0\x00\x7f\x00\x07\xf0\x00\xfe'\
b'\x00\x07\xfc\x03\xfe\x00\x03\xff\xff\xfc\x00\x01\xff\xff\xf8\x00'\
b'\x00\xff\xff\xf0\x00\x00\x7f\xff\xe0\x00\x00\x1f\xff\x80\x00\x00'\
b'\x07\xfc\x00\x00\x25\x00\x00\x00\x00\x00\x00\x00\x01\xfe\x00\x00'\
b'\x00\x0f\xff\xc0\x00\x00\x3f\xff\xf0\x00\x00\x7f\xff\xf8\x00\x00'\
b'\xff\xff\xfc\x00\x01\xff\xff\xfe\x00\x03\xff\x03\xfe\x00\x03\xf8'\
b'\x00\xff\x00\x07\xf0\x00\x7f\x00\x07\xf0\x00\x3f\x00\x0f\xe0\x00'\
b'\x3f\x80\x0f\xc0\x00\x1f\x80\x0f\xc0\x00\x00\x00\x1f\x80\x00\x00'\
b'\x00\x1f\x80\x00\x00\x00\x1f\x80\x00\x00\x00\x1f\x80\x00\x00\x00'\
b'\x1f\x00\xff\x00\x00\x3f\x07\xff\xc0\x00\x3f\x0f\xff\xf0\x00\x3f'\
b'\x3f\xff\xf8\x00\x3f\x7f\xff\xfc\x00\x3f\x7f\xff\xfe\x00\x3f\xfe'\
b'\x03\xff\x00\x3f\xf0\x00\xff\x00\x3f\xe0\x00\x7f\x80\x3f\xc0\x00'\
b'\x3f\x80\x3f\x80\x00\x1f\x80\x3f\x80\x00\x1f\xc0\x3f\x00\x00\x0f'\
b'\xc0\x3f\x00\x00\x0f\xc0\x3f\x00\x00\x0f\xc0\x3f\x00\x00\x0f\xc0'\
b'\x1f\x00\x00\x0f\xc0\x1f\x00\x00\x0f\xc0\x1f\x00\x00\x0f\xc0\x1f'\
b'\x80\x00\x1f\xc0\x1f\x80\x00\x1f\x80\x0f\xc0\x00\x1f\x80\x0f\xc0'\
b'\x00\x3f\x80\x07\xe0\x00\x7f\x00\x07\xf8\x00\xff\x00\x03\xfe\x03'\
b'\xfe\x00\x01\xff\xff\xfc\x00\x01\xff\xff\xfc\x00\x00\x7f\xff\xf8'\
b'\x00\x00\x3f\xff\xe0\x00\x00\x0f\xff\xc0\x00\x00\x01\xfe\x00\x00'\
b'\x25\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f\xff\xff\xff'\
b'\xc0\x1f\xff\xff\xff\xc0\x1f\xff\xff\xff\xc0\x1f\xff\xff\xff\xc0'\
b'\x1f\xff\xff\xff\xc0\x1f\xff\xff\xff\x80\x00\x00\x00\x0f\x80\x00'\
b'\x00\x00\x1f\x00\x00\x00\x00\x3e\x00\x00\x00\x00\x7c\x00\x00\x00'\
b'\x00\xfc\x00\x00\x00\x01\xf8\x00\x00\x00\x01\xf0\x00\x00\x00\x03'\
b'\xf0\x00\x00\x00\x07\xe0\x00\x00\x00\x07\xc0\x00\x00\x00\x0f\xc0'\
b'\x00\x00\x00\x1f\x80\x00\x00\x00\x1f\x80\x00\x00\x00\x3f\x00\x00'\
b'\x00\x00\x3f\x00\x00\x00\x00\x7e\x00\x00\x00\x00\x7e\x00\x00\x00'\
b'\x00\xfc\x00\x00\x00\x00\xfc\x00\x00\x00\x01\xf8\x00\x00\x00\x01'\
b'\xf8\x00\x00\x00\x03\xf0\x00\x00\x00\x03\xf0\x00\x00\x00\x03\xf0'\
b'\x00\x00\x00\x07\xe0\x00\x00\x00\x07\xe0\x00\x00\x00\x07\xe0\x00'\
b'\x00\x00\x0f\xc0\x00\x00\x00\x0f\xc0\x00\x00\x00\x0f\xc0\x00\x00'\
b'\x00\x0f\xc0\x00\x00\x00\x1f\x80\x00\x00\x00\x1f\x80\x00\x00\x00'\
b'\x1f\x80\x00\x00\x00\x1f\x80\x00\x00\x00\x1f\x80\x00\x00\x00\x3f'\
b'\x00\x00\x00\x00\x3f\x00\x00\x00\x00\x3f\x00\x00\x00\x00\x3f\x00'\
b'\x00\x00\x00\x3f\x00\x00\x00\x00\x00\x00\x00\x00\x25\x00\x00\x00'\
b'\x00\x00\x00\x00\x03\xfe\x00\x00\x00\x1f\xff\x80\x00\x00\x3f\xff'\
b'\xe0\x00\x00\x7f\xff\xf0\x00\x00\xff\xff\xf8\x00\x01\xff\xff\xfc'\
b'\x00\x03\xfe\x03\xfe\x00\x03\xf8\x00\xfe\x00\x03\xf0\x00\x7e\x00'\
b'\x07\xf0\x00\x7f\x00\x07\xe0\x00\x3f\x00\x07\xe0\x00\x3f\x00\x07'\
b'\xe0\x00\x3f\x00\x07\xe0\x00\x3f\x00\x07\xe0\x00\x3f\x00\x07\xf0'\
b'\x00\x7f\x00\x03\xf0\x00\x7e\x00\x03\xf8\x00\xfe\x00\x01\xfe\x03'\
b'\xfc\x00\x00\xff\xff\xf8\x00\x00\x7f\xff\xf0\x00\x00\x1f\xff\xc0'\
b'\x00\x00\x3f\xff\xe0\x00\x00\xff\xff\xf8\x00\x01\xff\xff\xfc\x00'\
b'\x03\xfe\x03\xfe\x00\x07\xf8\x00\xff\x00\x07\xe0\x00\x7f\x00\x0f'\
b'\xe0\x00\x3f\x80\x0f\xc0\x00\x1f\x80\x1f\xc0\x00\x1f\xc0\x1f\x80'\
b'\x00\x0f\xc0\x1f\x80\x00\x0f\xc0\x1f\x80\x00\x0f\xc0\x1f\x80\x00'\
b'\x0f\xc0\x1f\x80\x00\x0f\xc0\x1f\x80\x00\x0f\xc0\x1f\xc0\x00\x1f'\
b'\xc0\x0f\xc0\x00\x1f\x80\x0f\xc0\x00\x3f\x80\x0f\xe0\x00\x3f\x80'\
b'\x07\xf8\x00\xff\x00\x07\xfe\x03\xff\x00\x03\xff\xff\xfe\x00\x01'\
b'\xff\xff\xfc\x00\x00\xff\xff\xf8\x00\x00\x7f\xff\xf0\x00\x00\x1f'\
b'\xff\xc0\x00\x00\x03\xfe\x00\x00\x25\x00\x00\x00\x00\x00\x00\x00'\
b'\x03\xfc\x00\x00\x00\x1f\xff\x00\x00\x00\x7f\xff\xc0\x00\x00\xff'\
b'\xff\xf0\x00\x01\xff\xff\xf8\x00\x03\xff\xff\xfc\x00\x03\xfe\x03'\
b'\xfc\x00\x07\xf8\x00\xfe\x00\x0f\xf0\x00\x7e\x00\x0f\xe0\x00\x3f'\
b'\x00\x0f\xe0\x00\x1f\x00\x1f\xc0\x00\x1f\x80\x1f\xc0\x00\x0f\x80'\
b'\x1f\x80\x00\x0f\x80\x1f\x80\x00\x0f\x80\x1f\x80\x00\x0f\xc0\x1f'\
b'\x80\x00\x0f\xc0\x1f\x80\x00\x0f\xc0\x1f\x80\x00\x0f\xc0\x1f\x80'\
b'\x00\x0f\xc0\x1f\xc0\x00\x1f\xc0\x0f\xc0\x00\x1f\xc0\x0f\xe0\x00'\
b'\x3f\xc0\x0f\xf0\x00\x7f\xc0\x07\xf8\x00\xff\xc0\x07\xfe\x03\xff'\
b'\xc0\x03\xff\xff\xff\xc0\x01\xff\xff\xef\xc0\x00\xff\xff\xcf\xc0'\
b'\x00\x7f\xff\x0f\xc0\x00\x1f\xfe\x0f\xc0\x00\x07\xf0\x0f\xc0\x00'\
b'\x00\x00\x1f\x80\x00\x00\x00\x1f\x80\x00\x00\x00\x1f\x80\x00\x00'\
b'\x00\x1f\x80\x00\x00\x00\x3f\x00\x0f\xc0\x00\x3f\x00\x0f\xc0\x00'\
b'\x3f\x00\x0f\xe0\x00\x7e\x00\x07\xe0\x00\xfe\x00\x07\xf0\x01\xfc'\
b'\x00\x03\xfc\x07\xfc\x00\x03\xff\xff\xf8\x00\x01\xff\xff\xf0\x00'\
b'\x00\xff\xff\xe0\x00\x00\x7f\xff\xc0\x00\x00\x1f\xff\x00\x00\x00'\
b'\x07\xf8\x00\x00\x12\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x03\xf0\x00\x03\xf0\x00\x03\xf0\x00\x03\xf0\x00\x03\xf0\x00\x03'\
b'\xf0\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\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\x00\x00\x00\x03\xf0\x00\x03\xf0\x00\x03\xf0\x00'\
b'\x03\xf0\x00\x03\xf0\x00\x03\xf0\x00\x00\x00\x00'
_index =\
b'\x00\x00\xfc\x00\xf8\x01\xf4\x02\xf0\x03\xec\x04\xe8\x05\xe4\x06'\
b'\xe0\x07\xdc\x08\xd8\x09\xd4\x0a\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x6c\x0b'
_mvfont = memoryview(_font)
_mvi = memoryview(_index)
ifb = lambda l : l[0] | (l[1] << 8)
def get_ch(ch):
oc = ord(ch)
ioff = 2 * (oc - 48 + 1) if oc >= 48 and oc <= 63 else 0
doff = ifb(_mvi[ioff : ])
width = ifb(_mvfont[doff : ])
next_offs = doff + 2 + ((width - 1)//8 + 1) * 50
return _mvfont[doff + 2:next_offs], 50, width

Wyświetl plik

@ -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

296
gui/fonts/font10.py 100644
Wyświetl plik

@ -0,0 +1,296 @@
# Code generated by font_to_py.py.
# Font: FreeSans.ttf Char set: !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~£¬°Ωαβγδθλμπωϕ
# Cmd: ./font_to_py.py -xk extended FreeSans.ttf 17 font10.py
version = '0.33'
def height():
return 17
def baseline():
return 13
def max_width():
return 17
def hmap():
return True
def reverse():
return False
def monospaced():
return False
def min_ch():
return 32
def max_ch():
return 981
_font =\
b'\x09\x00\x1e\x00\x63\x80\x61\x80\x01\x80\x01\x80\x03\x00\x06\x00'\
b'\x04\x00\x0c\x00\x0c\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x06\x00\x30\x30\x30\x30\x30\x30\x30'\
b'\x30\x30\x30\x20\x00\x30\x00\x00\x00\x00\x06\x00\x00\x78\x78\x78'\
b'\x50\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00'\
b'\x00\x00\x00\x19\x00\x19\x00\x13\x00\x7f\x80\x12\x00\x32\x00\x32'\
b'\x00\xff\x80\x26\x00\x24\x00\x64\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x09\x00\x08\x00\x1e\x00\x2b\x00\x69\x80\x69\x80\x68\x00\x68'\
b'\x00\x1e\x00\x0b\x80\x09\x80\x69\x80\x6b\x00\x3e\x00\x08\x00\x00'\
b'\x00\x00\x00\x00\x00\x0f\x00\x00\x00\x3c\x10\x66\x20\x66\x40\x66'\
b'\x40\x64\x80\x18\x80\x01\x3c\x02\x66\x02\x66\x04\x66\x04\x66\x08'\
b'\x3c\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x0f\x00\x19'\
b'\x80\x19\x80\x19\x80\x0f\x00\x0c\x00\x3a\x60\x73\x60\x61\xc0\x60'\
b'\xc0\x71\xc0\x1e\x20\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00'\
b'\x60\x60\x60\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x06\x00\x08\x10\x10\x30\x20\x60\x60\x60\x60\x60\x60\x20\x30\x10'\
b'\x18\x08\x00\x06\x00\x40\x60\x20\x30\x10\x18\x18\x18\x18\x18\x18'\
b'\x10\x30\x20\x60\x40\x00\x07\x00\x10\x54\x38\x28\x28\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x0c\x00\x0c\x00\x3f\x00\x0c'\
b'\x00\x0c\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x60\x20\x20\x40\x00'\
b'\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x78\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x60\x00\x00\x00\x00\x05\x00\x08\x08\x10\x10\x10\x20\x20\x20'\
b'\x40\x40\x40\x80\x80\x00\x00\x00\x00\x09\x00\x00\x00\x1e\x00\x33'\
b'\x00\x21\x00\x61\x80\x61\x80\x61\x80\x61\x80\x61\x80\x61\x80\x21'\
b'\x00\x33\x00\x1e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00'\
b'\x00\x04\x00\x0c\x00\x3c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c'\
b'\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x09\x00\x00\x00\x1e\x00\x33\x00\x61\x80\x61\x80\x01\x80\x03'\
b'\x00\x06\x00\x1c\x00\x30\x00\x20\x00\x60\x00\x7f\x80\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x09\x00\x00\x00\x3e\x00\x73\x80\x61\x80\x01'\
b'\x80\x01\x00\x0e\x00\x03\x80\x01\x80\x01\x80\x61\x80\x73\x00\x1e'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x03\x00\x03'\
b'\x00\x07\x00\x0b\x00\x0b\x00\x13\x00\x23\x00\x23\x00\x3f\x80\x03'\
b'\x00\x03\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00'\
b'\x00\x3f\x00\x20\x00\x20\x00\x40\x00\x5e\x00\x73\x00\x01\x80\x01'\
b'\x80\x01\x80\x61\x80\x33\x00\x1e\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x09\x00\x00\x00\x1e\x00\x33\x00\x21\x80\x60\x00\x60\x00\x7e'\
b'\x00\x73\x00\x61\x80\x61\x80\x61\x80\x33\x00\x1e\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x09\x00\x00\x00\xff\x00\x03\x00\x02\x00\x06'\
b'\x00\x04\x00\x0c\x00\x08\x00\x18\x00\x18\x00\x10\x00\x30\x00\x30'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x1e\x00\x33'\
b'\x00\x61\x80\x61\x80\x33\x00\x1e\x00\x33\x00\x61\x80\x61\x80\x61'\
b'\x80\x33\x00\x1e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00'\
b'\x00\x1e\x00\x33\x00\x61\x80\x61\x80\x61\x80\x33\x80\x1d\x80\x01'\
b'\x80\x01\x80\x61\x00\x33\x00\x1e\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x04\x00\x00\x00\x00\x00\x60\x00\x00\x00\x00\x00\x00\x00\x60'\
b'\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x60\x00\x00\x00\x00'\
b'\x00\x00\x60\x20\x20\x40\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x01\x80\x07\x00\x1c\x00\x60\x00\x70\x00\x1c\x00\x03'\
b'\x80\x00\x80\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\x7f\x80\x00\x00\x00'\
b'\x00\x7f\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x60\x00\x38\x00\x0e'\
b'\x00\x01\x80\x03\x80\x0e\x00\x70\x00\x40\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x09\x00\x1e\x00\x63\x80\x61\x80\x01\x80\x01\x80\x03'\
b'\x00\x06\x00\x04\x00\x0c\x00\x0c\x00\x00\x00\x00\x00\x0c\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x11\x00\x03\xf0\x00\x06\x1c\x00\x18'\
b'\x06\x00\x10\x03\x00\x31\xdb\x80\x26\x39\x80\x66\x31\x80\x6c\x31'\
b'\x80\x6c\x31\x80\x6c\x23\x00\x6e\x67\x00\x37\xbc\x00\x18\x00\x00'\
b'\x0c\x00\x00\x07\xf0\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x06\x00'\
b'\x0e\x00\x0b\x00\x1b\x00\x1b\x00\x11\x80\x31\x80\x31\x80\x3f\xc0'\
b'\x60\xc0\x60\x40\x40\x60\xc0\x60\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x0b\x00\x7f\x00\x61\xc0\x60\xc0\x60\xc0\x60\xc0\x61\x80\x7f\x00'\
b'\x60\xc0\x60\x60\x60\x60\x60\x60\x60\xc0\x7f\x80\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x0c\x00\x0f\xc0\x18\x60\x30\x30\x20\x30\x60\x00'\
b'\x60\x00\x60\x00\x60\x00\x60\x00\x20\x30\x30\x30\x18\x60\x0f\xc0'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x7f\x80\x60\xc0\x60\x60'\
b'\x60\x30\x60\x30\x60\x30\x60\x30\x60\x30\x60\x30\x60\x30\x60\x60'\
b'\x60\xc0\x7f\x80\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x7f\x80'\
b'\x60\x00\x60\x00\x60\x00\x60\x00\x60\x00\x7f\x80\x60\x00\x60\x00'\
b'\x60\x00\x60\x00\x60\x00\x7f\x80\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x0a\x00\x7f\x80\x60\x00\x60\x00\x60\x00\x60\x00\x60\x00\x7f\x00'\
b'\x60\x00\x60\x00\x60\x00\x60\x00\x60\x00\x60\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x0d\x00\x07\xe0\x18\x30\x30\x18\x30\x00\x60\x00'\
b'\x60\x00\x60\xf8\x60\x18\x60\x18\x30\x18\x30\x38\x18\x78\x07\x88'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x60\x60\x60\x60\x60\x60'\
b'\x60\x60\x60\x60\x60\x60\x7f\xe0\x60\x60\x60\x60\x60\x60\x60\x60'\
b'\x60\x60\x60\x60\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x30\x30'\
b'\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x00\x00\x00\x00\x09'\
b'\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03'\
b'\x00\x03\x00\x63\x00\x63\x00\x62\x00\x3c\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x0b\x00\x60\x60\x60\xc0\x61\x80\x63\x00\x66\x00\x6c'\
b'\x00\x7e\x00\x73\x00\x63\x00\x61\x80\x60\xc0\x60\xc0\x60\x60\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x60\x00\x60\x00\x60\x00\x60'\
b'\x00\x60\x00\x60\x00\x60\x00\x60\x00\x60\x00\x60\x00\x60\x00\x60'\
b'\x00\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x70\x1c\x70'\
b'\x1c\x78\x3c\x78\x3c\x68\x2c\x6c\x6c\x6c\x6c\x64\x4c\x66\xcc\x66'\
b'\xcc\x62\x8c\x63\x8c\x63\x8c\x00\x00\x00\x00\x00\x00\x00\x00\x0c'\
b'\x00\x70\x30\x70\x30\x78\x30\x68\x30\x6c\x30\x66\x30\x62\x30\x63'\
b'\x30\x61\xb0\x61\xb0\x60\xf0\x60\x70\x60\x70\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x0d\x00\x0f\xc0\x18\x60\x30\x30\x70\x30\x60\x18\x60'\
b'\x18\x60\x18\x60\x18\x60\x18\x70\x30\x30\x30\x18\x60\x0f\xc0\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x7f\x80\x60\xc0\x60\x60\x60'\
b'\x60\x60\x60\x60\xc0\x7f\x80\x60\x00\x60\x00\x60\x00\x60\x00\x60'\
b'\x00\x60\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x00\x0f\xc0\x18'\
b'\x60\x30\x30\x70\x30\x60\x18\x60\x18\x60\x18\x60\x18\x60\x18\x70'\
b'\xb0\x30\xf0\x18\x60\x0f\xf0\x00\x10\x00\x00\x00\x00\x00\x00\x0c'\
b'\x00\x7f\x80\x60\xe0\x60\x60\x60\x60\x60\x60\x60\xc0\x7f\x80\x60'\
b'\xe0\x60\x60\x60\x60\x60\x60\x60\x60\x60\x70\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x0b\x00\x1f\x80\x30\xc0\x60\x60\x60\x00\x60\x00\x30'\
b'\x00\x1f\x00\x03\xc0\x00\xe0\x60\x60\x60\x60\x30\xc0\x1f\x80\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x3f\xc0\x06\x00\x06\x00\x06'\
b'\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06'\
b'\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x60\x60\x60'\
b'\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60'\
b'\x60\x60\x60\x30\xc0\x1f\x80\x00\x00\x00\x00\x00\x00\x00\x00\x0b'\
b'\x00\xc0\x60\x40\x40\x60\xc0\x60\xc0\x20\x80\x31\x80\x31\x80\x11'\
b'\x00\x1b\x00\x0b\x00\x0a\x00\x0e\x00\x06\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x10\x00\xc1\x83\xc1\x82\x42\x86\x62\xc6\x62\xc6\x62'\
b'\x44\x24\x44\x24\x6c\x34\x2c\x3c\x28\x18\x38\x18\x38\x18\x18\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x60\x40\x20\xc0\x31\x80\x19'\
b'\x00\x1b\x00\x0e\x00\x06\x00\x0e\x00\x1b\x00\x11\x80\x31\x80\x60'\
b'\xc0\x40\x60\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x40\x60\x60'\
b'\x60\x30\xc0\x30\xc0\x19\x80\x0d\x00\x0f\x00\x06\x00\x06\x00\x06'\
b'\x00\x06\x00\x06\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a'\
b'\x00\x7f\xc0\x00\xc0\x01\x80\x03\x00\x03\x00\x06\x00\x0c\x00\x0c'\
b'\x00\x18\x00\x30\x00\x30\x00\x60\x00\x7f\xc0\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x05\x00\x70\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60'\
b'\x60\x60\x60\x60\x60\x70\x05\x00\x80\x80\x40\x40\x40\x20\x20\x20'\
b'\x10\x10\x10\x08\x08\x00\x00\x00\x00\x05\x00\xe0\x60\x60\x60\x60'\
b'\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\xe0\x08\x00\x00\x18'\
b'\x18\x3c\x24\x24\x42\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a'\
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\xff\xc0\x00\x00\x00'\
b'\x00\x00\x00\x04\x00\xc0\x40\x20\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x3e\x00\x63\x00\x03\x00\x03\x00\x3f\x00\x63\x00\x63\x00\x67\x00'\
b'\x3b\x80\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x60\x00\x60\x00'\
b'\x60\x00\x60\x00\x6f\x00\x71\x80\x60\x80\xe0\x80\xe0\x80\xe0\x80'\
b'\xe0\x80\xf1\x80\x6f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x1e\x00\x33\x00\x61\x80\x60\x00'\
b'\x60\x00\x60\x00\x61\x80\x33\x00\x1e\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x0a\x00\x01\x80\x01\x80\x01\x80\x01\x80\x1d\x80\x33\x80'\
b'\x61\x80\x61\x80\x61\x80\x61\x80\x61\x80\x33\x80\x1d\x80\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x1e\x00\x33\x00\x61\x80\x61\x80\x7f\x80\x60\x00\x61\x80\x33\x00'\
b'\x1e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x18\x30\x30\x30'\
b'\x78\x30\x30\x30\x30\x30\x30\x30\x30\x00\x00\x00\x00\x09\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x1d\x80\x33\x80\x61\x80\x61\x80\x61'\
b'\x80\x61\x80\x61\x80\x33\x80\x1d\x80\x01\x80\x01\x80\x63\x00\x3e'\
b'\x00\x09\x00\x60\x00\x60\x00\x60\x00\x60\x00\x6f\x00\x71\x80\x61'\
b'\x80\x61\x80\x61\x80\x61\x80\x61\x80\x61\x80\x61\x80\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x04\x00\x60\x00\x00\x00\x60\x60\x60\x60\x60'\
b'\x60\x60\x60\x60\x00\x00\x00\x00\x04\x00\x30\x00\x00\x00\x30\x30'\
b'\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\xe0\x09\x00\x60\x00\x60'\
b'\x00\x60\x00\x60\x00\x63\x00\x66\x00\x6c\x00\x7c\x00\x74\x00\x66'\
b'\x00\x63\x00\x63\x00\x61\x80\x00\x00\x00\x00\x00\x00\x00\x00\x04'\
b'\x00\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x00\x00'\
b'\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x6e\xf0\x73\x98'\
b'\x63\x18\x63\x18\x63\x18\x63\x18\x63\x18\x63\x18\x63\x18\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x6f\x00\x71\x80\x61\x80\x61\x80\x61\x80\x61\x80\x61\x80\x61\x80'\
b'\x61\x80\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x1e\x00\x33\x00\x61\x80\x61\x80\x61\x80\x61\x80'\
b'\x61\x80\x33\x00\x1e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x6f\x00\x71\x80\x60\x80\xe0\x80'\
b'\xe0\x80\xe0\x80\xe0\x80\xf1\x80\x6f\x00\x60\x00\x60\x00\x60\x00'\
b'\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1d\x80\x33\x80'\
b'\x61\x80\x61\x80\x61\x80\x61\x80\x61\x80\x33\x80\x1d\x80\x01\x80'\
b'\x01\x80\x01\x80\x00\x00\x06\x00\x00\x00\x00\x00\x6c\x70\x60\x60'\
b'\x60\x60\x60\x60\x60\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x3e'\
b'\x63\x60\x60\x38\x07\x63\x63\x3e\x00\x00\x00\x00\x05\x00\x00\x00'\
b'\x30\x30\x78\x30\x30\x30\x30\x30\x30\x30\x38\x00\x00\x00\x00\x09'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x61\x80\x61\x80\x61\x80\x61'\
b'\x80\x61\x80\x61\x80\x61\x80\x63\x80\x3d\x80\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x08\x00\x00\x00\x00\x00\xc3\x43\x62\x66\x26\x34\x3c'\
b'\x18\x18\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\xc6\x30\x46\x30\x47\x20\x6f\x20\x69\x60\x29\x60\x29\xc0\x39\xc0'\
b'\x10\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00'\
b'\x42\x66\x34\x18\x18\x1c\x24\x66\x43\x00\x00\x00\x00\x08\x00\x00'\
b'\x00\x00\x00\xc3\x42\x42\x66\x24\x24\x3c\x18\x18\x18\x10\x30\x60'\
b'\x08\x00\x00\x00\x00\x00\x7f\x06\x04\x0c\x18\x30\x20\x60\x7f\x00'\
b'\x00\x00\x00\x06\x00\x18\x30\x30\x30\x30\x30\x30\x70\x60\x70\x30'\
b'\x30\x30\x30\x30\x30\x18\x04\x00\x30\x30\x30\x30\x30\x30\x30\x30'\
b'\x30\x30\x30\x30\x30\x30\x30\x30\x00\x06\x00\x60\x30\x30\x30\x30'\
b'\x30\x30\x38\x18\x38\x30\x30\x30\x30\x30\x30\x60\x09\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x31\x00\x4f\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x09\x00\x1f\x00\x31\x80\x61\x80\x60\x00\x60\x00\x30\x00\xfc\x00'\
b'\x18\x00\x18\x00\x18\x00\x30\x00\x7c\x80\x47\x80\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x3f\xc0\x00\xc0\x00\xc0\x00\xc0\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x0e\x00\x11\x00'\
b'\x11\x00\x11\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x00\x0f\xc0'\
b'\x18\x60\x30\x30\x30\x18\x60\x18\x60\x18\x60\x18\x60\x18\x60\x18'\
b'\x30\x30\x10\x60\x08\xc0\x7c\xf8\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1d\x80\x33\x80\x61\x80'\
b'\x61\x80\x61\x80\x61\x80\x61\x80\x33\x80\x1f\xc0\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x09\x00\x3e\x00\x23\x00\x63\x00\x63\x00\x63\x00'\
b'\x66\x00\x6c\x00\x63\x00\x61\x80\x61\x80\x61\x80\x73\x00\x7e\x00'\
b'\x60\x00\x60\x00\x60\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\xc1\x80\x61\x80\x21\x00\x33\x00\x32\x00\x1e\x00\x1e\x00'\
b'\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x00\x00\x09\x00\x3f\x00'\
b'\x30\x00\x1c\x00\x0e\x00\x1f\x00\x33\x00\x61\x80\x61\x80\x61\x80'\
b'\x61\x80\x61\x80\x33\x00\x1e\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x09\x00\x1e\x00\x33\x00\x21\x00\x61\x80\x61\x80\x61\x80\x7f\x80'\
b'\x61\x80\x61\x80\x61\x80\x61\x00\x33\x00\x1e\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x09\x00\x60\x00\x10\x00\x18\x00\x18\x00\x18\x00'\
b'\x1c\x00\x34\x00\x34\x00\x26\x00\x62\x00\x62\x00\x43\x00\xc1\x80'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x61\x80\x61\x80\x61\x80\x61\x80\x61\x80\x61\x80\x61\x80'\
b'\x73\x80\x7f\x80\x60\x00\x60\x00\x60\x00\x00\x00\x0a\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x7f\xc0\x31\x80\x31\x80\x31\x80\x31\x80'\
b'\x31\x80\x31\x80\x31\x80\x31\xc0\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x60\x30\x30\x20\x10'\
b'\x63\x10\xe3\x10\xe3\x10\xe3\x10\xb3\x30\x1c\xe0\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x0c\x00\x00\x00\x06\x00\x06\x00\x06\x00\x1f\x80'\
b'\x36\x60\x66\x30\x66\x30\x66\x30\x66\x30\x66\x30\x36\x60\x1f\xc0'\
b'\x06\x00\x06\x00\x06\x00\x00\x00'
_sparse =\
b'\x20\x00\x24\x00\x21\x00\x37\x00\x22\x00\x4a\x00\x23\x00\x5d\x00'\
b'\x24\x00\x81\x00\x25\x00\xa5\x00\x26\x00\xc9\x00\x27\x00\xed\x00'\
b'\x28\x00\x00\x01\x29\x00\x13\x01\x2a\x00\x26\x01\x2b\x00\x39\x01'\
b'\x2c\x00\x5d\x01\x2d\x00\x70\x01\x2e\x00\x83\x01\x2f\x00\x96\x01'\
b'\x30\x00\xa9\x01\x31\x00\xcd\x01\x32\x00\xf1\x01\x33\x00\x15\x02'\
b'\x34\x00\x39\x02\x35\x00\x5d\x02\x36\x00\x81\x02\x37\x00\xa5\x02'\
b'\x38\x00\xc9\x02\x39\x00\xed\x02\x3a\x00\x11\x03\x3b\x00\x24\x03'\
b'\x3c\x00\x37\x03\x3d\x00\x5b\x03\x3e\x00\x7f\x03\x3f\x00\xa3\x03'\
b'\x40\x00\xc7\x03\x41\x00\xfc\x03\x42\x00\x20\x04\x43\x00\x44\x04'\
b'\x44\x00\x68\x04\x45\x00\x8c\x04\x46\x00\xb0\x04\x47\x00\xd4\x04'\
b'\x48\x00\xf8\x04\x49\x00\x1c\x05\x4a\x00\x2f\x05\x4b\x00\x53\x05'\
b'\x4c\x00\x77\x05\x4d\x00\x9b\x05\x4e\x00\xbf\x05\x4f\x00\xe3\x05'\
b'\x50\x00\x07\x06\x51\x00\x2b\x06\x52\x00\x4f\x06\x53\x00\x73\x06'\
b'\x54\x00\x97\x06\x55\x00\xbb\x06\x56\x00\xdf\x06\x57\x00\x03\x07'\
b'\x58\x00\x27\x07\x59\x00\x4b\x07\x5a\x00\x6f\x07\x5b\x00\x93\x07'\
b'\x5c\x00\xa6\x07\x5d\x00\xb9\x07\x5e\x00\xcc\x07\x5f\x00\xdf\x07'\
b'\x60\x00\x03\x08\x61\x00\x16\x08\x62\x00\x3a\x08\x63\x00\x5e\x08'\
b'\x64\x00\x82\x08\x65\x00\xa6\x08\x66\x00\xca\x08\x67\x00\xdd\x08'\
b'\x68\x00\x01\x09\x69\x00\x25\x09\x6a\x00\x38\x09\x6b\x00\x4b\x09'\
b'\x6c\x00\x6f\x09\x6d\x00\x82\x09\x6e\x00\xa6\x09\x6f\x00\xca\x09'\
b'\x70\x00\xee\x09\x71\x00\x12\x0a\x72\x00\x36\x0a\x73\x00\x49\x0a'\
b'\x74\x00\x5c\x0a\x75\x00\x6f\x0a\x76\x00\x93\x0a\x77\x00\xa6\x0a'\
b'\x78\x00\xca\x0a\x79\x00\xdd\x0a\x7a\x00\xf0\x0a\x7b\x00\x03\x0b'\
b'\x7c\x00\x16\x0b\x7d\x00\x29\x0b\x7e\x00\x3c\x0b\xa3\x00\x60\x0b'\
b'\xac\x00\x84\x0b\xb0\x00\xa8\x0b\xa9\x03\xcc\x0b\xb1\x03\xf0\x0b'\
b'\xb2\x03\x14\x0c\xb3\x03\x38\x0c\xb4\x03\x5c\x0c\xb8\x03\x80\x0c'\
b'\xbb\x03\xa4\x0c\xbc\x03\xc8\x0c\xc0\x03\xec\x0c\xc9\x03\x10\x0d'\
b'\xd5\x03\x34\x0d'
_mvfont = memoryview(_font)
_mvsp = memoryview(_sparse)
ifb = lambda l : l[0] | (l[1] << 8)
def bs(lst, val):
while True:
m = (len(lst) & ~ 7) >> 1
v = ifb(lst[m:])
if v == val:
return ifb(lst[m + 2:])
if not m:
return 0
lst = lst[m:] if v < val else lst[:m]
def get_ch(ch):
doff = bs(_mvsp, ord(ch))
width = ifb(_mvfont[doff : ])
next_offs = doff + 2 + ((width - 1)//8 + 1) * 17
return _mvfont[doff + 2:next_offs], 17, width

395
gui/fonts/font14.py 100644
Wyświetl plik

@ -0,0 +1,395 @@
# Code generated by font_to_py.py.
# Font: FreeSans.ttf Char set: !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~£¬°Ωαβγδθλμπωϕ
# Cmd: ./font_to_py.py -x -k extended FreeSans.ttf 23 font14.py
version = '0.33'
def height():
return 23
def baseline():
return 18
def max_width():
return 23
def hmap():
return True
def reverse():
return False
def monospaced():
return False
def min_ch():
return 32
def max_ch():
return 981
_font =\
b'\x0d\x00\x00\x00\x0f\xc0\x1f\xe0\x38\x70\x30\x30\x30\x30\x00\x30'\
b'\x00\x70\x00\x60\x01\xc0\x01\x80\x03\x00\x03\x00\x03\x00\x00\x00'\
b'\x00\x00\x03\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x06\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\x08\x00\x00\x18\x18\x18\x18'\
b'\x18\x18\x18\x18\x18\x18\x18\x18\x18\x00\x00\x18\x18\x00\x00\x00'\
b'\x00\x00\x08\x00\x00\x00\x66\x66\x66\x66\x44\x44\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x00\x00\x00\x00'\
b'\x00\x04\x60\x04\x60\x0c\x60\x0c\x40\x7f\xf8\x7f\xf8\x08\xc0\x18'\
b'\xc0\x18\xc0\x18\x80\xff\xf0\xff\xf0\x31\x80\x31\x80\x31\x00\x33'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x00\x02\x00\x0f'\
b'\x80\x3f\xe0\x32\x60\x62\x30\x62\x30\x62\x00\x62\x00\x3a\x00\x3f'\
b'\x00\x0f\xe0\x02\xf0\x02\x30\x62\x30\x62\x30\x72\x70\x3f\xe0\x0f'\
b'\x80\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x02\x00\x1e\x02\x00\x3f\x04\x00\x73\x8c\x00\x61'\
b'\x88\x00\x73\x98\x00\x3f\x10\x00\x1e\x20\x00\x00\x23\xc0\x00\x47'\
b'\xe0\x00\xce\x70\x00\x8c\x30\x01\x8c\x30\x01\x0e\x70\x02\x07\xe0'\
b'\x02\x03\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x0f\x00\x00\x00\x00\x00\x07\x80\x0f\xc0\x1c\xe0\x18\x60'\
b'\x18\x60\x0c\xc0\x0f\x80\x0f\x00\x1b\x98\x31\xd8\x60\xf8\x60\x70'\
b'\x60\x30\x70\xf8\x3f\xd8\x1f\x0c\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x05\x00\x00\x00\x60\x60\x60\x60\x40\x40\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x02\x04'\
b'\x0c\x08\x18\x18\x10\x30\x30\x30\x30\x30\x30\x30\x30\x18\x18\x18'\
b'\x0c\x0c\x04\x02\x08\x00\x00\x40\x20\x30\x10\x18\x18\x08\x0c\x0c'\
b'\x0c\x0c\x0c\x0c\x0c\x0c\x18\x18\x18\x30\x30\x20\x40\x09\x00\x00'\
b'\x00\x08\x00\x08\x00\x6b\x00\x3e\x00\x1c\x00\x36\x00\x24\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\x0d\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x03'\
b'\x00\x03\x00\x03\x00\x3f\xf0\x3f\xf0\x03\x00\x03\x00\x03\x00\x03'\
b'\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30'\
b'\x30\x10\x10\x20\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x7c\x7c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x30\x30\x00\x00\x00\x00\x00\x06\x00\x00\x04\x0c\x08\x08\x18'\
b'\x10\x10\x10\x30\x20\x20\x20\x60\x40\x40\xc0\x80\x00\x00\x00\x00'\
b'\x00\x0d\x00\x00\x00\x00\x00\x0f\x80\x1f\xc0\x38\xe0\x30\x60\x60'\
b'\x30\x60\x30\x60\x30\x60\x30\x60\x30\x60\x30\x60\x30\x60\x30\x30'\
b'\x60\x38\xe0\x1f\xc0\x0f\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x0d\x00\x00\x00\x00\x00\x01\x00\x03\x00\x07\x00\x1f\x00\x1f'\
b'\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03'\
b'\x00\x03\x00\x03\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x0d\x00\x00\x00\x00\x00\x0f\x80\x3f\xe0\x30\x70\x60\x30\x60'\
b'\x30\x00\x30\x00\x30\x00\xe0\x01\xc0\x07\x80\x0e\x00\x18\x00\x10'\
b'\x00\x20\x00\x3f\xf0\x3f\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x0d\x00\x00\x00\x00\x00\x0f\xc0\x3f\xe0\x30\x70\x60\x30\x60'\
b'\x30\x00\x70\x07\xe0\x07\xe0\x00\x70\x00\x30\x00\x30\x60\x30\x60'\
b'\x30\x30\x60\x3f\xe0\x0f\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x0d\x00\x00\x00\x00\x00\x00\xc0\x00\xc0\x01\xc0\x03\xc0\x06'\
b'\xc0\x04\xc0\x0c\xc0\x18\xc0\x10\xc0\x20\xc0\x3f\xf0\x3f\xf0\x00'\
b'\xc0\x00\xc0\x00\xc0\x00\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x0d\x00\x00\x00\x00\x00\x3f\xe0\x3f\xe0\x30\x00\x30\x00\x30'\
b'\x00\x37\x80\x3f\xe0\x70\xe0\x00\x70\x00\x30\x00\x30\x00\x30\x60'\
b'\x30\x70\xe0\x3f\xc0\x0f\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x0d\x00\x00\x00\x00\x00\x07\x80\x1f\xe0\x38\x60\x30\x70\x20'\
b'\x00\x60\x00\x67\x80\x7f\xe0\x70\x60\x60\x30\x60\x30\x60\x30\x20'\
b'\x30\x38\x60\x1f\xe0\x0f\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x0d\x00\x00\x00\x00\x00\x7f\xe0\x7f\xe0\x00\x40\x00\xc0\x00'\
b'\x80\x01\x80\x03\x00\x03\x00\x06\x00\x06\x00\x04\x00\x0c\x00\x0c'\
b'\x00\x08\x00\x18\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x0d\x00\x00\x00\x00\x00\x0f\x80\x1f\xc0\x38\xe0\x30\x60\x30'\
b'\x60\x38\xe0\x1f\xc0\x1f\xc0\x38\xe0\x60\x30\x60\x30\x60\x30\x60'\
b'\x30\x30\x60\x1f\xc0\x0f\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x0d\x00\x00\x00\x00\x00\x0f\x00\x3f\xc0\x30\xc0\x60\x60\x60'\
b'\x60\x60\x60\x60\x60\x30\xe0\x3f\xe0\x1f\x60\x00\x60\x00\x60\x60'\
b'\xc0\x31\xc0\x3f\x80\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x06\x00\x00\x00\x00\x00\x00\x00\x30\x30\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x30\x30\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00'\
b'\x00\x00\x30\x30\x00\x00\x00\x00\x00\x00\x00\x00\x30\x30\x10\x10'\
b'\x20\x00\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x10\x00\xf0\x03\xc0\x0f\x00\x78\x00\x60\x00\x3c'\
b'\x00\x0f\x00\x03\xe0\x00\x70\x00\x10\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x7f\xf0\x7f\xf0\x00\x00\x00\x00\x7f'\
b'\xf0\x7f\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x60\x00\x78\x00\x1e\x00\x07\xc0\x00\xf0\x00\x30\x00'\
b'\xf0\x07\x80\x1e\x00\x78\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x0d\x00\x00\x00\x0f\xc0\x1f\xe0\x38\x70\x30\x30\x30'\
b'\x30\x00\x30\x00\x70\x00\x60\x01\xc0\x01\x80\x03\x00\x03\x00\x03'\
b'\x00\x00\x00\x00\x00\x03\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x17\x00\x00\x00\x00\x00\x7f\x00\x03\xff\xc0\x07\x81'\
b'\xe0\x0e\x00\x70\x1c\x38\x38\x38\xfd\x98\x30\xc7\x8c\x31\x83\x0c'\
b'\x63\x83\x0c\x63\x03\x0c\x63\x02\x0c\x63\x06\x18\x63\x06\x18\x73'\
b'\x8e\x30\x31\xff\xf0\x38\xe3\xc0\x1c\x00\x00\x0f\x00\x00\x03\xff'\
b'\x00\x00\xfe\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x00\x03\x80'\
b'\x03\xc0\x07\xc0\x06\xc0\x06\xe0\x0e\x60\x0c\x60\x0c\x70\x18\x30'\
b'\x18\x30\x1f\xf8\x3f\xf8\x30\x18\x30\x1c\x70\x0c\x60\x0c\x60\x0e'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x00\x3f\xe0'\
b'\x3f\xf0\x30\x38\x30\x18\x30\x18\x30\x18\x30\x30\x3f\xe0\x3f\xf0'\
b'\x30\x18\x30\x0c\x30\x0c\x30\x0c\x30\x0c\x30\x18\x3f\xf8\x3f\xf0'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x03\xf0'\
b'\x0f\xf8\x1c\x1c\x38\x0e\x30\x06\x60\x00\x60\x00\x60\x00\x60\x00'\
b'\x60\x00\x60\x00\x60\x06\x30\x06\x38\x0c\x1c\x1c\x0f\xf8\x03\xe0'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x3f\xe0'\
b'\x3f\xf8\x30\x38\x30\x0c\x30\x0c\x30\x06\x30\x06\x30\x06\x30\x06'\
b'\x30\x06\x30\x06\x30\x06\x30\x0c\x30\x0c\x30\x38\x3f\xf0\x3f\xe0'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x00\x3f\xf8'\
b'\x3f\xf8\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x3f\xf8\x3f\xf8'\
b'\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x3f\xfc\x3f\xfc'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x3f\xf8'\
b'\x3f\xf8\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x3f\xf0\x3f\xf0'\
b'\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x12\x00\x00\x00\x00\x03'\
b'\xf0\x00\x0f\xfc\x00\x1c\x0e\x00\x38\x07\x00\x30\x03\x00\x70\x00'\
b'\x00\x60\x00\x00\x60\x00\x00\x60\x3f\x00\x60\x3f\x00\x60\x03\x00'\
b'\x70\x03\x00\x30\x03\x00\x38\x07\x00\x1c\x1f\x00\x0f\xfb\x00\x03'\
b'\xe1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x11\x00\x00\x00\x00\x30\x06\x00\x30\x06\x00\x30\x06\x00\x30'\
b'\x06\x00\x30\x06\x00\x30\x06\x00\x30\x06\x00\x3f\xfe\x00\x3f\xfe'\
b'\x00\x30\x06\x00\x30\x06\x00\x30\x06\x00\x30\x06\x00\x30\x06\x00'\
b'\x30\x06\x00\x30\x06\x00\x30\x06\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x30\x30\x30\x30\x30'\
b'\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x00\x00\x00\x00'\
b'\x00\x0c\x00\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\x60\xc0\x60'\
b'\xc0\x71\xc0\x3f\x80\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x0f\x00\x00\x00\x30\x0e\x30\x1c\x30\x38\x30\x70\x30\xe0\x31'\
b'\xc0\x33\x80\x37\x80\x3f\x80\x3c\xc0\x38\xe0\x30\x60\x30\x30\x30'\
b'\x38\x30\x18\x30\x0c\x30\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x0d\x00\x00\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30'\
b'\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30'\
b'\x00\x30\x00\x3f\xf0\x3f\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x13\x00\x00\x00\x00\x38\x01\xc0\x38\x01\xc0\x3c\x03\xc0\x3c'\
b'\x03\xc0\x36\x02\xc0\x36\x06\xc0\x36\x06\xc0\x33\x04\xc0\x33\x0c'\
b'\xc0\x33\x0c\xc0\x31\x98\xc0\x31\x98\xc0\x31\x98\xc0\x30\xf0\xc0'\
b'\x30\xf0\xc0\x30\xf0\xc0\x30\x60\xc0\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x38\x06\x00'\
b'\x38\x06\x00\x3c\x06\x00\x3c\x06\x00\x36\x06\x00\x37\x06\x00\x33'\
b'\x06\x00\x31\x86\x00\x31\xc6\x00\x30\xc6\x00\x30\x66\x00\x30\x66'\
b'\x00\x30\x36\x00\x30\x3e\x00\x30\x1e\x00\x30\x0e\x00\x30\x0e\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x12'\
b'\x00\x00\x00\x00\x03\xf0\x00\x0f\xfc\x00\x1c\x0e\x00\x38\x07\x00'\
b'\x30\x03\x00\x70\x03\x80\x60\x01\x80\x60\x01\x80\x60\x01\x80\x60'\
b'\x01\x80\x60\x01\x80\x70\x03\x80\x30\x03\x00\x38\x07\x00\x1c\x0e'\
b'\x00\x0f\xfc\x00\x03\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x00\x3f\xe0\x3f\xf8\x30\x18'\
b'\x30\x0c\x30\x0c\x30\x0c\x30\x0c\x30\x1c\x3f\xf8\x3f\xf0\x30\x00'\
b'\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x12\x00\x00\x00\x00\x03\xf0\x00\x0f\xfc'\
b'\x00\x1c\x0e\x00\x38\x07\x00\x30\x03\x00\x70\x03\x80\x60\x01\x80'\
b'\x60\x01\x80\x60\x01\x80\x60\x01\x80\x60\x01\x80\x70\x03\x80\x30'\
b'\x13\x00\x38\x1f\x00\x1c\x1e\x00\x0f\xff\x00\x03\xf3\x00\x00\x01'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00'\
b'\x00\x3f\xf0\x3f\xf8\x30\x1c\x30\x0c\x30\x0c\x30\x0c\x30\x0c\x30'\
b'\x18\x3f\xf0\x3f\xf8\x30\x1c\x30\x0c\x30\x0c\x30\x0c\x30\x0c\x30'\
b'\x0c\x30\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x00'\
b'\x00\x07\xe0\x1f\xf8\x18\x38\x30\x0c\x30\x0c\x30\x00\x38\x00\x1f'\
b'\x00\x0f\xf0\x00\xf8\x00\x1c\x00\x0c\x60\x0c\x60\x0c\x38\x18\x1f'\
b'\xf8\x07\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x00'\
b'\x00\x7f\xf8\x7f\xf8\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03'\
b'\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03'\
b'\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00'\
b'\x00\x00\x30\x06\x00\x30\x06\x00\x30\x06\x00\x30\x06\x00\x30\x06'\
b'\x00\x30\x06\x00\x30\x06\x00\x30\x06\x00\x30\x06\x00\x30\x06\x00'\
b'\x30\x06\x00\x30\x06\x00\x30\x06\x00\x38\x0e\x00\x1c\x1c\x00\x0f'\
b'\xf8\x00\x07\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x0f\x00\x00\x00\x60\x0c\x60\x0c\x60\x1c\x30\x18'\
b'\x30\x18\x38\x38\x18\x30\x18\x30\x1c\x70\x0c\x60\x0c\x60\x0e\xe0'\
b'\x06\xc0\x06\xc0\x07\xc0\x03\x80\x03\x80\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x16\x00\x00\x00\x00\xe0\x70\x18\x60\x70\x38\x60'\
b'\x78\x38\x60\x78\x30\x70\xd8\x30\x30\xd8\x30\x30\xcc\x70\x30\xcc'\
b'\x60\x39\x8c\x60\x19\x86\x60\x19\x86\xe0\x1b\x86\xc0\x1f\x06\xc0'\
b'\x0f\x03\xc0\x0f\x03\xc0\x0e\x03\x80\x0e\x03\x80\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x00\x70'\
b'\x0c\x30\x18\x38\x38\x1c\x30\x0c\x60\x0e\xe0\x06\xc0\x03\x80\x03'\
b'\x80\x07\xc0\x06\xc0\x0e\x60\x1c\x70\x18\x30\x38\x18\x70\x1c\x60'\
b'\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x60'\
b'\x0e\x70\x0c\x38\x1c\x18\x18\x1c\x38\x0c\x30\x06\x60\x07\xe0\x03'\
b'\xc0\x03\xc0\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01'\
b'\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x7f'\
b'\xf8\x7f\xf8\x00\x38\x00\x30\x00\x70\x00\xe0\x01\xc0\x01\x80\x03'\
b'\x00\x07\x00\x0e\x00\x1c\x00\x18\x00\x38\x00\x70\x00\x7f\xf8\x7f'\
b'\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x78\x78'\
b'\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60'\
b'\x60\x60\x78\x78\x06\x00\x00\x80\xc0\x40\x40\x60\x20\x20\x20\x30'\
b'\x10\x10\x10\x18\x08\x08\x0c\x04\x00\x00\x00\x00\x00\x06\x00\x00'\
b'\x78\x78\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18'\
b'\x18\x18\x18\x18\x78\x78\x0b\x00\x00\x00\x00\x00\x0c\x00\x0e\x00'\
b'\x0a\x00\x1b\x00\x13\x00\x31\x00\x31\x80\x20\x80\x60\xc0\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\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\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\xff\xf8\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x06\x00\x00\x30\x18\x0c\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f\x80\x3f'\
b'\xc0\x60\xe0\x00\x60\x00\x60\x0f\xe0\x3e\x60\x60\x60\x60\x60\x61'\
b'\xe0\x3f\x70\x1e\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d'\
b'\x00\x00\x00\x60\x00\x60\x00\x60\x00\x60\x00\x60\x00\x67\x80\x7f'\
b'\xe0\x78\xe0\x70\x70\x60\x30\x60\x30\x60\x30\x60\x30\x70\x70\x78'\
b'\xe0\x6f\xc0\x67\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x80\x1f'\
b'\xc0\x30\xe0\x70\x60\x60\x00\x60\x00\x60\x00\x60\x00\x70\x60\x30'\
b'\xe0\x3f\xc0\x0f\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d'\
b'\x00\x00\x00\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x0f\x30\x1f'\
b'\xf0\x38\xf0\x70\x70\x60\x30\x60\x30\x60\x30\x60\x30\x70\x70\x38'\
b'\xf0\x1f\xb0\x0f\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x80\x1f'\
b'\xc0\x38\x60\x70\x30\x60\x30\x7f\xf0\x7f\xf0\x60\x00\x60\x30\x30'\
b'\x60\x1f\xe0\x0f\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06'\
b'\x00\x00\x18\x38\x30\x30\x30\x78\x78\x30\x30\x30\x30\x30\x30\x30'\
b'\x30\x30\x30\x00\x00\x00\x00\x00\x0d\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x0f\x60\x3f\xe0\x30\xe0\x70\xe0\x60\x60'\
b'\x60\x60\x60\x60\x60\x60\x70\xe0\x30\xe0\x3f\x60\x0e\x60\x00\x60'\
b'\x00\xe0\x60\xc0\x3f\xc0\x1f\x00\x0c\x00\x00\x00\x60\x00\x60\x00'\
b'\x60\x00\x60\x00\x60\x00\x67\x80\x6f\xe0\x78\xe0\x70\x60\x60\x60'\
b'\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x30\x30\x00\x00\x00'\
b'\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x00\x00\x00\x00'\
b'\x00\x06\x00\x00\x30\x30\x00\x00\x00\x30\x30\x30\x30\x30\x30\x30'\
b'\x30\x30\x30\x30\x30\x30\x30\x30\xf0\xe0\x0c\x00\x00\x00\x60\x00'\
b'\x60\x00\x60\x00\x60\x00\x60\x00\x60\xc0\x61\x80\x63\x00\x66\x00'\
b'\x6e\x00\x7e\x00\x77\x00\x63\x00\x61\x80\x61\xc0\x60\xc0\x60\x60'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x60\x60\x60'\
b'\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x00\x00'\
b'\x00\x00\x00\x13\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x67\x1f\x00\x6f\xbf\x80\x71\xe3\x80'\
b'\x60\xc1\x80\x60\xc1\x80\x60\xc1\x80\x60\xc1\x80\x60\xc1\x80\x60'\
b'\xc1\x80\x60\xc1\x80\x60\xc1\x80\x60\xc1\x80\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x67\x80\x6f\xe0\x78\xe0\x70\x60'\
b'\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x80\x1f\xc0\x38\xe0\x70\x70'\
b'\x60\x30\x60\x30\x60\x30\x60\x30\x70\x70\x38\xe0\x1f\xc0\x0f\x80'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x67\x80\x6f\xc0\x78\xe0\x70\x70'\
b'\x60\x30\x60\x30\x60\x30\x60\x30\x70\x70\x78\xe0\x7f\xc0\x67\x80'\
b'\x60\x00\x60\x00\x60\x00\x60\x00\x00\x00\x0d\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x30\x1f\xf0\x38\xf0\x70\x70'\
b'\x60\x30\x60\x30\x60\x30\x60\x30\x70\x70\x38\xf0\x3f\xf0\x0f\x30'\
b'\x00\x30\x00\x30\x00\x30\x00\x30\x00\x00\x08\x00\x00\x00\x00\x00'\
b'\x00\x00\x66\x6e\x78\x60\x60\x60\x60\x60\x60\x60\x60\x60\x00\x00'\
b'\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x1f\x80\x3f\xe0\x70\x60\x60\x00\x70\x00\x3f\x00\x0f\xc0\x00'\
b'\xe0\x60\x60\x70\xe0\x3f\xc0\x1f\x80\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x06\x00\x00\x00\x00\x00\x30\x30\x78\x78\x30\x30\x30'\
b'\x30\x30\x30\x30\x30\x38\x38\x00\x00\x00\x00\x00\x0c\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x60\x60\x60\x60\x60\x60'\
b'\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\xe0\x71\xe0\x7f\x60'\
b'\x1e\x60\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x60\x60\xe0\x60\xc0'\
b'\x70\xc0\x31\x80\x31\x80\x19\x80\x1b\x00\x1b\x00\x0f\x00\x0e\x00'\
b'\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\xc1\xc3\x00\x61\xc3\x00\x63\xc3\x00\x63\xc6\x00\x33\x66\x00\x33'\
b'\x66\x00\x32\x66\x00\x36\x6c\x00\x1e\x3c\x00\x1e\x3c\x00\x1c\x38'\
b'\x00\x0c\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x60\xc0\x71\xc0\x31\x80\x1b\x00\x0e\x00\x0e\x00\x0e\x00\x1b'\
b'\x00\x1b\x00\x31\x80\x60\xc0\x60\xc0\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\xc0\xe0\x60\xc0\x60\xc0\x61\x80\x31\x80\x31\x80\x33\x00\x1b'\
b'\x00\x1b\x00\x1e\x00\x0e\x00\x0e\x00\x0c\x00\x0c\x00\x18\x00\x78'\
b'\x00\x70\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x7f\xc0\x7f\xc0\x01\xc0\x01\x80\x03\x00\x06\x00\x0c\x00\x18'\
b'\x00\x30\x00\x60\x00\x7f\xc0\x7f\xc0\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x08\x00\x00\x0c\x1c\x18\x18\x18\x18\x18\x18\x18\x18'\
b'\x70\x70\x18\x18\x18\x18\x18\x18\x18\x18\x1c\x0c\x06\x00\x00\x30'\
b'\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30'\
b'\x30\x30\x30\x30\x30\x08\x00\x00\x30\x38\x18\x18\x18\x18\x18\x18'\
b'\x18\x18\x0e\x0e\x18\x18\x18\x18\x18\x18\x18\x18\x38\x30\x0c\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x00'\
b'\x7c\x00\x4e\x40\x07\xc0\x01\x80\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x00'\
b'\x00\x00\x0f\xc0\x1f\xe0\x38\x60\x60\x30\x60\x30\x60\x00\x70\x00'\
b'\x38\x00\xff\x00\xff\x00\x1c\x00\x0c\x00\x0c\x00\x18\x00\x2f\x10'\
b'\x7f\xf0\x61\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x3f\xf8\x3f\xf8\x00\x18\x00\x18\x00\x18\x00\x18\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00'\
b'\x00\x00\x00\x00\x07\x00\x0f\x80\x18\xc0\x10\x40\x18\xc0\x0f\x80'\
b'\x07\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\x11\x00'\
b'\x00\x00\x00\x03\xe0\x00\x0f\xf8\x00\x1c\x1c\x00\x38\x0e\x00\x30'\
b'\x06\x00\x60\x03\x00\x60\x03\x00\x60\x03\x00\x60\x03\x00\x60\x03'\
b'\x00\x60\x03\x00\x30\x06\x00\x30\x0e\x00\x18\x0c\x00\x0c\x38\x00'\
b'\x7e\x3f\x00\x7e\x3f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x0f\x60\x3f\xe0\x30\xe0\x60\xe0\x60\x60\x60\x60\x60'\
b'\x60\x60\x60\x60\xe0\x30\xe0\x3f\xf0\x0f\x70\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x0d\x00\x00\x00\x1f\x80\x3f\xc0\x70\xe0\x60'\
b'\x60\x60\x60\x60\xc0\x63\x80\x63\xc0\x60\x60\x60\x30\x60\x30\x60'\
b'\x30\x60\x30\x60\x30\x70\x60\x7f\xe0\x6f\x80\x60\x00\x60\x00\x60'\
b'\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\xe0\x60\xe0\x60\x70\xc0\x30\xc0\x30\xc0\x39\x80\x19'\
b'\x80\x1b\x80\x0f\x00\x0f\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06'\
b'\x00\x06\x00\x06\x00\x0d\x00\x00\x00\x3f\xe0\x3f\xe0\x18\x00\x0e'\
b'\x00\x03\x00\x0f\x80\x3f\xc0\x38\xe0\x70\x70\x60\x30\x60\x30\x60'\
b'\x30\x60\x30\x70\x70\x30\xe0\x3f\xc0\x0f\x80\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x0f\x00\x1f\x80\x30\xc0\x30'\
b'\xc0\x60\x60\x60\x60\x60\x60\x7f\xe0\x7f\xe0\x60\x60\x60\x60\x60'\
b'\x60\x60\x60\x30\xc0\x30\xc0\x1f\x80\x0f\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x0d\x00\x00\x00\x70\x00\x78\x00\x0c\x00\x0c'\
b'\x00\x06\x00\x06\x00\x0e\x00\x0f\x00\x1b\x00\x19\x00\x19\x80\x31'\
b'\x80\x30\xc0\x30\xc0\x60\xc0\x60\x70\xe0\x70\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60\x60'\
b'\x60\x60\x60\x60\x60\x70\xe0\x7f\xe0\x6f\x60\x60\x00\x60\x00\x60'\
b'\x00\x60\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x7f\xf8\x7f\xf8\x30\x30\x30\x30\x30\x30\x30\x30\x30'\
b'\x30\x30\x30\x30\x30\x30\x30\x30\x3c\x30\x1c\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x0c\x00\x30\x0e\x00\x30'\
b'\x06\x00\x61\x87\x00\x61\x83\x00\x61\x83\x00\x61\x83\x00\x61\x83'\
b'\x00\x61\x83\x00\x33\xc6\x00\x3f\x7e\x00\x1e\x38\x00\x00\x00\x00'\
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00'\
b'\x00\x00\x01\x80\x01\x80\x01\x80\x01\x80\x0f\xf0\x1f\xf8\x39\x9c'\
b'\x71\x8e\x61\x86\x61\x86\x61\x86\x61\x86\x71\x8e\x39\x9c\x1f\xf8'\
b'\x0f\xf0\x01\x80\x01\x80\x01\x80\x01\x80\x00\x00'
_sparse =\
b'\x20\x00\x30\x00\x21\x00\x49\x00\x22\x00\x62\x00\x23\x00\x7b\x00'\
b'\x24\x00\xab\x00\x25\x00\xdb\x00\x26\x00\x22\x01\x27\x00\x52\x01'\
b'\x28\x00\x6b\x01\x29\x00\x84\x01\x2a\x00\x9d\x01\x2b\x00\xcd\x01'\
b'\x2c\x00\xfd\x01\x2d\x00\x16\x02\x2e\x00\x2f\x02\x2f\x00\x48\x02'\
b'\x30\x00\x61\x02\x31\x00\x91\x02\x32\x00\xc1\x02\x33\x00\xf1\x02'\
b'\x34\x00\x21\x03\x35\x00\x51\x03\x36\x00\x81\x03\x37\x00\xb1\x03'\
b'\x38\x00\xe1\x03\x39\x00\x11\x04\x3a\x00\x41\x04\x3b\x00\x5a\x04'\
b'\x3c\x00\x73\x04\x3d\x00\xa3\x04\x3e\x00\xd3\x04\x3f\x00\x03\x05'\
b'\x40\x00\x33\x05\x41\x00\x7a\x05\x42\x00\xaa\x05\x43\x00\xda\x05'\
b'\x44\x00\x0a\x06\x45\x00\x3a\x06\x46\x00\x6a\x06\x47\x00\x9a\x06'\
b'\x48\x00\xe1\x06\x49\x00\x28\x07\x4a\x00\x41\x07\x4b\x00\x71\x07'\
b'\x4c\x00\xa1\x07\x4d\x00\xd1\x07\x4e\x00\x18\x08\x4f\x00\x5f\x08'\
b'\x50\x00\xa6\x08\x51\x00\xd6\x08\x52\x00\x1d\x09\x53\x00\x4d\x09'\
b'\x54\x00\x7d\x09\x55\x00\xad\x09\x56\x00\xf4\x09\x57\x00\x24\x0a'\
b'\x58\x00\x6b\x0a\x59\x00\x9b\x0a\x5a\x00\xcb\x0a\x5b\x00\xfb\x0a'\
b'\x5c\x00\x14\x0b\x5d\x00\x2d\x0b\x5e\x00\x46\x0b\x5f\x00\x76\x0b'\
b'\x60\x00\xa6\x0b\x61\x00\xbf\x0b\x62\x00\xef\x0b\x63\x00\x1f\x0c'\
b'\x64\x00\x4f\x0c\x65\x00\x7f\x0c\x66\x00\xaf\x0c\x67\x00\xc8\x0c'\
b'\x68\x00\xf8\x0c\x69\x00\x28\x0d\x6a\x00\x41\x0d\x6b\x00\x5a\x0d'\
b'\x6c\x00\x8a\x0d\x6d\x00\xa3\x0d\x6e\x00\xea\x0d\x6f\x00\x1a\x0e'\
b'\x70\x00\x4a\x0e\x71\x00\x7a\x0e\x72\x00\xaa\x0e\x73\x00\xc3\x0e'\
b'\x74\x00\xf3\x0e\x75\x00\x0c\x0f\x76\x00\x3c\x0f\x77\x00\x6c\x0f'\
b'\x78\x00\xb3\x0f\x79\x00\xe3\x0f\x7a\x00\x13\x10\x7b\x00\x43\x10'\
b'\x7c\x00\x5c\x10\x7d\x00\x75\x10\x7e\x00\x8e\x10\xa3\x00\xbe\x10'\
b'\xac\x00\xee\x10\xb0\x00\x1e\x11\xa9\x03\x4e\x11\xb1\x03\x95\x11'\
b'\xb2\x03\xc5\x11\xb3\x03\xf5\x11\xb4\x03\x25\x12\xb8\x03\x55\x12'\
b'\xbb\x03\x85\x12\xbc\x03\xb5\x12\xc0\x03\xe5\x12\xc9\x03\x15\x13'\
b'\xd5\x03\x5c\x13'
_mvfont = memoryview(_font)
_mvsp = memoryview(_sparse)
ifb = lambda l : l[0] | (l[1] << 8)
def bs(lst, val):
while True:
m = (len(lst) & ~ 7) >> 1
v = ifb(lst[m:])
if v == val:
return ifb(lst[m + 2:])
if not m:
return 0
lst = lst[m:] if v < val else lst[:m]
def get_ch(ch):
doff = bs(_mvsp, ord(ch))
width = ifb(_mvfont[doff : ])
next_offs = doff + 2 + ((width - 1)//8 + 1) * 23
return _mvfont[doff + 2:next_offs], 23, width

176
gui/fonts/font6.py 100644
Wyświetl plik

@ -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

Wyświetl plik

@ -0,0 +1,288 @@
# Code generated by font-to-py.py.
# Font: FreeSans.ttf
version = '0.25'
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\x2a\x00\x40\x00\x40\x00\x56\x00\x56\x00\x6c\x00'\
b'\x6c\x00\x96\x00\x96\x00\xc0\x00\xc0\x00\xfe\x00\xfe\x00\x28\x01'\
b'\x28\x01\x3e\x01\x3e\x01\x54\x01\x54\x01\x6a\x01\x6a\x01\x80\x01'\
b'\x80\x01\xaa\x01\xaa\x01\xc0\x01\xc0\x01\xd6\x01\xd6\x01\xec\x01'\
b'\xec\x01\x02\x02\x02\x02\x2c\x02\x2c\x02\x56\x02\x56\x02\x80\x02'\
b'\x80\x02\xaa\x02\xaa\x02\xd4\x02\xd4\x02\xfe\x02\xfe\x02\x28\x03'\
b'\x28\x03\x52\x03\x52\x03\x7c\x03\x7c\x03\xa6\x03\xa6\x03\xbc\x03'\
b'\xbc\x03\xd2\x03\xd2\x03\xfc\x03\xfc\x03\x26\x04\x26\x04\x50\x04'\
b'\x50\x04\x7a\x04\x7a\x04\xb8\x04\xb8\x04\xe2\x04\xe2\x04\x0c\x05'\
b'\x0c\x05\x36\x05\x36\x05\x60\x05\x60\x05\x8a\x05\x8a\x05\xb4\x05'\
b'\xb4\x05\xde\x05\xde\x05\x08\x06\x08\x06\x1e\x06\x1e\x06\x48\x06'\
b'\x48\x06\x72\x06\x72\x06\x9c\x06\x9c\x06\xda\x06\xda\x06\x04\x07'\
b'\x04\x07\x2e\x07\x2e\x07\x58\x07\x58\x07\x82\x07\x82\x07\xac\x07'\
b'\xac\x07\xd6\x07\xd6\x07\x00\x08\x00\x08\x2a\x08\x2a\x08\x54\x08'\
b'\x54\x08\x92\x08\x92\x08\xbc\x08\xbc\x08\xe6\x08\xe6\x08\x10\x09'\
b'\x10\x09\x26\x09\x26\x09\x3c\x09\x3c\x09\x52\x09\x52\x09\x7c\x09'\
b'\x7c\x09\xa6\x09\xa6\x09\xbc\x09\xbc\x09\xe6\x09\xe6\x09\x10\x0a'\
b'\x10\x0a\x3a\x0a\x3a\x0a\x64\x0a\x64\x0a\x8e\x0a\x8e\x0a\xa4\x0a'\
b'\xa4\x0a\xce\x0a\xce\x0a\xf8\x0a\xf8\x0a\x0e\x0b\x0e\x0b\x24\x0b'\
b'\x24\x0b\x4e\x0b\x4e\x0b\x64\x0b\x64\x0b\x8e\x0b\x8e\x0b\xb8\x0b'\
b'\xb8\x0b\xe2\x0b\xe2\x0b\x0c\x0c\x0c\x0c\x36\x0c\x36\x0c\x4c\x0c'\
b'\x4c\x0c\x76\x0c\x76\x0c\x8c\x0c\x8c\x0c\xb6\x0c\xb6\x0c\xe0\x0c'\
b'\xe0\x0c\x0a\x0d\x0a\x0d\x34\x0d\x34\x0d\x5e\x0d\x5e\x0d\x88\x0d'\
b'\x88\x0d\x9e\x0d\x9e\x0d\xb4\x0d\xb4\x0d\xca\x0d\xca\x0d\xf4\x0d'\
_mvfont = memoryview(_font)
def get_ch(ch):
ordch = ord(ch)
ordch = ordch + 1 if ordch >= 32 and ordch <= 126 else 32
idx_offs = 4 * (ordch - 32)
offset = int.from_bytes(_index[idx_offs : idx_offs + 2], 'little')
next_offs = int.from_bytes(_index[idx_offs + 2 : idx_offs + 4], 'little')
width = int.from_bytes(_font[offset:offset + 2], 'little')
return _mvfont[offset + 2:next_offs], 20, width

Wyświetl plik

@ -0,0 +1,31 @@
# __init__.py Common functions for uasyncio primitives
# Copyright (c) 2018-2020 Peter Hinch
# Released under the MIT License (MIT) - see LICENSE file
try:
import uasyncio as asyncio
except ImportError:
import asyncio
async def _g():
pass
type_coro = type(_g())
# If a callback is passed, run it and return.
# If a coro is passed initiate it and return.
# coros are passed by name i.e. not using function call syntax.
def launch(func, tup_args):
res = func(*tup_args)
if isinstance(res, type_coro):
res = asyncio.create_task(res)
return res
def set_global_exception():
def _handle_exception(loop, context):
import sys
sys.print_exception(context["exception"])
sys.exit()
loop = asyncio.get_event_loop()
loop.set_exception_handler(_handle_exception)

Wyświetl plik

@ -0,0 +1,71 @@
# delay_ms.py Now uses ThreadSafeFlag and has extra .wait() API
# Usage:
# from primitives.delay_ms import Delay_ms
# Copyright (c) 2018-2021 Peter Hinch
# Released under the MIT License (MIT) - see LICENSE file
import uasyncio as asyncio
from utime import ticks_add, ticks_diff, ticks_ms
from . import launch
class Delay_ms:
class DummyTimer: # Stand-in for the timer class. Can be cancelled.
def cancel(self):
pass
_fake = DummyTimer()
def __init__(self, func=None, args=(), duration=1000):
self._func = func
self._args = args
self._durn = duration # Default duration
self._retn = None # Return value of launched callable
self._tend = None # Stop time (absolute ms).
self._busy = False
self._trig = asyncio.ThreadSafeFlag()
self._tout = asyncio.Event() # Timeout event
self.wait = self._tout.wait # Allow: await wait_ms.wait()
self._ttask = self._fake # Timer task
asyncio.create_task(self._run())
async def _run(self):
while True:
await self._trig.wait() # Await a trigger
self._ttask.cancel() # Cancel and replace
await asyncio.sleep_ms(0)
dt = max(ticks_diff(self._tend, ticks_ms()), 0) # Beware already elapsed.
self._ttask = asyncio.create_task(self._timer(dt))
async def _timer(self, dt):
await asyncio.sleep_ms(dt)
self._tout.set() # Only gets here if not cancelled.
self._tout.clear()
self._busy = False
if self._func is not None:
self._retn = launch(self._func, self._args)
# API
# trigger may be called from hard ISR.
def trigger(self, duration=0): # Update absolute end time, 0-> ctor default
self._tend = ticks_add(ticks_ms(), duration if duration > 0 else self._durn)
self._retn = None # Default in case cancelled.
self._busy = True
self._trig.set()
def stop(self):
self._ttask.cancel()
self._ttask = self._fake
self._busy = False
def __call__(self): # Current running status
return self._busy
running = __call__
def rvalue(self):
return self._retn
def callback(self, func=None, args=()):
self._func = func
self._args = args

Wyświetl plik

@ -0,0 +1,107 @@
# pushbutton.py
# Copyright (c) 2018-2021 Peter Hinch
# Released under the MIT License (MIT) - see LICENSE file
import uasyncio as asyncio
import utime as time
from . import launch
from gui.primitives.delay_ms import Delay_ms
# An alternative Pushbutton solution with lower RAM use is available here
# https://github.com/kevinkk525/pysmartnode/blob/dev/pysmartnode/utils/abutton.py
class Pushbutton:
debounce_ms = 50
long_press_ms = 1000
double_click_ms = 400
def __init__(self, pin, suppress=False, sense=None):
self.pin = pin # Initialise for input
self._supp = suppress
self._dblpend = False # Doubleclick waiting for 2nd click
self._dblran = False # Doubleclick executed user function
self._tf = False
self._ff = False
self._df = False
self._ld = False # Delay_ms instance for long press
self._dd = False # Ditto for doubleclick
self.sense = pin.value() if sense is None else sense # Convert from electrical to logical value
self.state = self.rawstate() # Initial state
asyncio.create_task(self.buttoncheck()) # Thread runs forever
def press_func(self, func=False, args=()):
self._tf = func
self._ta = args
def release_func(self, func=False, args=()):
self._ff = func
self._fa = args
def double_func(self, func=False, args=()):
self._df = func
self._da = args
if func: # If double timer already in place, leave it
if not self._dd:
self._dd = Delay_ms(self._ddto)
else:
self._dd = False # Clearing down double func
def long_func(self, func=False, args=()):
if func:
if self._ld:
self._ld.callback(func, args)
else:
self._ld = Delay_ms(func, args)
else:
self._ld = False
# Current non-debounced logical button state: True == pressed
def rawstate(self):
return bool(self.pin.value() ^ self.sense)
# Current debounced state of button (True == pressed)
def __call__(self):
return self.state
def _ddto(self): # Doubleclick timeout: no doubleclick occurred
self._dblpend = False
if self._supp and not self.state:
if not self._ld or (self._ld and not self._ld()):
launch(self._ff, self._fa)
async def buttoncheck(self):
while True:
state = self.rawstate()
# State has changed: act on it now.
if state != self.state:
self.state = state
if state: # Button pressed: launch pressed func
if self._tf:
launch(self._tf, self._ta)
if self._ld: # There's a long func: start long press delay
self._ld.trigger(Pushbutton.long_press_ms)
if self._df:
if self._dd(): # Second click: timer running
self._dd.stop()
self._dblpend = False
self._dblran = True # Prevent suppressed launch on release
launch(self._df, self._da)
else:
# First click: start doubleclick timer
self._dd.trigger(Pushbutton.double_click_ms)
self._dblpend = True # Prevent suppressed launch on release
else: # Button release. Is there a release func?
if self._ff:
if self._supp:
d = self._ld
# If long delay exists, is running and doubleclick status is OK
if not self._dblpend and not self._dblran:
if (d and d()) or not d:
launch(self._ff, self._fa)
else:
launch(self._ff, self._fa)
if self._ld:
self._ld.stop() # Avoid interpreting a second click as a long push
self._dblran = False
# Ignore state changes until switch has settled
await asyncio.sleep_ms(Pushbutton.debounce_ms)

Wyświetl plik

@ -0,0 +1,42 @@
# switch.py
# Copyright (c) 2018-2020 Peter Hinch
# Released under the MIT License (MIT) - see LICENSE file
import uasyncio as asyncio
import utime as time
from . import launch
class Switch:
debounce_ms = 50
def __init__(self, pin):
self.pin = pin # Should be initialised for input with pullup
self._open_func = False
self._close_func = False
self.switchstate = self.pin.value() # Get initial state
asyncio.create_task(self.switchcheck()) # Thread runs forever
def open_func(self, func, args=()):
self._open_func = func
self._open_args = args
def close_func(self, func, args=()):
self._close_func = func
self._close_args = args
# Return current state of switch (0 = pressed)
def __call__(self):
return self.switchstate
async def switchcheck(self):
while True:
state = self.pin.value()
if state != self.switchstate:
# State has changed: act on it now.
self.switchstate = state
if state == 0 and self._close_func:
launch(self._close_func, self._close_args)
elif state == 1 and self._open_func:
launch(self._open_func, self._open_args)
# Ignore further state changes until switch has settled
await asyncio.sleep_ms(Switch.debounce_ms)

Wyświetl plik

Wyświetl plik

@ -0,0 +1,218 @@
# buttons.py Extension to ugui providing pushbutton classes
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
import uasyncio as asyncio
from gui.core.ugui import Screen, Widget, display
from gui.primitives.delay_ms import Delay_ms
from gui.core.colors import *
dolittle = lambda *_ : None
class Button(Widget):
lit_time = 1000
long_press_time = 1000
def __init__(self, writer, row, col, *, shape=RECTANGLE, height=20, width=50,
fgcolor=None, bgcolor=None, bdcolor=False, textcolor=None, litcolor=None, text='',
callback=dolittle, args=[], onrelease=False, lp_callback=dolittle, lp_args=[]):
sl = writer.stringlen(text)
if shape == CIRCLE: # Only height need be specified
width = max(sl, height)
height = width
else:
width = max(sl, width)
super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor, False, True)
self.shape = shape
self.radius = height // 2
self.fill = bgcolor is not None # Draw background if color specified
self.litcolor = litcolor
self.textcolor = self.fgcolor if textcolor is None else textcolor
self.orig_fgcolor = self.fgcolor
self.orig_bgcolor = self.bgcolor
self.text = text
self.callback = callback
self.callback_args = args
self.onrelease = onrelease
self.lp_callback = lp_callback
self.lp_args = lp_args
if self.litcolor is not None:
self.delay = Delay_ms(self.shownormal)
self.litcolor = litcolor if self.fgcolor is not None else None
def show(self):
if self.screen is not Screen.current_screen:
return
x = self.col
y = self.row
w = self.width
h = self.height
if not self.visible: # erase the button
display.usegrey(False)
display.fill_rect(x, y, w, h, BGCOLOR)
return
super().show() # Blank rectangle containing button
if self.shape == CIRCLE: # Button coords are of top left corner of bounding box
x += self.radius
y += self.radius
if self.fill:
display.fillcircle(x, y, self.radius, self.bgcolor)
display.circle(x, y, self.radius, self.fgcolor)
if len(self.text):
display.print_centred(self.writer, x, y, self.text, self.textcolor, self.bgcolor)
else:
xc = x + w // 2
yc = y + h // 2
if self.shape == RECTANGLE: # rectangle
if self.fill:
display.fill_rect(x, y, w, h, self.bgcolor)
display.rect(x, y, w, h, self.fgcolor)
if len(self.text):
display.print_centred(self.writer, xc, yc, self.text, self.textcolor, self.bgcolor)
elif self.shape == CLIPPED_RECT: # clipped rectangle
if self.fill:
display.fill_clip_rect(x, y, w, h, self.bgcolor)
display.clip_rect(x, y, w, h, self.fgcolor)
if len(self.text):
display.print_centred(self.writer, xc, yc, self.text, self.textcolor, self.bgcolor)
async def shownormal(self):
# Handle case where screen changed while timer was active: delay repaint
# until screen is current. Pathological app behaviour where another
# control caused a screen change while timer running.
while self.screen is not Screen.current_screen:
await asyncio.sleep_ms(500)
self.bgcolor = self.orig_bgcolor
self.draw = True # Redisplay
def do_sel(self): # Select was pushed
if not self.onrelease:
self.callback(self, *self.callback_args) # CB takes self as 1st arg.
if self.litcolor is not None and self.has_focus(): # CB may have changed focus
self.bgcolor = self.litcolor
self.draw = True # Redisplay
self.delay.trigger(Button.lit_time)
def unsel(self): # Select was released
if self.onrelease:
self.callback(self, *self.callback_args) # Callback not a bound method so pass self
def longsel(self): # Select was long pressed
self.lp_callback(self, *self.lp_args)
# Preferred way to close a screen or dialog. Produces an X button at the top RHS.
# Note that if the bottom screen is closed, the application terminates.
class CloseButton(Button):
def __init__(self, writer, width=0, callback=dolittle, args=(), bgcolor=RED):
scr = Screen.current_screen
wd = max(writer.stringlen('X') + 6, width)
self.user_cb = callback
self.user_args = args
super().__init__(writer, *scr.locn(4, scr.width - wd - 4),
width = wd, height = wd, bgcolor = bgcolor,
callback = self.cb, text = 'X')
def cb(self, _):
self.user_cb(self, *self.user_args)
Screen.back()
# Group of buttons, typically at same location, where pressing one shows
# the next e.g. start/stop toggle or sequential select from short list
class ButtonList:
def __init__(self, callback=dolittle):
self.user_callback = callback
self.lstbuttons = []
self.current = None # No current button
self._greyed_out = False
def add_button(self, *args, **kwargs):
button = Button(*args, **kwargs)
self.lstbuttons.append(button)
active = self.current is None # 1st button added is active
button.visible = active
button.callback = self._callback
if active:
self.current = button
return button
def value(self, button=None, new_cb=False):
if button is not None and button is not self.current:
old = self.current
new = button
self.current = new
old.visible = False
new.visible = True
new.draw = True # Redisplay without changing currency
# Args for user callback: button instance followed by any specified.
# Normal behaviour is to run cb of old button: this mimics a button press.
# Optionally programmatic value changes can run the cb of new button.
if new_cb: # Forced value change, callback is that of new button
self.user_callback(new, *new.callback_args)
else: # A button was pressed
# Callback context is button just pressed, not the new one
self.user_callback(old, *old.callback_args)
return self.current
def greyed_out(self, val=None):
if val is not None and self._greyed_out != val:
self._greyed_out = val
for button in self.lstbuttons:
button.greyed_out(val)
self.current.draw = True
return self._greyed_out
def _callback(self, button, *_):
old = button
old_index = self.lstbuttons.index(button)
new = self.lstbuttons[(old_index + 1) % len(self.lstbuttons)]
self.current = new
old.visible = False
new.visible = True
Screen.select(new) # Move currency and redisplay
# Callback context is button just pressed, not the new one
self.user_callback(old, *old.callback_args)
# Group of buttons at different locations, where pressing one shows
# only current button highlighted and oes callback from current one
class RadioButtons:
def __init__(self, highlight, callback=dolittle, selected=0):
self.user_callback = callback
self.lstbuttons = []
self.current = None # No current button
self.highlight = highlight
self.selected = selected
self._greyed_out = False
def add_button(self, *args, **kwargs):
button = Button(*args, **kwargs)
self.lstbuttons.append(button)
button.callback = self._callback
active = len(self.lstbuttons) == self.selected + 1
button.bgcolor = self.highlight if active else button.orig_bgcolor
if active:
self.current = button
return button
def value(self, button=None):
if button is not None and button is not self.current:
self._callback(button, *button.callback_args)
return self.current
def greyed_out(self, val=None):
if val is not None and self._greyed_out != val:
self._greyed_out = val
for button in self.lstbuttons:
button.greyed_out(val)
return self._greyed_out
def _callback(self, button, *args):
for but in self.lstbuttons:
if but is button:
but.bgcolor = self.highlight
self.current = button
else:
but.bgcolor = but.orig_bgcolor
but.draw = True
self.user_callback(button, *args) # user gets button with args they specified

Wyświetl plik

@ -0,0 +1,36 @@
# checkbox.py Extension to ugui providing the Checkbox class
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2019-2021 Peter Hinch
from gui.core.ugui import Widget, display
dolittle = lambda *_ : None
class Checkbox(Widget):
def __init__(self, writer, row, col, *, height=30, fillcolor=None,
fgcolor=None, bgcolor=None, bdcolor=False,
callback=dolittle, args=[], value=False, active=True):
super().__init__(writer, row, col, height, height, fgcolor, bgcolor, bdcolor, value, active)
super()._set_callbacks(callback, args)
self.fillcolor = fillcolor
def show(self):
if super().show():
x = self.col
y = self.row
ht = self.height
x1 = x + ht - 1
y1 = y + ht - 1
if self._value:
if self.fillcolor is not None:
display.fill_rect(x, y, ht, ht, self.fillcolor)
else:
display.fill_rect(x, y, ht, ht, self.bgcolor)
display.rect(x, y, ht, ht, self.fgcolor)
if self.fillcolor is None and self._value:
display.line(x, y, x1, y1, self.fgcolor)
display.line(x, y1, x1, y, self.fgcolor)
def do_sel(self): # Select was pushed
self.value(not self._value) # Upddate and refresh

104
gui/widgets/dial.py 100644
Wyświetl plik

@ -0,0 +1,104 @@
# dial.py Dial and Pointer classes for micro-gui
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2020 Peter Hinch
import cmath
from gui.core.ugui import Widget, display
from gui.widgets.label import Label
# Line defined by polar coords; origin and line are complex
def polar(display, origin, line, color):
xs, ys = origin.real, origin.imag
theta = cmath.polar(line)[1]
display.line(round(xs), round(ys), round(xs + line.real), round(ys - line.imag), color)
def conj(v): # complex conjugate
return v.real - v.imag * 1j
# Draw an arrow; origin and vec are complex, scalar lc defines length of chevron.
# cw and ccw are unit vectors of +-3pi/4 radians for chevrons (precompiled)
def arrow(display, origin, vec, lc, color, ccw=cmath.exp(3j * cmath.pi/4), cw=cmath.exp(-3j * cmath.pi/4)):
length, theta = cmath.polar(vec)
uv = cmath.rect(1, theta) # Unit rotation vector
start = -vec
if length > 3 * lc: # If line is long
ds = cmath.rect(lc, theta)
start += ds # shorten to allow for length of tail chevrons
chev = lc + 0j
polar(display, origin, vec, color) # Origin to tip
polar(display, origin, start, color) # Origin to tail
polar(display, origin + conj(vec), chev*ccw*uv, color) # Tip chevron
polar(display, origin + conj(vec), chev*cw*uv, color)
if length > lc: # Confusing appearance of very short vectors with tail chevron
polar(display, origin + conj(start), chev*ccw*uv, color) # Tail chevron
polar(display, origin + conj(start), chev*cw*uv, color)
class Pointer():
def __init__(self, dial):
self.dial = dial
dial.vectors.add(self)
self.val = 0 + 0j
self.color = None
def value(self, v=None, color=None):
self.color = color
if v is not None:
if isinstance(v, complex):
l = cmath.polar(v)[0]
if l > 1:
self.val = v/l
else:
self.val = v
else:
raise ValueError('Pointer value must be complex.')
self.dial.draw = True
return self.val
class Dial(Widget):
CLOCK = 0
COMPASS = 1
def __init__(self, writer, row, col, *, height=50,
fgcolor=None, bgcolor=None, bdcolor=False, ticks=4,
label=None, style=0, pip=None):
super().__init__(writer, row, col, height, height, fgcolor, bgcolor, bdcolor)
self.style = style
self.pip = self.fgcolor if pip is None else pip
if label is not None:
self.label = Label(writer, row + height + 3, col, label)
radius = int(height / 2)
self.radius = radius
self.ticks = ticks
self.xorigin = col + radius
self.yorigin = row + radius
self.vectors = set()
def show(self):
if super().show():
# cache bound variables
ticks = self.ticks
radius = self.radius
xo = self.xorigin
yo = self.yorigin
# vectors (complex)
vor = xo + 1j * yo
vtstart = 0.9 * radius + 0j # start of tick
vtick = 0.1 * radius + 0j # tick
vrot = cmath.exp(2j * cmath.pi/ticks) # unit rotation
for _ in range(ticks):
polar(display, vor + conj(vtstart), vtick, self.fgcolor)
vtick *= vrot
vtstart *= vrot
display.circle(xo, yo, radius, self.fgcolor)
vshort = 1000 # Length of shortest vector
for v in self.vectors:
color = self.fgcolor if v.color is None else v.color
val = v.value() * radius # val is complex
vshort = min(vshort, cmath.polar(val)[0])
if self.style == Dial.CLOCK:
polar(display, vor, val, color)
else:
arrow(display, vor, val, 5, color)
if isinstance(self.pip, int) and vshort > 5:
display.fillcircle(xo, yo, 2, self.pip)

Wyświetl plik

@ -0,0 +1,52 @@
# dialog.py Extension to ugui providing the DialogBox class
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
from gui.core.ugui import display, Window, Screen
from gui.core.colors import *
from gui.widgets.label import Label
from gui.widgets.buttons import Button, CloseButton
dolittle = lambda *_ : None
class DialogBox(Window):
def __init__(self, writer, row=20, col=20, *, elements, label=None,
bgcolor=DARKGREEN, buttonwidth=25, closebutton=True, callback=dolittle, args=[]):
def back(button, text): # Callback for normal buttons
Window.value(text)
callback(Window, *args)
Screen.back()
def backbutton(button, text):
Window.value(text)
callback(Window, *args)
height = 80
spacing = 10
buttonwidth = max(max(writer.stringlen(e[0]) for e in elements) + 4, buttonwidth)
buttonheight = max(writer.height, 15)
nelements = len(elements)
width = spacing + (buttonwidth + spacing) * nelements
if label is not None:
width = max(width, writer.stringlen(label) + 2 * spacing)
super().__init__(row, col, height, width, bgcolor = bgcolor)
col = spacing # Coordinates relative to window
row = self.height - buttonheight - 10
gap = 0
if nelements > 1:
gap = ((width - 2 * spacing) - nelements * buttonwidth) // (nelements - 1)
if label is not None:
r, c = self.locn(10, col)
Label(writer, r, c, label, bgcolor = bgcolor)
for text, color in elements:
Button(writer, *self.locn(row, col), height = buttonheight, width = buttonwidth,
textcolor = BLACK, bgcolor = color,
text = text, shape = RECTANGLE,
callback = back, args = (text,))
col += buttonwidth + gap
if closebutton:
CloseButton(writer, callback = backbutton, args = ('Close',))

Wyświetl plik

@ -0,0 +1,92 @@
# dropdown.py Extension to ugui providing the Dropdown class
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
from gui.core.ugui import Widget, display, Window, Screen
from gui.core.colors import *
from gui.widgets.listbox import Listbox
dolittle = lambda *_ : None
# Next and Prev close the listbox without updating the Dropdown. This is
# handled by Screen .move bound method
class _ListDialog(Window):
def __init__(self, writer, row, col, dropdown, textwidth):
dd = dropdown
elements = dd.elements
# Need to determine Window dimensions from size of Listbox, which
# depends on number and length of elements.
entry_height, lb_height, textwidth = Listbox.dimensions(writer, elements)
lb_width = textwidth + 2
# Calculate Window dimensions
ap_height = lb_height + 6 # Allow for listbox border
ap_width = lb_width + 6
super().__init__(row, col, ap_height, ap_width)
self.listbox = Listbox(writer, row + 3, col + 3, elements = elements, width = lb_width,
fgcolor = dd.fgcolor, bgcolor = dd.bgcolor, bdcolor=False,
fontcolor = WHITE, select_color = dd.select_color,
value = dd.value(), callback = self.callback)
self.dropdown = dd
def callback(self, obj_listbox):
Screen.back()
self.dropdown.value(obj_listbox.value()) # Update it
class Dropdown(Widget):
def __init__(self, writer, row, col, *, elements, width=None, value=0,
fgcolor=None, bgcolor=None, bdcolor=False, fontcolor=None, select_color=DARKBLUE,
callback=dolittle, args=[]):
self.entry_height = writer.height + 2 # Allow a pixel above and below text
height = self.entry_height
if width is None: # Allow for square at end for arrow
self.textwidth = max(writer.stringlen(s) for s in elements)
width = self.textwidth + 2 + height
else:
self.textwidth = width
super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor, value, True)
super()._set_callbacks(callback, args)
self.select_color = select_color
self.fontcolor = self.fgcolor if fontcolor is None else fontcolor
self.elements = elements
def show(self):
if super().show():
x, y = self.col, self.row
self._draw(x, y)
if self._value is not None:
display.print_left(self.writer, x, y + 1, self.elements[self._value], self.fontcolor)
def textvalue(self, text=None): # if no arg return current text
if text is None:
return self.elements[self._value]
else: # set value by text
try:
v = self.elements.index(text)
except ValueError:
v = None
else:
if v != self._value:
self.value(v)
return v
def _draw(self, x, y):
self.draw_border()
display.vline(x + self.width - self.height, y, self.height, self.fgcolor)
xcentre = x + self.width - self.height // 2 # Centre of triangle
ycentre = y + self.height // 2
halflength = (self.height - 8) // 2
length = halflength * 2
if length > 0:
display.hline(xcentre - halflength, ycentre - halflength, length, self.fgcolor)
display.line(xcentre - halflength, ycentre - halflength, xcentre, ycentre + halflength, self.fgcolor)
display.line(xcentre + halflength, ycentre - halflength, xcentre, ycentre + halflength, self.fgcolor)
def do_sel(self): # Select was pushed
if len(self.elements) > 1:
args = (self.writer, self.row - 2, self.col - 2, self, self.textwidth)
Screen.change(_ListDialog, args = args)

Wyświetl plik

@ -0,0 +1,259 @@
# graph.py Graph plotting widgets for micro-gui
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
from hardware_setup import ssd, display # Create a display instance
from gui.core.ugui import Widget
from cmath import rect, pi
from micropython import const
from array import array
type_gen = type((lambda: (yield))())
# Line clipping outcode bits
_TOP = const(1)
_BOTTOM = const(2)
_LEFT = const(4)
_RIGHT = const(8)
# Bounding box for line clipping
_XMAX = const(1)
_XMIN = const(-1)
_YMAX = const(1)
_YMIN = const(-1)
class Curve():
@staticmethod
def _outcode(x, y):
oc = _TOP if y > 1 else 0
oc |= _BOTTOM if y < -1 else 0
oc |= _RIGHT if x > 1 else 0
oc |= _LEFT if x < -1 else 0
return oc
def __init__(self, graph, color, populate=None, origin=(0, 0), excursion=(1, 1)):
if not isinstance(self, PolarCurve): # Check not done in subclass
if isinstance(graph, PolarGraph) or not isinstance(graph, CartesianGraph):
raise ValueError('Curve must use a CartesianGraph instance.')
self.graph = graph
self.origin = origin
self.excursion = excursion
self.color = color if color is not None else graph.fgcolor
self.lastpoint = None
self.newpoint = None
if populate is not None and self._valid(populate):
for x, y in populate:
self.point(x, y)
def _valid(self, populate):
if not isinstance(populate, type_gen):
raise ValueError('populate must be a generator.')
return True
def point(self, x=None, y=None):
if x is None or y is None:
self.newpoint = None
self.lastpoint = None
return
self.newpoint = self._scale(x, y) # In-range points scaled to +-1 bounding box
if self.lastpoint is None: # Nothing to plot. Save for next line.
self.lastpoint = self.newpoint
return
res = self._clip(*(self.lastpoint + self.newpoint)) # Clip to +-1 box
if res is not None: # Ignore lines which don't intersect
self.graph.line(res[0:2], res[2:], self.color)
self.lastpoint = self.newpoint # Scaled but not clipped
# Cohen–Sutherland line clipping algorithm
# If self.newpoint and self.lastpoint are valid clip them so that both lie
# in +-1 range. If both are outside the box return None.
def _clip(self, x0, y0, x1, y1):
oc1 = self._outcode(x0, y0)
oc2 = self._outcode(x1, y1)
while True:
if not oc1 | oc2: # OK to plot
return x0, y0, x1, y1
if oc1 & oc2: # Nothing to do
return
oc = oc1 if oc1 else oc2
if oc & _TOP:
x = x0 + (_YMAX - y0)*(x1 - x0)/(y1 - y0)
y = _YMAX
elif oc & _BOTTOM:
x = x0 + (_YMIN - y0)*(x1 - x0)/(y1 - y0)
y = _YMIN
elif oc & _RIGHT:
y = y0 + (_XMAX - x0)*(y1 - y0)/(x1 - x0)
x = _XMAX
elif oc & _LEFT:
y = y0 + (_XMIN - x0)*(y1 - y0)/(x1 - x0)
x = _XMIN
if oc is oc1:
x0, y0 = x, y
oc1 = self._outcode(x0, y0)
else:
x1, y1 = x, y
oc2 = self._outcode(x1, y1)
def _scale(self, x, y): # Scale to +-1.0
x0, y0 = self.origin
xr, yr = self.excursion
xs = (x - x0) / xr
ys = (y - y0) / yr
return xs, ys
class PolarCurve(Curve): # Points are complex
def __init__(self, graph, color, populate=None):
if not isinstance(graph, PolarGraph):
raise ValueError('PolarCurve must use a PolarGraph instance.')
super().__init__(graph, color)
if populate is not None and self._valid(populate):
for z in populate:
self.point(z)
def point(self, z=None):
if z is None:
self.newpoint = None
self.lastpoint = None
return
self.newpoint = self._scale(z.real, z.imag) # In-range points scaled to +-1 bounding box
if self.lastpoint is None: # Nothing to plot. Save for next line.
self.lastpoint = self.newpoint
return
res = self._clip(*(self.lastpoint + self.newpoint)) # Clip to +-1 box
if res is not None: # At least part of line was in box
start = res[0] + 1j*res[1]
end = res[2] + 1j*res[3]
self.graph.cline(start, end, self.color)
self.lastpoint = self.newpoint # Scaled but not clipped
class TSequence(Curve):
def __init__(self, graph, color, size, yorigin=0, yexc=1):
super().__init__(graph, color, origin=(0, yorigin), excursion=(1, yexc))
self.data = array('f', (0 for _ in range(size)))
self.cur = 0
self.size = size
self.count = 0
def add(self, v):
p = self.cur
size = self.size
self.data[self.cur] = v
self.cur += 1
self.cur %= size
if self.count < size:
self.count += 1
x = 0
dx = 1/size
for _ in range(self.count):
self.point(x, self.data[p])
x -= dx
p -= 1
p %= size
self.point()
class Graph(Widget):
def __init__(self, writer, row, col, height, width, fgcolor, bgcolor, bdcolor, gridcolor):
super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor)
self.x0 = col
self.x1 = col + width
self.y0 = row
self.y1 = row + height
if gridcolor is None:
gridcolor = self.fgcolor
self.gridcolor = gridcolor
def clear(self):
self.draw = True # Clear working area
def show(self):
return super().show() # Draw or erase border
class CartesianGraph(Graph):
def __init__(self, writer, row, col, *, height=90, width = 120, fgcolor=None, bgcolor=None, bdcolor=None,
gridcolor=None, xdivs=10, ydivs=10, xorigin=5, yorigin=5):
super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor, gridcolor)
self.xdivs = xdivs
self.ydivs = ydivs
self.x_axis_len = max(xorigin, xdivs - xorigin) * width / xdivs # Max distance from origin in pixels
self.y_axis_len = max(yorigin, ydivs - yorigin) * height / ydivs
self.xp_origin = self.x0 + xorigin * width / xdivs # Origin in pixels
self.yp_origin = self.y0 + (ydivs - yorigin) * height / ydivs
self.xorigin = xorigin
self.yorigin = yorigin
self.draw = True
def show(self):
if super().show(): # Clear working area
x0 = self.x0
x1 = self.x1
y0 = self.y0
y1 = self.y1
if self.ydivs > 0:
dy = self.height / (self.ydivs) # Y grid line
for line in range(self.ydivs + 1):
color = self.fgcolor if line == self.yorigin else self.gridcolor
ypos = round(self.y1 - dy * line)
ssd.hline(x0, ypos, x1 - x0, color)
if self.xdivs > 0:
width = x1 - x0
dx = width / (self.xdivs) # X grid line
for line in range(self.xdivs + 1):
color = self.fgcolor if line == self.xorigin else self.gridcolor
xpos = round(x0 + dx * line)
ssd.vline(xpos, y0, y1 - y0, color)
# Called by Curve
def line(self, start, end, color): # start and end relative to origin and scaled -1 .. 0 .. +1
xs = round(self.xp_origin + start[0] * self.x_axis_len)
ys = round(self.yp_origin - start[1] * self.y_axis_len)
xe = round(self.xp_origin + end[0] * self.x_axis_len)
ye = round(self.yp_origin - end[1] * self.y_axis_len)
ssd.line(xs, ys, xe, ye, color)
class PolarGraph(Graph):
def __init__(self, writer, row, col, *, height=90, fgcolor=None, bgcolor=None, bdcolor=None,
gridcolor=None, adivs=3, rdivs=4):
super().__init__(writer, row, col, height, height, fgcolor, bgcolor, bdcolor, gridcolor)
self.adivs = adivs * 2 # No. of divisions of Pi radians
self.rdivs = rdivs
self.radius = round(height / 2) # Unit: pixels
self.xp_origin = self.x0 + self.radius # Origin in pixels
self.yp_origin = self.y0 + self.radius
self.draw = True
def show(self):
if super().show(): # Clear working area
display.usegrey(False)
x0 = self.x0
y0 = self.y0
radius = self.radius
adivs = self.adivs
rdivs = self.rdivs
diam = 2 * radius
if rdivs > 0:
for r in range(1, rdivs + 1):
display.circle(self.xp_origin, self.yp_origin, round(radius * r / rdivs), self.gridcolor)
if adivs > 0:
v = complex(1)
m = rect(1, pi / adivs)
for _ in range(adivs):
self.cline(-v, v, self.gridcolor)
v *= m
ssd.vline(x0 + radius, y0, diam, self.fgcolor)
ssd.hline(x0, y0 + radius, diam, self.fgcolor)
def cline(self, start, end, color): # start and end are complex, 0 <= magnitude <= 1
height = self.radius # Unit: pixels
xs = round(self.xp_origin + start.real * height)
ys = round(self.yp_origin - start.imag * height)
xe = round(self.xp_origin + end.real * height)
ye = round(self.yp_origin - end.imag * height)
ssd.line(xs, ys, xe, ye, color)

Wyświetl plik

@ -0,0 +1,60 @@
# knob.py Extension to microgui providing a control knob (rotary potentiometer) widget
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
from gui.core.ugui import LinearIO, display
import math
TWOPI = 2 * math.pi
# Null function
dolittle = lambda *_ : None
# *********** CONTROL KNOB CLASS ***********
class Knob(LinearIO):
def __init__(self, writer, row, col, *, height=70, arc=TWOPI, ticks=9, value=0.0,
fgcolor=None, bgcolor=None, color=None, bdcolor=None,
callback=dolittle, args=[], active=True):
super().__init__(writer, row, col, height, height, fgcolor, bgcolor, bdcolor, value, active)
super()._set_callbacks(callback, args)
radius = height / 2
self.arc = min(max(arc, 0), TWOPI) # Usable angle of control
self.radius = radius
self.xorigin = col + radius
self.yorigin = row + radius
self.ticklen = 0.1 * radius
self.pointerlen = radius - self.ticklen - 5
self.ticks = max(ticks, 2) # start and end of travel
self.color = color
self.draw = True # Ensure a redraw on next refresh
if active: # Run callback (e.g. to set dynamic colors)
self.callback(self, *self.args)
def show(self):
if super().show(False): # Honour bgcolor
arc = self.arc
ticks = self.ticks
radius = self.radius
ticklen = self.ticklen
for tick in range(ticks):
theta = (tick / (ticks - 1)) * arc - arc / 2
x_start = int(self.xorigin + radius * math.sin(theta))
y_start = int(self.yorigin - radius * math.cos(theta))
x_end = int(self.xorigin + (radius - ticklen) * math.sin(theta))
y_end = int(self.yorigin - (radius - ticklen) * math.cos(theta))
display.line(x_start, y_start, x_end, y_end, self.fgcolor)
if self.color is not None:
display.fillcircle(self.xorigin, self.yorigin, radius - ticklen, self.color)
display.circle(self.xorigin, self.yorigin, radius - ticklen, self.fgcolor)
display.circle(self.xorigin, self.yorigin, radius - ticklen - 3, self.fgcolor)
self._drawpointer(self._value, self.fgcolor) # draw new
def _drawpointer(self, value, color):
arc = self.arc
length = self.pointerlen
angle = value * arc - arc / 2
x_end = int(self.xorigin + length * math.sin(angle))
y_end = int(self.yorigin - length * math.cos(angle))
display.line(int(self.xorigin), int(self.yorigin), x_end, y_end, color)

Wyświetl plik

@ -0,0 +1,38 @@
# label.py Extension to micro-gui providing the Label class
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
from gui.core.ugui import Widget, display
from gui.core.writer import Writer
from gui.core.colors import *
# text: str display string int save width
class Label(Widget):
def __init__(self, writer, row, col, text, invert=False, fgcolor=None, bgcolor=BLACK, bdcolor=False):
# Determine width of object
if isinstance(text, int):
width = text
text = None
else:
width = writer.stringlen(text)
height = writer.height
self.invert = invert
super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor)
if text is not None:
self.value(text, invert)
def value(self, text=None, invert=False, fgcolor=None, bgcolor=None, bdcolor=None):
txt = super().value(text) # Sets .draw ensuring refresh
# Redraw even if no text supplied: colors may have changed.
self.invert = invert
self.fgcolor = self.def_fgcolor if fgcolor is None else fgcolor
self.bgcolor = self.def_bgcolor if bgcolor is None else bgcolor
if bdcolor is False:
self.def_bdcolor = False
self.bdcolor = self.def_bdcolor if bdcolor is None else bdcolor
return txt
def show(self):
if super().show(): # Draw or erase border
if isinstance(txt := super().value(), str):
display.print_left(self.writer, self.col, self.row, txt, self.fgcolor, self.bgcolor, self.invert)

27
gui/widgets/led.py 100644
Wyświetl plik

@ -0,0 +1,27 @@
# led.py Extension to ugui providing the LED class
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
from gui.core.ugui import Widget, display
from gui.core.colors import *
class LED(Widget):
def __init__(self, writer, row, col, *, height=30, fgcolor=None, bgcolor=None, bdcolor=False, color=RED):
super().__init__(writer, row, col, height, height, fgcolor, bgcolor, bdcolor, False)
self._value = False
self._color = color
self.radius = self.height // 2
self.x = col + self.radius
self.y = row + self.radius
def show(self):
if super().show(): # Draw or erase border
color = self._color if self._value else BLACK
display.fillcircle(int(self.x), int(self.y), int(self.radius), color)
display.circle(int(self.x), int(self.y), int(self.radius), self.fgcolor)
def color(self, color):
self._color = color
self.draw = True

Wyświetl plik

@ -0,0 +1,106 @@
# listbox.py Extension to ugui providing the Listbox class
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
from micropython import const
from gui.core.ugui import Widget, display
from gui.core.colors import *
dolittle = lambda *_ : None
# Behaviour has issues compared to touch displays because movement between
# entries is sequential. This can affect the choice in when the callback runs.
# It always runs when select is pressed. See 'also' ctor arg.
ON_MOVE = const(1) # Also run whenever the currency moves.
ON_LEAVE = const(2) # Also run on exit from the control.
class Listbox(Widget):
@staticmethod
def dimensions(writer, elements):
entry_height = writer.height + 2 # Allow a pixel above and below text
le = len(elements)
height = entry_height * le + 2
textwidth = max(writer.stringlen(s) for s in elements) + 4
return entry_height, height, textwidth
def __init__(self, writer, row, col, *, elements, width=None, value=0,
fgcolor=None, bgcolor=None, bdcolor=False, fontcolor=None, select_color=DARKBLUE,
callback=dolittle, args=[], also=0):
self.entry_height, height, textwidth = self.dimensions(writer, elements)
self.also = also
if width is None:
width = textwidth
if not isinstance(value, int) or value >= len(elements):
value = 0
super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor, value, True)
self.cb = callback
self.cb_args = args
self.select_color = select_color
self.fontcolor = fontcolor
fail = False
try:
self.elements = [s for s in elements if type(s) is str]
except:
fail = True
else:
fail = len(self.elements) == 0
if fail:
raise ValueError('elements must be a list or tuple of one or more strings')
self._value = value # No callback until user selects
self.ev = value
def show(self):
if not super().show(False): # Clear to self.bgcolor
return
length = len(self.elements)
x = self.col
y = self.row
for n in range(length):
if n == self._value:
display.fill_rect(x, y + 1, self.width, self.entry_height - 1, self.select_color)
display.print_left(self.writer, x + 2, y + 1, self.elements[n], self.fgcolor, self.select_color)
else:
display.print_left(self.writer, x + 2, y + 1, self.elements[n], self.fgcolor, self.bgcolor)
y += self.entry_height
def textvalue(self, text=None): # if no arg return current text
if text is None:
return self.elements[self._value]
else: # set value by text
try:
v = self.elements.index(text)
except ValueError:
v = None
else:
if v != self._value:
self.value(v)
return v
def do_up(self, _):
if v := self._value:
self.value(v - 1)
if (self.also & ON_MOVE): # Treat as if select pressed
self.do_sel()
def do_down(self, _):
if (v := self._value) < len(self.elements) - 1:
self.value(v + 1)
if (self.also & ON_MOVE):
self.do_sel()
# Callback runs if select is pressed. Also (if ON_LEAVE) if user changes
# list currency and then moves off the control. Otherwise if we have a
# callback that refreshes another control, that second control does not
# track currency.
def do_sel(self): # Select was pushed
self.ev = self._value
self.cb(self, *self.cb_args)
def enter(self):
self.ev = self._value # Value change detection
def leave(self):
if (self.also & ON_LEAVE) and self._value != self.ev:
self.do_sel()

Wyświetl plik

@ -0,0 +1,64 @@
# meter.py Extension to ugui providing a linear "meter" widget.
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2019 Peter Hinch
from gui.core.ugui import Widget, display
from gui.widgets.label import Label
from gui.core.colors import *
# Null function
dolittle = lambda *_ : None
class Meter(Widget):
BAR = 1
LINE = 0
def __init__(self, writer, row, col, *, height=50, width=10,
fgcolor=None, bgcolor=BLACK, ptcolor=None, bdcolor=None,
divisions=5, label=None, style=0, legends=None, value=0):
super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor) # TODO Consider active
self.divisions = divisions
if label is not None:
Label(writer, row + height + 3, col, label)
self.style = style
self.ptcolor = ptcolor if ptcolor is not None else self.fgcolor
if legends is not None: # Legends
x = col + width + 4
y = row + height
dy = 0 if len(legends) <= 1 else height / (len(legends) -1)
yl = y - writer.height / 2 # Start at bottom
for legend in legends:
Label(writer, int(yl), x, legend)
yl -= dy
self.value(value)
def value(self, n=None, color=None):
if n is None:
return super().value()
n = super().value(min(1, max(0, n)))
if color is not None:
self.ptcolor = color
return n
def show(self):
if super().show(): # Draw or erase border
val = super().value()
wri = self.writer
width = self.width
height = self.height
x0 = self.col
x1 = self.col + width
y0 = self.row
y1 = self.row + height
if self.divisions > 0:
dy = height / (self.divisions) # Tick marks
for tick in range(self.divisions + 1):
ypos = int(y0 + dy * tick)
display.hline(x0 + 2, ypos, x1 - x0 - 4, self.fgcolor)
y = int(y1 - val * height) # y position of slider
if self.style == self.LINE:
display.hline(x0, y, width, self.ptcolor) # Draw pointer
else:
w = width / 2
display.fill_rect(int(x0 + w - 2), y, 4, y1 - y, self.ptcolor)

Wyświetl plik

@ -0,0 +1,132 @@
# scale.py Extension to micro-gui providing the Scale class
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# Usage:
# from gui.widgets.scale import Scale
from gui.core.ugui import LinearIO, display
from hardware_setup import ssd # Display driver for Writer
from gui.core.writer import Writer
from gui.core.colors import BLACK
dolittle = lambda *_ : None
class Scale(LinearIO):
def __init__(self, writer, row, col, *,
ticks=200, legendcb=None, tickcb=None,
height=0, width=100, bdcolor=None, fgcolor=None, bgcolor=None,
callback=dolittle, args=[],
pointercolor=None, fontcolor=None, value=0.0, active=False):
if ticks % 2:
raise ValueError('ticks arg must be divisible by 2')
self.ticks = ticks
self.tickcb = tickcb
def lcb(f):
return '{:3.1f}'.format(f)
self.legendcb = legendcb if legendcb is not None else lcb
bgcolor = BLACK if bgcolor is None else bgcolor
text_ht = writer.font.height()
ctrl_ht = 12 # Minimum height for ticks
# Add 2 pixel internal border to give a little more space
min_ht = text_ht + 6 # Ht of text, borders and gap between text and ticks
if height < min_ht + ctrl_ht:
height = min_ht + ctrl_ht # min workable height
else:
ctrl_ht = height - min_ht # adjust ticks for greater height
width &= 0xfffe # Make divisible by 2: avoid 1 pixel pointer offset
super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor, self._to_int(value), active)
if active:
super()._set_callbacks(callback, args)
self.minval = -1.0 # By default scales run from -1.0 to +1.0
self.fontcolor = fontcolor if fontcolor is not None else self.fgcolor
self.x0 = col + 2
self.x1 = col + self.width - 2
self.y0 = row + 2
self.y1 = row + self.height - 2
self.ptrcolor = pointercolor if pointercolor is not None else self.fgcolor
# Define tick dimensions
ytop = self.y0 + text_ht + 2 # Top of scale graphic (2 pixel gap)
ycl = ytop + (self.y1 - ytop) // 2 # Centre line
self.sdl = round(ctrl_ht * 1 / 3) # Length of small tick.
self.sdy0 = ycl - self.sdl // 2
self.mdl = round(ctrl_ht * 2 / 3) # Medium tick
self.mdy0 = ycl - self.mdl // 2
self.ldl = ctrl_ht # Large tick
self.ldy0 = ycl - self.ldl // 2
self.draw = True # Ensure a redraw on next refresh
if active: # Run callback (e.g. to set dynamic colors)
self.callback(self, *self.args)
def show(self):
wri = self.writer
x0: int = self.x0 # Internal rectangle occupied by scale and text
x1: int = self.x1
y0: int = self.y0
y1: int = self.y1
if super().show():
# Scale is drawn using ints. Each division is 10 units.
val: int = self._value # 0..ticks*10
# iv increments for each tick. Its value modulo N determines tick length
iv: int # val / 10 at a tick position
d: int # val % 10: offset relative to a tick position
fx: int # X offset of current tick in value units
if val >= 100: # Whole LHS of scale will be drawn
iv, d = divmod(val - 100, 10) # Initial value
fx = 10 - d
iv += 1
else: # Scale will scroll right
iv = 0
fx = 100 - val
# Window shows 20 divisions, each of which corresponds to 10 units of value.
# So pixels per unit value == win_width/200
win_width: int = x1 - x0
ticks: int = self.ticks # Total # of ticks visible and hidden
while True:
x: int = x0 + (fx * win_width) // 200 # Current X position
ys: int # Start Y position for tick
yl: int # tick length
if x > x1 or iv > ticks: # Out of space or data (scroll left)
break
if not iv % 10:
txt = self.legendcb(self._fvalue(iv * 10))
tlen = wri.stringlen(txt)
Writer.set_textpos(ssd, y0, min(x, x1 - tlen))
wri.setcolor(self.fontcolor, self.bgcolor)
wri.printstring(txt)
wri.setcolor()
ys = self.ldy0 # Large tick
yl = self.ldl
elif not iv % 5:
ys = self.mdy0
yl = self.mdl
else:
ys = self.sdy0
yl = self.sdl
if self.tickcb is None:
color = self.fgcolor
else:
color = self.tickcb(self._fvalue(iv * 10), self.fgcolor)
display.vline(x, ys, yl, color) # Draw tick
fx += 10
iv += 1
display.vline(x0 + (x1 - x0) // 2, y0, y1 - y0, self.ptrcolor) # Draw pointer
def _to_int(self, v):
return round((v + 1.0) * self.ticks * 5) # 0..self.ticks*10
def _fvalue(self, v=None):
return v / (5 * self.ticks) - 1.0
def value(self, val=None): # User method to get or set value
if val is not None:
val = min(max(val, - 1.0), 1.0)
v = self._to_int(val)
if v != self._value:
self._value = v
self.draw = True # Ensure a redraw on next refresh
self.callback(self, *self.args)
return self._fvalue(self._value)

Wyświetl plik

@ -0,0 +1,157 @@
# scale_log.py Extension to micro-gui providing the ScaleLog class
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# A logarithmic Scale which responds to user input
# Usage:
# from gui.widgets.scale_log import ScaleLog
import uasyncio as asyncio
from time import ticks_ms, ticks_diff
from math import log10
from gui.core.ugui import LinearIO, display
from hardware_setup import ssd # Display driver for Writer
from gui.core.writer import Writer
from gui.core.colors import BLACK
# Null function
dolittle = lambda *_ : None
# Start value is 1.0. User applies scaling to value and ticks callback.
class ScaleLog(LinearIO):
def __init__(self, writer, row, col, *,
decades=5, height=0, width=160,
bdcolor=None, fgcolor=None, bgcolor=None,
pointercolor=None, fontcolor=None,
legendcb=None, tickcb=None,
callback=dolittle, args=[],
value=1.0, delta=0.01, active=False):
# For correct text rendering inside control must explicitly set bgcolor
bgcolor = BLACK if bgcolor is None else bgcolor
if decades < 3:
raise ValueError('decades must be >= 3')
self.mval = 10**decades # Max value
self.tickcb = tickcb
self.delta = delta # Min multiplier = 1 + delta
def lcb(f):
return '{:<1.0f}'.format(f)
self.legendcb = legendcb if legendcb is not None else lcb
text_ht = writer.height
ctrl_ht = 12 # Minimum height for ticks
min_ht = text_ht + 8 # Ht of text and gap between text and ticks
if height < min_ht + ctrl_ht:
height = min_ht + ctrl_ht # min workable height
else:
ctrl_ht = height - min_ht # adjust ticks for greater height
width &= 0xfffe # Make divisible by 2: avoid 1 pixel pointer offset
super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor, self._constrain(value), active)
if active:
super()._set_callbacks(callback, args)
self.fontcolor = fontcolor if fontcolor is not None else self.fgcolor
self.x0 = col + 2
self.x1 = col + self.width - 2
self.y0 = row + 2
self.y1 = row + self.height - 2
self.ptrcolor = pointercolor if pointercolor is not None else self.fgcolor
# Define tick dimensions
ytop = self.y0 + text_ht + 2 # Top of scale graphic (2 pixel gap)
ycl = ytop + (self.y1 - ytop) // 2 # Centre line
self.sdl = round(ctrl_ht * 1 / 3) # Length of small tick.
self.sdy0 = ycl - self.sdl // 2
self.mdl = round(ctrl_ht * 2 / 3) # Medium tick
self.mdy0 = ycl - self.mdl // 2
self.ldl = ctrl_ht # Large tick
self.ldy0 = ycl - self.ldl // 2
self.dw = (self.x1 - self.x0) // 2 # Pixel width of a decade
self.draw = True # Ensure a redraw on next refresh
if active: # Run callback (e.g. to set dynamic colors)
self.callback(self, *self.args)
# Pre calculated log10(x) for x in range(1, 10)
def show(self, logs=(0.0, 0.3010, 0.4771, 0.6021, 0.6990, 0.7782, 0.8451, 0.9031, 0.9542)):
#start = ticks_ms()
x0: int = self.x0 # Internal rectangle occupied by scale and text
x1: int = self.x1
y0: int = self.y0
y1: int = self.y1
xc: int = x0 + (x1 - x0) // 2 # x location of pointer
dw: int = self.dw # Width of a decade in pixels
wri = self.writer
if super().show():
vc = self._value # Current value, corresponds to centre of display
d = int(log10(vc)) - 1 # 10**d is start of a decade guaranteed to be outside display
vs = max(10 ** d, 1.0) # vs: start value of current decade
while True: # For each decade until we run out of space
done = True # Assume completion
xs: float = xc - dw * log10(vc / vs) # x location of start of scale
tick: int
q: float
# log10 ~38us on Pi Pico
for tick, q in enumerate(logs):
vt: float = vs * (1 + tick) # Value of current tick
x: int = round(xs + q * dw) # x location of current tick
if x >= x1:
break # All visible ticks drawn
elif x > x0: # Tick is visible
if not tick:
txt = self.legendcb(vt)
tlen = wri.stringlen(txt)
Writer.set_textpos(ssd, y0, min(x, x1 - tlen))
wri.setcolor(self.fontcolor, self.bgcolor)
wri.printstring(txt)
ys = self.ldy0 # Large tick
yl = self.ldl
elif tick == 4:
ys = self.mdy0
yl = self.mdl
else:
ys = self.sdy0
yl = self.sdl
if self.tickcb is None:
color = self.fgcolor
else:
color = self.tickcb(vt, self.fgcolor)
display.vline(x, ys, yl, color) # Draw tick
if (not tick) and (vt > 0.999 * self.mval):
break # Drawn last tick at end of data
else:
vs *= 10 # More to do. Next decade.
done = False
if done:
break
display.vline(xc, y0, y1 - y0, self.ptrcolor) # Draw pointer
#print(ticks_diff(ticks_ms(), start)) 75-95ms on Pyboard D depending on calbacks
def _constrain(self, v):
return min(max(v, 1.0), self.mval)
def value(self, val=None): # User method to get or set value
if val is not None:
v = self._constrain(val)
if self._value is None or v != self._value:
self._value = v
self.draw = True # Ensure a redraw on next refresh
self.callback(self, *self.args)
return self._value
async def btnhan(self, button, up):
up = up == 1
delta = self.delta
maxdelta = 0.64
smul= (1 + delta) if up else (1 / (1 + delta))
self.value(self.value() * smul)
t = ticks_ms()
while not button():
await asyncio.sleep_ms(0) # Quit fast on button release
if ticks_diff(ticks_ms(), t) > 500: # Button was held down
delta = min(maxdelta, delta * 2)
smul = (1 + delta) if up else (1 / (1 + delta))
self.value(self.value() * smul)
t = ticks_ms()

Wyświetl plik

@ -0,0 +1,161 @@
# sliders.py Extension to ugui providing linear "potentiometer" widgets.
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
from micropython import const
from gui.core.ugui import LinearIO, display # Class and instance
from gui.widgets.label import Label
# Null function
dolittle = lambda *_ : None
# *********** SLIDER CLASSES ***********
# A slider's text items lie outside its bounding box (area sensitive to touch)
_SLIDE_DEPTH = const(6) # Must be divisible by 2
_TICK_VISIBLE = const(3) # No. of tick pixels visible either side of slider
_HALF_SLOT_WIDTH = const(2) # Width of slot /2
# Slider ontrols have been rewritten to avoid the need for reading back framebuffer
# contents as this is unreliable on RA8875.
class Slider(LinearIO):
def __init__(self, writer, row, col, *,
height=100, width=20, divisions=10, legends=None,
fgcolor=None, bgcolor=None, fontcolor=None, bdcolor=None, slotcolor=None,
callback=dolittle, args=[], value=0.0, active=True):
width &= 0xfe # ensure divisible by 2
super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor, value, active)
if active:
super()._set_callbacks(callback, args)
self.divisions = divisions
self.legends = legends
self.fontcolor = self.fgcolor if fontcolor is None else fontcolor
self.slotcolor = self.bgcolor if slotcolor is None else slotcolor
# Define slider
self.slide_x0 = col + _TICK_VISIBLE # Draw slider shorter than ticks
self.slide_w = width - 2 * _TICK_VISIBLE
# y coord of top left hand corner of slide when value == 0
self.slide_y0 = row + height - _SLIDE_DEPTH - 1
# Slot coordinates
centre = col + width // 2
self.slot_x0 = centre - _HALF_SLOT_WIDTH
self.slot_y0 = row + _SLIDE_DEPTH // 2
self.slot_h = height - _SLIDE_DEPTH - 1
# Prevent Label objects being added to display list when already there.
self.drawn = False
self.draw = True # Ensure a redraw on next refresh
if active: # Run callback (e.g. to set dynamic colors)
self.callback(self, *self.args)
def show(self):
# Blank slot, ticks and slider
if super().show(False): # Honour bgcolor
x = self.col
y = self.slot_y0
# Length of travel of slider
slot_len = self.slot_h # Dimensions of slot
slot_w = 2 * _HALF_SLOT_WIDTH
if self.divisions > 0:
dy = slot_len / (self.divisions) # Tick marks
xs = x + 1
xe = x + self.width -1
for tick in range(self.divisions + 1):
ypos = int(y + dy * tick)
display.line(xs, ypos, xe, ypos, self.fgcolor)
# Blank and redraw slot
display.fill_rect(self.slot_x0, self.slot_y0, slot_w, slot_len, self.slotcolor)
display.rect(self.slot_x0, self.slot_y0, slot_w, slot_len, self.fgcolor)
# Legends: if redrawing, they are already on the Screen's display list
if self.legends is not None and not self.drawn:
if len(self.legends) <= 1:
dy = 0
else:
dy = slot_len / (len(self.legends) -1)
yl = y + slot_len # Start at bottom
wri = self.writer
fhdelta = wri.height / 2
for legend in self.legends:
Label(wri, int(yl - fhdelta), x + self.width + 4, legend, fgcolor = self.fontcolor)
yl -= dy
slide_y = round(self.slide_y0 - self._value * slot_len)
display.fill_rect(self.slide_x0, slide_y, self.slide_w, _SLIDE_DEPTH, self.fgcolor)
self.drawn = True
def color(self, color):
if color != self.fgcolor:
self.fgcolor = color
self.draw = True
class HorizSlider(LinearIO):
def __init__(self, writer, row, col, *,
height=20, width=100, divisions=10, legends=None,
fgcolor=None, bgcolor=None, fontcolor=None, bdcolor=None,
slotcolor=None,
callback=dolittle, args=[], value=0.0, active=True):
height &= 0xfe # ensure divisible by 2
super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor, value, active)
if active:
super()._set_callbacks(callback, args)
self.divisions = divisions
self.legends = legends
self.fontcolor = self.fgcolor if fontcolor is None else fontcolor
self.slotcolor = self.bgcolor if slotcolor is None else slotcolor
# Define slider
self.slide_y0 = row + _TICK_VISIBLE # Draw slider shorter than ticks
self.slide_h = height - 2 * _TICK_VISIBLE
# Slot coordinates
self.slot_x0 = col + _SLIDE_DEPTH // 2
self.slot_w = width - _SLIDE_DEPTH - 1
centre = row + height // 2
self.slot_y0 = centre - _HALF_SLOT_WIDTH
# Prevent Label objects being added to display list when already there.
self.drawn = False
self.draw = True # Ensure a redraw on next refresh
if active: # Run callback (e.g. to set dynamic colors)
self.callback(self, *self.args)
def show(self):
# Blank slot, ticks and slider
if super().show(False): # Honour bgcolor
x = self.slot_x0
y = self.row
slot_len = self.slot_w # Slot dimensions
slot_h = 2 * _HALF_SLOT_WIDTH
if self.divisions > 0:
dx = slot_len / (self.divisions) # Tick marks
ys = y + 1
ye = y + self.height - 1
for tick in range(self.divisions + 1):
xpos = int(x + dx * tick)
display.line(xpos, ys, xpos, ye, self.fgcolor)
# Blank and redraw slot
display.fill_rect(self.slot_x0, self.slot_y0, slot_len, slot_h, self.slotcolor)
display.rect(self.slot_x0, self.slot_y0, slot_len, slot_h, self.fgcolor)
# Legends: if redrawing, they are already on the Screen's display list
if self.legends is not None and not self.drawn:
if len(self.legends) <= 1:
dx = 0
else:
dx = slot_len / (len(self.legends) -1)
xl = x
wri = self.writer
for legend in self.legends:
offset = wri.stringlen(legend) / 2
Label(wri, y - wri.height - 4, int(xl - offset), legend, fgcolor = self.fontcolor)
xl += dx
self.slide_x = round(self.col + self._value * slot_len)
display.fill_rect(self.slide_x, self.slide_y0, _SLIDE_DEPTH, self.slide_h, self.fgcolor)
self.drawn = True
def color(self, color):
if color != self.fgcolor:
self.fgcolor = color
self.draw = True

Wyświetl plik

@ -0,0 +1,140 @@
# textbox.py Extension to nanogui providing the Textbox class
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2020 Peter Hinch
# Usage:
# from gui.widgets.textbox import Textbox
from gui.core.ugui import LinearIO
from hardware_setup import ssd # Display driver for Writer
from gui.core.writer import Writer
from time import ticks_diff, ticks_ms
import uasyncio as asyncio
# Reason for no tab support in nano-gui/private/reason_for_no_tabs
class Textbox(LinearIO):
def __init__(self, writer, row, col, width, nlines, *, bdcolor=None, fgcolor=None,
bgcolor=None, clip=True, active=False):
height = nlines * writer.height
devht = writer.device.height
devwd = writer.device.width
if ((row + height + 2) > devht) or ((col + width + 2) > devwd):
raise ValueError('Textbox extends beyond physical screen.')
super().__init__(writer, row, col, height, width,
fgcolor, bgcolor, bdcolor, 0, active)
self.nlines = nlines
self.clip = clip
self.lines = []
self.start = 0 # Start line for display
def _add_lines(self, s):
width = self.width
font = self.writer.font
n = -1 # Index into string
newline = True
while True:
n += 1
if newline:
newline = False
ls = n # Start of line being processed
col = 0 # Column relative to text area
if n >= len(s): # End of string
if n > ls:
self.lines.append(s[ls :])
return
c = s[n] # Current char
if c == '\n':
self.lines.append(s[ls : n])
newline = True
continue # Line fits window
col += font.get_ch(c)[2] # width of current char
if col > width:
if self.clip:
p = s[ls :].find('\n') # end of 1st line
if p == -1:
self.lines.append(s[ls : n]) # clip, discard all to right
return
self.lines.append(s[ls : n]) # clip, discard to 1st newline
n = p # n will move to 1st char after newline
elif c == ' ': # Easy word wrap
self.lines.append(s[ls : n])
else: # Edge splits a word
p = s.rfind(' ', ls, n + 1)
if p >= 0: # spacechar in line: wrap at space
assert (p > 0), 'space char in position 0'
self.lines.append(s[ls : p])
n = p
else: # No spacechar: wrap at end
self.lines.append(s[ls : n])
n -= 1 # Don't skip current char
newline = True
def _print_lines(self):
if len(self.lines) == 0:
return
wri = self.writer
col = self.col
row = self.row
left = col
ht = wri.height
wri.setcolor(self.fgcolor, self.bgcolor)
# Print the first (or last?) lines that fit widget's height
#for line in self.lines[-self.nlines : ]:
for line in self.lines[self.start : self.start + self.nlines]:
Writer.set_textpos(ssd, row, col)
wri.printstring(line)
row += ht
col = left
wri.setcolor() # Restore defaults
def show(self):
if super().show():
self._print_lines()
def append(self, s, ntrim=None, line=None):
self._add_lines(s)
if ntrim is None: # Default to no. of lines that can fit
ntrim = self.nlines
if len(self.lines) > ntrim:
self.lines = self.lines[-ntrim:]
self.goto(line)
def scroll(self, n): # Relative scrolling
value = len(self.lines)
if n == 0 or value <= self.nlines: # Nothing to do
return False
s = self.start
self.start = max(0, min(self.start + n, value - self.nlines))
if s != self.start:
self.draw = True # Cause a refresh
return True
return False
def value(self):
return len(self.lines)
def clear(self):
self.lines = []
self.draw = True # Cause a refresh
def goto(self, line=None): # Absolute scrolling
if line is None:
self.start = max(0, len(self.lines) - self.nlines)
else:
self.start = max(0, min(line, len(self.lines) - self.nlines))
self.draw = True # Cause a refresh
async def btnhan(self, button, up):
self.scroll(-up)
t = ticks_ms()
d = 1
while not button():
await asyncio.sleep_ms(0) # Quit fast on button release
if ticks_diff(ticks_ms(), t) > 500: # Button was held down
d = min(16, d * 2)
self.scroll(-up * d)
t = ticks_ms()

Wyświetl plik

@ -0,0 +1,120 @@
# vectors.py Extension to ugui providing vector display
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# VectorDial class is display-only: no practical way to perform
# input on multiple vectors
from micropython import const
from gui.core.ugui import Screen, Widget, display
from gui.widgets.label import Label
from gui.core.colors import *
import cmath
conj = lambda v : v.real - v.imag * 1j # Complex conjugate
# Draw a vector in complex coordinates. Origin and end are complex.
# End is relative to origin.
def pline(origin, vec, color):
xs, ys = origin.real, origin.imag
display.line(round(xs), round(ys), round(xs + vec.real), round(ys - vec.imag), color)
# Draw an arrow; origin and vec are complex, scalar lc defines length of chevron.
# cw and ccw are unit vectors of +-3pi/4 radians for chevrons.
def arrow(origin, vec, lc, color):
ccw = cmath.exp(3j * cmath.pi/4) # Unit vectors
cw = cmath.exp(-3j * cmath.pi/4)
length, theta = cmath.polar(vec)
uv = cmath.rect(1, theta) # Unit rotation vector
start = -vec
if length > 3 * lc: # If line is long
ds = cmath.rect(lc, theta)
start += ds # shorten to allow for length of tail chevrons
chev = lc + 0j
pline(origin, vec, color) # Origin to tip
pline(origin, start, color) # Origin to tail
pline(origin + conj(vec), chev*ccw*uv, color) # Tip chevron
pline(origin + conj(vec), chev*cw*uv, color)
if length > lc: # Confusing appearance of very short vectors with tail chevron
pline(origin + conj(start), chev*ccw*uv, color) # Tail chevron
pline(origin + conj(start), chev*cw*uv, color)
# Vector display
class Pointer:
def __init__(self, dial):
dial.vectors.add(self)
self.dial = dial
self.color = WHITE
self.val = 0j
def value(self, v=None, color=None):
if color is not None:
self.color = color
if v is not None:
if isinstance(v, complex):
l = cmath.polar(v)[0]
newval = v / l if l > 1 else v # Max length = 1.0
else:
raise ValueError('Pointer value must be complex.')
if v != self.val and self.dial.screen is Screen.current_screen:
self.show(newval)
self.val = newval
return self.val
def show(self, newval=None):
v = self.val if newval is None else newval
dial = self.dial
color = self.color
vor = dial.vor # Dial's origin as a vector
r = dial.radius * (1 - dial.TICKLEN)
if dial.arrow:
arrow(vor, r * v, 5, color)
else:
pline(vor, r * v, color)
self.dial.draw = True # Mark dial as dirty
class VectorDial(Widget):
TICKLEN = 0.1
def __init__(self, writer, row, col, *,
height=100, fgcolor=None, bgcolor=None, bdcolor=None,
ticks=4, arrow=False, pip=None):
super().__init__(writer, row, col, height, height,
fgcolor, bgcolor, bdcolor, 0, False) # Display only
self.arrow = arrow
self.pip = self.fgcolor if pip is None else pip
radius = height / 2
self.radius = radius
self.ticks = ticks
self.xorigin = col + radius
self.yorigin = row + radius
self.vor = self.xorigin + 1j * self.yorigin # Origin as a vector
self.vectors = set()
self.draw = True
def show(self):
if super().show():
# cache bound variables
ticks = self.ticks
radius = self.radius
xo = self.xorigin
yo = self.yorigin
vor = self.vor
vtstart = (1 - self.TICKLEN) * radius + 0j # start of tick
vtick = self.TICKLEN * radius + 0j # tick
vrot = cmath.exp(2j * cmath.pi/ticks) # unit rotation
for _ in range(ticks):
pline(vor + conj(vtstart), vtick, self.fgcolor)
vtick *= vrot
vtstart *= vrot
display.circle(xo, yo, radius, self.fgcolor)
vshort = 1000 # Length of shortest vector
for v in self.vectors:
val = v.value() * radius # val is complex
vshort = min(vshort, cmath.polar(val)[0])
v.show()
if isinstance(self.pip, tuple) and vshort > 9:
display.fillcircle(xo, yo, 3, self.pip)
self.draw = False

55
hardware_setup.py 100644
Wyświetl plik

@ -0,0 +1,55 @@
# ili9341_pico.py Customise for your hardware config
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# As written, supports:
# ili9341 240x320 displays on Pi Pico
# Edit the driver import for other displays.
# Demo of initialisation procedure designed to minimise risk of memory fail
# when instantiating the frame buffer. The aim is to do this as early as
# possible before importing other modules.
# WIRING
# Pico Display
# GPIO Pin
# 3v3 36 Vin
# IO6 9 CLK Hardware SPI0
# IO7 10 DATA (AKA SI MOSI)
# IO8 11 DC
# IO9 12 Rst
# Gnd 13 Gnd
# IO10 14 CS
# Pushbuttons are wired between the pin and Gnd
# Pico pin Meaning
# 16 Operate current control
# 17 Decrease value of current control
# 18 Select previous control
# 19 Select next control
# 20 Increase value of current control
from machine import Pin, SPI, freq
import gc
from drivers.ili93xx.ili9341 import ILI9341 as SSD
freq(250_000_000) # RP2 overclock
# Create and export an SSD instance
pdc = Pin(8, Pin.OUT, value=0) # Arbitrary pins
prst = Pin(9, Pin.OUT, value=1)
pcs = Pin(10, Pin.OUT, value=1)
spi = SPI(0, baudrate=30_000_000)
gc.collect() # Precaution before instantiating framebuf
ssd = SSD(spi, pcs, pdc, prst, usd=True)
from gui.core.ugui import Display, setup
# Create and export a Display instance
# Define control buttons
nxt = Pin(19, Pin.IN, Pin.PULL_UP) # Move to next control
sel = Pin(16, Pin.IN, Pin.PULL_UP) # Operate current control
prev = Pin(18, Pin.IN, Pin.PULL_UP) # Move to previous control
increase = Pin(20, Pin.IN, Pin.PULL_UP) # Increase control's value
decrease = Pin(17, Pin.IN, Pin.PULL_UP) # Decrease control's value
display = Display(ssd, nxt, sel, prev, increase, decrease)
setup(display)

Wyświetl plik

@ -0,0 +1,55 @@
# ili9341_pico.py Customise for your hardware config
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch
# As written, supports:
# ili9341 240x320 displays on Pi Pico
# Edit the driver import for other displays.
# Demo of initialisation procedure designed to minimise risk of memory fail
# when instantiating the frame buffer. The aim is to do this as early as
# possible before importing other modules.
# WIRING
# Pico Display
# GPIO Pin
# 3v3 36 Vin
# IO6 9 CLK Hardware SPI0
# IO7 10 DATA (AKA SI MOSI)
# IO8 11 DC
# IO9 12 Rst
# Gnd 13 Gnd
# IO10 14 CS
# Pushbuttons are wired between the pin and Gnd
# Pico pin Meaning
# 16 Operate current control
# 17 Decrease value of current control
# 18 Select previous control
# 19 Select next control
# 20 Increase value of current control
from machine import Pin, SPI, freq
import gc
from drivers.ili93xx.ili9341 import ILI9341 as SSD
freq(250_000_000) # RP2 overclock
# Create and export an SSD instance
pdc = Pin(8, Pin.OUT, value=0) # Arbitrary pins
prst = Pin(9, Pin.OUT, value=1)
pcs = Pin(10, Pin.OUT, value=1)
spi = SPI(0, baudrate=30_000_000)
gc.collect() # Precaution before instantiating framebuf
ssd = SSD(spi, pcs, pdc, prst, usd=True)
from gui.core.ugui import Display, setup
# Create and export a Display instance
# Define control buttons
nxt = Pin(19, Pin.IN, Pin.PULL_UP) # Move to next control
sel = Pin(16, Pin.IN, Pin.PULL_UP) # Operate current control
prev = Pin(18, Pin.IN, Pin.PULL_UP) # Move to previous control
increase = Pin(20, Pin.IN, Pin.PULL_UP) # Increase control's value
decrease = Pin(17, Pin.IN, Pin.PULL_UP) # Decrease control's value
display = Display(ssd, nxt, sel, prev, increase, decrease)
setup(display)

Wyświetl plik

@ -0,0 +1,123 @@
# color_setup.py Customise for your hardware config
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2021 Peter Hinch, Ihor Nehrutsa
# Supports:
# TTGO T-Display 1.14" 135*240(Pixel) based on ST7789V
# http://www.lilygo.cn/claprod_view.aspx?TypeId=62&Id=1274
# http://www.lilygo.cn/prod_view.aspx?TypeId=50044&Id=1126
# https://github.com/Xinyuan-LilyGO/TTGO-T-Display
# https://github.com/Xinyuan-LilyGO/TTGO-T-Display/blob/master/image/pinmap.jpg
# https://github.com/Xinyuan-LilyGO/TTGO-T-Display/blob/master/schematic/ESP32-TFT(6-26).pdf
# WIRING (TTGO T-Display pin numbers and names).
# Pinout of TFT Driver
# ST7789 ESP32
# TFT_MISO N/A
TFT_MOSI = 19 # (SDA on schematic pdf) SPI interface output/input pin.
TFT_SCLK = 18 # This pin is used to be serial interface clock.
TFT_CS = 5 # Chip selection pin, low enable, high disable.
TFT_DC = 16 # Display data/command selection pin in 4-line serial interface.
TFT_RST = 23 # This signal will reset the device,Signal is active low.
TFT_BL = 4 # (LEDK on schematic pdf) Display backlight control pin
ADC_IN = 34 # Measuring battery or USB voltage, see comment below
ADC_EN = 14 # (PWR_EN on schematic pdf) is the ADC detection enable port
BUTTON1 = 35 # right of the USB connector
BUTTON2 = 0 # left of the USB connector
# ESP32 pins, free for use in user applications
#I2C_SDA = 21 # hardware ID 0
#I2C_SCL = 22
#UART2TXD = 17
#GPIO2 = 2
#GPIO15 = 15
#GPIO13 = 13
#GPIO12 = 12
#GPIO37 = 37
#GPIO38 = 38
#UART1TXD = 4
#UART1RXD = 5
#GPIO18 = 18
#GPIO19 = 19
#GPIO17 = 17
#DAC1 = 25
#DAC2 = 26
# Input only pins
#GPIO36 = 36 # input only
#GPIO39 = 39 # input only
# Demo of initialisation procedure designed to minimise risk of memory fail
# when instantiating the frame buffer. The aim is to do this as early as
# possible before importing other modules.
from machine import Pin, SPI, ADC, freq
import gc
from drivers.st7789.st7789_4bit import *
SSD = ST7789
pdc = Pin(TFT_DC, Pin.OUT, value=0) # Arbitrary pins
pcs = Pin(TFT_CS, Pin.OUT, value=1)
prst = Pin(TFT_RST, Pin.OUT, value=1)
pbl = Pin(TFT_BL, Pin.OUT, value=1)
gc.collect() # Precaution before instantiating framebuf
# Conservative low baudrate. Can go to 62.5MHz.
spi = SPI(1, 30_000_000, sck=Pin(TFT_SCLK), mosi=Pin(TFT_MOSI))
freq(160_000_000)
''' TTGO
v +----------------+
40 | | |
^ | +------+ | pin 36
| | | | |
| | | | |
240 | | | | |
| | | | |
| | | | |
v | +------+ |
40 | | | Reset button
^ +----------------+
>----<------>----<
52 135 xx
BUTTON2 BUTTON1
'''
# Right way up landscape: defined as top left adjacent to pin 36
ssd = SSD(spi, height=135, width=240, dc=pdc, cs=pcs, rst=prst, disp_mode=LANDSCAPE, display=TDISPLAY)
# Normal portrait display: consistent with TTGO logo at top
# ssd = SSD(spi, height=240, width=135, dc=pdc, cs=pcs, rst=prst, disp_mode=PORTRAIT, display=TDISPLAY)
from gui.core.ugui import Display, setup
# Create and export a Display instance
# Define control buttons
nxt = Pin(36, Pin.IN, Pin.PULL_UP) # Move to next control
sel = Pin(37, Pin.IN, Pin.PULL_UP) # Operate current control
prev = Pin(38, Pin.IN, Pin.PULL_UP) # Move to previous control
increase = Pin(39, Pin.IN, Pin.PULL_UP) # Increase control's value
decrease = Pin(32, Pin.IN, Pin.PULL_UP) # Decrease control's value
display = Display(ssd, nxt, sel, prev, increase, decrease)
setup(display)
# optional
# b1 = Pin(BUTTON1, Pin.IN)
# b2 = Pin(BUTTON2, Pin.IN)
# adc_en = Pin(ADC_EN, Pin.OUT, value=1)
# adc_in = ADC(Pin(ADC_IN))
# adc_en.value(0)
'''
Set ADC_EN to "1" and read voltage in BAT_ADC,
if this voltage more than 4.3 V device have powered from USB.
If less then 4.3 V - device have power from battery.
To save battery you can set ADC_EN to "0" and in this case the USB converter
will be power off and do not use your battery.
When you need to measure battery voltage first set ADC_EN to "1",
measure voltage and then set ADC_EN back to "0" for save battery.
'''