Update WRITER.md remove most debug print statements.

col_clip
Peter Hinch 2021-01-30 17:38:59 +00:00
rodzic 103a6bae85
commit 081ef12d90
2 zmienionych plików z 108 dodań i 141 usunięć

Wyświetl plik

@ -48,30 +48,31 @@ Labels and Fields (from nanogui.py).
# Contents # Contents
1. [Introduction](./WRITER.md#1-introduction) 1. [Introduction](./WRITER.md#1-introduction)
1.1 [Hardware](./WRITER.md#11-hardware) 1.1 [Release notes](./WRITER.md#11-release-notes)
1.2 [Files](./WRITER.md#11-files) 1.2 [Hardware](./WRITER.md#12-hardware)
1.3 [Fonts](./WRITER.md#11-fonts) 1.3 [Files](./WRITER.md#13-files)
1.4 [Fonts](./WRITER.md#14-fonts)
2. [Writer and CWriter classes](./WRITER.md#2-writer-and-cwriter-classes) 2. [Writer and CWriter classes](./WRITER.md#2-writer-and-cwriter-classes)
2.1 [The Writer class](./WRITER.md#21-the-writer-class) For monochrome displays. 2.1 [The Writer class](./WRITER.md#21-the-writer-class) For monochrome displays.
     2.1.1 [Static Method](./WRITER.md#211-static-method)      2.1.1 [Static Method](./WRITER.md#211-static-method)
     2.1.2.[Constructor](./WRITER.md#212-constructor)      2.1.2.[Constructor](./WRITER.md#212-constructor)
     2.1.3 [Methods](./WRITER.md#213-methods)      2.1.3 [Methods](./WRITER.md#213-methods)
2.2 [The CWriter class](./WRITER.md#22-the-cwriter-class) For colour displays 2.2 [The CWriter class](./WRITER.md#22-the-cwriter-class) For colour displays.
and for upside-down rendering.      2.2.1 [Constructor](./WRITER.md#221-constructor)
     2.2.1 [Static Method](./WRITER.md#221-static-method)      2.2.2 [Methods](./WRITER.md#222-methods)
     2.2.2 [Constructor](./WRITER.md#222-constructor)      2.2.3 [A performance boost](./WRITER.md#223-a-performance-boost)
     2.2.3 [Methods](./WRITER.md#223-methods) 3. [Notes](./WRITER.md#3-notes)
     2.2.4 [A performance boost](./WRITER.md#224-a-performance-boost)
3. [Notes](./WRITER.md#4-notes)
###### [Main README](../README.md) ###### [Main README](../README.md)
# 1. Introduction # 1. Introduction
The original `Writer` class was a proof of concept intended to demonstrate The module provides a `Writer` class for rendering bitmapped monochrome fonts
rendering, on an SSD1306 OLED display, fonts created by`font_to_py.py`. 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`.
This update for practical applications has the following features: The module has the following features:
* Genarality: capable of working with any `framebuf` derived driver. * Genarality: capable of working with any `framebuf` derived driver.
* Multiple display operation. * Multiple display operation.
* Text display of fixed and variable pitch fonts with wrapping and vertical * Text display of fixed and variable pitch fonts with wrapping and vertical
@ -80,29 +81,42 @@ This update for practical applications has the following features:
* Tab support. * Tab support.
* String metrics to enable right or centre justification. * String metrics to enable right or centre justification.
* Inverse (background color on foreground color) display. * Inverse (background color on foreground color) display.
* Inverted display option.
Note that these changes have significantly increased code size. On the ESP8266 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 it is likely that `writer.py` will need to be frozen as bytecode. The original
very simple version still exists as `writer_minimal.py`. very simple version still exists as `writer_minimal.py`.
## 1.1 Hardware ## 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. 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 Wiring is specified in `ssd1306_setup.py`. Edit this to use a different bus or
for a non-Pyboard target. for a non-Pyboard target.
## 1.2 Files ## 1.3 Files
1. `writer.py` Supports `Writer` and `CWriter` classes. 1. `writer.py` Supports `Writer` and `CWriter` classes.
2. `writer_gui.py` Provides optional GUI objects. 2. `ssd1306_setup.py` Hardware initialisation for SSD1306. Requires the
3. `ssd1306_setup.py` Hardware initialisation for SSD1306. Requires the
official [SSD1306 driver](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py). official [SSD1306 driver](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py).
4. `writer_demo.py` Demo using a 128*64 SSD1306 OLED display. Import to see 3. `writer_demo.py` Demo using a 128*64 SSD1306 OLED display. Import to see
usage information. usage information.
5. `writer_tests.py` Test/demo scripts. Import to see usage information. 4. `writer_tests.py` Test/demo scripts. Import to see usage information.
6. `writer_minimal.py` A minimal version for highly resource constrained 5. `writer_minimal.py` A minimal version for highly resource constrained
devices. devices.
6. `framebuf_utils.framebuf_utils.mpy` A means of improving rendering speed
on color displays. Discussed [in 2.2.3](./WRITER.md#223-a-performance-boost)
Sample fonts: Sample fonts:
1. `freesans20.py` Variable pitch font file. 1. `freesans20.py` Variable pitch font file.
@ -110,25 +124,24 @@ Sample fonts:
3. `font10.py` Smaller variable pitch fonts. 3. `font10.py` Smaller variable pitch fonts.
4. `font6.py` 4. `font6.py`
## 1.3 Fonts ## 1.4 Fonts
Python font files should be created using `font-to-py.py` using horizontal 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 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 fonts may be frozen as bytecode reducing the RAM impact of each font to about
340 bytes. 340 bytes. This is highly recommended.
###### [Contents](./WRITER.md#contents) ###### [Contents](./WRITER.md#contents)
# 2. Writer and CWriter classes # 2. Writer and CWriter classes
The `Writer` class provides fast rendering to monochrome displays using bit The `Writer` class provides fast rendering to monochrome displays using bit
blitting. Most applications will use this class. blitting.
The `CWriter` class is a subclass of `Writer`. It can optionally support color The `CWriter` class is a subclass of `Writer` to support color displays. Owing
displays. It provides additional functionality in the form of an upside-down to limitations in the `frmebuf.blit` method the `CWriter` class renders glyphs
display option. Owing to limitations in the `frmebuf.blit` method the one pixel at a time; rendering is therefore slower than the `Writer` class. A
`CWriter` class renders glyphs one pixel at a time; rendering is therefore substantial improvement is possible. See [2.2.3](./WRITER.md#223-a-performance-boost).
slower than the `Writer` class.
Multiple screens are supported. On any screen multiple `Writer` or `CWriter` 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 instances may be used, each using a different font. A class variable holds the
@ -176,10 +189,10 @@ SSD1306 OLED display and the official
The `Writer` class exposes the following static method: The `Writer` class exposes the following static method:
1. `set_textpos` Args: `device`,`row=None`, `col=None`. The `device` is the 1. `set_textpos(device, row=None, col=None)`. The `device` is the display
display instance. This method determines where on screen subsequent text is to instance. This method determines where on screen subsequent text is to be
be rendered. The initial value is (0, 0) - the top left corner. Arguments are rendered. The initial value is (0, 0) - the top left corner. Arguments are in
in pixels with positive values representing down and right respectively. The pixels with positive values representing down and right respectively. The
insertion point defines the top left hand corner of the next character to be insertion point defines the top left hand corner of the next character to be
output. output.
@ -199,45 +212,37 @@ This takes the following args:
### 2.1.3 Methods ### 2.1.3 Methods
1. `printstring` Args: `string`, `invert=False`. Outputs a text string at the 1. `printstring(string, invert=False)`. Renders the string at the current
current insertion point. Newline and Tab characters are honoured. If `invert` insertion point. Newline and Tab characters are honoured. If `invert` is
is `True` the text is output as black on white. `True` the text is output with foreground and background colors transposed.
2. `height` No args. Returns the font height in pixels. 2. `height()` Returns the font height in pixels.
3. `stringlen` Arg: `string`. Returns the length of a string in pixels. Used 3. `stringlen(string, oh=False)` Returns the length of a string in pixels.
for right or centre justification. Appications can use this for right or centre justification.
4. `set_clip` Args: `row_clip=None`, `col_clip=None`, `wrap=None`. If The `oh` arg is for internal use. If set, the method returns a `bool`, `True`
`row_clip` and/or `col_clip` are `True`, characters will be clipped if they if the string would overhang the display edge if rendered at the current
extend beyond the boundaries of the physical display. If `col_clip` is insertion point.
`False` characters will wrap onto the next line. If `row_clip` is `False` the 4. `set_clip(row_clip=None, col_clip=None, wrap=None)`. If `row_clip` and/or
display will, where necessary, scroll up to ensure the line is rendered. If `col_clip` are `True`, characters will be clipped if they extend beyond the
`wrap` is `True` word-wrapping will be performed, assuming words are separated boundaries of the physical display. If `col_clip` is `False` characters will
by spaces. 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. If any arg is `None`, that value will be left unchanged.
Returns the current values of `row_clip`, `col_clip` and `wrap`. Returns the current values of `row_clip`, `col_clip` and `wrap`.
5. `tabsize` Arg `value=None`. If `value` is an integer sets the tab size. 5. `tabsize(value=None)`. If `value` is an integer sets the tab size. Returns
Returns the current tab size (initial default is 4). Tabs only work properly the current tab size (initial default is 4). Tabs only work properly with
with fixed pitch fonts. fixed pitch fonts.
###### [Contents](./WRITER.md#contents) ###### [Contents](./WRITER.md#contents)
## 2.2 The CWriter class ## 2.2 The CWriter class
This extends the `Writer` class by adding support for upside-down and/or color This extends the `Writer` class by adding support for color displays. A color
displays. A color value is an integer whose interpretation is dependent on the value is an integer whose interpretation is dependent on the display hardware
display hardware and device driver. 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 Static method ### 2.2.1 Constructor
The following static method is added:
1. `invert_display` Args `device`, `value=True`. The `device` is the display
instance. If `value` is set, causes text to be rendered upside down. The
`set_textpos` method should be called to ensure that text is rendered from the
bottom right hand corner (viewing the display in its normal orientation).
If a display is to be run inverted, this method must be called prior to
instantiating a `Writer` for this display.
### 2.2.2 Constructor
This takes the following args: This takes the following args:
1. `device` The hardware device driver instance for the screen in use. 1. `device` The hardware device driver instance for the screen in use.
@ -246,20 +251,20 @@ This takes the following args:
4. `bgcolor=None` Background 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. 5. `verbose=True` If `True` the constructor emits console printout.
### 2.2.3 Methods ### 2.2.2 Methods
All methods of the base class are supported. Additional method: All methods of the base class are supported. Additional method:
1. `setcolor` Args: `fgcolor=None`, `bgcolor=None`. Sets the foreground and 1. `setcolor(fgcolor=None, bgcolor=None)`. Sets the foreground and background
background colors. If one is `None` that value is left unchanged. If both colors. If one is `None` that value is left unchanged. If both are `None` the
are `None` the constructor defaults are restored. Constructor defaults are constructor defaults are restored. Constructor defaults are 1 and 0
1 and 0 for monochrome displays (`Writer`). Returns foreground for monochrome displays (`Writer`). Returns foreground and background color
and background color values. values.
The `printstring` method works as per the base class except that the string is 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 rendered in foreground color on background color (or reversed if `invert` is
`True`). `True`).
### 2.2.4 A performance boost ### 2.2.3 A performance boost
Rendering performance of the `Cwriter` class is slow: owing to limitations in 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 the `framebuf.blit` method the class renders glyphs one pixel at a time. There
@ -269,8 +274,8 @@ consists of a native C module.
On import, `writer.py` attempts to import a module `framebuf_utils`. If this 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 succeeds, glyph rendering will be substantially faster. If the file is not
present the class will work using normal rendering. If the file exists but was present the class will work using normal rendering. If the file exists but was
compiled for a different architecture a warning message will be printed but the compiled for a different architecture a warning message will be printed. This
class will work using normal rendering. is a harmless advisory - the code will run using normal rendering.
The directory `framebuf_utils` contains the source file, the makefile and a The directory `framebuf_utils` contains the source file, the makefile and a
version of `framebuf_utils.mpy` for `armv7m` architecture (e.g. Pyboards). version of `framebuf_utils.mpy` for `armv7m` architecture (e.g. Pyboards).
@ -280,10 +285,6 @@ to specify the `xtensawin` arch and rebuild.
It is suggested that moving the appropriate `framebuf_utils.mpy` to the target It is suggested that moving the appropriate `framebuf_utils.mpy` to the target
is only done once the basic operation of an application has been verified. is only done once the basic operation of an application has been verified.
The native module does not support the `CWriter.invert_display` option. If this
is used, the presence of the native module will have no effect. The module has
no effect on the `Writer` class which uses fast rendering by default.
The module has a `fast_mode` variable which is set `True` on import if the mode 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. was successfully engaged. User code should treat this as read-only.

Wyświetl plik

@ -1,6 +1,9 @@
# writer.py Implements the Writer class. # writer.py Implements the Writer class.
# V0.35 Peter Hinch Sept 2020 Fast rendering option for color displays # Handles colour, word wrap and tab stops
# Handles colour, upside down diplays, word wrap and tab stops
# V0.40 Jan 2021 Improved handling of word wrap and line clip. Upside-down
# rendering no longer supported: delegate to device driver.
# V0.35 Sept 2020 Fast rendering option for color displays
# Released under the MIT License (MIT). See LICENSE. # Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2019-2020 Peter Hinch # Copyright (c) 2019-2020 Peter Hinch
@ -21,6 +24,8 @@
import framebuf import framebuf
from uctypes import bytearray_at, addressof from uctypes import bytearray_at, addressof
__version__ = (0, 4, 0)
fast_mode = True fast_mode = True
try: try:
try: try:
@ -39,7 +44,6 @@ class DisplayState():
def __init__(self): def __init__(self):
self.text_row = 0 self.text_row = 0
self.text_col = 0 self.text_col = 0
self.usd = False
def _get_id(device): def _get_id(device):
if not isinstance(device, framebuf.FrameBuffer): if not isinstance(device, framebuf.FrameBuffer):
@ -60,11 +64,11 @@ class Writer():
if row is not None: if row is not None:
if row < 0 or row >= device.height: if row < 0 or row >= device.height:
raise ValueError('row is out of range') raise ValueError('row is out of range')
s.text_row = device.height - 1 - row if s.usd else row s.text_row = row
if col is not None: if col is not None:
if col < 0 or col >= device.width: if col < 0 or col >= device.width:
raise ValueError('col is out of range') raise ValueError('col is out of range')
s.text_col = device.width -1 - col if s.usd else col s.text_col = col
return s.text_row, s.text_col return s.text_row, s.text_col
def __init__(self, device, font, verbose=True): def __init__(self, device, font, verbose=True):
@ -73,7 +77,6 @@ class Writer():
if self.devid not in Writer.state: if self.devid not in Writer.state:
Writer.state[self.devid] = DisplayState() Writer.state[self.devid] = DisplayState()
self.font = font self.font = font
self.usd = Writer.state[self.devid].usd
# Allow to work with reverse or normal font mapping # Allow to work with reverse or normal font mapping
if font.hmap(): if font.hmap():
@ -105,20 +108,12 @@ class Writer():
def _newline(self): def _newline(self):
s = self._getstate() s = self._getstate()
height = self.font.height() height = self.font.height()
if self.usd:
s.text_row -= height
s.text_col = self.screenwidth - 1
margin = s.text_row - height
y = 0
else:
s.text_row += height s.text_row += height
s.text_col = 0 s.text_col = 0
margin = self.screenheight - (s.text_row + height) margin = self.screenheight - (s.text_row + height)
y = self.screenheight + margin y = self.screenheight + margin
if margin < 0: if margin < 0:
if not self.row_clip: if not self.row_clip:
if self.usd:
margin = -margin
self.device.scroll(0, margin) self.device.scroll(0, margin)
self.device.fill_rect(0, y, self.screenwidth, abs(margin), self.bgcolor) self.device.fill_rect(0, y, self.screenwidth, abs(margin), self.bgcolor)
s.text_row += margin s.text_row += margin
@ -157,7 +152,6 @@ class Writer():
if pos > 0: if pos > 0:
rstr = string[pos + 1:] rstr = string[pos + 1:]
string = lstr string = lstr
#print("[", string, "] [", lstr, "] [", rstr, "]", pos)
for char in string: for char in string:
self._printchar(char, invert) self._printchar(char, invert)
@ -167,14 +161,12 @@ class Writer():
def stringlen(self, string, oh=False): def stringlen(self, string, oh=False):
sc = self._getstate().text_col # Start column sc = self._getstate().text_col # Start column
#print('stringlen sc =', sc)
wd = self.screenwidth wd = self.screenwidth
l = 0 l = 0
for char in string[:-1]: for char in string[:-1]:
_, _, char_width = self.font.get_ch(char) _, _, char_width = self.font.get_ch(char)
l += char_width l += char_width
if oh and l + sc > wd: if oh and l + sc > wd:
print('sl1', string, l + sc, wd)
return True # All done. Save time. return True # All done. Save time.
char = string[-1] char = string[-1]
_, _, char_width = self.font.get_ch(char) _, _, char_width = self.font.get_ch(char)
@ -182,7 +174,6 @@ class Writer():
l += self._truelen(char) # Last char might have blank cols on RHS l += self._truelen(char) # Last char might have blank cols on RHS
else: else:
l += char_width # Public method. Return same value as old code. l += char_width # Public method. Return same value as old code.
print('sl2', string, l + sc, wd, char, char_width)
return l + sc > wd if oh else l return l + sc > wd if oh else l
# Return the printable width of a glyph less any blank columns on RHS # Return the printable width of a glyph less any blank columns on RHS
@ -204,7 +195,7 @@ class Writer():
break break
if mc + 1 == wd: if mc + 1 == wd:
break # All done: no trailing space break # All done: no trailing space
print('Truelen', char, wd, mc + 1) print('Truelen', char, wd, mc + 1) # TEST
return mc + 1 return mc + 1
def _get_char(self, char, recurse): def _get_char(self, char, recurse):
@ -228,20 +219,6 @@ class Writer():
glyph, char_height, char_width = self.font.get_ch(char) glyph, char_height, char_width = self.font.get_ch(char)
s = self._getstate() s = self._getstate()
np = None # Allow restriction on printable columns np = None # Allow restriction on printable columns
if self.usd:
if s.text_row - char_height < 0:
if self.row_clip:
return
self._newline()
oh = s.text_col - char_width # Amount glyph would overhang edge (-ve)
if oh < 0:
if self.col_clip or self.wrap:
np = char_width + oh # No of printable columns
if np <= 0:
return
else:
self._newline()
else:
if s.text_row + char_height > self.screenheight: if s.text_row + char_height > self.screenheight:
if self.row_clip: if self.row_clip:
return return
@ -286,12 +263,6 @@ class Writer():
# Writer for colour displays or upside down rendering # Writer for colour displays or upside down rendering
class CWriter(Writer): class CWriter(Writer):
@staticmethod
def invert_display(device, value=True):
devid = id(device)
if devid not in Writer.state:
Writer.state[devid] = DisplayState()
Writer.state[devid].usd = value
def __init__(self, device, font, fgcolor=None, bgcolor=None, verbose=True): def __init__(self, device, font, fgcolor=None, bgcolor=None, verbose=True):
super().__init__(device, font, verbose) super().__init__(device, font, verbose)
@ -301,9 +272,8 @@ class CWriter(Writer):
self.fgcolor = fgcolor self.fgcolor = fgcolor
self.def_bgcolor = self.bgcolor self.def_bgcolor = self.bgcolor
self.def_fgcolor = self.fgcolor self.def_fgcolor = self.fgcolor
fm = fast_mode and not self.usd self._printchar = self._pchfast if fast_mode else self._pchslow
self._printchar = self._pchfast if fm else self._pchslow verbose and print('Render {} using fast mode'.format('is' if fast_mode else 'not'))
verbose and print('Render {} using fast mode'.format('is' if fm else 'not'))
def _pchfast(self, char, invert=False, recurse=False): def _pchfast(self, char, invert=False, recurse=False):
s = self._getstate() s = self._getstate()
@ -333,25 +303,21 @@ class CWriter(Writer):
device = self.device device = self.device
fgcolor = self.bgcolor if invert else self.fgcolor fgcolor = self.bgcolor if invert else self.fgcolor
bgcolor = self.fgcolor if invert else self.bgcolor bgcolor = self.fgcolor if invert else self.bgcolor
usd = self.usd
drow = s.text_row # Destination row drow = s.text_row # Destination row
wcol = s.text_col # Destination column of character start wcol = s.text_col # Destination column of character start
for srow in range(char_height): # Source row for srow in range(char_height): # Source row
for scol in range(clip_width): # Source column for scol in range(clip_width): # Source column
# Destination column: add/subtract writer column # Destination column: add writer column
if usd:
dcol = wcol - scol
else:
dcol = wcol + scol dcol = wcol + scol
gbyte, gbit = divmod(scol, 8) gbyte, gbit = divmod(scol, 8)
if gbit == 0: # Next glyph byte if gbit == 0: # Next glyph byte
data = self.glyph[srow * gbytes + gbyte] data = self.glyph[srow * gbytes + gbyte]
pixel = fgcolor if data & (1 << (7 - gbit)) else bgcolor pixel = fgcolor if data & (1 << (7 - gbit)) else bgcolor
device.pixel(dcol, drow, pixel) device.pixel(dcol, drow, pixel)
drow += -1 if usd else 1 drow += 1
if drow >= self.screenheight or drow < 0: if drow >= self.screenheight or drow < 0:
break break
s.text_col += -char_width if usd else char_width s.text_col += char_width
self.cpos += 1 self.cpos += 1
def setcolor(self, fgcolor=None, bgcolor=None): def setcolor(self, fgcolor=None, bgcolor=None):