kopia lustrzana https://github.com/peterhinch/micropython-font-to-py
update writer to support rotation and be ~3x faster
rodzic
0de8876e67
commit
85f90b0900
20
DRIVERS.md
20
DRIVERS.md
|
@ -18,9 +18,9 @@ display = Display(args_required_by_driver)
|
||||||
wri_serif = Writer(display, freeserif)
|
wri_serif = Writer(display, freeserif)
|
||||||
wri_sans = Writer(display, freesans20)
|
wri_sans = Writer(display, freesans20)
|
||||||
Writer.set_clip(True, True)
|
Writer.set_clip(True, True)
|
||||||
wri_serif.printstring('Tuesday\n')
|
wri_serif.draw_text('Tuesday\n')
|
||||||
wri_sans.printstring('8 Nov 2016\n')
|
wri_sans.draw_text('8 Nov 2016\n')
|
||||||
wri_sans.printstring('10.30am')
|
wri_sans.draw_text('10.30am')
|
||||||
|
|
||||||
display.show() # Display the result
|
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:
|
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
|
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
|
corner. Arguments are in pixels with positive values representing right and
|
||||||
right respectively. They reference the top left hand corner of the first
|
down respectively. They reference the top left hand corner of the first
|
||||||
character to be output.
|
character to be output.
|
||||||
2. ``set_clip`` Args: boolean ``row_clip``, ``col_clip``. If these are
|
2. ``set_clip`` Args: boolean ``row_clip``, ``col_clip``. If these are
|
||||||
``True``, characters will be clipped if they extend beyond the boundaries of
|
``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
|
## 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.
|
insertion point. Newline characters are honoured.
|
||||||
|
|
||||||
## Note on the Writer class
|
## Note on the Writer class
|
||||||
|
@ -110,7 +110,7 @@ buffer/device.
|
||||||
|
|
||||||
### Specifying the font file
|
### 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
|
argument. It returns a memoryview instance providing access to a bytearray
|
||||||
corresponding to the individual glyph. The layout of this data is determined by
|
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
|
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)
|
_mvfont = memoryview(_font)
|
||||||
# Boilerplate code omitted
|
# Boilerplate code omitted
|
||||||
|
|
||||||
def get_ch(ch):
|
def get_char(ch):
|
||||||
# validate ch, if out of range use '?'
|
# validate ch, if out of range use '?'
|
||||||
# get offsets into _font and retrieve char width
|
# get offsets into _font and retrieve char width
|
||||||
# Return: memoryview of bitmap, height and 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
|
are maximum width, it can be rendered rapidly without doing a character by
|
||||||
character check.
|
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
|
and contains all the bytes required to render the character including trailing
|
||||||
space.
|
space.
|
||||||
|
|
||||||
|
|
|
@ -114,7 +114,7 @@ gc.collect()
|
||||||
micropython.mem_info()
|
micropython.mem_info()
|
||||||
|
|
||||||
def foo():
|
def foo():
|
||||||
addr, height, width = freeserif.get_ch('a')
|
addr, height, width = freeserif.get_char('a')
|
||||||
|
|
||||||
foo()
|
foo()
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ all pixels of a character are rendered identically.
|
||||||
|
|
||||||
Converting font files programmatically works best for larger fonts. For small
|
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
|
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.
|
size.
|
||||||
|
|
||||||
# Font file interface
|
# Font file interface
|
||||||
|
@ -79,13 +79,11 @@ provided to font-to-py:
|
||||||
``hmap`` Returns ``True`` if font is horizontally mapped. Should return ``True``
|
``hmap`` Returns ``True`` if font is horizontally mapped. Should return ``True``
|
||||||
``reverse`` Returns ``True`` if bit reversal was specified. Should return ``False``
|
``reverse`` Returns ``True`` if bit reversal was specified. Should return ``False``
|
||||||
``monospaced`` Returns ``True`` if monospaced rendering was specified.
|
``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:
|
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 height in pixels.
|
||||||
* The character width in pixels.
|
* The character width in pixels.
|
||||||
|
|
||||||
|
|
|
@ -71,9 +71,9 @@ else: # I2C
|
||||||
serif = Writer(display, freeserif)
|
serif = Writer(display, freeserif)
|
||||||
sans = Writer(display, freesans20)
|
sans = Writer(display, freesans20)
|
||||||
Writer.set_clip(True, True) # Disable auto scrolling and wrapping.
|
Writer.set_clip(True, True) # Disable auto scrolling and wrapping.
|
||||||
serif.printstring('Tuesday\n')
|
serif.draw_text('Tuesday\n')
|
||||||
sans.printstring('8 Nov 2016\n')
|
sans.draw_text('8 Nov 2016\n')
|
||||||
sans.printstring('10.30am')
|
sans.draw_text('10.30am')
|
||||||
display.show()
|
display.show()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -143,7 +143,7 @@ def test_font(fontfile, string):
|
||||||
height = myfont.height()
|
height = myfont.height()
|
||||||
for row in range(height):
|
for row in range(height):
|
||||||
for char in string:
|
for char in string:
|
||||||
data, _, width = myfont.get_ch(char)
|
data, _, width = myfont.get_char(char)
|
||||||
if myfont.hmap():
|
if myfont.hmap():
|
||||||
render_row_hmap(data, row, height, width, myfont.reverse())
|
render_row_hmap(data, row, height, width, myfont.reverse())
|
||||||
else:
|
else:
|
||||||
|
@ -167,7 +167,7 @@ def test_file(fontfile, height, string, *, minchar=32, maxchar=126, defchar=ord(
|
||||||
height = myfont.height()
|
height = myfont.height()
|
||||||
for row in range(height):
|
for row in range(height):
|
||||||
for char in string:
|
for char in string:
|
||||||
data, _, width = myfont.get_ch(char)
|
data, _, width = myfont.get_char(char)
|
||||||
if myfont.hmap():
|
if myfont.hmap():
|
||||||
render_row_hmap(data, row, height, width, myfont.reverse())
|
render_row_hmap(data, row, height, width, myfont.reverse())
|
||||||
else:
|
else:
|
||||||
|
|
157
writer.py
157
writer.py
|
@ -29,71 +29,128 @@
|
||||||
|
|
||||||
|
|
||||||
class Writer(object):
|
class Writer(object):
|
||||||
text_row = 0 # attributes common to all Writer instances
|
# these attributes and set_position are common to all Writer instances
|
||||||
text_col = 0
|
x_pos = 0
|
||||||
row_clip = False # Clip or scroll when screen full
|
y_pos = 0
|
||||||
col_clip = False # Clip or new line when row is full
|
device = None
|
||||||
|
screen_height = 0
|
||||||
|
screen_width = 0
|
||||||
|
draw_pixel = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def set_textpos(cls, row, col):
|
def set_position(cls, x, y):
|
||||||
cls.text_row = row
|
cls.x_pos = x
|
||||||
cls.text_col = col
|
cls.y_pos = y
|
||||||
|
|
||||||
@classmethod
|
def __init__(self, device, font, rotation=None):
|
||||||
def set_clip(cls, row_clip, col_clip):
|
|
||||||
cls.row_clip = row_clip
|
|
||||||
cls.col_clip = col_clip
|
|
||||||
|
|
||||||
def __init__(self, device, font):
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.device = device
|
self.device = device
|
||||||
|
self.set_font(font)
|
||||||
|
self.set_rotation(rotation)
|
||||||
|
|
||||||
|
def set_font(self, font):
|
||||||
self.font = font
|
self.font = font
|
||||||
|
self._draw_char = self._draw_vmap_char
|
||||||
if font.hmap():
|
if font.hmap():
|
||||||
raise OSError('Font must be vertically mapped')
|
self._draw_char = self._draw_hmap_char
|
||||||
self.screenwidth = device.width # In pixels
|
|
||||||
self.screenheight = device.height
|
@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):
|
def _newline(self):
|
||||||
height = self.font.height()
|
Writer.x_pos = 0
|
||||||
Writer.text_row += height
|
Writer.y_pos += self.font.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):
|
def draw_text(self, string):
|
||||||
for char in 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':
|
if char == '\n':
|
||||||
self._newline()
|
self._newline()
|
||||||
return
|
return
|
||||||
glyph, char_height, char_width = self.font.get_ch(char)
|
|
||||||
if Writer.text_row + char_height > self.screenheight:
|
glyph, char_height, char_width = self.font.get_char(char)
|
||||||
if Writer.row_clip:
|
|
||||||
return
|
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()
|
self._newline()
|
||||||
if Writer.text_col + char_width > self.screenwidth:
|
|
||||||
if Writer.col_clip:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
self._newline()
|
|
||||||
|
|
||||||
div, mod = divmod(char_height, 8)
|
div, mod = divmod(char_height, 8)
|
||||||
gbytes = div + 1 if mod else div # No. of bytes per column of glyph
|
bytes_per_col = div + 1 if mod else div
|
||||||
device = self.device
|
|
||||||
for scol in range(char_width): # Source column
|
for glyph_col_i in range(char_width):
|
||||||
dcol = scol + Writer.text_col # Destination column
|
glyph_col_start = glyph_col_i * bytes_per_col
|
||||||
drow = Writer.text_row # Destination row
|
glyph_col = int.from_bytes(
|
||||||
for srow in range(char_height): # Source row
|
glyph[glyph_col_start:glyph_col_start + bytes_per_col],
|
||||||
gbyte, gbit = divmod(srow, 8)
|
'little'
|
||||||
if drow >= self.screenheight:
|
)
|
||||||
break
|
if not glyph_col:
|
||||||
if gbit == 0: # Next glyph byte
|
continue
|
||||||
data = glyph[scol * gbytes + gbyte]
|
x = Writer.x_pos + glyph_col_i
|
||||||
device.pixel(dcol, drow, data & (1 << gbit))
|
y = Writer.y_pos
|
||||||
drow += 1
|
for glyph_row_i in range(char_height):
|
||||||
Writer.text_col += char_width
|
if glyph_col & (1 << glyph_row_i):
|
||||||
|
self.draw_pixel(x, y, 1)
|
||||||
|
y += 1
|
||||||
|
|
||||||
|
Writer.x_pos += char_width
|
||||||
|
|
Ładowanie…
Reference in New Issue