diff --git a/adafruit_display_text/label.py b/adafruit_display_text/label.py index ca634fe..4546e49 100755 --- a/adafruit_display_text/label.py +++ b/adafruit_display_text/label.py @@ -72,13 +72,19 @@ def __init__( color=0xFFFFFF, background_color=None, line_spacing=1.25, + background_tight=False, + padding_top=0, + padding_bottom=0, + padding_left=0, + padding_right=0, **kwargs ): if not max_glyphs and not text: raise RuntimeError("Please provide a max size, or initial text") if not max_glyphs: - max_glyphs = len(text) + max_glyphs = len(text) + 1 # add one for the background bitmap tileGrid super().__init__(max_size=max_glyphs, **kwargs) + self.width = max_glyphs self._font = font self._text = None @@ -87,28 +93,94 @@ def __init__( self.y = y self.palette = displayio.Palette(2) - if background_color is not None: - self.palette[0] = background_color - self.palette.make_opaque(0) - self._transparent_background = False - else: - self.palette[0] = 0 - self.palette.make_transparent(0) - self._transparent_background = True + self.palette[0] = 0 + self.palette.make_transparent(0) self.palette[1] = color - bounds = self._font.get_bounding_box() - self.height = bounds[1] + self.height = self._font.get_bounding_box()[1] self._line_spacing = line_spacing self._boundingbox = None + self._background_tight = ( + background_tight # sets padding status for text background box + ) + + self._background_color = background_color + self._background_palette = displayio.Palette(1) + self.append( + displayio.TileGrid( + displayio.Bitmap(0, 0, 1), pixel_shader=self._background_palette + ) + ) # initialize with a blank tilegrid placeholder for background + + self._padding_top = padding_top + self._padding_bottom = padding_bottom + self._padding_left = padding_left + self._padding_right = padding_right + if text is not None: self._update_text(str(text)) + def _create_background_box(self, lines, y_offset): + + left = self._boundingbox[0] + + if self._background_tight: # draw a tight bounding box + box_width = self._boundingbox[2] + box_height = self._boundingbox[3] + x_box_offset = 0 + y_box_offset = self._boundingbox[1] + + else: # draw a "loose" bounding box to include any ascenders/descenders. + + # check a few glyphs for maximum ascender and descender height + # Enhancement: it would be preferred to access the font "FONT_ASCENT" and + # "FONT_DESCENT" in the imported BDF file + glyphs = "M j'" # choose glyphs with highest ascender and lowest + # descender, will depend upon font used + ascender_max = descender_max = 0 + for char in glyphs: + this_glyph = self._font.get_glyph(ord(char)) + ascender_max = max(ascender_max, this_glyph.height + this_glyph.dy) + descender_max = max(descender_max, -this_glyph.dy) + + box_width = self._boundingbox[2] + self._padding_left + self._padding_right + x_box_offset = -self._padding_left + box_height = ( + (ascender_max + descender_max) + + int((lines - 1) * self.height * self._line_spacing) + + self._padding_top + + self._padding_bottom + ) + y_box_offset = -ascender_max + y_offset - self._padding_top + + self._update_background_color(self._background_color) + box_width = max(0, box_width) # remove any negative values + box_height = max(0, box_height) # remove any negative values + + background_bitmap = displayio.Bitmap(box_width, box_height, 1) + tile_grid = displayio.TileGrid( + background_bitmap, + pixel_shader=self._background_palette, + x=left + x_box_offset, + y=y_box_offset, + ) + + return tile_grid + + def _update_background_color(self, new_color): + + if new_color is None: + self._background_palette.make_transparent(0) + else: + self._background_palette.make_opaque(0) + self._background_palette[0] = new_color + self._background_color = new_color + def _update_text(self, new_text): # pylint: disable=too-many-locals x = 0 y = 0 - i = 0 + i = 1 old_c = 0 y_offset = int( ( @@ -118,17 +190,19 @@ def _update_text(self, new_text): # pylint: disable=too-many-locals / 2 ) left = right = top = bottom = 0 + lines = 1 for character in new_text: if character == "\n": y += int(self.height * self._line_spacing) x = 0 + lines += 1 continue glyph = self._font.get_glyph(ord(character)) if not glyph: continue - right = max(right, x + glyph.width + glyph.shift_x) + right = max(right, x + glyph.shift_x) if y == 0: # first line, find the Ascender height - top = min(top, -glyph.height + y_offset) + top = min(top, -glyph.height - glyph.dy + y_offset) bottom = max(bottom, y - glyph.dy + y_offset) position_y = y - glyph.height - glyph.dy + y_offset position_x = x + glyph.dx @@ -161,6 +235,7 @@ def _update_text(self, new_text): # pylint: disable=too-many-locals else: self.append(face) elif self._text and character == self._text[old_c]: + try: self[i].position = (position_x, position_y) except AttributeError: @@ -168,7 +243,6 @@ def _update_text(self, new_text): # pylint: disable=too-many-locals self[i].y = position_y x += glyph.shift_x - # TODO skip this for control sequences or non-printables. i += 1 old_c += 1 @@ -187,6 +261,7 @@ def _update_text(self, new_text): # pylint: disable=too-many-locals self.pop() self._text = new_text self._boundingbox = (left, top, left + right, bottom - top) + self[0] = self._create_background_box(lines, y_offset) @property def bounding_box(self): @@ -216,20 +291,11 @@ def color(self, new_color): @property def background_color(self): """Color of the background as an RGB hex number.""" - if not self._transparent_background: - return self.palette[0] - return None + return self._background_color @background_color.setter def background_color(self, new_color): - if new_color is not None: - self.palette[0] = new_color - self.palette.make_opaque(0) - self._transparent_background = False - else: - self.palette[0] = 0 - self.palette.make_transparent(0) - self._transparent_background = True + self._update_background_color(new_color) @property def text(self): @@ -253,8 +319,7 @@ def font(self, new_font): current_anchored_position = self.anchored_position self._text = "" self._font = new_font - bounds = self._font.get_bounding_box() - self.height = bounds[1] + self.height = self._font.get_bounding_box()[1] self._update_text(str(old_text)) self.anchored_position = current_anchored_position diff --git a/examples/display_text_background_color_padding.py b/examples/display_text_background_color_padding.py new file mode 100755 index 0000000..91b694b --- /dev/null +++ b/examples/display_text_background_color_padding.py @@ -0,0 +1,125 @@ +""" +This examples shows the use color and background_color +""" +import time +import board +import displayio + + +# from adafruit_st7789 import ST7789 +from adafruit_ili9341 import ILI9341 +from adafruit_display_text import label +from adafruit_bitmap_font import bitmap_font + +# Setup the SPI display + +print("Starting the display...") # goes to serial only +displayio.release_displays() + + +spi = board.SPI() +tft_cs = board.D9 # arbitrary, pin not used +tft_dc = board.D10 +tft_backlight = board.D12 +tft_reset = board.D11 + +while not spi.try_lock(): + spi.configure(baudrate=32000000) +spi.unlock() + +display_bus = displayio.FourWire( + spi, + command=tft_dc, + chip_select=tft_cs, + reset=tft_reset, + baudrate=32000000, + polarity=1, + phase=1, +) + +print("spi.frequency: {}".format(spi.frequency)) + +DISPLAY_WIDTH = 320 +DISPLAY_HEIGHT = 240 + +# display = ST7789(display_bus, width=240, height=240, rotation=0, rowstart=80, colstart=0) +display = ILI9341( + display_bus, + width=DISPLAY_WIDTH, + height=DISPLAY_HEIGHT, + rotation=180, + auto_refresh=True, +) + +display.show(None) + +# font=terminalio.FONT # this is the Builtin fixed dimension font + +font = bitmap_font.load_font("fonts/BitstreamVeraSans-Roman-24.bdf") + + +text = [] +text.append("none") # no ascenders or descenders +text.append("pop quops") # only descenders +text.append("MONSTERs are tall") # only ascenders +text.append("MONSTERs ate pop quops") # both ascenders and descenders +text.append("MONSTER quops\nnewline quops") # with newline + +display.auto_refresh = True +myGroup = displayio.Group(max_size=6) +display.show(myGroup) + +text_area = [] +myPadding = 4 + +for i, thisText in enumerate(text): + text_area.append( + label.Label( + font, + text=thisText, + color=0xFFFFFF, + background_color=None, + background_tight=False, + padding_top=myPadding, + padding_bottom=myPadding, + padding_left=myPadding, + padding_right=myPadding, + ) + ) + + this_x = 10 + this_y = 10 + i * 40 + text_area[i].x = 10 + text_area[i].y = 3 + i * 50 + text_area[i].anchor_point = (0, 0) + text_area[i].anchored_position = (this_x, this_y) + myGroup.append(text_area[i]) + +print("background color is {}".format(text_area[0].background_color)) + + +while True: + time.sleep(2) + text_area[0].text = "text" # change some text in an existing text box + # Note: changed text must fit within existing number of characters + # when the Label was created + + for area in text_area: + area.background_color = 0xFF0000 + print("background color is {:06x}".format(text_area[0].background_color)) + time.sleep(2) + for area in text_area: + area.background_color = 0x000088 + print("background color is {:06x}".format(text_area[0].background_color)) + time.sleep(2) + for area in text_area: + area.background_color = 0x00FF00 + print("background color is {:06x}".format(text_area[0].background_color)) + time.sleep(2) + for area in text_area: + area.background_color = 0xFF0000 + print("background color is {:06x}".format(text_area[0].background_color)) + time.sleep(2) + for area in text_area: + area.background_color = None + print("background color is {}".format(text_area[0].background_color))