Skip to content

Commit ad55877

Browse files
committed
accent functionality moved into bitmap_label
1 parent 727a102 commit ad55877

File tree

3 files changed

+195
-4
lines changed

3 files changed

+195
-4
lines changed

adafruit_display_text/bitmap_label.py

Lines changed: 144 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# SPDX-FileCopyrightText: 2020 Kevin Matocha
2+
# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries
23
#
34
# SPDX-License-Identifier: MIT
45

@@ -27,6 +28,7 @@
2728
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git"
2829

2930
import displayio
31+
from micropython import const
3032

3133
from adafruit_display_text import LabelBase
3234

@@ -43,6 +45,12 @@
4345
except ImportError:
4446
pass
4547

48+
# constant indexes for accent_ranges
49+
ACCENT_START = const(0)
50+
ACCENT_END = const(1)
51+
ACCENT_FG = const(2)
52+
ACCENT_BG = const(3)
53+
4654

4755
class Label(LabelBase):
4856
"""A label displaying a string of text that is stored in a bitmap.
@@ -101,13 +109,34 @@ class Label(LabelBase):
101109
"RTL": (False, False, False),
102110
}
103111

104-
def __init__(self, font: FontProtocol, save_text: bool = True, **kwargs) -> None:
112+
def __init__(
113+
self,
114+
font: FontProtocol,
115+
save_text: bool = True,
116+
color_palette: Optional[displayio.Palette] = None,
117+
**kwargs,
118+
) -> None:
105119
self._bitmap = None
106120
self._tilegrid = None
107121
self._prev_label_direction = None
108122

109123
super().__init__(font, **kwargs)
110124

125+
if color_palette is not None:
126+
if len(color_palette) <= 2:
127+
raise ValueError(
128+
"color_palette should be at least 3 colors to "
129+
"provide enough for normal and accented text. "
130+
"color_palette argument can be omitted if not "
131+
"using accents."
132+
)
133+
self._palette = color_palette
134+
self.color = self._color
135+
self.background_color = self._background_color
136+
137+
self._accent_ranges = []
138+
self._tmp_glyph_bitmap = None
139+
111140
self._save_text = save_text
112141
self._text = self._replace_tabs(self._text)
113142

@@ -381,7 +410,8 @@ def _place_text(
381410
top = bottom = y_start
382411
line_spacing = self._line_spacing
383412

384-
for char in text:
413+
for char_idx in range(len(text)):
414+
char = text[char_idx]
385415
if char == "\n": # newline
386416
xposition = x_start # reset to left column
387417
yposition = yposition + self._line_spacing_ypixels(
@@ -390,6 +420,10 @@ def _place_text(
390420

391421
else:
392422
my_glyph = font.get_glyph(ord(char))
423+
if self._tmp_glyph_bitmap is None and len(self._accent_ranges) > 0:
424+
self._tmp_glyph_bitmap = displayio.Bitmap(
425+
my_glyph.bitmap.width, my_glyph.bitmap.height, len(self._palette)
426+
)
393427

394428
if my_glyph is None: # Error checking: no glyph found
395429
print(f"Glyph not found: {repr(char)}")
@@ -429,16 +463,37 @@ def _place_text(
429463
if self._verbose:
430464
print(f'Warning: Glyph clipped, exceeds descent property: "{char}"')
431465

466+
accented = False
467+
if len(self._accent_ranges) > 0:
468+
for accent_range in self._accent_ranges:
469+
if accent_range[ACCENT_START] <= char_idx < accent_range[ACCENT_END]:
470+
self._tmp_glyph_bitmap.fill(accent_range[ACCENT_BG])
471+
472+
bitmaptools.blit(
473+
self._tmp_glyph_bitmap,
474+
my_glyph.bitmap,
475+
0,
476+
0,
477+
skip_source_index=0,
478+
)
479+
bitmaptools.replace_color(
480+
self._tmp_glyph_bitmap, 1, accent_range[ACCENT_FG]
481+
)
482+
accented = True
483+
break
484+
432485
self._blit(
433486
bitmap,
434487
max(xposition + my_glyph.dx, 0),
435488
y_blit_target,
436-
my_glyph.bitmap,
489+
my_glyph.bitmap if not accented else self._tmp_glyph_bitmap,
437490
x_1=glyph_offset_x,
438491
y_1=y_clip,
439492
x_2=glyph_offset_x + my_glyph.width,
440493
y_2=my_glyph.height,
441-
skip_index=skip_index, # do not copy over any 0 background pixels
494+
skip_index=skip_index
495+
if not accented
496+
else None, # do not copy over any 0 background pixels if not accented
442497
)
443498

444499
xposition = xposition + my_glyph.shift_x
@@ -571,3 +626,88 @@ def bitmap(self) -> displayio.Bitmap:
571626
:rtype: displayio.Bitmap
572627
"""
573628
return self._bitmap
629+
630+
def add_accent_range(self, start, end, foreground_color, background_color):
631+
"""
632+
Set a range of text to get accented with the specified colors.
633+
634+
:param start: The start index of the range of text to accent, inclusive.
635+
:param end: The end index of the range of text to accent, exclusive.
636+
:param foreground_color: The color index within ``color_palette`` to use for
637+
the accent foreground color.
638+
:param background_color: The color index within ``color_palette`` to use for
639+
the accent background color.
640+
:return: None
641+
"""
642+
self._accent_ranges.append((start, end, foreground_color, background_color))
643+
self._reset_text(text=str(self._text))
644+
645+
def remove_accent_range(self, start):
646+
"""
647+
Remove the accent that starts at the specified index within the text.
648+
649+
:param start: The start index of the range of accented text, inclusive.
650+
:return: None
651+
"""
652+
for accent_range in reversed(self._accent_ranges):
653+
if accent_range[0] == start:
654+
self._accent_ranges.remove(accent_range)
655+
self._reset_text(text=str(self._text))
656+
657+
def add_accent_to_substring(self, substring, foreground_color, background_color, start=0):
658+
"""
659+
Add accent to the first occurrence of ``substring`` found in the labels text,
660+
starting from ``start``.
661+
662+
:param substring: the substring to accent within the text.
663+
:param foreground_color: The color index within ``color_palette`` to use for
664+
the accent foreground color.
665+
:param background_color: The color index within ``color_palette`` to use for
666+
the accent background color.
667+
:param start: The index within text to start searching for the substring.
668+
Defaults is 0 to search the whole text.
669+
:return: True if the substring was found, False otherwise.
670+
"""
671+
672+
index = self._text.find(substring, start)
673+
if index != -1:
674+
self.add_accent_range(index, index + len(substring), foreground_color, background_color)
675+
return True
676+
else:
677+
return False
678+
679+
def remove_accent_from_substring(self, substring, start=0):
680+
"""
681+
Remove the accent for the first instance of the specified ``substring``
682+
starting at ``start``.
683+
684+
:param substring: the substring to accent within the text.
685+
:param start: The index within text to start searching for the substring.
686+
Defaults is 0 to search the whole text.
687+
:return: True if the substring was found, False otherwise.
688+
"""
689+
690+
index = self._text.find(substring, start)
691+
if index != -1:
692+
self.remove_accent_range(index)
693+
return True
694+
else:
695+
return False
696+
697+
@property
698+
def accent_ranges(self):
699+
"""
700+
The list of ranges that are accented.
701+
:return: List of Tuples containing (start, end, foreground_color, background_color).
702+
"""
703+
return self._accent_ranges
704+
705+
def clear_accent_ranges(self):
706+
"""
707+
Remove all accents from the text. All text will return to default
708+
foreground and background colors.
709+
710+
:return: None
711+
"""
712+
self._accent_ranges = []
713+
self._reset_text(text=str(self._text))

