kopia lustrzana https://github.com/peterhinch/micropython-font-to-py
Docs updated
rodzic
bb4c0415d6
commit
1bcf06c531
|
@ -0,0 +1,216 @@
|
||||||
|
# DRIVERS.md
|
||||||
|
|
||||||
|
This document comprises two sections, the first for users of an existing device
|
||||||
|
driver and the second for writers of device drivers.
|
||||||
|
|
||||||
|
# User Documentation: the Writer class
|
||||||
|
|
||||||
|
This class facilitates rendering characters from Python font files to a device,
|
||||||
|
assuming the device has a driver conforming to the specification below. Typical
|
||||||
|
use is as follows:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from writer import Writer
|
||||||
|
from device_driver import Display
|
||||||
|
import freeserif
|
||||||
|
import freesans20
|
||||||
|
display = Display(args_required_by_driver)
|
||||||
|
wri_serif = Writer(display, freeserif)
|
||||||
|
wri_sans = Writer(display, freesans20)
|
||||||
|
Writer.set_clip(True, True)
|
||||||
|
wri_serif.printstring('Tuesday\n')
|
||||||
|
wri_sans.printstring('8 Nov 2016\n')
|
||||||
|
wri_sans.printstring('10.30am')
|
||||||
|
|
||||||
|
display.show() # Display the result
|
||||||
|
```
|
||||||
|
|
||||||
|
The file ``driver_test.py`` illustrates the use of font files with an SSD1306
|
||||||
|
display and a complete example of an SSD1306 driver may be found
|
||||||
|
[here](https://github.com/peterhinch/micropython-samples/tree/master/SSD1306).
|
||||||
|
|
||||||
|
## Class Methods
|
||||||
|
|
||||||
|
The ``Writer`` class exposes the following class methods:
|
||||||
|
|
||||||
|
1. ``set_textpos`` Args: ``row``, ``col``. This determines where on screen any
|
||||||
|
subsequent text is to be rendered. The initial value is (0, 0) - the top left
|
||||||
|
corner. Arguments are in pixels with positive values representing down and
|
||||||
|
right respectively. They reference the top left hand corner of the first
|
||||||
|
character to be output.
|
||||||
|
2. ``set_clip`` Args: boolean ``row_clip``, ``col_clip``. If these are
|
||||||
|
``True``, characters will be clipped if they extend beyond the boundaries of
|
||||||
|
the physical display. If ``col_clip`` is ``False`` characters will wrap onto
|
||||||
|
the next line. If ``row_clip`` is ``False`` the display will, where necessary,
|
||||||
|
scroll up to ensure the line is rendered.
|
||||||
|
3. ``mapping`` Arg: an integer. This defines the mapping of bytes in the
|
||||||
|
buffer onto pixels. The module exposes three constants for use here: ``VERT``
|
||||||
|
``HORIZ`` and ``WEIRD``, the latter being specific to the official SSD1306
|
||||||
|
driver. ``VERT`` is for true vertically mapped displays. ``HORIZ``, for
|
||||||
|
horizontally mapped devices, is currently unsupported. By default the mapping
|
||||||
|
is for SSD1306 devices using the official driver.
|
||||||
|
|
||||||
|
As class methods these settings apply to all font objects. The insertion point
|
||||||
|
of characters is maintained regardless of the font in use.
|
||||||
|
|
||||||
|
## Method
|
||||||
|
|
||||||
|
1. ``printstring`` Arg: a text string. Outputs a text string at the current
|
||||||
|
insertion point. Newline characters are honoured.
|
||||||
|
|
||||||
|
# Device Driver Implementation
|
||||||
|
|
||||||
|
Display devices comprise two varieties, depending on whether the framebuffer is
|
||||||
|
located on the controlling system or on the physical display device. In the
|
||||||
|
former case the ``Writer`` class simplifies the design of the driver. It merely
|
||||||
|
has to expose certin attributes and methods with ``Writer`` instances taking
|
||||||
|
care of text rendering. It is strongly recommended that such device drivers use
|
||||||
|
the oficial ``framebuf`` module, as per the official SSD1306 driver.
|
||||||
|
|
||||||
|
Where the buffer is located on the display device the means of controlling the
|
||||||
|
text insertion point will be device dependent. The driver will need to
|
||||||
|
implement the functionality of the ``Writer`` class itself.
|
||||||
|
|
||||||
|
## Drivers with local buffers
|
||||||
|
|
||||||
|
The writer of a device driver need not be concerned with the structure of a
|
||||||
|
Python font file so long as the driver exposes certain attributes and methods
|
||||||
|
required by the ``Writer`` class. These are as follows:
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
|
||||||
|
1. ``buffer`` The underlying ``bytearray`` instance holding the display
|
||||||
|
buffer.
|
||||||
|
2. ``height`` The screen height in pixels.
|
||||||
|
3. ``width`` The screen width in pixels.
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
|
||||||
|
1. ``show`` Display the current buffer contents.
|
||||||
|
2. ``scroll`` Arguments ``x``, ``y`` amount to scroll horizontal and vertical.
|
||||||
|
3. ``fill`` Argument ``col`` colour 1 == fill 0 == clear.
|
||||||
|
|
||||||
|
An example of such a driver, using the official ``framebuf`` module, is the
|
||||||
|
SSD1306 driver (drivers/display/ssd1306.py in the source tree).
|
||||||
|
|
||||||
|
The driver documentation should specify the arguments for font_to_py.py to
|
||||||
|
ensure users create font files with a layout corresponding to that of the
|
||||||
|
buffer/device.
|
||||||
|
|
||||||
|
## Drivers for remote buffers
|
||||||
|
|
||||||
|
### Specifying the font file
|
||||||
|
|
||||||
|
Each font file has a ``get_ch()`` function accepting an ASCII character as its
|
||||||
|
argument. It returns a memoryview instance providing access to a bytearray
|
||||||
|
corresponding to the individual glyph. The layout of this data is determined by
|
||||||
|
the command line arguments presented to the ``font_to_py.py`` utility. The
|
||||||
|
driver documentation should specify these to ensure the layout is optimal to
|
||||||
|
simplify and speed transfer to the device. Mapping may be horizontal or
|
||||||
|
vertical, and the bit order of individual bytes may be defined. These are
|
||||||
|
detailed below.
|
||||||
|
|
||||||
|
The Python source file provides a fast means of accessing the byte data
|
||||||
|
corresponding to an individual character. It is the responsibility of the
|
||||||
|
driver to copy that data to the physical device. The ``font_to_py.py``
|
||||||
|
utility has command line arguments specifying the organisation of data returned
|
||||||
|
by the font's ``get_ch()`` function.
|
||||||
|
|
||||||
|
The purpose of the command line arguments specified to the user is to
|
||||||
|
ensure that the data layout is optimised for the device so that the copy is a
|
||||||
|
simple bytewise copy or SPI/I2C transfer.
|
||||||
|
|
||||||
|
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. Consider maintaining the class's interface
|
||||||
|
to simplify the writing of user code capable of being ported between displays.
|
||||||
|
|
||||||
|
## 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.1'
|
||||||
|
|
||||||
|
def height():
|
||||||
|
return 21
|
||||||
|
|
||||||
|
def max_width():
|
||||||
|
return 22
|
||||||
|
|
||||||
|
def hmap():
|
||||||
|
return False
|
||||||
|
|
||||||
|
def reverse():
|
||||||
|
return False
|
||||||
|
|
||||||
|
def monospaced():
|
||||||
|
return False
|
||||||
|
|
||||||
|
_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'\
|
||||||
|
|
||||||
|
# Boilerplate code omitted
|
||||||
|
|
||||||
|
def get_ch(ch):
|
||||||
|
# validate ch, if out of range use '?'
|
||||||
|
# get offsets into _font and retrieve char width
|
||||||
|
# Return: address of start of bitmap, height and width
|
||||||
|
return memoryview(_font[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.
|
||||||
|
|
||||||
|
## Mapping
|
||||||
|
|
||||||
|
A character occupies a space where (0, 0) represents the coordinates of the top
|
||||||
|
left hand corner of the bitmap. It comprises a set of pixels where increasing x
|
||||||
|
values represent locations to the right of the origin and increasing y values
|
||||||
|
represent downward positions. Mapping is the process whereby this two
|
||||||
|
dimensional array of bits is transformed into a linear sequence of bytes.
|
||||||
|
|
||||||
|
Vertical mapping means that the LSB of first byte is pixel (0,0), MSB of first
|
||||||
|
byte is (0, 7). The second byte (assuming the height is greater than 8 pixels)
|
||||||
|
is (0, 8) to (0, 15). Once the column is complete the next byte represents
|
||||||
|
(1, 0) to (1, 7).
|
||||||
|
|
||||||
|
Horizontal mapping means that the MSB of byte 0 is pixel (0,0) with LSB at
|
||||||
|
(7,0), with the second byte covering (8, 0) to (15, 0) if the width is greater
|
||||||
|
than 8.
|
||||||
|
|
||||||
|
Bit reversal provides for the case where the bit order of each byte is reversed
|
||||||
|
i.e. a byte comprising bits [b7b6b5b4b3b2b1b0] becomes [b0b1b2b3b4b5b6b7].
|
||||||
|
|
||||||
|
# Specification and Project Notes
|
||||||
|
|
||||||
|
The design aims primarily to minimise RAM usage. Minimising the size of the
|
||||||
|
bytecode is a secondary aim. Indexed addressing is used to reduce this in
|
||||||
|
the case of proportional fonts, at a small cost in performance. The size of the
|
||||||
|
Python source file is a lesser consideration, with readability being prioritised
|
||||||
|
over size. Hence they are "pretty formatted" with the large bytes objects
|
||||||
|
split over multiple lines for readability.
|
||||||
|
|
||||||
|
The approach has been tested on SSD1306 devices using both the pseudo-horizontal
|
||||||
|
and true vertical mapping.
|
||||||
|
|
||||||
|
The ``font_to_py`` utility has been extensively tested with each of the mapping
|
||||||
|
options.
|
|
@ -0,0 +1,97 @@
|
||||||
|
# font_to_py.py
|
||||||
|
|
||||||
|
Convert a font file to Python source code.
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
``font_to_py.py`` is a command line utility written in Python 3. It is run on a
|
||||||
|
PC. It takes as input a font file with a ``ttf`` or ``otf`` extension and a
|
||||||
|
required height in pixels and outputs a Python 3 source file. The pixel layout
|
||||||
|
is determined by command arguments. By default fonts are stored in variable
|
||||||
|
pitch form. This may be overidden by a command line argument.
|
||||||
|
|
||||||
|
Further arguments ensure that the byte contents and layout are correct for the
|
||||||
|
target display hardware. Their usage should be specified in the documentation
|
||||||
|
for the device driver.
|
||||||
|
|
||||||
|
Example usage to produce a file ``myfont.py`` with height of 23 pixels:
|
||||||
|
``font_to_py.py FreeSans.ttf 23 myfont.py``
|
||||||
|
|
||||||
|
## Arguments
|
||||||
|
|
||||||
|
### Mandatory positional arguments:
|
||||||
|
|
||||||
|
1. Font file path. Must be a ttf or otf file.
|
||||||
|
2. Height in pixels.
|
||||||
|
3. Output file path. Must have a .py extension.
|
||||||
|
|
||||||
|
### Optional arguments:
|
||||||
|
|
||||||
|
* -f or --fixed If specified, all characters will have the same width. By
|
||||||
|
default fonts are assumed to be variable pitch.
|
||||||
|
* -x Specifies horizontal mapping (default is vertical).
|
||||||
|
* -b Specifies bit reversal in each font byte.
|
||||||
|
|
||||||
|
Optional arguments other than the fixed pitch argument will be specified in the
|
||||||
|
device driver documentation. Bit reversal is required by some display hardware.
|
||||||
|
|
||||||
|
## The font file
|
||||||
|
|
||||||
|
Assume that the you have employed the utility to create a file ``myfont.py``. In
|
||||||
|
your code you will issue
|
||||||
|
|
||||||
|
```python
|
||||||
|
import myfont
|
||||||
|
```
|
||||||
|
|
||||||
|
The ``myfont`` module name will then be passed to the device driver to render
|
||||||
|
strings on demand. The detailed layout of the Python file may be seen [here](./DRIVERS.md).
|
||||||
|
|
||||||
|
# Dependencies, links and licence
|
||||||
|
|
||||||
|
The code is released under the MIT licence. It 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).
|
||||||
|
|
||||||
|
# Appendix: RAM utilisation Test Results
|
||||||
|
|
||||||
|
A font file was created, frozen as bytecode and deployed to a version 1.0
|
||||||
|
Pyboard. The font was saved as variable pitch with a height of 19 pixels. The
|
||||||
|
following code was then pasted at the REPL:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import gc, micropython
|
||||||
|
gc.collect()
|
||||||
|
micropython.mem_info()
|
||||||
|
|
||||||
|
import freeserif
|
||||||
|
|
||||||
|
gc.collect()
|
||||||
|
micropython.mem_info()
|
||||||
|
|
||||||
|
def foo():
|
||||||
|
addr, height, width = freeserif.get_ch('a')
|
||||||
|
|
||||||
|
foo()
|
||||||
|
|
||||||
|
gc.collect()
|
||||||
|
micropython.mem_info()
|
||||||
|
print(len(freeserif._font) + len(freeserif._index))
|
||||||
|
```
|
||||||
|
|
||||||
|
The memory used was 5408, 5648, and 5696 bytes. As increments over the initial
|
||||||
|
state this corresponds to 240 and 288 bytes. The ``print`` statement shows the
|
||||||
|
RAM which would be consumed by the data arrays: this was 3271 bytes.
|
||||||
|
|
||||||
|
The ``foo()`` function emulates the behaviour of a device driver in rendering a
|
||||||
|
character to a display. The local variables constitute memory which will be
|
||||||
|
reclaimed on exit from the function. Its additional RAM use was 48 bytes.
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
With a font of height 19 pixels RAM saving was an order of magnitude. The
|
||||||
|
saving will be greater if larger fonts are used
|
269
README.md
269
README.md
|
@ -1,21 +1,9 @@
|
||||||
# font_to_py.py
|
# MicroPython font handling
|
||||||
|
|
||||||
This is a utility written in Python 3 and intended to be run on a PC. It takes
|
This is an attempt to offer a standard method of creating and deploying fonts
|
||||||
as input a font file in ttf or otf form and a height and outputs a Python
|
to MicroPython display drivers.
|
||||||
source file containing the font data. The purpose is to enable font files to be
|
|
||||||
used on microcontrollers running MicroPython: Python source files may be frozen
|
|
||||||
as bytecode. In this form they can be accessed at speed while using very little
|
|
||||||
RAM. The design has the following aims:
|
|
||||||
|
|
||||||
* Independence of specific display hardware.
|
# Introduction
|
||||||
* The path from font file to Python code to be fully open source.
|
|
||||||
|
|
||||||
The first is achieved by supplying hardware specific arguments to the utility.
|
|
||||||
These define horizontal or vertical mapping and the bit order for font data.
|
|
||||||
|
|
||||||
The second is achieved by using Freetype and the Freetype Python bindings.
|
|
||||||
|
|
||||||
# Rationale
|
|
||||||
|
|
||||||
MicroPython platforms generally have limited RAM, but more abundant storage in
|
MicroPython platforms generally have limited RAM, but more abundant storage in
|
||||||
the form of flash memory. Font files tend to be relatively large. The
|
the form of flash memory. Font files tend to be relatively large. The
|
||||||
|
@ -26,16 +14,46 @@ demand is too slow for reasonable performance on most display devices.
|
||||||
|
|
||||||
This alternative implements a font as a Python source file, with the data being
|
This alternative implements a font as a Python source file, with the data being
|
||||||
declared as ``bytes`` objects. Such a file may be frozen as bytecode. On import
|
declared as ``bytes`` objects. Such a file may be frozen as bytecode. On import
|
||||||
very little RAM is used, yet the data may be accessed fast.
|
very little RAM is used, yet the data may be accessed fast. Note that the use
|
||||||
|
of frozen bytecode is entirely optional: font files may be imported in the
|
||||||
|
normal way if RAM usage is not an issue.
|
||||||
|
|
||||||
It is intended that the resultant file be usable with a variety of display
|
It is intended that the resultant file be usable with two varieties of display
|
||||||
devices and drivers. These include:
|
devices and drivers. These comprise:
|
||||||
|
|
||||||
1. A driver for the official ``framebuffer`` class.
|
1. Drivers using ``bytearray`` instances as frame buffers, including the
|
||||||
2. Drivers using ``bytearray`` instances as frame buffers.
|
official ``framebuffer`` class.
|
||||||
3. Drivers for displays where the frame buffer is implemented in the display
|
2. Drivers for displays where the frame buffer is implemented in the display
|
||||||
device hardware.
|
device hardware.
|
||||||
|
|
||||||
|
# The proposed solution
|
||||||
|
|
||||||
|
This consists of three components:
|
||||||
|
|
||||||
|
1. font_to_py.py This is a utility intended to be run on a PC and converts a
|
||||||
|
font file to Python source. See below.
|
||||||
|
2. The Writer class (writer.py) This facilitates writing text to a device
|
||||||
|
given a suitably designed device driver. See [here](./DRIVERS.md).
|
||||||
|
3. A device driver specification. This includes an example for rendering text
|
||||||
|
to an SSD1306 device with arbitrary fonts. See [here](./DRIVERS.md).
|
||||||
|
|
||||||
|
# font_to_py.py
|
||||||
|
|
||||||
|
This is a command line utility written in Python 3 to be run on a PC. It takes
|
||||||
|
as input a font file in ttf or otf form and a height and outputs a Python
|
||||||
|
source file containing the font data. Fixed and variable pitch rendering are
|
||||||
|
supported. The design has the following aims:
|
||||||
|
|
||||||
|
* Independence of specific display hardware.
|
||||||
|
* The path from font file to Python code to be fully open source.
|
||||||
|
|
||||||
|
The first is achieved by supplying hardware specific arguments to the utility.
|
||||||
|
These define horizontal or vertical mapping and the bit order for font data.
|
||||||
|
|
||||||
|
The second is achieved by using Freetype and the Freetype Python bindings. Its
|
||||||
|
use is documented [here](./FONT_TO_PY.md). This also details measurements of
|
||||||
|
RAM usage when importing fonts stored as frozen bytecode.
|
||||||
|
|
||||||
# Limitations
|
# Limitations
|
||||||
|
|
||||||
Only the ASCII character set from ``chr(32)`` to ``chr(126)`` is supported.
|
Only the ASCII character set from ``chr(32)`` to ``chr(126)`` is supported.
|
||||||
|
@ -43,207 +61,10 @@ 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
|
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.
|
stage. It does assume that all pixels of a character are rendered identically.
|
||||||
|
|
||||||
# Usage
|
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
|
||||||
|
binary font files: these are hand designed for rendering at a specific size.
|
||||||
|
|
||||||
``font_to_py.py`` is a command line utility written in Python 3. It is run on a
|
# Licence
|
||||||
PC. It takes as input a font file with a ``ttf`` or ``otf`` extension and a
|
|
||||||
required height in pixels and outputs a Python 3 source file. The pixel layout
|
|
||||||
is determined by command arguments. By default fonts are stored in variable
|
|
||||||
pitch form. This may be overidden by a command line argument.
|
|
||||||
|
|
||||||
Further arguments ensure that the byte contents and layout are correct for the
|
All code is released under the MIT licence.
|
||||||
target display hardware. Their usage should be specified in the documentation
|
|
||||||
for the device driver.
|
|
||||||
|
|
||||||
Example usage to produce a file ``myfont.py`` with height of 23 pixels:
|
|
||||||
``font_to_py.py FreeSans.ttf 23 myfont.py``
|
|
||||||
|
|
||||||
## Arguments
|
|
||||||
|
|
||||||
### Mandatory positional arguments:
|
|
||||||
|
|
||||||
1. Font file path. Must be a ttf or otf file.
|
|
||||||
2. Height in pixels.
|
|
||||||
3. Output file path. Must have a .py extension.
|
|
||||||
|
|
||||||
### Optional arguments:
|
|
||||||
|
|
||||||
* -f or --fixed If specified, all characters will have the same width. By
|
|
||||||
default fonts are assumed to be variable pitch.
|
|
||||||
* -x Specifies horizontal mapping (default is vertical).
|
|
||||||
* -b Specifies bit reversal in each font byte.
|
|
||||||
|
|
||||||
Optional arguments other than the fixed pitch argument will be specified in the
|
|
||||||
device driver documentation. Bit reversal is required by some display hardware.
|
|
||||||
|
|
||||||
## The font file
|
|
||||||
|
|
||||||
Assume that the you have employed the utility to create a file ``myfont.py``. In
|
|
||||||
your code you will issue
|
|
||||||
|
|
||||||
```python
|
|
||||||
import myfont
|
|
||||||
```
|
|
||||||
|
|
||||||
The ``myfont`` module name will then be passed to the device driver to render
|
|
||||||
strings on demand.
|
|
||||||
|
|
||||||
# Test Results: RAM utilisation
|
|
||||||
|
|
||||||
A font file was created, frozen as bytecode and deployed to a version 1.0
|
|
||||||
Pyboard. The font was saved as variable pitch with a height of 19 pixels. The
|
|
||||||
following code was then pasted at the REPL:
|
|
||||||
|
|
||||||
```python
|
|
||||||
import gc, micropython
|
|
||||||
gc.collect()
|
|
||||||
micropython.mem_info()
|
|
||||||
|
|
||||||
from uctypes import bytearray_at
|
|
||||||
import freeserif
|
|
||||||
|
|
||||||
gc.collect()
|
|
||||||
micropython.mem_info()
|
|
||||||
|
|
||||||
def foo():
|
|
||||||
addr, height, width = freeserif.get_ch('a')
|
|
||||||
arr = bytearray_at(addr, 3*2)
|
|
||||||
|
|
||||||
foo()
|
|
||||||
|
|
||||||
gc.collect()
|
|
||||||
micropython.mem_info()
|
|
||||||
print(len(freeserif._font) + len(freeserif._index))
|
|
||||||
```
|
|
||||||
|
|
||||||
The memory used was 4416, 4704, and 4736 bytes. As increments over the initial
|
|
||||||
state this corresponds to 288 and 320 bytes. The ``print`` statement shows the
|
|
||||||
RAM which would be consumed by the data arrays: this was 3271 bytes.
|
|
||||||
|
|
||||||
The ``foo()`` function emulates the behaviour of a device driver in rendering a
|
|
||||||
character to a display. The local variables constitute memory which will be
|
|
||||||
reclaimed on exit from the function. Its additional RAM use was 32 bytes.
|
|
||||||
|
|
||||||
## Conclusion
|
|
||||||
|
|
||||||
With a font of height 19 pixels RAM saving was an order of magnitude. The
|
|
||||||
saving will be greater if larger fonts are used
|
|
||||||
|
|
||||||
|
|
||||||
# Dependencies, links and licence
|
|
||||||
|
|
||||||
The code is released under the MIT licence. It 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).
|
|
||||||
|
|
||||||
# Implementation
|
|
||||||
|
|
||||||
This section of the README is intended for writers of device drivers.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The Python source file produced by ``font_to_py.py`` provides a fast means of
|
|
||||||
accessing the byte data corresponding to an individual character. It is the
|
|
||||||
responsibility of the driver to copy that data to the framebuffer or physical
|
|
||||||
device. The purpose of the command line arguments specified to the user is to
|
|
||||||
ensure that the data layout is optimised for the device so that the copy is a
|
|
||||||
simple bytewise copy.
|
|
||||||
|
|
||||||
The user program imports a Python font file. When the user program needs to
|
|
||||||
display a string it passes the module name to the device driver. The module
|
|
||||||
exposes appropriate font metrics (defined in pixels) and a ``get_ch()``
|
|
||||||
function. The latter provides fast access to the bytes corresponding to an
|
|
||||||
individual character together with character specific metrics.
|
|
||||||
|
|
||||||
Fixed width characters include blank bits after the character bits to pad out
|
|
||||||
the width. Variable pitch characters include a small, character specific,
|
|
||||||
number of blank "advance" bits to provide correct spacing between characters.
|
|
||||||
|
|
||||||
## 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.1'
|
|
||||||
|
|
||||||
def height():
|
|
||||||
return 21
|
|
||||||
|
|
||||||
def max_width():
|
|
||||||
return 22
|
|
||||||
|
|
||||||
def hmap():
|
|
||||||
return False
|
|
||||||
|
|
||||||
def reverse():
|
|
||||||
return False
|
|
||||||
|
|
||||||
def monospaced():
|
|
||||||
return False
|
|
||||||
|
|
||||||
_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'\
|
|
||||||
|
|
||||||
# Boilerplate code omitted
|
|
||||||
|
|
||||||
def get_ch(ch):
|
|
||||||
# validate ch, if out of range use '?'
|
|
||||||
# get offsets into _font and retrieve char width
|
|
||||||
# Return: address of start of bitmap, height and width
|
|
||||||
return memoryview(_font[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.
|
|
||||||
|
|
||||||
## Mapping
|
|
||||||
|
|
||||||
A character occupies a space where (0, 0) represents the coordinates of the top
|
|
||||||
left hand corner of the bitmap. It comprises a set of pixels where increasing x
|
|
||||||
values represent locations to the right of the origin and increasing y values
|
|
||||||
represent downward positions. Mapping is the process whereby this two
|
|
||||||
dimensional array of bits is transformed into a 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 Notes
|
|
||||||
|
|
||||||
The design aims primarily to minimise RAM usage. Minimising the size of the
|
|
||||||
bytecode is a secondary aim. Indexed addressing will be 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 will be "pretty printed" with the large bytes objects
|
|
||||||
split over multiple lines for readability.
|
|
||||||
|
|
||||||
This general approach has been tested on a Pyboard connected to LCD hardware
|
|
||||||
having an onboard frame buffer. The visual performance is good.
|
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
# driver_test.py Simple test for rendering text to an ssd1306 display in
|
||||||
|
# arbitrary fonts
|
||||||
|
# V0.1 Peter Hinch Nov 2016
|
||||||
|
|
||||||
|
# The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016 Peter Hinch
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
|
||||||
|
# This demo uses a modified version of the official SSD1306 driver to fix an
|
||||||
|
# issue with the scroll method. Once the official version is fixed, it should
|
||||||
|
# be substituted for ssd1306_drv
|
||||||
|
|
||||||
|
import machine
|
||||||
|
import utime
|
||||||
|
from writer import Writer
|
||||||
|
from ssd1306_drv import SSD1306_I2C, SSD1306_SPI # Until official module is fixed
|
||||||
|
import freeserif
|
||||||
|
import freesans20
|
||||||
|
import inconsolata16
|
||||||
|
|
||||||
|
# Display parameters
|
||||||
|
WIDTH = const(128)
|
||||||
|
SPI = False
|
||||||
|
# Note that HEIGHT is set below
|
||||||
|
|
||||||
|
if SPI:
|
||||||
|
# Pyb SSD
|
||||||
|
# 3v3 Vin
|
||||||
|
# Gnd Gnd
|
||||||
|
# X1 DC
|
||||||
|
# X2 CS
|
||||||
|
# X3 Rst
|
||||||
|
# X6 CLK
|
||||||
|
# X8 DATA
|
||||||
|
HEIGHT = 32
|
||||||
|
pdc = machine.Pin('X1', machine.Pin.OUT_PP)
|
||||||
|
pcs = machine.Pin('X2', machine.Pin.OUT_PP)
|
||||||
|
prst = machine.Pin('X3', machine.Pin.OUT_PP)
|
||||||
|
spi = machine.SPI(1)
|
||||||
|
display = SSD1306_SPI(WIDTH, HEIGHT, spi, pdc, prst, pcs)
|
||||||
|
else: # I2C
|
||||||
|
# Pyb SSD
|
||||||
|
# 3v3 Vin
|
||||||
|
# Gnd Gnd
|
||||||
|
# Y9 CLK
|
||||||
|
# Y10 DATA
|
||||||
|
HEIGHT = 64
|
||||||
|
pscl = machine.Pin('Y9', machine.Pin.OUT_PP)
|
||||||
|
psda = machine.Pin('Y10', machine.Pin.OUT_PP)
|
||||||
|
i2c = machine.I2C(scl=pscl, sda=psda)
|
||||||
|
display = SSD1306_I2C(WIDTH, HEIGHT, i2c)
|
||||||
|
|
||||||
|
serif = Writer(display, freeserif)
|
||||||
|
sans = Writer(display, freesans20)
|
||||||
|
Writer.set_clip(True, True) # Disable auto scrolling and wrapping.
|
||||||
|
serif.printstring('Tuesday\n')
|
||||||
|
sans.printstring('8 Nov 2016\n')
|
||||||
|
sans.printstring('10.30am')
|
||||||
|
display.show()
|
||||||
|
|
||||||
|
|
||||||
|
def scroll_test(x, y):
|
||||||
|
t = utime.ticks_us()
|
||||||
|
display.scroll(x, y) # 125ms
|
||||||
|
print(utime.ticks_diff(utime.ticks_us(), t))
|
||||||
|
display.show()
|
|
@ -325,25 +325,19 @@ version = '0.1'
|
||||||
"""
|
"""
|
||||||
|
|
||||||
STR02 = """
|
STR02 = """
|
||||||
try:
|
|
||||||
from uctypes import addressof
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _chr_addr(ordch):
|
def _chr_addr(ordch):
|
||||||
offset = 2 * (ordch - 32)
|
offset = 2 * (ordch - 32)
|
||||||
return int.from_bytes(_index[offset:offset + 2], 'little')
|
return int.from_bytes(_index[offset:offset + 2], 'little')
|
||||||
|
|
||||||
def get_ch(ch, test=False):
|
def get_ch(ch):
|
||||||
ordch = ord(ch)
|
ordch = ord(ch)
|
||||||
ordch = ordch if ordch >= 32 and ordch <= 126 else ord('?')
|
ordch = ordch if ordch >= 32 and ordch <= 126 else ord('?')
|
||||||
offset = _chr_addr(ordch)
|
offset = _chr_addr(ordch)
|
||||||
width = int.from_bytes(_font[offset:offset + 2], 'little')
|
width = int.from_bytes(_font[offset:offset + 2], 'little')
|
||||||
if test:
|
next_offs = _chr_addr(ordch +1)
|
||||||
next_offs = _chr_addr(ordch +1)
|
return memoryview(_font[offset + 2:next_offs]), {}, width
|
||||||
return _font[offset + 2:next_offs], {}, width
|
|
||||||
return addressof(_font) + offset + 2, {}, width
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def write_func(stream, name, arg):
|
def write_func(stream, name, arg):
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
# ssd1306_drv.py An implementation of a Display class for SSD1306 based displays
|
||||||
|
# V0.1 Peter Hinch Nov 2016
|
||||||
|
|
||||||
|
# https://learn.adafruit.com/monochrome-oled-breakouts/wiring-128x32-spi-oled-display
|
||||||
|
# https://www.proto-pic.co.uk/monochrome-128x32-oled-graphic-display.html
|
||||||
|
|
||||||
|
# Version supports vertical and "horizontal" modes.
|
||||||
|
|
||||||
|
# The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016 Peter Hinch
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
|
||||||
|
# SSD1306 classes to fix the official one whose scroll method is broken
|
||||||
|
# This also demonstrates a way to do scrolling for devices with true vertical
|
||||||
|
# mapping (the official driver sets the device into its "horizontal" mode).
|
||||||
|
# This is a hybrid mode where bytes are aligned vertically but arranged
|
||||||
|
# horizontally
|
||||||
|
|
||||||
|
import ssd1306
|
||||||
|
|
||||||
|
class SSD1306(object):
|
||||||
|
def __init__(self, device):
|
||||||
|
self.width = device.width # In pixels
|
||||||
|
self.height = device.height
|
||||||
|
self.bpc = (self.height + 7) >> 3
|
||||||
|
self.device = device
|
||||||
|
self.vmap = False # Currently the official driver uses "horizontal"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def buffer(self): # property emulates underlying device
|
||||||
|
return self.device.buffer
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
self.device.show()
|
||||||
|
|
||||||
|
# Return map-independent offset into buffer
|
||||||
|
def _offset(self, x, y):
|
||||||
|
if self.vmap:
|
||||||
|
return y + x * self.bpc
|
||||||
|
else:
|
||||||
|
return y * self.width + x
|
||||||
|
|
||||||
|
def scroll(self, x, y):
|
||||||
|
dy = abs(y)
|
||||||
|
if dy:
|
||||||
|
self._scrolly(dy, y > 0)
|
||||||
|
dx = abs(x)
|
||||||
|
if dx:
|
||||||
|
self._scrollx(dx, x > 0)
|
||||||
|
|
||||||
|
def _scrolly(self, ystep, down):
|
||||||
|
buf = self.device.buffer
|
||||||
|
bpc = self.bpc
|
||||||
|
ystep_bytes = (ystep >> 3) + 1
|
||||||
|
ystep_bits = ystep & 7
|
||||||
|
if down:
|
||||||
|
for x in range(self.width):
|
||||||
|
for ydest in range(bpc - 1, -1, -1):
|
||||||
|
ysource = ydest - ystep_bytes
|
||||||
|
data = 0
|
||||||
|
if ysource + 1 >= 0:
|
||||||
|
data = buf[self._offset(x, ysource + 1)] << ystep_bits
|
||||||
|
if ysource >= 0:
|
||||||
|
data |= buf[self._offset(x, ysource)] >> 8 - ystep_bits
|
||||||
|
buf[self._offset(x, ydest)] = data
|
||||||
|
else:
|
||||||
|
for x in range(self.width):
|
||||||
|
for ydest in range(bpc):
|
||||||
|
ysource = ydest + ystep_bytes
|
||||||
|
data = 0
|
||||||
|
if ysource < bpc:
|
||||||
|
data = buf[self._offset(x, ysource)] << (8-ystep_bits)
|
||||||
|
if ysource - 1 < bpc:
|
||||||
|
data |= buf[self._offset(x, ysource - 1)] >> ystep_bits
|
||||||
|
buf[self._offset(x, ydest)] = data
|
||||||
|
|
||||||
|
def _scrollx(self, xstep, right): # scroll x
|
||||||
|
buf = self.device.buffer
|
||||||
|
bpc = self.bpc
|
||||||
|
for y in range(bpc):
|
||||||
|
if right: # Scroll right
|
||||||
|
for xdest in range(self.width - 1, -1, -1):
|
||||||
|
data = 0
|
||||||
|
xsource = xdest - xstep
|
||||||
|
if xsource >= 0:
|
||||||
|
data = buf[self._offset(xsource, y)]
|
||||||
|
buf[self._offset(xdest, y)] = data
|
||||||
|
else:
|
||||||
|
for xdest in range(0, self.width):
|
||||||
|
data = 0
|
||||||
|
xsource = xdest + xstep
|
||||||
|
if xsource < self.width:
|
||||||
|
data = buf[self._offset(xsource, y)]
|
||||||
|
buf[self._offset(xdest, y)] = data
|
||||||
|
|
||||||
|
class SSD1306_I2C(SSD1306):
|
||||||
|
def __init__(self, width, height, i2c, addr=0x3c, external_vcc=False):
|
||||||
|
device = ssd1306.SSD1306_I2C(width, height, i2c, addr, external_vcc)
|
||||||
|
super().__init__(device)
|
||||||
|
|
||||||
|
class SSD1306_SPI(SSD1306):
|
||||||
|
def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
|
||||||
|
device = ssd1306.SSD1306_SPI(width, height, spi, dc, res, cs, external_vcc)
|
||||||
|
super().__init__(device)
|
|
@ -0,0 +1,123 @@
|
||||||
|
# writer.py Implements the Writer class.
|
||||||
|
# V0.1 Peter Hinch Nov 2016
|
||||||
|
|
||||||
|
# The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016 Peter Hinch
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
|
||||||
|
# A Writer supports rendering text to a Display instance in a given font.
|
||||||
|
# Currently supports vertical mapping and the SSD1306 "pseudo-horizontal" map
|
||||||
|
# only
|
||||||
|
# Multiple Writer instances may be created, each rendering a font to the
|
||||||
|
# same Display object.
|
||||||
|
|
||||||
|
VERT = 0
|
||||||
|
HORIZ = 1
|
||||||
|
WEIRD = 2
|
||||||
|
|
||||||
|
class Writer(object):
|
||||||
|
text_row = 0 # attributes common to all Writer instances
|
||||||
|
text_col = 0
|
||||||
|
row_clip = False # Clip or scroll when screen full
|
||||||
|
col_clip = False # Clip or new line when row is full
|
||||||
|
bmap = WEIRD # SSD1306 pseudo-horizontal
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def set_textpos(cls, row, col):
|
||||||
|
cls.text_row = row
|
||||||
|
cls.text_col = col
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def set_clip(cls, row_clip, col_clip):
|
||||||
|
cls.row_clip = row_clip
|
||||||
|
cls.col_clip = col_clip
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def mapping(cls, bmap):
|
||||||
|
if bmap in (VERT, HORIZ):
|
||||||
|
cls.bmap = bmap
|
||||||
|
else:
|
||||||
|
raise ValueError('Unsupported mapping')
|
||||||
|
|
||||||
|
def __init__(self, device, font):
|
||||||
|
super().__init__()
|
||||||
|
self.device = device
|
||||||
|
self.font = font
|
||||||
|
if font.hmap():
|
||||||
|
raise OSError('Font must be vertically mapped')
|
||||||
|
self.screenwidth = device.width # In pixels
|
||||||
|
self.screenheight = device.height
|
||||||
|
div, mod = divmod(device.height, 8)
|
||||||
|
self.bytes_per_col = div + 1 if mod else div
|
||||||
|
|
||||||
|
def _newline(self):
|
||||||
|
height = self.font.height()
|
||||||
|
Writer.text_row += height
|
||||||
|
Writer.text_col = 0
|
||||||
|
margin = self.screenheight - (Writer.text_row + height)
|
||||||
|
if margin < 0:
|
||||||
|
if not Writer.row_clip:
|
||||||
|
self.device.scroll(0, margin)
|
||||||
|
Writer.text_row += margin
|
||||||
|
|
||||||
|
def printstring(self, string):
|
||||||
|
for char in string:
|
||||||
|
self._printchar(char)
|
||||||
|
|
||||||
|
def _printchar(self, char):
|
||||||
|
bmap = Writer.bmap # Buffer mapping
|
||||||
|
if char == '\n':
|
||||||
|
self._newline()
|
||||||
|
return
|
||||||
|
fbuff = self.device.buffer
|
||||||
|
glyph, char_height, char_width = self.font.get_ch(char)
|
||||||
|
if Writer.text_row+char_height > self.screenheight and Writer.row_clip:
|
||||||
|
return
|
||||||
|
if Writer.text_col + char_width > self.screenwidth:
|
||||||
|
if Writer.col_clip:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self._newline()
|
||||||
|
div, mod = divmod(char_height, 8)
|
||||||
|
gbytes = div + 1 if mod else div # No. of bytes per column of glyph
|
||||||
|
start_row, align = divmod(Writer.text_row, 8)
|
||||||
|
for scol in range(0, char_width): # Source column
|
||||||
|
dcol = scol + Writer.text_col # Destination column
|
||||||
|
dest_row_byte = start_row
|
||||||
|
for gbyte in range(gbytes): # Each glyph byte in column
|
||||||
|
if bmap == VERT:
|
||||||
|
dest = dcol * self.bytes_per_col + dest_row_byte
|
||||||
|
elif bmap == WEIRD:
|
||||||
|
dest = dcol + dest_row_byte * self.screenwidth
|
||||||
|
source = scol * gbytes + gbyte
|
||||||
|
data = fbuff[dest] & (0xff >> (8 - align))
|
||||||
|
fbuff[dest] = data | glyph[source] << align
|
||||||
|
if align and (dest_row_byte + 1) < self.bytes_per_col:
|
||||||
|
if bmap == VERT:
|
||||||
|
dest += 1
|
||||||
|
elif bmap == WEIRD:
|
||||||
|
dest += self.screenwidth
|
||||||
|
data = fbuff[dest] & (0xff << align)
|
||||||
|
fbuff[dest] = data | glyph[source] >> (8 - align)
|
||||||
|
dest_row_byte += 1
|
||||||
|
if dest_row_byte >= self.bytes_per_col:
|
||||||
|
break
|
||||||
|
Writer.text_col += char_width
|
Ładowanie…
Reference in New Issue