micropython-font-to-py/writer/WRITER.md

14 KiB

Writer and Cwriter classes

These classes facilitate rendering Python font files to displays where the display driver is subclassed from the framebuf class. Examples are:

Basic support is for scrolling text display using multiple fonts. The nanogui 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.

Image
Scrolling text, multiple fonts.

Image
A field containing variable length text with a border.

Image
Right justified text.

Image
Mixed text and graphics.

Image
Labels and Fields (from nanogui.py).

Contents

  1. Introduction
    1.1 Release notes
    1.2 Hardware
    1.3 Files
    1.4 Fonts
  2. Writer and CWriter classes
    2.1 The Writer class For monochrome displays.
         2.1.1 Static Method
         2.1.2.Constructor
         2.1.3 Methods
    2.2 The CWriter class For colour displays.
         2.2.1 Constructor
         2.2.2 Methods
         2.2.3 A performance boost
  3. Notes
Main README

1. Introduction

The module provides a Writer class for rendering bitmapped monochrome fonts created by font_to_py.py. The CWriter class extends this to support color rendering. Rendering is to a FrameBuffer instance, e.g. to a display whose driver is subclassed from a FrameBuffer.

The module has the following features:

  • Genarality: capable of working with any framebuf derived driver.
  • Multiple display operation.
  • Text display of fixed and variable pitch fonts with wrapping and vertical scrolling.
  • Wrap/clip options: clip, character wrap or word wrap.
  • Tab support.
  • String metrics to enable right or centre justification.
  • Inverse (background color on foreground color) display.

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 Release Notes

V0.4.0 Jan 2021
Improved handling of the col_clip and wrap options. Improved accuracy avoids needless word wrapping. The clip option now displays as much of the last visible glyph as possible: formerly a glyph which would not fit in its entirety was discarded.

The inverted display option has been withdrawn. It added significant code size and was not an optimal solution. Display inversion should be done at the device driver level. Such a solution works for graphics objects and GUI widgets, while the old option only affected rendered text.

1.2 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.

1.3 Files

  1. writer.py Supports Writer and CWriter classes.
  2. ssd1306_setup.py Hardware initialisation for SSD1306. Requires the official SSD1306 driver.
  3. writer_demo.py Demo using a 128*64 SSD1306 OLED display. Import to see usage information.
  4. writer_tests.py Test/demo scripts. Import to see usage information.
  5. writer_minimal.py A minimal version for highly resource constrained devices.
  6. framebuf_utils.framebuf_utils.mpy A means of improving rendering speed on color displays. Discussed in 2.2.3

Sample fonts:

  1. freesans20.py Variable pitch font file.
  2. courier20.py Fixed pitch font file.
  3. font10.py Smaller variable pitch fonts.
  4. font6.py

1.4 Fonts

Python font files should be created using font-to-py.py using horizontal mapping (-x option). The -r option is not required. If RAM is critical fonts may be frozen as bytecode reducing the RAM impact of each font to about 340 bytes. This is highly recommended.

Contents

2. Writer and CWriter classes

The Writer class provides fast rendering to monochrome displays using bit blitting.

The CWriter class is a subclass of Writer to support color displays. Owing to limitations in the frmebuf.blit method the CWriter class renders glyphs one pixel at a time; rendering is therefore slower than the Writer class. A substantial improvement is possible. See 2.2.3.

Multiple screens are supported. On any screen multiple Writer or CWriter instances may be used, each using a different font. A class variable holds the state of each screen to ensure that the insertion point is managed across multiple instances/fonts.

Contents

2.1 The Writer class

This class facilitates rendering characters from Python font files to a device, assuming the device has a driver subclassed from framebuf. It supports three ways of handling text which would overflow the display: clipping, character wrapping and simple word wrapping.

It handles newline and tab characters, black-on-white inversion, and field blanking to enable variable length contents to be updated at a fixed location.

Typical use with an SSD1306 display and the official driver is as follows:

from ssd1306_setup import WIDTH, HEIGHT, setup
from writer import Writer
import freesans20  # Font to use

use_spi=False  # Tested with a 128*64 I2C connected SSD1306 display
ssd = setup(use_spi)  # Instantiate display: must inherit from framebuf
# Demo drawing geometric shpes
rhs = WIDTH -1
ssd.line(rhs - 20, 0, rhs, 20, 1)  # Demo underlying framebuf methods
square_side = 10
ssd.fill_rect(rhs - square_side, 0, square_side, square_side, 1)
# Instantiate a writer for a specific font
wri = Writer(ssd, freesans20)  # verbose = False to suppress console output
Writer.set_textpos(ssd, 0, 0)  # In case a previous test has altered this
wri.printstring('Sunday\n12 Aug 2018\n10.30am')
ssd.show()

