Skip to content

Directional label #135

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 19, 2021
27 changes: 26 additions & 1 deletion adafruit_display_text/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,10 @@ 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``"""

# pylint: disable=unused-argument, too-many-instance-attributes, too-many-locals, too-many-arguments
def __init__(
Expand All @@ -207,6 +210,7 @@ def __init__(
scale: int = 1,
base_alignment: bool = False,
tab_replacement: Tuple[int, str] = (4, " "),
label_direction: str = "LTR",
**kwargs,
) -> None:
super().__init__(max_size=1, x=x, y=y, scale=1)
Expand All @@ -226,6 +230,11 @@ 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.baseline = -1.0

self.base_alignment = base_alignment
Expand Down Expand Up @@ -384,3 +393,19 @@ 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)
200 changes: 170 additions & 30 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,6 +125,7 @@ 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")

if text is not None:
self._update_text(str(text))
Expand All @@ -136,7 +140,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 +149,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 +184,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 +257,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 +278,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 +296,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 +385,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 +466,10 @@ 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))