kopia lustrzana https://github.com/peterhinch/micropython-font-to-py
add font line mapping format + rendering code
rodzic
85f90b0900
commit
2ef0403ed4
|
@ -0,0 +1,96 @@
|
|||
import ssd1306
|
||||
|
||||
from writer import Writer
|
||||
|
||||
|
||||
class Display(ssd1306.SSD1306_I2C):
|
||||
screen_height = 0
|
||||
screen_width = 0
|
||||
|
||||
def __init__(self, width, height, i2c, addr=0x3c, external_vcc=False,
|
||||
default_font=None, rotation=None):
|
||||
super().__init__(width, height, i2c, addr, external_vcc)
|
||||
self.set_default_font(default_font)
|
||||
self.set_rotation(rotation)
|
||||
|
||||
def set_default_font(self, default_font):
|
||||
self.default_font = default_font
|
||||
|
||||
def set_position(self, x, y):
|
||||
Writer.set_position(x, y)
|
||||
|
||||
def draw_text(self, text, x=None, y=None, font=None):
|
||||
if x is not None and y is not None:
|
||||
Writer.set_position(x, y)
|
||||
if font:
|
||||
font.draw_text(text)
|
||||
else:
|
||||
self.default_font.draw_text(font)
|
||||
|
||||
def clear(self):
|
||||
self.fill(0)
|
||||
self.show()
|
||||
|
||||
def set_rotation(self, rotation=None):
|
||||
rotation = 0 if not rotation else rotation % 360
|
||||
if not rotation:
|
||||
self.pixel = self._pixel
|
||||
self.hline = self._hline
|
||||
self.vline = self._vline
|
||||
elif rotation == 90:
|
||||
self.pixel = self._pixel_90
|
||||
self.hline = self._hline_90
|
||||
self.vline = self._vline_90
|
||||
elif rotation == 180:
|
||||
self.pixel = self._pixel_180
|
||||
self.hline = self._hline_180
|
||||
self.vline = self._vline_180
|
||||
elif rotation == 270:
|
||||
self.pixel = self._pixel_270
|
||||
self.hline = self._hline_270
|
||||
self.vline = self._vline_270
|
||||
else:
|
||||
raise ValueError('rotation must be falsy or one of 90, 180 or 270')
|
||||
|
||||
if not rotation or rotation == 180:
|
||||
self.screen_width = self.width
|
||||
self.screen_height = self.height
|
||||
else:
|
||||
self.screen_width = self.height
|
||||
self.screen_height = self.width
|
||||
|
||||
def _pixel(self, x, y, color=1):
|
||||
self.framebuf.pixel(x, y, color)
|
||||
|
||||
def _hline(self, x, y, length, color=1):
|
||||
self.framebuf.hline(x, y, length, color)
|
||||
|
||||
def _vline(self, x, y, length, color=1):
|
||||
self.framebuf.vline(x, y, length, color)
|
||||
|
||||
def _pixel_90(self, x, y, color=1):
|
||||
self.framebuf.pixel(self.width - y, x, color)
|
||||
|
||||
def _hline_90(self, x, y, length, color=1):
|
||||
self.framebuf.vline(self.width - y - 1, x, length, color)
|
||||
|
||||
def _vline_90(self, x, y, length, color=1):
|
||||
self.framebuf.hline(self.width - y - length, x, length, color)
|
||||
|
||||
def _pixel_180(self, x, y, color=1):
|
||||
self.framebuf.pixel(self.width - x, self.height - y, color)
|
||||
|
||||
def _hline_180(self, x, y, length, color=1):
|
||||
self.framebuf.hline(self.width - x - length, self.height - y - 1, length, color)
|
||||
|
||||
def _vline_180(self, x, y, length, color=1):
|
||||
self.framebuf.vline(self.width - x - 1, self.height - y - length, length, color)
|
||||
|
||||
def _pixel_270(self, x, y, color=1):
|
||||
self.framebuf.pixel(y, self.height - x, color)
|
||||
|
||||
def _hline_270(self, x, y, length, color=1):
|
||||
self.framebuf.vline(y, self.height - x - length, length, color)
|
||||
|
||||
def _vline_270(self, x, y, length, color=1):
|
||||
self.framebuf.hline(y, self.height - x - 1, length, color)
|
|
@ -0,0 +1,34 @@
|
|||
import gc
|
||||
import machine
|
||||
import utime
|
||||
|
||||
from display import Display
|
||||
from writer import Writer
|
||||
|
||||
import DejaVuSans24_l
|
||||
|
||||
|
||||
i2c = machine.I2C(sda=machine.Pin(5), scl=machine.Pin(4))
|
||||
display = Display(128, 64, i2c)
|
||||
sans24 = Writer(display, DejaVuSans24_l)
|
||||
display.set_default_font(sans24)
|
||||
|
||||
rotation = 0
|
||||
while True:
|
||||
start = utime.ticks_us()
|
||||
display.clear()
|
||||
display.set_position(0, 0)
|
||||
display.set_rotation(rotation)
|
||||
display.draw_text('abcdefghijklmnopqrstuvwxyz')
|
||||
# display.hline(0, 0, display.screen_width)
|
||||
# display.hline(0, display.screen_height-1, display.screen_width)
|
||||
# display.vline(0, 0, display.screen_height)
|
||||
# display.vline(display.screen_width-1, 0, display.screen_height)
|
||||
|
||||
display.show()
|
||||
end = utime.ticks_us()
|
||||
print("time: %0.2fms" % ((end - start) / 1e3))
|
||||
gc.collect()
|
||||
print("memory:", gc.mem_alloc())
|
||||
utime.sleep(5)
|
||||
rotation += 90
|
292
font_to_py.py
292
font_to_py.py
|
@ -33,6 +33,7 @@ import argparse
|
|||
import sys
|
||||
import os
|
||||
import freetype
|
||||
import itertools
|
||||
|
||||
# UTILITIES FOR WRITING PYTHON SOURCECODE TO A FILE
|
||||
|
||||
|
@ -43,6 +44,10 @@ import freetype
|
|||
# Lines are broken with \ for readability.
|
||||
|
||||
|
||||
def flatten(l):
|
||||
return list(itertools.chain.from_iterable(l))
|
||||
|
||||
|
||||
class ByteWriter(object):
|
||||
bytes_per_line = 16
|
||||
|
||||
|
@ -89,26 +94,65 @@ def var_write(stream, name, value):
|
|||
# FONT HANDLING
|
||||
|
||||
|
||||
def byte(data, signed=False):
|
||||
return data.to_bytes(1, byteorder='little', signed=signed)
|
||||
|
||||
|
||||
def byte_pair(data, signed=False):
|
||||
return data.to_bytes(2, byteorder='little', signed=signed)
|
||||
|
||||
|
||||
class Bitmap(object):
|
||||
"""
|
||||
A 2D bitmap image represented as a list of byte values. Each byte indicates
|
||||
the state of a single pixel in the bitmap. A value of 0 indicates that the
|
||||
pixel is `off` and any other value indicates that it is `on`.
|
||||
"""
|
||||
def __init__(self, width, height, pixels=None):
|
||||
def __init__(self, char, width, height, pixels=None):
|
||||
self.char = char
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.pixels = pixels or bytearray(width * height)
|
||||
self.lh_data = []
|
||||
self.lv_data = []
|
||||
|
||||
def display(self):
|
||||
"""Print the bitmap's pixels."""
|
||||
for row in range(self.height):
|
||||
for col in range(self.width):
|
||||
char = '#' if self.pixels[row * self.width + col] else '.'
|
||||
lh_count = len(flatten(self.lh_data))
|
||||
print('{} horizontal line mapping: {} hline draw calls. {} bytes'.format(
|
||||
self.char,
|
||||
lh_count,
|
||||
len(list(self._stream_lhmap()))
|
||||
))
|
||||
print('v' * len(''.join([str(i) for i in range(self.width)])), ' y [(x, length)]')
|
||||
for y in range(self.height):
|
||||
for x in range(self.width):
|
||||
space = ' ' if x < 10 else ' '
|
||||
char = space if self.pixels[y * self.width + x] else x
|
||||
print(char, end='')
|
||||
print()
|
||||
print(' ', '%2d' % y, self.lh_data[y])
|
||||
print()
|
||||
|
||||
lv_count = len(flatten(self.lv_data))
|
||||
print('{} vertical line mapping: {} vline draw calls. {} bytes'.format(
|
||||
self.char,
|
||||
lv_count,
|
||||
len(list(self._stream_lvmap()))
|
||||
))
|
||||
print('>' * len(''.join([str(i) for i in range(self.height)])), ' x [(y, length)]')
|
||||
for x in range(self.width)[::-1]:
|
||||
for y in range(self.height):
|
||||
space = ' ' if y < 10 else ' '
|
||||
char = space if self.pixels[y * self.width + x] else y
|
||||
print(char, end='')
|
||||
print(' ', '%2d' % x, self.lv_data[x])
|
||||
print()
|
||||
|
||||
print('selecting {} mapping for {} char\n'.format(
|
||||
'lhmap horizontal' if self.is_char_lhmap() else 'lvmap vertical',
|
||||
self.char
|
||||
))
|
||||
|
||||
def bitblt(self, src, row):
|
||||
"""Copy all pixels from `src` into this bitmap"""
|
||||
srcpixel = 0
|
||||
|
@ -122,50 +166,135 @@ class Bitmap(object):
|
|||
dstpixel += 1
|
||||
dstpixel += row_offset
|
||||
|
||||
# calc horizontal line mapping
|
||||
for y in range(self.height):
|
||||
self.lh_data.append([])
|
||||
x = 0
|
||||
while x < self.width:
|
||||
if self.pixels[y * self.width + x]:
|
||||
line_start = x
|
||||
line_end = x
|
||||
inline_x = x
|
||||
while inline_x <= self.width:
|
||||
if inline_x < self.width and self.pixels[y * self.width + inline_x]:
|
||||
inline_x += 1
|
||||
else:
|
||||
line_end = inline_x
|
||||
break
|
||||
self.lh_data[y].append((line_start, line_end - line_start))
|
||||
x = line_end + 1
|
||||
else:
|
||||
x += 1
|
||||
|
||||
# calc vertical line mapping
|
||||
for x in range(self.width):
|
||||
self.lv_data.append([])
|
||||
y = 0
|
||||
while y < self.height:
|
||||
if self.pixels[y * self.width + x]:
|
||||
line_start = y
|
||||
line_end = y
|
||||
inline_y = y
|
||||
while inline_y <= self.height:
|
||||
if inline_y < self.height and self.pixels[inline_y * self.width + x]:
|
||||
inline_y += 1
|
||||
else:
|
||||
line_end = inline_y
|
||||
break
|
||||
self.lv_data[x].append((line_start, line_end - line_start))
|
||||
y = line_end + 1
|
||||
else:
|
||||
y += 1
|
||||
|
||||
def is_char_lhmap(self):
|
||||
len_lhmap = len(flatten(self.lh_data))
|
||||
len_lvmap = len(flatten(self.lv_data))
|
||||
if len_lhmap == len_lvmap:
|
||||
return len(list(self._stream_lhmap())) <= len(list(self._stream_lvmap()))
|
||||
return len_lhmap <= len_lvmap
|
||||
|
||||
def stream(self):
|
||||
if self.is_char_lhmap():
|
||||
yield from self._stream_lhmap()
|
||||
else:
|
||||
yield from self._stream_lvmap()
|
||||
|
||||
def _stream_lhmap(self):
|
||||
prev_row = None
|
||||
for y, row in enumerate(self.lh_data):
|
||||
if not row:
|
||||
prev_row = None
|
||||
continue
|
||||
elif row == prev_row:
|
||||
yield byte(0)
|
||||
else:
|
||||
yield byte(len(row))
|
||||
yield byte(y)
|
||||
for x, length in row:
|
||||
yield byte(x)
|
||||
yield byte(length)
|
||||
prev_row = row
|
||||
|
||||
def _stream_lvmap(self):
|
||||
prev_col = None
|
||||
for x, col in enumerate(self.lv_data):
|
||||
if not col:
|
||||
prev_col = None
|
||||
continue
|
||||
elif col == prev_col:
|
||||
yield byte(0)
|
||||
else:
|
||||
yield byte(len(col))
|
||||
yield byte(x)
|
||||
for y, length in col:
|
||||
yield byte(y)
|
||||
yield byte(length)
|
||||
prev_col = col
|
||||
|
||||
# Horizontal mapping generator function
|
||||
def get_hbyte(self, reverse):
|
||||
for row in range(self.height):
|
||||
col = 0
|
||||
for y in range(self.height):
|
||||
x = 0
|
||||
while True:
|
||||
bit = col % 8
|
||||
bit = x % 8
|
||||
if bit == 0:
|
||||
if col >= self.width:
|
||||
if x >= self.width:
|
||||
break
|
||||
byte = 0
|
||||
if col < self.width:
|
||||
if x < self.width:
|
||||
if reverse:
|
||||
byte |= self.pixels[row * self.width + col] << bit
|
||||
byte |= self.pixels[y * self.width + x] << bit
|
||||
else:
|
||||
# Normal map MSB of byte 0 is (0, 0)
|
||||
byte |= self.pixels[row * self.width + col] << (7 - bit)
|
||||
byte |= self.pixels[y * self.width + x] << (7 - bit)
|
||||
if bit == 7:
|
||||
yield byte
|
||||
col += 1
|
||||
x += 1
|
||||
|
||||
# Vertical mapping
|
||||
def get_vbyte(self, reverse):
|
||||
for col in range(self.width):
|
||||
row = 0
|
||||
for x in range(self.width):
|
||||
y = 0
|
||||
while True:
|
||||
bit = row % 8
|
||||
bit = y % 8
|
||||
if bit == 0:
|
||||
if row >= self.height:
|
||||
if y >= self.height:
|
||||
break
|
||||
byte = 0
|
||||
if row < self.height:
|
||||
if y < self.height:
|
||||
if reverse:
|
||||
byte |= self.pixels[row * self.width + col] << (7 - bit)
|
||||
byte |= self.pixels[y * self.width + x] << (7 - bit)
|
||||
else:
|
||||
# Normal map MSB of byte 0 is (0, 7)
|
||||
byte |= self.pixels[row * self.width + col] << bit
|
||||
byte |= self.pixels[y * self.width + x] << bit
|
||||
if bit == 7:
|
||||
yield byte
|
||||
row += 1
|
||||
y += 1
|
||||
|
||||
|
||||
class Glyph(object):
|
||||
def __init__(self, pixels, width, height, top, advance_width):
|
||||
self.bitmap = Bitmap(width, height, pixels)
|
||||
def __init__(self, char, pixels, width, height, top, advance_width):
|
||||
self.bitmap = Bitmap(char, width, height, pixels)
|
||||
|
||||
# The glyph bitmap's top-side bearing, i.e. the vertical distance from
|
||||
# the baseline to the bitmap's top-most scanline.
|
||||
|
@ -190,7 +319,7 @@ class Glyph(object):
|
|||
return self.bitmap.height
|
||||
|
||||
@staticmethod
|
||||
def from_glyphslot(slot):
|
||||
def from_glyphslot(char, slot):
|
||||
"""Construct and return a Glyph object from a FreeType GlyphSlot."""
|
||||
pixels = Glyph.unpack_mono_bitmap(slot.bitmap)
|
||||
width, height = slot.bitmap.width, slot.bitmap.rows
|
||||
|
@ -200,7 +329,7 @@ class Glyph(object):
|
|||
# which means that the pixel values are multiples of 64.
|
||||
advance_width = slot.advance.x / 64
|
||||
|
||||
return Glyph(pixels, width, height, top, advance_width)
|
||||
return Glyph(char, pixels, width, height, top, advance_width)
|
||||
|
||||
@staticmethod
|
||||
def unpack_mono_bitmap(bitmap):
|
||||
|
@ -308,36 +437,51 @@ class Font(dict):
|
|||
# render a monochromatic bitmap representation.
|
||||
self._face.load_char(char, freetype.FT_LOAD_RENDER |
|
||||
freetype.FT_LOAD_TARGET_MONO)
|
||||
return Glyph.from_glyphslot(self._face.glyph)
|
||||
return Glyph.from_glyphslot(char, self._face.glyph)
|
||||
|
||||
def _render_char(self, char):
|
||||
glyph = self._glyphs[char]['glyph']
|
||||
char_width = int(max(glyph.width, glyph.advance_width)) # Actual width
|
||||
width = self.width if self.width else char_width # Space required if monospaced
|
||||
outbuffer = Bitmap(width, self.height)
|
||||
bitmap = Bitmap(char, width, self.height)
|
||||
|
||||
# The vertical drawing position should place the glyph
|
||||
# on the baseline as intended.
|
||||
row = self.height - int(glyph.ascent) - self._max_descent
|
||||
outbuffer.bitblt(glyph.bitmap, row)
|
||||
self[char] = [outbuffer, width, char_width]
|
||||
bitmap.bitblt(glyph.bitmap, row)
|
||||
self[char] = [bitmap, width, char_width]
|
||||
|
||||
def stream_char(self, char, hmap, reverse):
|
||||
outbuffer, _, _ = self[char]
|
||||
bitmap, _, _ = self[char]
|
||||
bitmap.display()
|
||||
if hmap:
|
||||
gen = outbuffer.get_hbyte(reverse)
|
||||
gen = bitmap.get_hbyte(reverse)
|
||||
else:
|
||||
gen = outbuffer.get_vbyte(reverse)
|
||||
gen = bitmap.get_vbyte(reverse)
|
||||
yield from gen
|
||||
|
||||
def build_lmap_arrays(self):
|
||||
data = bytearray()
|
||||
index = bytearray((0, 0))
|
||||
for char in self.charset:
|
||||
bitmap, width, char_width = self[char]
|
||||
bitmap.display()
|
||||
data += byte(1 if bitmap.is_char_lhmap() else 0)
|
||||
data += byte(width)
|
||||
# data += byte_pair(width)
|
||||
for b in bitmap.stream():
|
||||
data += b
|
||||
index += byte_pair(len(data))
|
||||
return data, index
|
||||
|
||||
def build_arrays(self, hmap, reverse):
|
||||
data = bytearray()
|
||||
index = bytearray((0, 0))
|
||||
for char in self.charset:
|
||||
width = self[char][1]
|
||||
data += (width).to_bytes(2, byteorder='little')
|
||||
data += byte_pair(width)
|
||||
data += bytearray(self.stream_char(char, hmap, reverse))
|
||||
index += (len(data)).to_bytes(2, byteorder='little')
|
||||
index += byte_pair(len(data))
|
||||
return data, index
|
||||
|
||||
def build_binary_array(self, hmap, reverse, sig):
|
||||
|
@ -350,34 +494,68 @@ class Font(dict):
|
|||
|
||||
# PYTHON FILE WRITING
|
||||
|
||||
STR01 = """# Code generated by font-to-py.py.
|
||||
HEADER = """# Code generated by font-to-py.py.
|
||||
# Font: %(font)s
|
||||
version = '0.2'
|
||||
|
||||
def from_bytes(data):
|
||||
return int.from_bytes(data, 'little')
|
||||
"""
|
||||
|
||||
HEADER_CHARSET = """# Code generated by font-to-py.py.
|
||||
# Font: %(font)s
|
||||
version = '0.2'
|
||||
CHARSET = %(charset)s
|
||||
|
||||
def from_bytes(data):
|
||||
return int.from_bytes(data, 'little')
|
||||
"""
|
||||
|
||||
STR02 = """_mvfont = memoryview(_font)
|
||||
CHAR_BOUNDS = """\
|
||||
def _char_bounds(ch):
|
||||
index = ord(ch) - %(minchar)d
|
||||
offset = 2 * index
|
||||
start = from_bytes(_index[offset:offset+2])
|
||||
next_offset = 2 * (index + 1)
|
||||
end = from_bytes(_index[next_offset:next_offset+2])
|
||||
return start, end
|
||||
"""
|
||||
|
||||
CHAR_BOUNDS_CHARSET = """\
|
||||
def _char_bounds(ch):
|
||||
index = CHARSET[ch]
|
||||
offset = 2 * index
|
||||
start = int.from_bytes(_index[offset:offset+2], 'little')
|
||||
start = from_bytes(_index[offset:offset+2])
|
||||
next_offset = 2 * (index + 1)
|
||||
end = int.from_bytes(_index[next_offset:next_offset+2], 'little')
|
||||
end = from_bytes(_index[next_offset:next_offset+2])
|
||||
return start, end
|
||||
"""
|
||||
|
||||
GET_CHAR = """
|
||||
_mvfont = memoryview(_font)
|
||||
|
||||
def get_char(ch):
|
||||
start, end = _char_bounds(ch)
|
||||
width = int.from_bytes(_mvfont[start:start + 2], 'little')
|
||||
width = from_bytes(_mvfont[start:start + 2])
|
||||
return _mvfont[start + 2:end], %(height)s, width
|
||||
"""
|
||||
|
||||
GET_CHAR_LMAP = """
|
||||
_mvfont = memoryview(_font)
|
||||
|
||||
def get_char(ch):
|
||||
start, end = _char_bounds(ch)
|
||||
is_lhmap = _mvfont[start]
|
||||
width = _mvfont[start+1]
|
||||
return is_lhmap, _mvfont[start + 2:end], %(height)s, width
|
||||
"""
|
||||
|
||||
|
||||
def write_func(stream, name, arg):
|
||||
stream.write('def {}():\n return {}\n\n'.format(name, arg))
|
||||
|
||||
|
||||
def write_font(op_path, font_path, height, monospaced, hmap, reverse, charset):
|
||||
def write_font(op_path, font_path, height, monospaced, hmap, lmap, reverse, charset):
|
||||
try:
|
||||
fnt = Font(font_path, height, charset, monospaced)
|
||||
except freetype.ft_errors.FT_Exception:
|
||||
|
@ -385,32 +563,48 @@ def write_font(op_path, font_path, height, monospaced, hmap, reverse, charset):
|
|||
return False
|
||||
try:
|
||||
with open(op_path, 'w') as stream:
|
||||
write_data(stream, fnt, font_path, monospaced, hmap, reverse, charset)
|
||||
write_data(stream, fnt, font_path, monospaced, hmap, lmap, reverse, charset)
|
||||
except OSError:
|
||||
print("Can't open", op_path, 'for writing')
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def write_data(stream, fnt, font_path, monospaced, hmap, reverse, charset):
|
||||
def write_data(stream, fnt, font_path, monospaced, hmap, lmap, reverse, charset):
|
||||
height = fnt.height # Actual height, not target height
|
||||
stream.write(STR01 % {'font': os.path.split(font_path)[1],
|
||||
'charset': {ch: i for i, ch in enumerate(charset)}
|
||||
})
|
||||
sequential_charset = not bool(len([x for x in range(len(charset) - 1)
|
||||
if ord(charset[x]) + 1 != ord(charset[x+1])]))
|
||||
header_data = {'font': os.path.split(font_path)[1],
|
||||
'charset': {ch: i for i, ch in enumerate(charset)}}
|
||||
if sequential_charset:
|
||||
stream.write(HEADER % header_data)
|
||||
else:
|
||||
stream.write(HEADER_CHARSET % header_data)
|
||||
stream.write('\n')
|
||||
write_func(stream, 'height', height)
|
||||
write_func(stream, 'max_width', fnt.max_width)
|
||||
write_func(stream, 'hmap', hmap)
|
||||
write_func(stream, 'lmap', lmap)
|
||||
write_func(stream, 'reverse', reverse)
|
||||
write_func(stream, 'monospaced', monospaced)
|
||||
data, index = fnt.build_arrays(hmap, reverse)
|
||||
if lmap:
|
||||
data, index = fnt.build_lmap_arrays()
|
||||
else:
|
||||
data, index = fnt.build_arrays(hmap, reverse)
|
||||
bw_font = ByteWriter(stream, '_font')
|
||||
bw_font.odata(data)
|
||||
bw_font.eot()
|
||||
bw_index = ByteWriter(stream, '_index')
|
||||
bw_index.odata(index)
|
||||
bw_index.eot()
|
||||
stream.write(STR02 % {'height': height})
|
||||
if sequential_charset:
|
||||
stream.write(CHAR_BOUNDS % {'minchar': ord(charset[0])})
|
||||
else:
|
||||
stream.write(CHAR_BOUNDS_CHARSET)
|
||||
if lmap:
|
||||
stream.write(GET_CHAR_LMAP % {'height': height})
|
||||
else:
|
||||
stream.write(GET_CHAR % {'height': height})
|
||||
|
||||
# BINARY OUTPUT
|
||||
# hmap reverse magic bytes
|
||||
|
@ -470,6 +664,8 @@ if __name__ == "__main__":
|
|||
|
||||
parser.add_argument('-x', '--xmap', action='store_true',
|
||||
help='Horizontal (x) mapping')
|
||||
parser.add_argument('-L', '--lmap', action='store_true',
|
||||
help='Line mapping')
|
||||
parser.add_argument('-r', '--reverse', action='store_true',
|
||||
help='Bit reversal')
|
||||
parser.add_argument('-f', '--fixed', action='store_true',
|
||||
|
@ -519,7 +715,7 @@ if __name__ == "__main__":
|
|||
|
||||
print('Writing Python font file.')
|
||||
if not write_font(args.outfile, args.infile, args.height, args.fixed,
|
||||
args.xmap, args.reverse, charset):
|
||||
args.xmap, args.lmap, args.reverse, charset):
|
||||
sys.exit(1)
|
||||
|
||||
print(args.outfile, 'written successfully.')
|
||||
|
|
154
writer.py
154
writer.py
|
@ -28,64 +28,34 @@
|
|||
# same Display object.
|
||||
|
||||
|
||||
def from_bytes(data, signed=False):
|
||||
return int.from_bytes(data, 'little', signed)
|
||||
|
||||
|
||||
class Writer(object):
|
||||
# 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_position(cls, x, y):
|
||||
cls.x_pos = x
|
||||
cls.y_pos = y
|
||||
|
||||
def __init__(self, device, font, rotation=None):
|
||||
def __init__(self, display, font):
|
||||
super().__init__()
|
||||
self.device = device
|
||||
self.set_display(display)
|
||||
self.set_font(font)
|
||||
self.set_rotation(rotation)
|
||||
|
||||
def set_display(self, display):
|
||||
self.display = display
|
||||
|
||||
def set_font(self, font):
|
||||
self.font = font
|
||||
self._draw_char = self._draw_vmap_char
|
||||
if font.hmap():
|
||||
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
|
||||
if font.lmap():
|
||||
self.draw_char = self._draw_lmap_char
|
||||
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)
|
||||
self.draw_char = self._draw_char
|
||||
|
||||
def _newline(self):
|
||||
Writer.x_pos = 0
|
||||
|
@ -93,64 +63,114 @@ class Writer(object):
|
|||
|
||||
def draw_text(self, string):
|
||||
for char in string:
|
||||
self._draw_char(char)
|
||||
self.draw_char(char)
|
||||
|
||||
def _draw_hmap_char(self, char):
|
||||
def _draw_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:
|
||||
if Writer.x_pos + char_width > self.display.screen_width:
|
||||
self._newline()
|
||||
|
||||
if self.font.hmap():
|
||||
self._draw_hmap_char(glyph, char_height, char_width)
|
||||
else:
|
||||
self._draw_vmap_char(glyph, char_height, char_width)
|
||||
|
||||
Writer.x_pos += char_width
|
||||
|
||||
def _draw_hmap_char(self, glyph, char_height, char_width):
|
||||
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'
|
||||
)
|
||||
glyph_row = from_bytes(glyph[glyph_row_start:glyph_row_start + bytes_per_row])
|
||||
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)
|
||||
self.display.pixel(x, y)
|
||||
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()
|
||||
|
||||
def _draw_vmap_char(self, glyph, char_height, char_width):
|
||||
div, mod = divmod(char_height, 8)
|
||||
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'
|
||||
)
|
||||
glyph_col = from_bytes(glyph[glyph_col_start:glyph_col_start + bytes_per_col])
|
||||
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)
|
||||
self.display.pixel(x, y)
|
||||
y += 1
|
||||
|
||||
def _draw_lmap_char(self, char):
|
||||
if char == '\n':
|
||||
self._newline()
|
||||
return
|
||||
|
||||
is_lhmap, data, char_height, char_width = self.font.get_char(char)
|
||||
|
||||
if Writer.x_pos + char_width > self.display.screen_width:
|
||||
self._newline()
|
||||
|
||||
if is_lhmap:
|
||||
self._draw_lhmap_char(data)
|
||||
else:
|
||||
self._draw_lvmap_char(data)
|
||||
|
||||
Writer.x_pos += char_width
|
||||
|
||||
def _draw_lhmap_char(self, data):
|
||||
prev_lines = []
|
||||
y = 0
|
||||
data_i = 0
|
||||
while data_i < len(data):
|
||||
num_lines = data[data_i]
|
||||
if num_lines:
|
||||
prev_lines = []
|
||||
y = Writer.y_pos + data[data_i + 1]
|
||||
for i in range(num_lines):
|
||||
lstart = data_i + 2 + (i * 2)
|
||||
x = Writer.x_pos + data[lstart]
|
||||
length = data[lstart + 1]
|
||||
prev_lines.append((x, length))
|
||||
self.display.hline(x, y, length)
|
||||
data_i = lstart + 2
|
||||
else:
|
||||
y += 1
|
||||
for line in prev_lines:
|
||||
self.display.hline(line[0], y, line[1])
|
||||
data_i += 1
|
||||
|
||||
def _draw_lvmap_char(self, data):
|
||||
prev_lines = []
|
||||
x = 0
|
||||
data_i = 0
|
||||
while data_i < len(data):
|
||||
num_lines = data[data_i]
|
||||
if num_lines:
|
||||
prev_lines = []
|
||||
x = Writer.x_pos + data[data_i + 1]
|
||||
for i in range(num_lines):
|
||||
lstart = data_i + 2 + (i * 2)
|
||||
y = Writer.y_pos + data[lstart]
|
||||
length = data[lstart + 1]
|
||||
prev_lines.append((y, length))
|
||||
self.display.vline(x, y, length)
|
||||
data_i = lstart + 2
|
||||
else:
|
||||
x += 1
|
||||
for line in prev_lines:
|
||||
self.display.vline(x, line[0], line[1])
|
||||
data_i += 1
|
||||
|
|
Ładowanie…
Reference in New Issue