Skip to content

Commit be8d832

Browse files
committed
integrate accents and outlines with scrolling. add support for outline accents. new example demoing all 3 working together.
1 parent 2776b8d commit be8d832

File tree

4 files changed

+172
-52
lines changed

4 files changed

+172
-52
lines changed

adafruit_display_text/bitmap_label.py

Lines changed: 121 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
ACCENT_END = const(1)
5252
ACCENT_FG = const(2)
5353
ACCENT_BG = const(3)
54+
ACCENT_TYPE = const(4)
5455

5556

5657
class Label(LabelBase):
@@ -191,8 +192,7 @@ def __init__(
191192
# outline handling vars
192193
self._outline_size = outline_size
193194
self._outline_color = outline_color
194-
if self._outline_color is not None:
195-
self._init_outline_stamp(outline_size)
195+
self._init_outline_stamp(outline_size)
196196

197197
self._save_text = save_text
198198
self._text = self._replace_tabs(self._text)
@@ -290,6 +290,10 @@ def _reset_text(
290290
box_x = box_x + self._padding_left + self._padding_right
291291
box_y = box_y + self._padding_top + self._padding_bottom
292292

293+
if self._outline_color is not None:
294+
box_x += self._outline_size * 2
295+
box_y += self._outline_size * 2
296+
293297
# Create the Bitmap unless it can be reused
294298
new_bitmap = None
295299
if self._bitmap is None or self._bitmap.width != box_x or self._bitmap.height != box_y:
@@ -487,7 +491,9 @@ def _place_text(
487491
my_glyph = font.get_glyph(ord(char))
488492
if self._tmp_glyph_bitmap is None and len(self._accent_ranges) > 0:
489493
self._tmp_glyph_bitmap = displayio.Bitmap(
490-
my_glyph.bitmap.width, my_glyph.bitmap.height, len(self._palette)
494+
my_glyph.width + self.outline_size * 2,
495+
my_glyph.height + self.outline_size * 2,
496+
len(self._palette),
491497
)
492498

493499
if my_glyph is None: # Error checking: no glyph found
@@ -529,58 +535,110 @@ def _place_text(
529535
print(f'Warning: Glyph clipped, exceeds descent property: "{char}"')
530536

531537
accented = False
538+
accent_type = "foreground_background"
532539
if len(self._accent_ranges) > 0:
533540
for accent_range in self._accent_ranges:
534-
if accent_range[ACCENT_START] <= char_idx < accent_range[ACCENT_END]:
535-
self._tmp_glyph_bitmap.fill(accent_range[ACCENT_BG])
536-
537-
bitmaptools.blit(
538-
self._tmp_glyph_bitmap,
539-
my_glyph.bitmap,
540-
0,
541-
0,
542-
skip_source_index=0,
543-
)
544-
bitmaptools.replace_color(
545-
self._tmp_glyph_bitmap, 1, accent_range[ACCENT_FG]
546-
)
547-
accented = True
541+
if (
542+
accent_range[ACCENT_START]
543+
<= (self.current_index + char_idx) % len(self._full_text)
544+
< accent_range[ACCENT_END]
545+
):
546+
accent_type = accent_range[ACCENT_TYPE]
547+
if accent_range[ACCENT_TYPE] == "foreground_background":
548+
self._tmp_glyph_bitmap.fill(accent_range[ACCENT_BG])
549+
550+
bitmaptools.blit(
551+
self._tmp_glyph_bitmap,
552+
my_glyph.bitmap,
553+
0,
554+
0,
555+
x1=glyph_offset_x,
556+
y1=y_clip,
557+
x2=glyph_offset_x + my_glyph.width,
558+
y2=my_glyph.height,
559+
skip_source_index=0,
560+
)
561+
bitmaptools.replace_color(
562+
self._tmp_glyph_bitmap, 1, accent_range[ACCENT_FG]
563+
)
564+
accented = True
565+
elif accent_range[ACCENT_TYPE] == "outline":
566+
self._tmp_glyph_bitmap.fill(0)
567+
bitmaptools.blit(
568+
self._tmp_glyph_bitmap,
569+
my_glyph.bitmap,
570+
self._outline_size,
571+
self._outline_size,
572+
x1=glyph_offset_x,
573+
y1=y_clip,
574+
x2=glyph_offset_x + my_glyph.width,
575+
y2=my_glyph.height,
576+
skip_source_index=0,
577+
)
578+
self._add_outline(self._tmp_glyph_bitmap)
579+
bitmaptools.replace_color(
580+
self._tmp_glyph_bitmap, 1, accent_range[ACCENT_FG]
581+
)
582+
bitmaptools.replace_color(
583+
self._tmp_glyph_bitmap, 2, accent_range[ACCENT_BG]
584+
)
585+
accented = True
586+
587+
# only one accent range can effect a given character
548588
break
549589

550-
self._blit(
551-
bitmap,
552-
max(xposition + my_glyph.dx, 0),
553-
y_blit_target,
554-
my_glyph.bitmap if not accented else self._tmp_glyph_bitmap,
555-
x_1=glyph_offset_x,
556-
y_1=y_clip,
557-
x_2=glyph_offset_x + my_glyph.width,
558-
y_2=my_glyph.height,
559-
skip_index=skip_index
560-
if not accented
561-
else None, # do not copy over any 0 background pixels if not accented
562-
)
563-
564-
xposition = xposition + my_glyph.shift_x
565-
566-
self._add_outline()
590+
if accented:
591+
bitmaptools.blit(
592+
bitmap,
593+
self._tmp_glyph_bitmap,
594+
max(xposition + my_glyph.dx, 0),
595+
y_blit_target,
596+
)
597+
else:
598+
try:
599+
self._blit(
600+
bitmap,
601+
max(xposition + my_glyph.dx, 0),
602+
y_blit_target,
603+
my_glyph.bitmap if not accented else self._tmp_glyph_bitmap,
604+
x_1=glyph_offset_x,
605+
y_1=y_clip,
606+
x_2=glyph_offset_x + my_glyph.width,
607+
y_2=my_glyph.height,
608+
skip_index=skip_index
609+
if not accented
610+
else None, # do not copy any 0 background pixels if not accented
611+
)
612+
except ValueError:
613+
# It's possible to overshoot the width of the bitmap if max_characters
614+
# is enabled and outline is used on at least some of the text.
615+
# In this case just skip any characters that fall outside the
616+
# max_characters box size without accounting for outline size.
617+
pass
618+
619+
if accented and accent_type == "outline":
620+
xposition = xposition + my_glyph.shift_x + self._outline_size
621+
else:
622+
xposition = xposition + my_glyph.shift_x
623+
624+
self._add_outline(self.bitmap)
567625
# bounding_box
568626
return left, top, right - left, bottom - top
569627

570-
def _add_outline(self):
628+
def _add_outline(self, bitmap):
571629
"""
572630
Blit the outline into the labels Bitmap. Will blit self._stamp_source for each
573631
pixel of the foreground color but skip the foreground color when we blit,
574632
creating an outline.
575633
:return: None
576634
"""
577-
if self._outline_color is not None:
578-
for y in range(self.bitmap.height):
579-
for x in range(self.bitmap.width):
580-
if self.bitmap[x, y] == 1:
635+
if bitmap is not self.bitmap or self._outline_color is not None:
636+
for y in range(bitmap.height):
637+
for x in range(bitmap.width):
638+
if bitmap[x, y] == 1:
581639
try:
582640
bitmaptools.blit(
583-
self.bitmap,
641+
bitmap,
584642
self._stamp_source,
585643
x - self._outline_size,
586644
y - self._outline_size,
@@ -855,7 +913,9 @@ def outline_size(self, new_outline_size):
855913
scale=self.scale,
856914
)
857915

858-
def add_accent_range(self, start, end, foreground_color, background_color):
916+
def add_accent_range(
917+
self, start, end, foreground_color, background_color, accent_type="foreground_background"
918+
):
859919
"""
860920
Set a range of text to get accented with the specified colors.
861921
@@ -865,9 +925,12 @@ def add_accent_range(self, start, end, foreground_color, background_color):
865925
the accent foreground color.
866926
:param background_color: The color index within ``color_palette`` to use for
867927
the accent background color.
928+
:param accent_type: The type of accent to use, either "foreground_background" or "outline"
868929
:return: None
869930
"""
870-
self._accent_ranges.append((start, end, foreground_color, background_color))
931+
if accent_type not in {"foreground_background", "outline"}:
932+
raise ValueError("accent_type must be either 'foreground_background' or 'outline'")
933+
self._accent_ranges.append((start, end, foreground_color, background_color, accent_type))
871934
self._reset_text(text=str(self._text))
872935

873936
def remove_accent_range(self, start):
@@ -882,7 +945,14 @@ def remove_accent_range(self, start):
882945
self._accent_ranges.remove(accent_range)
883946
self._reset_text(text=str(self._text))
884947

885-
def add_accent_to_substring(self, substring, foreground_color, background_color, start=0):
948+
def add_accent_to_substring(
949+
self,
950+
substring,
951+
foreground_color,
952+
background_color,
953+
accent_type="foreground_background",
954+
start=0,
955+
):
886956
"""
887957
Add accent to the first occurrence of ``substring`` found in the labels text,
888958
starting from ``start``.
@@ -892,14 +962,18 @@ def add_accent_to_substring(self, substring, foreground_color, background_color,
892962
the accent foreground color.
893963
:param background_color: The color index within ``color_palette`` to use for
894964
the accent background color.
965+
:param accent_type: The type of accent to use, either "foreground_background" or "outline"
895966
:param start: The index within text to start searching for the substring.
896967
Defaults is 0 to search the whole text.
897968
:return: True if the substring was found, False otherwise.
898969
"""
899-
900-
index = self._text.find(substring, start)
970+
if accent_type not in {"foreground_background", "outline"}:
971+
raise ValueError("accent_type must be either 'foreground_background' or 'outline'")
972+
index = self._full_text.find(substring, start)
901973
if index != -1:
902-
self.add_accent_range(index, index + len(substring), foreground_color, background_color)
974+
self.add_accent_range(
975+
index, index + len(substring), foreground_color, background_color, accent_type
976+
)
903977
return True
904978
else:
905979
return False
@@ -915,7 +989,7 @@ def remove_accent_from_substring(self, substring, start=0):
915989
:return: True if the substring was found, False otherwise.
916990
"""
917991

918-
index = self._text.find(substring, start)
992+
index = self._full_text.find(substring, start)
919993
if index != -1:
920994
self.remove_accent_range(index)
921995
return True

docs/examples.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ Basic example of scrolling 'marquee' text
2525
:caption: examples/display_text_scrolling_label.py
2626
:linenos:
2727

28+
Accent Scrolling Label Example
29+
------------------------------
30+
31+
Advanced example of scrolling 'marquee' text including different accents
32+
33+
.. literalinclude:: ../examples/display_text_accent_scrolling_example.py
34+
:caption: examples/display_text_accent_scrolling_example.py
35+
:linenos:
36+
2837
Outline Example
2938
---------------
3039

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries
2+
# SPDX-License-Identifier: MIT
3+
4+
import displayio
5+
import supervisor
6+
import terminalio
7+
8+
from adafruit_display_text.bitmap_label import Label
9+
10+
display = supervisor.runtime.display
11+
text = "Hello world CircuitPython Labels are awesome!"
12+
13+
accent_palette = displayio.Palette(7)
14+
accent_palette[3] = 0x3774A7
15+
accent_palette[4] = 0xFFD748
16+
accent_palette[5] = 0xFFFFFF
17+
accent_palette[6] = 0x652F8F
18+
19+
scrolling_label = Label(
20+
terminalio.FONT,
21+
text=text,
22+
max_characters=20,
23+
animate_time=0.3,
24+
color_palette=accent_palette,
25+
color=0xAAAAAA,
26+
scale=2,
27+
)
28+
29+
scrolling_label.x = 10
30+
scrolling_label.y = 10
31+
display.root_group = scrolling_label
32+
33+
scrolling_label.add_accent_to_substring("CircuitPython", 5, 6)
34+
scrolling_label.add_accent_to_substring("awesome!", 3, 4, "outline")
35+
36+
while True:
37+
scrolling_label.update()

examples/display_text_scrolling_label.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88

99
display = supervisor.runtime.display
1010
text = "Hello world CircuitPython scrolling label"
11-
my_scrolling_label = Label(terminalio.FONT, text=text, max_characters=20, animate_time=0.3)
12-
my_scrolling_label.x = 10
13-
my_scrolling_label.y = 10
14-
display.root_group = my_scrolling_label
11+
scrolling_label = Label(terminalio.FONT, text=text, max_characters=20, animate_time=0.3)
12+
scrolling_label.x = 10
13+
scrolling_label.y = 10
14+
display.root_group = scrolling_label
1515
while True:
16-
my_scrolling_label.update()
16+
scrolling_label.update()

0 commit comments

Comments
 (0)