kopia lustrzana https://github.com/peterhinch/micropython-font-to-py
194 wiersze
7.9 KiB
Markdown
194 wiersze
7.9 KiB
Markdown
|
# Device Driver Implementation
|
||
|
|
||
|
Display devices comprise two varieties, depending on whether the hardware
|
||
|
includes a frame buffer or whether the frame buffer is located on the
|
||
|
controlling system.
|
||
|
|
||
|
In the latter case the [Writer](./WRITER.md) class extends the capability of
|
||
|
the driver to use multiple fonts plus additional functionality.
|
||
|
|
||
|
Where the buffer is located on the display device, the means of controlling the
|
||
|
text insertion point and the means of performing partial buffer updates will be
|
||
|
device dependent. If the functionality of the `Writer` class is required it
|
||
|
must be implemented at device driver level.
|
||
|
|
||
|
###### [Main README](../README.md)
|
||
|
|
||
|
## Drivers for unbuffered displays
|
||
|
|
||
|
Where the buffer is held on the MicroPython host the driver should be
|
||
|
subclassed from the official `framebuf` module. An example of such a driver is
|
||
|
the [official SSD1306 driver](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py).
|
||
|
In addition the driver class should have bound variables `width` and `height`
|
||
|
containing the size of the display in pixels.
|
||
|
|
||
|
The device driver defines a buffer of the correct size to hold a full frame of
|
||
|
data and instantiates the `framebuf.FrameBuffer` superclass to reference it.
|
||
|
The `FrameBuffer` mode is selected to match the layout of the target display.
|
||
|
The driver implements a `show` method to efficiently copy the buffer contents
|
||
|
to the display hardware.
|
||
|
|
||
|
This design enables the supplied `Writer` and `CWriter` classes to be used for
|
||
|
rendering arbitrary fonts to the display. The author of the device driver need
|
||
|
not be concerned with the format of Python font files.
|
||
|
|
||
|
The `Writer` and `CWriter` classes require horizontally mapped fonts. This is
|
||
|
regardless of the mapping used in the device driver's `FrameBuffer`: the
|
||
|
`Writer.printstring` method deals transparently with any mismatch.
|
||
|
|
||
|
## Drivers for buffered displays
|
||
|
|
||
|
Authors of such drivers will need to have an understanding of the font file
|
||
|
format.
|
||
|
|
||
|
### Specifying the font layout
|
||
|
|
||
|
Each font file has a `get_ch()` function accepting an ASCII character as its
|
||
|
argument. It returns a memoryview instance providing access to a bytearray
|
||
|
corresponding to the individual glyph. The layout of this data is determined by
|
||
|
the command line arguments presented to the `font_to_py.py` utility. It is
|
||
|
the responsibility of the driver to copy that data to the physical device.
|
||
|
|
||
|
The purpose of the `font_to_py.py` command line arguments specified to the
|
||
|
user is to ensure that the data layout is optimised for the device so that this
|
||
|
copy operation is a fast bytewise copy or SPI/I2C transfer. The driver
|
||
|
documentation should therefore specify these arguments to ensure the layout is
|
||
|
optimal. Mapping may be horizontal or vertical, and the bit order of individual
|
||
|
bytes may be defined. These are detailed below.
|
||
|
|
||
|
In the case of devices with their own frame buffer the `Writer` class will need
|
||
|
to be re-written or adapted to match the hardware's method of tracking such
|
||
|
things as the text insertion point. Consideration should be given to employing
|
||
|
the same interface as the `Writer` class to simplify the porting of user code
|
||
|
between displays with differing hardware.
|
||
|
|
||
|
## Python Font files
|
||
|
|
||
|
Assume the user has run the utility to produce a file `myfont.py` This then
|
||
|
has the following outline definition (in practice the bytes objects are large):
|
||
|
|
||
|
```python
|
||
|
# Code generated by font-to-py.py.
|
||
|
# Font: FreeSerif.ttf
|
||
|
version = '0.2'
|
||
|
|
||
|
def height():
|
||
|
return 21
|
||
|
|
||
|
def max_width():
|
||
|
return 22
|
||
|
|
||
|
def hmap():
|
||
|
return False
|
||
|
|
||
|
def reverse():
|
||
|
return False
|
||
|
|
||
|
def monospaced():
|
||
|
return False
|
||
|
|
||
|
def min_ch():
|
||
|
return 32
|
||
|
|
||
|
def max_ch():
|
||
|
return 126
|
||
|
|
||
|
_font =\
|
||
|
b'\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
|
||
|
b'\x00\x00\x00\x00\x08\x00\xfe\xc7\x00\x7e\xc0\x00\x00\x00\x00\x00'\
|
||
|
|
||
|
_index =\
|
||
|
b'\x00\x00\x14\x00\x2e\x00\x4b\x00\x71\x00\x97\x00\xd2\x00\x0a\x01'\
|
||
|
b'\x1b\x01\x35\x01\x4f\x01\x75\x01\x9e\x01\xb2\x01\xcc\x01\xe0\x01'\
|
||
|
|
||
|
_mvfont = memoryview(_font)
|
||
|
# Boilerplate code omitted
|
||
|
|
||
|
def get_ch(ch):
|
||
|
# validate ch, if out of range use '?'
|
||
|
# get offsets into _font and retrieve char width
|
||
|
# Return: memoryview of bitmap, height and width
|
||
|
return mvfont[offset + 2, next_offset], height, width
|
||
|
```
|
||
|
|
||
|
`height` and `width` are specified in bits (pixels).
|
||
|
|
||
|
In the case of monospaced fonts the `max_width` function returns the width of
|
||
|
every character. For variable pitch fonts it returns the width of the widest
|
||
|
character. Device drivers can use this to rapidly determine whether a string
|
||
|
will fit the available space. If it will fit on the assumption that all chars
|
||
|
are maximum width, it can be rendered rapidly without doing a character by
|
||
|
character check.
|
||
|
|
||
|
`get_ch()` returns a memoryview of an individual glyph with its dimensions
|
||
|
and contains all the bytes required to render the character including trailing
|
||
|
space.
|
||
|
|
||
|
## Fixed width fonts
|
||
|
|
||
|
If a Python font file is created with the `-f` argument, all characters will
|
||
|
be saved with the width of the widest. In general it is not necessary to
|
||
|
specify this option. The driver can perform fixed pich rendering by rendering
|
||
|
the character as variable pitch, then blanking and advancing the pixel column
|
||
|
by the value returned by `font.max_width()`.
|
||
|
|
||
|
## Binary font files
|
||
|
|
||
|
This format is unlikely to find application beyond the e-paper driver. It was
|
||
|
designed for micropower applications where the Pyboard has no SD card. Fonts
|
||
|
are stored as random access files on power-switched Flash storage or SD card.
|
||
|
This method is probably too slow for anything other than e-paper displays.
|
||
|
|
||
|
The format is as follows. Files are binary with a four byte header and 126
|
||
|
fixed length records. The header consists of two file identifiers enabling the
|
||
|
file format to be checked, followed by bytes specifying the width and height.
|
||
|
The length of each record is (width + 1) bytes.
|
||
|
|
||
|
The file indentifiers depend on the -x and -r arguments specified to `font_to_py.py`
|
||
|
and are as follows:
|
||
|
|
||
|
hmap reverse byte
|
||
|
-x -r 0 1
|
||
|
0 0 0x3f 0xe7
|
||
|
1 0 0x40 0xe7
|
||
|
0 1 0x41 0xe7
|
||
|
1 1 0x42 0xe7
|
||
|
|
||
|
Each record starts with a width byte specifying the x dimension of the glyph if
|
||
|
rendered proportionally spaced, followed by the glyph data. This data includes
|
||
|
trailing space ensuring that all records have the size specified in the header.
|
||
|
|
||
|
## Mapping (Python and Binary fonts)
|
||
|
|
||
|
A character occupies a space where (0, 0) represents the coordinates of the top
|
||
|
left hand corner of the bitmap. It comprises a set of pixels where increasing x
|
||
|
values represent locations to the right of the origin and increasing y values
|
||
|
represent downward positions. Mapping defines the relationship between this
|
||
|
abstract two dimensional array of bits and the physical linear sequence of bytes.
|
||
|
|
||
|
Vertical mapping means that the LSB of first byte is pixel (0,0), MSB of first
|
||
|
byte is (0, 7). The second byte (assuming the height is greater than 8 pixels)
|
||
|
is (0, 8) to (0, 15). Once the column is complete the next byte represents
|
||
|
(1, 0) to (1, 7).
|
||
|
|
||
|
Horizontal mapping means that the MSB of byte 0 is pixel (0,0) with LSB at
|
||
|
(7,0), with the second byte covering (8, 0) to (15, 0) if the width is greater
|
||
|
than 8.
|
||
|
|
||
|
Bit reversal provides for the case where the bit order of each byte is reversed
|
||
|
i.e. a byte comprising bits [b7b6b5b4b3b2b1b0] becomes [b0b1b2b3b4b5b6b7].
|
||
|
|
||
|
# Specification and Project Notes
|
||
|
|
||
|
The design aims primarily to minimise RAM usage. Minimising the size of the
|
||
|
bytecode is a secondary aim. Indexed addressing is used to reduce this in
|
||
|
the case of proportional fonts, at a small cost in performance. The size of the
|
||
|
Python source file is a lesser consideration, with readability being prioritised
|
||
|
over size. Hence they are "pretty formatted" with the large bytes objects
|
||
|
split over multiple lines for readability.
|
||
|
|
||
|
Fonts created with the `font_to_py` utility have been extensively tested with
|
||
|
each of the mapping options. They are used with drivers for SSD1306 OLEDs,
|
||
|
SSD1963 LCD displays, the official LCD160CR and the Digital Artists 2.7 inch
|
||
|
e-paper display.
|