update writer to support rotation and be ~3x faster

pull/3/head
Brian Cappello 2017-06-24 09:06:54 -04:00
rodzic 0de8876e67
commit 85f90b0900
6 zmienionych plików z 126 dodań i 71 usunięć

Wyświetl plik

@ -18,9 +18,9 @@ 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')
wri_serif.draw_text('Tuesday\n')
wri_sans.draw_text('8 Nov 2016\n')
wri_sans.draw_text('10.30am')
display.show() # Display the result
```
@ -33,10 +33,10 @@ display and a complete example of an SSD1306 driver may be found
The ``Writer`` class exposes the following class methods:
1. ``set_textpos`` Args: ``row``, ``col``. This determines where on screen any
1. ``set_position`` Args: ``x``, ``y``. 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
corner. Arguments are in pixels with positive values representing right and
down 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
@ -49,7 +49,7 @@ of characters is maintained regardless of the font in use.
## Method
1. ``printstring`` Arg: a text string. Outputs a text string at the current
1. ``draw_text`` Arg: a text string. Outputs a text string at the current
insertion point. Newline characters are honoured.
## Note on the Writer class
@ -110,7 +110,7 @@ buffer/device.
### Specifying the font file
Each font file has a ``get_ch()`` function accepting an ASCII character as its
Each font file has a ``get_char()`` 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
@ -171,7 +171,7 @@ 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):
def get_char(ch):
# validate ch, if out of range use '?'
# get offsets into _font and retrieve char width
# Return: memoryview of bitmap, height and width
@ -187,7 +187,7 @@ 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
``get_char()`` returns a memoryview of an individual glyph with its dimensions
and contains all the bytes required to render the character including trailing
space.

Wyświetl plik

@ -114,7 +114,7 @@ gc.collect()
micropython.mem_info()
def foo():
addr, height, width = freeserif.get_ch('a')
addr, height, width = freeserif.get_char('a')
foo()

Wyświetl plik

@ -65,7 +65,7 @@ all pixels of a character are rendered identically.
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
hand-designed binary font files: these are optiised for rendering at a specific
hand-designed binary font files: these are optimised for rendering at a specific
size.
# Font file interface
@ -79,13 +79,11 @@ provided to font-to-py:
``hmap`` Returns ``True`` if font is horizontally mapped. Should return ``True``
``reverse`` Returns ``True`` if bit reversal was specified. Should return ``False``
``monospaced`` Returns ``True`` if monospaced rendering was specified.
``min_ch`` Returns the ordinal value of the lowest character in the file.
``max_ch`` Returns the ordinal value of the highest character in the file.
Glyphs are returned with the ``get_ch`` method. Its argument is a character
Glyphs are returned with the ``get_char`` method. Its argument is a character
and it returns the following values:
* A ``memoryview`` object containg the glyph bytes.
* A ``memoryview`` object containing the glyph bytes.
* The height in pixels.
* The character width in pixels.

Wyświetl plik

@ -71,9 +71,9 @@ else: # 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')
serif.draw_text('Tuesday\n')
sans.draw_text('8 Nov 2016\n')
sans.draw_text('10.30am')
display.show()

Wyświetl plik

@ -143,7 +143,7 @@ def test_font(fontfile, string):
height = myfont.height()
for row in range(height):
for char in string:
data, _, width = myfont.get_ch(char)
data, _, width = myfont.get_char(char)
if myfont.hmap():
render_row_hmap(data, row, height, width, myfont.reverse())
else:
@ -167,7 +167,7 @@ def test_file(fontfile, height, string, *, minchar=32, maxchar=126, defchar=ord(
height = myfont.height()
for row in range(height):
for char in string:
data, _, width = myfont.get_ch(char)
data, _, width = myfont.get_char(char)
if myfont.hmap():
render_row_hmap(data, row, height, width, myfont.reverse())
else:

157
writer.py
Wyświetl plik

@ -29,71 +29,128 @@
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
# these attributes and set_position are common to all Writer instances
x_pos = 0
y_pos = 0
device = None
screen_height = 0
screen_width = 0
draw_pixel = None
@classmethod
def set_textpos(cls, row, col):
cls.text_row = row
cls.text_col = col
def set_position(cls, x, y):
cls.x_pos = x
cls.y_pos = y
@classmethod
def set_clip(cls, row_clip, col_clip):
cls.row_clip = row_clip
cls.col_clip = col_clip
def __init__(self, device, font):
def __init__(self, device, font, rotation=None):
super().__init__()
self.device = device
self.set_font(font)
self.set_rotation(rotation)
def set_font(self, font):
self.font = font
self._draw_char = self._draw_vmap_char
if font.hmap():
raise OSError('Font must be vertically mapped')
self.screenwidth = device.width # In pixels
self.screenheight = device.height
self._draw_char = self._draw_hmap_char
@classmethod
def set_rotation(cls, rotation=None):
rotation = 0 if not rotation else rotation % 360
if not rotation:
cls.draw_pixel = cls._draw_pixel
elif rotation == 90:
cls.draw_pixel = cls._draw_pixel_90
elif rotation == 180:
cls.draw_pixel = cls._draw_pixel_180
elif rotation == 270:
cls.draw_pixel = cls._draw_pixel_270
else:
raise ValueError('rotation must be falsy or one of 90, 180 or 270')
if not rotation or rotation == 180:
cls.screen_width = cls.device.width
cls.screen_height = cls.device.height
else:
cls.screen_width = cls.device.height
cls.screen_height = cls.device.width
def _draw_pixel(self, x, y, color):
self.device.pixel(x, y, color)
def _draw_pixel_90(self, x, y, color):
self.device.pixel(self.device.width - y, x, color)
def _draw_pixel_180(self, x, y, color):
self.device.pixel(self.device.width - x, self.device.height - y, color)
def _draw_pixel_270(self, x, y, color):
self.device.pixel(y, self.device.height - x, color)
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
Writer.x_pos = 0
Writer.y_pos += self.font.height()
def printstring(self, string):
def draw_text(self, string):
for char in string:
self._printchar(char)
self._draw_char(char)
def _printchar(self, char):
def _draw_hmap_char(self, char):
if char == '\n':
self._newline()
return
glyph, char_height, char_width = self.font.get_ch(char)
if Writer.text_row + char_height > self.screenheight:
if Writer.row_clip:
return
glyph, char_height, char_width = self.font.get_char(char)
if Writer.x_pos + char_width > self.screen_width:
self._newline()
div, mod = divmod(char_width, 8)
bytes_per_row = div + 1 if mod else div
for glyph_row_i in range(char_height):
glyph_row_start = glyph_row_i * bytes_per_row
glyph_row = int.from_bytes(
glyph[glyph_row_start:glyph_row_start + bytes_per_row],
'little'
)
if not glyph_row:
continue
x = Writer.x_pos
y = Writer.y_pos + glyph_row_i
for glyph_col_i in range(char_width):
if glyph_row & (1 << glyph_col_i):
self.draw_pixel(x, y, 1)
x += 1
Writer.x_pos += char_width
def _draw_vmap_char(self, char):
if char == '\n':
self._newline()
return
glyph, char_height, char_width = self.font.get_char(char)
if Writer.x_pos + char_width > self.screen_width:
self._newline()
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
device = self.device
for scol in range(char_width): # Source column
dcol = scol + Writer.text_col # Destination column
drow = Writer.text_row # Destination row
for srow in range(char_height): # Source row
gbyte, gbit = divmod(srow, 8)
if drow >= self.screenheight:
break
if gbit == 0: # Next glyph byte
data = glyph[scol * gbytes + gbyte]
device.pixel(dcol, drow, data & (1 << gbit))
drow += 1
Writer.text_col += char_width
bytes_per_col = div + 1 if mod else div
for glyph_col_i in range(char_width):
glyph_col_start = glyph_col_i * bytes_per_col
glyph_col = int.from_bytes(
glyph[glyph_col_start:glyph_col_start + bytes_per_col],
'little'
)
if not glyph_col:
continue
x = Writer.x_pos + glyph_col_i
y = Writer.y_pos
for glyph_row_i in range(char_height):
if glyph_col & (1 << glyph_row_i):
self.draw_pixel(x, y, 1)
y += 1
Writer.x_pos += char_width