From da5131a4a2b8927baabcd76a3f2647da04c05c72 Mon Sep 17 00:00:00 2001 From: Peter Hinch Date: Sat, 22 Oct 2016 11:28:23 +0100 Subject: [PATCH] Initial commit of specification --- README.md | 161 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 160 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3208aa7..9f037eb 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,161 @@ # micropython-font-to-py -A Python 3 utility to convert fonts to Python source capable of being frozen as bytecode + +This is currently a work in progress. This document specifies a forthcoming +module. + +# Rationale + +MicroPython platforms generally have limited RAM, but more abundant storage in +the form of flash memory. Font files tend to be relatively large. The +conventional technique of rendering strings to a device involves loading the +entire font into RAM. This is fast but ram intensive. The alternative of storing +the font as a random access file and loading individual characters into RAM on +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 +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. + +It is intended that the resultant file be usable with a variety of display +devices and drivers. These include: + + 1. Drivers using the official ``framebuffer`` class. + 2. Drivers using ``bytearray`` instances as frame buffers. + 3. Drivers for devices where the frame buffer is implemented in external + hardware. + +# Limitations + +Only the ASCII character set from chr(32) to chr(126) is supported. Kerning is +not supported. + +# 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. Arguments also define whether the font is to +be stored in proportional or fixed width form. + +Further arguments will be specified by the documentation for the specific +device driver in use. They ensure that the byte contents and layout are correct +for the target display hardware. + +Example usage to produce a file ``myfont.py`` with height of 23 pixels +``font_to_py.py FreeSans.ttf 23 -o myfont.py`` + +## Arguments + +### Mandatory arguments: + + 1. Font file path. Must be a ttf or otf file. + 2. Height in pixels. + 3. -o or --outfile Output file path. Must have a .py extension. + +### Optional arguments: + + * -f or --fixedpitch If specified, all characters will have the same width. By + default fonts are assumed to be variable pitch. + * -h Specifies horizontal mapping (default is vertical). + * -b Specifies big-endian bytes (default little endian). + * -n For variable pitch fonts specifies that blank advance bits should be + omitted from the character map. + +Optional arguments other than the fixed pitch argument will be specified in the +device driver documentation. + +## The font file + +Assume that the you have employed the utility to create a file ``myfont.py``. In +your code you will issue + +```python +from myfont import myfont +``` + +The ``myfont`` instance will then be used by the device driver to render strings +on demand. + +# 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. This instantiates a ``PyFont`` +object with appropriate constructor arguments such as the metrics of the +specific font. When the user program needs to display a string it passes the +instance to the device driver. The instance exposes appropriate font metrics +defined in pixels and a ``get_ch()`` method. The latter provides fast access to +the bytes corresponding to an individual character together with character +specific metrics. + +All fixed width characters include blank bits after the character bits to define +the width. By default variable pitch characters include blank "advance" bits to +provide correct spacing between characters. These may optionally be omitted from +the data with the -n argument. In this instance the driver may supply them: the +number of bits to be supplied is stored in byte 1 of the character data. + +## The PyFont class + +This is defined in the file ``pyfont.py``. An outline definition of the class is +as follows: + +```python +class PyFont(object): + def __init__(self, font, index, vert, horiz): + self.bits_horiz = horiz # Width of monospaced char or 0 if variable + self.bits_vert = vert # Height of all chars + self._index = index + self._font = font + + def get_ch(self, ch): + from uctypes import addressof + # Replace out of range characters with a default + # compute offset of current character bitmap and get char metrics + return addressof(self._font) + offset, self.bits_vert, char_width, advance) + + def get_properties(self): + return self.bits_vert, self.bits_horiz +``` + +The device driver calls the ``get_ch`` method for each character. If the driver +is to provide the advance (user told to use the -n option) the ``advance`` value +is the number of bits to supply. Otherwise its value will be 0. + +## 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 +import pyfont +_myfont = b'\x00\x00` +_myfont_index = b'\x00\x00\x23\x00\` +myfont = pyfont.PyFont(_myfont, _myfont_index, 24, 0) + +``` + +# 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. + +The bytes object for the font will store the character width in byte 0 and the +advance in byte 1. This will be transparent to the device driver, the pointer +returned by ``get_ch`` will be to the raw font data. This implies that the width +will be restricted to 256 pixels: huge in the context of realistic hardware. + +This general approach has been tested on a Pyboard connected to LCD hardware +having an onboard frame buffer. The visual performance is good.