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_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.
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
|
@ -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
157
writer.py
|
@ -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
|
||||
|
|
Ładowanie…
Reference in New Issue