Compare commits

...

10 commits

Author SHA1 Message Date
Jeff Epler
004085e34c bdf: Improve performance of bitmap font loading
Loading fonts can be one of the slow phases of a Magtag type project,
and run time decreases battery lifetime.  Therefore, it's warranted
to write less idiomatic Python here if it improves runtime significantly.

This change assumes that the lines within a character come in a specific
order.  While the document ostensibly defining the BDF file format
https://www.adobe.com/content/dam/acom/en/devnet/font/pdfs/5005.BDF_Spec.pdf
does not say that the order is defined, I inspected the source of the
Linux program "bdftopcf" which has been used for decades to convert
textual bdf font files into binary pcf files, and it too assumes that the
lines come in a defined order.  I doubt that bdf files that do not work
with the linux bdftopcf program are less scarce than unicorns.

I timed loading the characters "Adafruit CircuitPython" from the font
"GothamBlack-50.bdf" on a PyBadge in CircuitPython 6.0.0:

```python
font = load_font("/GothamBlack-50.bdf")
font.load_glyphs("Adafruit CircuitPython")
```

The time decreased from 919ms to 530ms (-42%).

Just reading all the lines in the font file takes 380ms.  However,
only the first 38% of the file (or so) needs to be read, so the I/O time
is about 140ms.
2020-12-05 16:00:17 -06:00
Jeff Epler
cbf2261016 bdf: Add ascent, descent properties
This will be used by a future version of adafruit_display_text to avoid
needing to load glyphs from the font to guess these values.
2020-12-05 16:00:01 -06:00
Scott Shawcroft
51e9a1fa2e
Merge pull request #30 from FoamyGuy/magtag_example
adding MagTag example
2020-11-23 15:16:04 -08:00
foamyguy
8c1fa9b684 a few comments in magtag example 2020-11-22 15:04:12 -06:00
foamyguy
c38baeb932 adding magtag example 2020-11-22 15:01:06 -06:00
foamyguy
7e153547f5
Merge pull request #29 from adafruit/tannewt-patch-1
Fix 2.6.0 lint
2020-09-03 19:33:27 -05:00
Scott Shawcroft
ca48d4afde
Merge pull request #28 from ronfischler/set-changed-during-iteration-fix
Make a copy of the set to iterate over, not the original set we will …
2020-09-02 16:57:41 -07:00
Scott Shawcroft
1f46cc3a68
reorder imports 2020-09-02 16:46:51 -07:00
Scott Shawcroft
977edcbd88
Reorder imports 2020-09-02 16:43:09 -07:00
ronfischler
2501072843 Make a copy of the set to iterate over, not the original set we will be removing items from 2020-09-02 12:06:32 -07:00
4 changed files with 138 additions and 100 deletions

View file

