Skip to content
Closed
44 changes: 43 additions & 1 deletion adafruit_display_text/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,11 @@ class LabelBase(Group):
This is helpful when two or more labels need to be aligned to the same baseline
:param (int,str) tab_replacement: tuple with tab character replace information. When
(4, " ") will indicate a tab replacement of 4 spaces, defaults to 4 spaces by
tab character"""
tab character
:param str label_direction: string defining the label text orientation. There are 5
configurations possibles ``LTR``-Left-To-Right ``RTL``-Right-To-Left
``TTB``-Top-To-Bottom ``UPR``-Upwards ``DWR``-Downwards. It defaults to ``LTR``
:param str label_style: string indicating style to be applied to the label"""

# pylint: disable=unused-argument, too-many-instance-attributes, too-many-locals, too-many-arguments
def __init__(
Expand All @@ -207,6 +211,8 @@ def __init__(
scale: int = 1,
base_alignment: bool = False,
tab_replacement: Tuple[int, str] = (4, " "),
label_direction: str = "LTR",
label_style: str = "Default",
**kwargs,
) -> None:
super().__init__(max_size=1, x=x, y=y, scale=1)
Expand All @@ -226,6 +232,12 @@ def __init__(
self.local_group = None

self._text = text

if label_direction not in ["LTR", "RTL", "UPR", "DWR", "TTB"]:
raise RuntimeError("Please provide a valid text direction")
self._label_direction = label_direction
self._label_style = label_style

self.baseline = -1.0

self.base_alignment = base_alignment
Expand Down Expand Up @@ -384,3 +396,33 @@ def _set_line_spacing(self, new_line_spacing: float) -> None:
@line_spacing.setter
def line_spacing(self, new_line_spacing: float) -> None:
self._set_line_spacing(new_line_spacing)

@property
def label_direction(self) -> str:
"""Set the text direction of the label"""
return self._label_direction

def _set_label_direction(self, new_label_direction: str) -> None:
# subclass should override this.
pass

@label_direction.setter
def label_direction(self, new_label_direction: str) -> None:
"""Set the text direction of the label"""
if new_label_direction not in ["LTR", "RTL", "UPR", "DWR", "TTB"]:
raise RuntimeError("Please provide a valid text direction")
self._set_label_direction(new_label_direction)

@property
def label_style(self) -> str:
"""Set the text direction of the label"""
return self._label_style

def _set_label_style(self, new_label_style: str) -> None:
# subclass should override this.
pass

@label_style.setter
def label_style(self, new_label_style: str) -> None:
"""Set the text direction of the label"""
self._set_label_style(new_label_style)
223 changes: 192 additions & 31 deletions adafruit_display_text/label.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ class Label(LabelBase):
This is helpful when two or more labels need to be aligned to the same baseline
:param (int,str) tab_replacement: tuple with tab character replace information. When
(4, " ") will indicate a tab replacement of 4 spaces, defaults to 4 spaces by
tab character"""
tab character
:param str label_direction: string defining the label text orientation. There are 5
configurations possibles ``LTR``-Left-To-Right ``RTL``-Right-To-Left
``TTB``-Top-To-Bottom ``UPR``-Upwards ``DWR``-Downwards. It defaults to ``LTR``"""

# pylint: disable=too-many-instance-attributes, too-many-locals
# This has a lot of getters/setters, maybe it needs cleanup.
Expand Down Expand Up @@ -122,9 +125,17 @@ def __init__(self, font, **kwargs) -> None:
self._padding_left = kwargs.get("padding_left", 0)
self._padding_right = kwargs.get("padding_right", 0)
self.base_alignment = kwargs.get("base_alignment", False)
self._label_direction = kwargs.get("label_direction", "LTR")
self._label_style = kwargs.get("label_style", "Default")

if text is not None:
if self.label_style != "Default":
self._update_text(str(text))

if text is not None:
if self.label_style != "Default":
self._set_label_style(self._label_style)
else:
self._update_text(str(text))
if (kwargs.get("anchored_position", None) is not None) and (
kwargs.get("anchor_point", None) is not None
):
Expand All @@ -136,7 +147,6 @@ def _create_background_box(self, lines: int, y_offset: int) -> None:
:param y_offset: int y pixel bottom coordinate for the background_box"""

left = self._bounding_box[0]

if self._background_tight: # draw a tight bounding box
box_width = self._bounding_box[2]
box_height = self._bounding_box[3]
Expand All @@ -146,14 +156,33 @@ def _create_background_box(self, lines: int, y_offset: int) -> None:
else: # draw a "loose" bounding box to include any ascenders/descenders.
ascent, descent = self._get_ascent_descent()

box_width = self._bounding_box[2] + self._padding_left + self._padding_right
x_box_offset = -self._padding_left
box_height = (
(ascent + descent)
+ int((lines - 1) * self.height * self._line_spacing)
+ self._padding_top
+ self._padding_bottom
)
if (
self._label_direction == "UPR"
or self._label_direction == "DWR"
or self._label_direction == "TTB"
):
box_height = (
self._bounding_box[3] + self._padding_top + self._padding_bottom
)
x_box_offset = -self._padding_bottom
box_width = (
(ascent + descent)
+ int((lines - 1) * self.width * self._line_spacing)
+ self._padding_left
+ self._padding_right
)
else:
box_width = (
self._bounding_box[2] + self._padding_left + self._padding_right
)
x_box_offset = -self._padding_left
box_height = (
(ascent + descent)
+ int((lines - 1) * self.height * self._line_spacing)
+ self._padding_top
+ self._padding_bottom
)

if self.base_alignment:
y_box_offset = -ascent - self._padding_top
else:
Expand All @@ -162,12 +191,25 @@ def _create_background_box(self, lines: int, y_offset: int) -> None:
box_width = max(0, box_width) # remove any negative values
box_height = max(0, box_height) # remove any negative values

if self._label_direction == "UPR":
movx = left + x_box_offset
movy = -box_height - x_box_offset
elif self._label_direction == "DWR":
movx = left + x_box_offset
movy = x_box_offset
elif self._label_direction == "TTB":
movx = left + x_box_offset
movy = x_box_offset
else:
movx = left + x_box_offset
movy = y_box_offset

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,
x=movx,
y=movy,
)

return tile_grid
Expand Down Expand Up @@ -222,7 +264,7 @@ def _update_background_color(self, new_color: int) -> None:
self._bounding_box[3] + self._padding_top + self._padding_bottom > 0
)
):
self.local_group[0] = self._create_background_box(lines, y_offset)
self.local_group[0] = self._create_background_box(lines, self._y_offset)
else: # delete the existing bitmap
self.local_group.pop(0)
self._added_background_tilegrid = False
Expand All @@ -243,8 +285,15 @@ def _update_text(
else:
self._y_offset = self._get_ascent() // 2

right = top = bottom = 0
left = None
if self._label_direction == "RTL":
left = top = bottom = 0
right = None
elif self._label_direction == "LTR":
right = top = bottom = 0
left = None
else:
top = right = left = 0
bottom = 0

for character in new_text:
if character == "\n":
Expand All @@ -254,17 +303,74 @@ def _update_text(
glyph = self._font.get_glyph(ord(character))
if not glyph:
continue
right = max(right, x + glyph.shift_x, x + glyph.width + glyph.dx)
if x == 0:
if left is None:
left = glyph.dx

if self._label_direction == "LTR" or self._label_direction == "RTL":
bottom = max(bottom, y - glyph.dy + self._y_offset)
if y == 0: # first line, find the Ascender height
top = min(top, -glyph.height - glyph.dy + self._y_offset)
position_y = y - glyph.height - glyph.dy + self._y_offset

if self._label_direction == "LTR":
right = max(right, x + glyph.shift_x, x + glyph.width + glyph.dx)
if x == 0:
if left is None:
left = glyph.dx
else:
left = min(left, glyph.dx)
position_x = x + glyph.dx
else:
left = min(left, glyph.dx)
if y == 0: # first line, find the Ascender height
top = min(top, -glyph.height - glyph.dy + self._y_offset)
bottom = max(bottom, y - glyph.dy + self._y_offset)
position_y = y - glyph.height - glyph.dy + self._y_offset
position_x = x + glyph.dx
left = max(
left, abs(x) + glyph.shift_x, abs(x) + glyph.width + glyph.dx
)
if x == 0:
if right is None:
right = glyph.dx
else:
right = max(right, glyph.dx)
position_x = x - glyph.width

if self._label_direction == "TTB":
if x == 0:
if left is None:
left = glyph.dx
else:
left = min(left, glyph.dx)
if y == 0:
top = min(top, -glyph.dy)

bottom = max(bottom, y + glyph.height, y + glyph.height + glyph.dy)
right = max(
right, x + glyph.width + glyph.dx, x + glyph.shift_x + glyph.dx
)
position_y = y + glyph.dy
position_x = x - glyph.width // 2 + self._y_offset

if self._label_direction == "UPR":
if x == 0:
if bottom is None:
bottom = -glyph.dx

if y == 0: # first line, find the Ascender height
bottom = min(bottom, -glyph.dy)
left = min(left, x - glyph.height + self._y_offset)
top = min(top, y - glyph.width - glyph.dx, y - glyph.shift_x)
right = max(right, x + glyph.height, x + glyph.height - glyph.dy)
position_y = y - glyph.width - glyph.dx
position_x = x - glyph.height - glyph.dy + self._y_offset

if self._label_direction == "DWR":
if y == 0:
if top is None:
top = -glyph.dx
top = min(top, -glyph.dx)
if x == 0:
left = min(left, -glyph.dy)
left = min(left, x, x - glyph.dy - self._y_offset)
bottom = max(bottom, y + glyph.width + glyph.dx, y + glyph.shift_x)
right = max(right, x + glyph.height)
position_y = y + glyph.dx
position_x = x + glyph.dy - self._y_offset

if glyph.width > 0 and glyph.height > 0:
try:
# pylint: disable=unexpected-keyword-arg
Expand All @@ -286,22 +392,58 @@ def _update_text(
x=position_x,
y=position_y,
)

if self._label_direction == "UPR":
face.transpose_xy = True
face.flip_x = True
if self._label_direction == "DWR":
face.transpose_xy = True
face.flip_y = True

if tilegrid_count < len(self.local_group):
self.local_group[tilegrid_count] = face
else:
self.local_group.append(face)
tilegrid_count += 1
x += glyph.shift_x

if self._label_direction == "RTL":
x = x - glyph.shift_x
if self._label_direction == "TTB":
if glyph.height < 2:
y = y + glyph.shift_x
else:
y = y + glyph.height + 1
if self._label_direction == "UPR":
y = y - glyph.shift_x
if self._label_direction == "DWR":
y = y + glyph.shift_x
if self._label_direction == "LTR":
x = x + glyph.shift_x

i += 1
# Remove the rest

if left is None:
if self._label_direction == "LTR" and left is None:
left = 0
if self._label_direction == "RTL" and right is None:
right = 0
if self._label_direction == "TTB" and top is None:
top = 0

while len(self.local_group) > tilegrid_count: # i:
self.local_group.pop()
# pylint: disable=invalid-unary-operand-type
if self._label_direction == "RTL":
self._bounding_box = (-left, top, left - right, bottom - top)
if self._label_direction == "TTB":
self._bounding_box = (left, top, right - left, bottom - top)
if self._label_direction == "UPR":
self._bounding_box = (left, top, right, bottom - top)
if self._label_direction == "DWR":
self._bounding_box = (left, top, right, bottom - top)
if self._label_direction == "LTR":
self._bounding_box = (left, top, right - left, bottom - top)

self._text = new_text
self._bounding_box = (left, top, right - left, bottom - top)

if self.background_color is not None:
self._update_background_color(self._background_color)
Expand Down Expand Up @@ -331,5 +473,24 @@ def _set_line_spacing(self, new_line_spacing: float) -> None:
def _set_text(self, new_text: str, scale: int) -> None:
self._reset_text(new_text)

def _set_background_color(self, new_color):
def _set_background_color(self, new_color: int) -> None:
self._update_background_color(new_color)

def _set_label_direction(self, new_label_direction: str) -> None:
self._label_direction = new_label_direction
old_text = self._text
self._update_text(str(old_text))

def _set_label_style(self, new_label_style: str) -> None:
# import would change after library packaging
# pylint: disable=import-outside-toplevel
from adafruit_colorsys import get_hex
from adafruit_colorsys import styles

self._label_style = new_label_style
colorset = styles.THEME
self.color = get_hex(colorset[self._label_style]["TEXT"])
self._background_color = get_hex(colorset[self._label_style]["BACKGROUND"])

old_text = self._text
self._update_text(str(old_text))