GUI elements split out to nanogui.

pull/16/head
Peter Hinch 2018-08-31 11:17:58 +01:00
rodzic 052a882f31
commit 6eb8adb31c
7 zmienionych plików z 118 dodań i 162 usunięć

Wyświetl plik

@ -27,7 +27,8 @@ pitch form. This may be overidden by a command line argument.
By default the ASCII character set (ordinal values 32 to 126 inclusive) is
supported. Command line arguments can modify this range as required, if
necessary to include extended ASCII characters up to 255.
necessary to include extended ASCII characters up to 255. Alternatively non
English or non-contiguous character sets may be defined.
Further arguments ensure that the byte contents and layout are correct for the
target display hardware. Their usage should be specified in the documentation
@ -61,27 +62,28 @@ Example usage to produce a file `myfont.py` with height of 23 pixels:
for alternative character sets such as Cyrillic: the file must contain the
character set to be included. An example file is `cyrillic`.
The -c option reduces the size of the font file. If the font file is frozen as
bytecode this will not reduce RAM usage but it will conserve flash. An example
usage for a digital clock font:
The -c option may be used to reduce the size of the font file by limiting the
character set. If the font file is frozen as bytecode this will not reduce RAM
usage but it will conserve flash. Example usage for a digital clock font:
```shell
$ font_to_py.py Arial.ttf 20 arial_clock.py -c 1234567890:
```
Example usage with the -k option:
```shell
font_to_py.py FreeSans.ttf 20 freesans_cyr_20.py -k cyrillic_subset
font_to_py.py FreeSans.ttf 20 freesans_cyr_20.py -k cyrillic
```
If a character set is specified, `--smallest` and `--largest` should not be
specified: these values are computed from the charcater set.
specified: these values are computed from the character set.
Any requirement for arguments -xr will be specified in the device driver
documentation. Bit reversal is required by some display hardware.
There have been reports that producing extended ASCII characters (ordinal
value > 127) from ttf files is unreliable. If the expected results are not
achieved, use an otf font. This may be a limitation of the `freetype` library.
achieved, use an otf font. However I have successfully created the Cyrillic
font from a `ttf`. Perhaps not all fonts are created equal...
### Output
@ -128,13 +130,15 @@ Consequently the following arguments are invalid:
# Dependencies, links and licence
The code is released under the MIT licence. It requires Python 3.2 or later.
The code is released under the MIT licence. The `font_to_py.py` utility
requires Python 3.2 or later.
The module relies on [Freetype](https://www.freetype.org/) which is included in most Linux distributions.
It uses the [Freetype Python bindings](http://freetype-py.readthedocs.io/en/latest/index.html)
which will need to be installed.
My solution draws on the excellent example code written by Daniel Bader. This
may be viewed [here](https://dbader.org/blog/monochrome-font-rendering-with-freetype-and-python) and [here](https://gist.github.com/dbader/5488053).
may be viewed [here](https://dbader.org/blog/monochrome-font-rendering-with-freetype-and-python)
and [here](https://gist.github.com/dbader/5488053).
# Appendix: RAM utilisation Test Results

Wyświetl plik

@ -1,9 +1,10 @@
# MicroPython font handling
This repository defines a method of creating and deploying fonts for use with
MicroPython display drivers. A PC utility converts industry standard font files
to Python sourcecode and a MicroPython module enables these to be rendered to
suitable device drivers, notably OLED displays using the SSD1306 chip.
MicroPython display drivers. A PC utility renders industry standard font files
as a bitmap in the form of Python sourcecode. A MicroPython module enables such
files to be displayed on devices with suitable device drivers. These include
OLED displays using the SSD1306 chip and the official device driver.
# Introduction
@ -34,8 +35,8 @@ This comprises three components:
1. [font_to_py.py](./FONT_TO_PY.md) This utility runs on a PC and converts a
font file to Python source. See below.
2. [The Writer class](./writer/WRITER.md) This facilitates rendering text to a
device having a suitably designed device driver.
2. [Writer and CWriter classes](./writer/WRITER.md) These facilitate rendering
text to a monochrome or colour display having a suitable device driver.
3. [Device driver notes](./writer/DRIVERS.md). Notes for authors of display
device drivers. Provides details of the font file format and information on
ensuring comptibility with the `Writer` classes.
@ -59,28 +60,31 @@ RAM usage when importing fonts stored as frozen bytecode.
# Limitations
By default the ASCII character set from `chr(32)` to `chr(126)` is supported
but command line arguments enable the range to be modified with extended ASCII
characters to `chr(255)` being included if required. Kerning is not supported.
Fonts are one bit per pixel. This does not rule out colour displays: the device
driver can add colour information at the rendering stage. It does assume that
all pixels of a character are rendered identically.
Kerning is not supported. Fonts are one bit per pixel. Colour displays are
supported by the `CWriter` class which adds colour information at the rendering
stage. This assumes that all pixels of a character are coloured identically.
Converting font files programmatically works best for larger fonts. For small
fonts, like the 8*8 default used by the SSD1306 driver, it is best to use
hand-designed binary font files: these are optiised for rendering at a specific
size.
By default the `font_to_py.py` utility produces the ASCII character set from
`chr(32)` to `chr(126)` inclusive. Command line options enable the character
set to be modified to include extended ASCII. Alternative sets may be specified
such as non-English languages or limited, non-contiguous sets for specialist
applications.
# Font file interface
A font file is imported in the usual way e.g. `import font14`. It contains
the following methods which return values defined by the arguments which were
provided to font-to-py:
provided to `font_to_py.py`:
`height` Returns height in pixels.
`max_width` Returns maximum width of a glyph in pixels.
`hmap` Returns `True` if font is horizontally mapped. Should return `True`
`reverse` Returns `True` if bit reversal was specified. Should return `False`
`hmap` Returns `True` if font is horizontally mapped.
`reverse` Returns `True` if bit reversal was specified.
`monospaced` Returns `True` if monospaced rendering was specified.
`min_ch` Returns the ordinal value of the lowest character in the file.
`max_ch` Returns the ordinal value of the highest character in the file.
@ -92,6 +96,12 @@ and it returns the following values:
* The height in pixels.
* The character width in pixels.
The `font_to_py.py` utility allows a default glyph to be specified (typically
`?`). If called with an undefined character, this glyph will be returned.
The `min_ch` and `max_ch` methods are mainly relevant to contiguous character
sets.
# Licence
All code is released under the MIT licence.

Wyświetl plik

@ -1,33 +1,50 @@
# Device Driver Implementation
Display devices comprise two varieties, depending on whether the hardware
includes a frame buffer or whether the frame buffer is located on the
includes a frame buffer or whether a frame buffer must be located on the
controlling system.
In the latter case the [Writer](./WRITER.md) class extends the capability of
the driver to use multiple fonts plus additional functionality.
If the device has no frame buffer then the device driver should be designed
to subclass `framebuf.FrameBuffer` with a suitably sized buffer on the host. If
the device has its own frame buffer there are two options for the driver. One
is to perform all display operations using the device's own firmware
primitives. This is efficient and avoids the need for a buffer on the host,
however it does involve some code complexity.
Where the buffer is located on the display device, the means of controlling the
text insertion point and the means of performing partial buffer updates will be
device dependent. If the functionality of the `Writer` class is required it
must be implemented at device driver level.
The second option is to subclass `framebuf.FrameBuffer`, provide a buffer on
the host, and copy its contents to the device's buffer when required. This can
result in a very simple device driver at cost of RAM use and update speed. It
also ensures compatibility with additional libraries to simplify display tasks.
If a device subclasses `framebuf.FrameBuffer` the following libraries enhance
its capability. The [Writer](./WRITER.md) class enables it to use multiple
fonts with additional functionality such as word wrap, string metrics and tab
handling. The [nano-gui](https://github.com/peterhinch/micropython-nano-gui.git)
provides rudimentary GUI capability.
If a driver relies on a buffer located on the display device, the means of
controlling the text insertion point, performing partial buffer updates and
executing graphics primitives will be device dependent. If the functionality of
the `writer` or `nanogui` libraries are required it will need to be
implemented at device driver level.
###### [Main README](../README.md)
## Drivers for unbuffered displays
# Drivers subclassed from framebuf
Where the buffer is held on the MicroPython host the driver should be
subclassed from the official `framebuf` module. An example of such a driver is
the [official SSD1306 driver](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py).
subclassed from the official `framebuf.FrameBuffer` class. An example of such a
driver is the [official SSD1306 driver](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py).
In addition the driver class should have bound variables `width` and `height`
containing the size of the display in pixels. Color displays should have a
bound variable `mode` holding the `framebuf` color mode.
containing the size of the display in pixels, plus a `show` method which copies
the buffer to the physical device.
The device driver defines a buffer of the correct size to hold a full frame of
data and instantiates the `framebuf.FrameBuffer` superclass to reference it.
The `FrameBuffer` mode is selected to match the layout of the target display.
The driver implements a `show` method to efficiently copy the buffer contents
to the display hardware.
Monochrome displays should define the frame buffer format to match the physical
characteristics of the display. In the case of colour displays RAM may be saved
by using `framebuf.GS8` 8-bit colour. The `show` method can map this to the
device's colour space if 8-bit mode is not supported.
This design enables the supplied `Writer` and `CWriter` classes to be used for
rendering arbitrary fonts to the display. The author of the device driver need
@ -37,14 +54,26 @@ The `Writer` and `CWriter` classes require horizontally mapped fonts. This is
regardless of the mapping used in the device driver's `FrameBuffer`: the
`Writer.printstring` method deals transparently with any mismatch.
## Drivers for buffered displays
## Example drivers
The following drivers are subclassed from `framebuf.FrameBuffer` and have been
tested with `writer.py` and `nanogui.py`.
* The [SSD1306 OLED driver](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py)
* The [Nokia 5110](https://github.com/mcauser/micropython-pcd8544/blob/master/pcd8544_fb.py)
* The [SSD1331 colour OLED](https://github.com/peterhinch/micropython-nano-gui/blob/master/drivers/ssd1331/ssd1331.py)
The latter example illustrates a very simple driver which provides full access
to `writer.py` and `nanogui.py` libraries.
# Drivers using the display buffer
Authors of such drivers will need to have an understanding of the font file
format.
### Specifying the font layout
## Specifying the font layout
Each font file has a `get_ch()` function accepting an ASCII character as its
Each font file has a `get_ch()` function accepting a character as its
argument. It returns a memoryview instance providing access to a bytearray
corresponding to the individual glyph. The layout of this data is determined by
the command line arguments presented to the `font_to_py.py` utility. It is
@ -196,10 +225,11 @@ i.e. a byte comprising bits [b7b6b5b4b3b2b1b0] becomes [b0b1b2b3b4b5b6b7].
The design aims primarily to minimise RAM usage. Minimising the size of the
bytecode is a secondary aim. Indexed addressing is used to reduce this in
the case of proportional fonts, at a small cost in performance. The size of the
Python source file is a lesser consideration, with readability being prioritised
over size. Hence they are "pretty formatted" with the large bytes objects
split over multiple lines for readability.
the case of proportional fonts, and also to facilitate non-contiguous fonts, at
a small cost in performance. The size of the Python source file is a lesser
consideration, with readability being prioritised over size. Hence they are
"pretty formatted" with the large bytes objects split over multiple lines for
readability.
Fonts created with the `font_to_py` utility have been extensively tested with
each of the mapping options. They are used with drivers for SSD1306 OLEDs,

Wyświetl plik

@ -1,12 +1,17 @@
# Writer and Cwriter classes
These classes facilitate rendering Python font files to displays where the
display driver is subclassed from the `framebuf` class. An example is the
official [SSD1306 driver](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py).
display driver is subclassed from the `framebuf` class. Examples are:
* The official [SSD1306 driver](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py).
* The [PCD8544/Nokia 5110](https://github.com/mcauser/micropython-pcd8544.git).
* The [Adafruit 0.96 inch color OLED](https://www.adafruit.com/product/684)
with [this driver](https://github.com/peterhinch/micropython-nano-gui/tree/master/drivers/ssd1331).
Basic support is for scrolling text display using multiple fonts. The
`writer_gui` module provides optional extensions for user interface objects
displayed at arbitrary locations on screen.
[nanogui](https://github.com/peterhinch/micropython-nano-gui.git) module has
optional extensions for user interface objects displayed at arbitrary locations
on screen.
Example code and images are for 128*64 SSD1306 OLED displays.
@ -23,7 +28,7 @@ Right justified text.
Mixed text and graphics.
![Image](images/fields.JPG)
Labels and Fields (from writer_gui.py).
Labels and Fields (from nanogui.py).
# Contents
@ -41,9 +46,7 @@ Labels and Fields (from writer_gui.py).
2.2.1 [Static Method](./WRITER.md#221-static-method)
2.2.2 [Constructor](./WRITER.md#222-constructor)
2.2.3 [Methods](./WRITER.md#223-methods)
3. [The writer_gui module](./WRITER.md#3-the-writer_gui-module)
3.1 [The Label class](./WRITER.md#31-the-label-class)
4. [Notes](./WRITER.md#4-notes)
3. [Notes](./WRITER.md#4-notes)
###### [Main README](../README.md)
@ -61,17 +64,19 @@ This update for practical applications has the following features:
* Tab support.
* String metrics to enable right or centre justification.
* Inverse (background color on foreground color) display.
* Labels: render static text at a fixed location.
* Fields - render dynamically changing text to a fixed rectangular region.
* Inverted display option.
Note that these changes have significantly increased code size. On the ESP8266
it is likely that `writer.py` will need to be frozen as bytecode. The original
very simple version still exists as `writer_minimal.py`.
## 1.1 Hardware
Tests and demos assume a 128*64 SSD1306 OLED display connected via I2C or SPI.
Wiring is specified in `ssd1306_setup.py`. Edit this to use a different bus or
for a non-Pyboard target. At the time of writing the default of software I2C
should be used: the official SSD1306 driver is not compatible with hardware I2C
(see [Notes](./WRITER.md#4-notes)).
(see [Notes](./WRITER.md#3-notes)).
## 1.2 Files
@ -240,58 +245,7 @@ The `printstring` method works as per the base class except that the string is
rendered in foreground color on background color (or reversed if `invert` is
`True`).
# 3. The writer_gui module
This supports user interface objects whose text components are drawn using the
`Writer` or `CWriter` classes. Upside down rendering is not supported: attempts
to specify it will produce unexpected results.
The objects are drawn at specific locations on screen and are incompatible with
the display of scrolling text: they are therefore not intended for use with the
writer's `printstring` method.
## 3.1 The Label class
This supports applications where text is to be rendered at specific screen
locations.
Text can be static or dynamic. In the case of dynamic text the background is
cleared to ensure that short strings can cleanly replace longer ones.
Labels can be displayed with an optional single pixel border.
Colors are handled flexibly. By default the colors used are those of the
`Writer` instance, however they can be changed dynamically, for example to warn
of overrange values.
Constructor args:
1. `writer` The `Writer` instance (font and screen) to use.
2. `row` Location on screen.
3. `col`
4. `text` If a string is passed it is displayed: typically used for static
text. If an integer is passed it is interpreted as the maximum text length
in pixels; typically obtained from `writer.stringlen('-99.99')`. Nothing is
dsplayed until `.value()` is called. Intended for dynamic text fields.
5. `invert=False` Display in inverted or normal style.
6. `fgcolor=None` Optionally override the `Writer` colors.
7. `bgcolor=None`
8. `bordercolor=False` If `False` no border is displayed. If `None` a border
is shown in the `Writer` forgeround color. If a color is passed, it is used.
The constructor displays the string at the required location.
Methods:
1. `value` Redraws the label. This takes the following args:
1. `text=None` The text to display. If `None` displays last value.
2. ` invert=False` If true, show inverse text.
3. `fgcolor=None` Foreground color: if `None` the `Writer` default is used.
4. `bgcolor=None` Background color, as per foreground.
5. `bordercolor=None` As per above except that if `False` is passed, no
border is displayed. This clears a previously drawn border.
Returns the current text string.
2. `show` No args. (Re)draws the label. For future/subclass use.
# 4. Notes
# 3. Notes
Possible future enhancements:
1. General rendering to a rectangular area. This may be problematic as the

Wyświetl plik

@ -102,7 +102,7 @@ class Writer():
def _newline(self):
s = self._getstate()
height = self.height()
height = self.font.height()
if self.usd:
s.text_row -= height
s.text_col = self.screenwidth - 1
@ -130,7 +130,8 @@ class Writer():
self.wrap = wrap
return self.row_clip, self.col_clip, self.wrap
def height(self):
@property
def height(self): # Property for consistency with device
return self.font.height()
def printstring(self, string, invert=False):

Wyświetl plik

@ -1,8 +1,10 @@
# ssd1306_test.py Demo pogram for rendering arbitrary fonts to an SSD1306 OLED display.
# writer_demo.py Demo pogram for rendering arbitrary fonts to an SSD1306 OLED display.
# Illustrates a minimal example. Requires ssd1306_setup.py which contains
# wiring details.
# The MIT License (MIT)
#
# Copyright (c) 2016 Peter Hinch
# Copyright (c) 2018 Peter Hinch
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal

Wyświetl plik

@ -1,8 +1,8 @@
# ssd1306_test.py Demo pogram for rendering arbitrary fonts to an SSD1306 OLED display.
# ssd1306_test.py Demo program for rendering arbitrary fonts to an SSD1306 OLED display.
# The MIT License (MIT)
#
# Copyright (c) 2016 Peter Hinch
# Copyright (c) 2018 Peter Hinch
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@ -33,12 +33,13 @@ import utime
import uos
from ssd1306_setup import WIDTH, HEIGHT, setup
from writer import Writer, CWriter
from writer_gui import Label
from writer_gui import Label, Meter
# Fonts
import freesans20
import courier20 as fixed
import font6 as small
import arial10
def inverse(use_spi=False, soft=True):
ssd = setup(use_spi, soft) # Create a display instance
@ -175,50 +176,6 @@ def wrap(use_spi=False, soft=True):
wri.printstring('the quick brown fox jumps over')
ssd.show()
def fields(use_spi=False, soft=True):
ssd = setup(use_spi, soft) # Create a display instance
Writer.set_textpos(ssd, 0, 0) # In case previous tests have altered it
wri = Writer(ssd, fixed, verbose=False)
wri.set_clip(False, False, False)
textfield = Label(wri, 0, 2, wri.stringlen('longer'))
numfield = Label(wri, 25, 2, wri.stringlen('99.99'), bordercolor=None)
countfield = Label(wri, 0, 90, wri.stringlen('1'))
n = 1
for s in ('short', 'longer', '1', ''):
textfield.value(s)
numfield.value('{:5.2f}'.format(int.from_bytes(uos.urandom(2),'little')/1000))
countfield.value('{:1d}'.format(n))
n += 1
ssd.show()
utime.sleep(2)
textfield.value('Done', True)
ssd.show()
def multi_fields(use_spi=False, soft=True):
ssd = setup(use_spi, soft) # Create a display instance
Writer.set_textpos(ssd, 0, 0) # In case previous tests have altered it
wri = Writer(ssd, small, verbose=False)
wri.set_clip(False, False, False)
nfields = []
dy = small.height() + 6
y = 2
col = 15
width = wri.stringlen('99.99')
for txt in ('X:', 'Y:', 'Z:'):
Label(wri, y, 0, txt)
nfields.append(Label(wri, y, col, width, bordercolor=None)) # Draw border
y += dy
for _ in range(10):
for field in nfields:
value = int.from_bytes(uos.urandom(3),'little')/167722
field.value('{:5.2f}'.format(value))
ssd.show()
utime.sleep(1)
Label(wri, 0, 64, ' DONE ', True)
ssd.show()
def dual(use_spi=False, soft=True):
ssd0 = setup(False, soft) # I2C display
ssd1 = setup(True, False) # SPI instance
@ -243,7 +200,7 @@ def dual(use_spi=False, soft=True):
for _ in range(10):
for n, wri in enumerate((wri0, wri1)):
for field in nfields[n]:
value = int.from_bytes(uos.urandom(3),'little')/167722
value = int.from_bytes(uos.urandom(3),'little')/167772
field.value('{:5.2f}'.format(value))
wri.device.show()
utime.sleep(1)
@ -268,8 +225,6 @@ fonts() Two fonts.
tabs() Tab stops.
usd_tabs() Upside-down tabs.
wrap() Word wrapping
fields() Label test with dynamic data.
multi_fields() More Labels.
dual() Test two displays on one host.'''
print(tstr)