docs/examples.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,15 @@ Wrap Pixel Test
133133
:caption: examples/display_text_wrap_pixels_test.py
134134
:linenos:
135135

136+
Accent Example
137+
-------------------
138+
139+
Demonstrates the accent feature
140+
141+
.. literalinclude:: ../examples/display_text_accent_example.py
142+
:caption: examples/display_text_accent_example.py
143+
:linenos:
144+
136145
Library Features Example
137146
------------------------
138147

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries
2+
# SPDX-License-Identifier: MIT
3+
"""
4+
Demonstrates how to use the AccentLabel to highlight part of the text
5+
with different foreground and background color.
6+
"""
7+
8+
import time
9+
10+
import displayio
11+
import supervisor
12+
import terminalio
13+
14+
from adafruit_display_text.bitmap_label import Label
15+
16+
display = supervisor.runtime.display
17+
18+
main_group = displayio.Group()
19+
20+
accent_palette = displayio.Palette(6)
21+
accent_palette[2] = 0x000000
22+
accent_palette[3] = 0xDDDD00
23+
accent_palette[4] = 0xFFFFFF
24+
accent_palette[5] = 0x652F8F
25+
26+
accent_lbl = Label(terminalio.FONT, color_palette=accent_palette, text="", color=0xAAAAAA)
27+
accent_lbl.anchor_point = (0, 0)
28+
accent_lbl.anchored_position = (4, 4)
29+
main_group.append(accent_lbl)
30+
display.root_group = main_group
31+
32+
text = "CircuitPython is amazing!"
33+
accent_lbl.text = text
34+
35+
time.sleep(1)
36+
accent_lbl.add_accent_to_substring("CircuitPython", 4, 5)
37+
time.sleep(2)
38+
accent_lbl.remove_accent_from_substring("CircuitPython")
39+
accent_lbl.add_accent_to_substring("amazing!", 2, 3)
40+
41+
while True:
42+
time.sleep(1)

0 commit comments

Comments
 (0)