The file writer_demo.py illustrates the use of font files with a 128*64 SSD1306 OLED display and the official SSD1306 driver.

2.1.1 Static Method

The Writer class exposes the following static method:

  1. set_textpos(device, row=None, col=None). The device is the display instance. This method determines where on screen subsequent text is to be rendered. The initial value is (0, 0) - the top left corner. Arguments are in pixels with positive values representing down and right respectively. The insertion point defines the top left hand corner of the next character to be output.

Where None is passed, the setting is left unchanged.
Return: row, col current settings.

The insertion point applies to all Writer instances having the same device. The insertion point on a given screen is maintained regardless of the font in use.

2.1.2 Constructor

This takes the following args:

  1. device The hardware device driver instance for the screen in use.
  2. font A Python font instance.
  3. verbose=True If True the constructor emits console printout.

2.1.3 Methods

  1. printstring(string, invert=False). Renders the string at the current insertion point. Newline and Tab characters are honoured. If invert is True the text is output with foreground and background colors transposed.
  2. height() Returns the font height in pixels.
  3. stringlen(string, oh=False) Returns the length of a string in pixels. Appications can use this for right or centre justification.
    The oh arg is for internal use. If set, the method returns a bool, True if the string would overhang the display edge if rendered at the current insertion point.
  4. set_clip(row_clip=None, col_clip=None, wrap=None). If row_clip and/or col_clip are True, characters will be clipped if they extend beyond the boundaries of the physical display. If col_clip is False characters will wrap onto the next line. If row_clip is False the display will, where necessary, scroll up to ensure the line is rendered. If wrap is True word-wrapping will be performed, assuming words are separated by spaces.
    If any arg is None, that value will be left unchanged.
    Returns the current values of row_clip, col_clip and wrap.
  5. tabsize(value=None). If value is an integer sets the tab size. Returns the current tab size (initial default is 4). Tabs only work properly with fixed pitch fonts.
Contents

2.2 The CWriter class

This extends the Writer class by adding support for color displays. A color value is an integer whose interpretation is dependent on the display hardware and device driver. The Python font file uses single bit pixels. On a color screen these are rendered using foreground and background colors.

2.2.1 Constructor

This takes the following args:

  1. device The hardware device driver instance for the screen in use.
  2. font A Python font instance.
  3. fgcolor=None Foreground color. If None a monochrome display is assumed.
  4. bgcolor=None Background color. If None a monochrome display is assumed.
  5. verbose=True If True the constructor emits console printout.

2.2.2 Methods

All methods of the base class are supported. Additional method:

  1. setcolor(fgcolor=None, bgcolor=None). Sets the foreground and background colors. If one is None that value is left unchanged. If both are None the constructor defaults are restored. Constructor defaults are 1 and 0 for monochrome displays (Writer). Returns foreground and background color values.

The printstring method works as per the base class except that the string is rendered in foreground color on background color (or reversed if invert is True).

2.2.3 A performance boost

Rendering performance of the Cwriter class is slow: owing to limitations in the framebuf.blit method the class renders glyphs one pixel at a time. There is a way to improve performance. It was developed by Jim Mussared (@jimmo) and consists of a native C module.

This works well on Pyboards (1.x and D) but I have had no success on other platforms including the Raspberry Pi Pico. The code will silently ignore this module on other platforms. The following applies only when run on a Pyboard.

On import, writer.py attempts to import a module framebuf_utils. If this succeeds, glyph rendering will be substantially faster. If the file is not present the class will work using normal rendering. If the file is missing or invalid a harmless advisory note is printed and the code will run using normal rendering.

The directory framebuf_utils contains the source file, the makefile and a version of framebuf_utils.mpy for armv7m architecture (e.g. Pyboards). This allows for recompiling for other architectures if anyone feels like experimenting. However the fact that it crashes the Pico suggests that the code is highly specific to the Pybaord.

The module has a fast_mode variable which is set True on import if the mode was successfully engaged. User code should treat this as read-only.

3. Notes

Possible future enhancements:

  1. General rendering to a rectangular area. This may be problematic as the framebuf scroll method is only capable of scrolling the entire buffer.
  2. Extend word wrapping to cases where words are separated by tabs or hyphens.
  3. An asynchronous version.
Contents