@ -57,37 +57,68 @@ class BDF(GlyphCache):
self.file.seek(0)
self.bitmap_class = bitmap_class
line = self.file.readline()
line = str(line, "utf-8")
if not line or not line.startswith("STARTFONT 2.1"):
if not line or not line.startswith(b"STARTFONT 2.1"):
raise ValueError("Unsupported file version")
self.point_size = None
self.x_resolution = None
self.y_resolution = None
self._ascent = None
self._descent = None
@property
def descent(self):
"""The number of pixels below the baseline of a typical descender"""
if self._descent is None:
self.file.seek(0)
while True:
line = self.file.readline()
if not line:
break
if line.startswith(b"FONT_DESCENT "):
self._descent = int(line.split()[1])
break
return self._descent
@property
def ascent(self):
"""The number of pixels above the baseline of a typical ascender"""
if self._ascent is None:
self.file.seek(0)
while True:
line = self.file.readline()
if not line:
break
if line.startswith(b"FONT_ASCENT "):
self._ascent = int(line.split()[1])
break
return self._ascent
def get_bounding_box(self):
"""Return the maximum glyph size as a 4-tuple of: width, height, x_offset, y_offset"""
self.file.seek(0)
while True:
line = self.file.readline()
line = str(line, "utf-8")
if not line:
break
if line.startswith("FONTBOUNDINGBOX "):
if line.startswith(b"FONTBOUNDINGBOX "):
_, x, y, x_offset, y_offset = line.split()
return (int(x), int(y), int(x_offset), int(y_offset))
return None
def _read_to(self, prefix):
_readline = self.file.readline
while True:
line = _readline()
if not line or line.startswith(prefix):
return line
def load_glyphs(self, code_points):
# pylint: disable=too-many-statements,too-many-branches,too-many-nested-blocks,too-many-locals
metadata = True
character = False
code_point = None
bytes_per_row = 1
desired_character = False
current_info = {}
current_y = 0
rounded_x = 1
if isinstance(code_points, int):
remaining = set()
remaining.add(code_points)
@ -97,98 +128,61 @@ class BDF(GlyphCache):
remaining = code_points
else:
remaining = set(code_points)
for code_point in remaining:
for code_point in remaining.copy():
if code_point in self._glyphs and self._glyphs[code_point]:
remaining.remove(code_point)
if not remaining:
return
x, _, _, _ = self.get_bounding_box()
_readline = self.file.readline
_read = self.file.read
self.file.seek(0)
while True:
line = self.file.readline()
_, point_size, x_resolution, y_resolution = self._read_to(b"SIZE ").split()
self.point_size = int(point_size)
self.x_resolution = int(x_resolution)
self.y_resolution = int(y_resolution)
while remaining:
line = self._read_to(b"ENCODING ")
if not line:
break
if line.startswith(b"CHARS "):
metadata = False
elif line.startswith(b"SIZE"):
_, self.point_size, self.x_resolution, self.y_resolution = line.split()
elif line.startswith(b"COMMENT"):
pass
elif line.startswith(b"STARTCHAR"):
# print(lineno, line.strip())
# _, character_name = line.split()
character = True
elif line.startswith(b"ENDCHAR"):
character = False
if desired_character:
bounds = current_info["bounds"]
shift = current_info["shift"]
gc.collect()
self._glyphs[code_point] = Glyph(
current_info["bitmap"],
0,
bounds[0],
bounds[1],
bounds[2],
bounds[3],
shift[0],
shift[1],
)
remaining.remove(code_point)
if not remaining:
return
desired_character = False
elif line.startswith(b"BBX"):
if desired_character:
_, x, y, x_offset, y_offset = line.split()
x = int(x)
y = int(y)
x_offset = int(x_offset)
y_offset = int(y_offset)
current_info["bounds"] = (x, y, x_offset, y_offset)
current_info["bitmap"] = self.bitmap_class(x, y, 2)
elif line.startswith(b"BITMAP"):
if desired_character:
rounded_x = x // 8
if x % 8 > 0:
rounded_x += 1
bytes_per_row = rounded_x
if bytes_per_row % 4 > 0:
bytes_per_row += 4 - bytes_per_row % 4
current_y = 0
elif line.startswith(b"ENCODING"):
_, code_point = line.split()
code_point = int(code_point)
if code_point in remaining:
desired_character = True
current_info = {"bitmap": None, "bounds": None, "shift": None}
elif line.startswith(b"DWIDTH"):
if desired_character:
_, shift_x, shift_y = line.split()
shift_x = int(shift_x)
shift_y = int(shift_y)
current_info["shift"] = (shift_x, shift_y)
elif line.startswith(b"SWIDTH"):
pass
elif character:
if desired_character:
bits = int(line.strip(), 16)
width = current_info["bounds"][0]
start = current_y * width
x = 0
for i in range(rounded_x):
val = (bits >> ((rounded_x - i - 1) * 8)) & 0xFF
for j in range(7, -1, -1):
if x >= width:
break
bit = 0
if val & (1 << j) != 0:
bit = 1
current_info["bitmap"][start + x] = bit
x += 1
current_y += 1
elif metadata:
# print(lineno, line.strip())
pass
_, code_point = line.split()
code_point = int(code_point)
if code_point not in remaining:
continue
line = self._read_to(b"DWIDTH ")
_, shift_x, shift_y = line.split()
shift_x = int(shift_x)
shift_y = int(shift_y)
line = self._read_to(b"BBX ")
_, x, y, x_offset, y_offset = line.split()
x = int(x)
y = int(y)
x_offset = int(x_offset)
y_offset = int(y_offset)
line = self._read_to(b"BITMAP")
bitmap = self.bitmap_class(x, y, 2)
start = 0
for _ in range(y):
idx = 0
for idx in range(x):
if idx % 4 == 0:
value = int(_read(1), 16)
if value & 8:
bitmap[start + idx] = 1
value <<= 1
_readline()
start += x
gc.collect()
self._glyphs[code_point] = Glyph(
bitmap, 0, x, y, x_offset, y_offset, shift_x, shift_y
)
remaining.remove(code_point)

View file

@ -8,8 +8,8 @@ bitmap with pixels matching glyphs from a given String
import board
from adafruit_bitmap_font import bitmap_font # pylint: disable=wrong-import-position
import displayio
from adafruit_bitmap_font import bitmap_font
font = bitmap_font.load_font("fonts/Arial-16.bdf")

View file

@ -0,0 +1,44 @@
"""
This example uses addfruit_display_text.label to display text using a custom font
loaded by adafruit_bitmap_font.
Adapted for use on MagTag
"""
import time
import board
from adafruit_display_text import label
from adafruit_bitmap_font import bitmap_font
# use built in display (PyPortal, PyGamer, PyBadge, CLUE, etc.)
# see guide for setting up external displays (TFT / OLED breakouts, RGB matrices, etc.)
# https://learn.adafruit.com/circuitpython-display-support-using-displayio/display-and-display-bus
display = board.DISPLAY
# wait until we can refresh the display
time.sleep(display.time_to_refresh)
# Set text, font, and color
text = "HELLO WORLD\nbitmap_font example"
font = bitmap_font.load_font("fonts/Arial-16.bdf")
color = 0xFFFFFF
background_color = 0x999999
# Create the tet label
text_area = label.Label(
font,
text=text,
color=color,
background_color=background_color,
padding_top=3,
padding_bottom=3,
padding_right=4,
padding_left=4,
)
text_area.line_spacing = 1.0
# Set the location
text_area.x = 20
text_area.y = 20
# Show it and refresh
display.show(text_area)
display.refresh()
while True:
pass

View file

@ -4,8 +4,8 @@ loaded by adafruit_bitmap_font
"""
import board
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text import label
from adafruit_bitmap_font import bitmap_font
display = board.DISPLAY