Skip to content

Commit a3112cd

Browse files
authored
Merge pull request #135 from jposada202020/directional_label
Directional label
2 parents a71adcd + 343ce75 commit a3112cd

File tree

2 files changed

+196
-31
lines changed

2 files changed

+196
-31
lines changed

adafruit_display_text/__init__.py

+26-1
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,10 @@ class LabelBase(Group):
187187
This is helpful when two or more labels need to be aligned to the same baseline
188188
:param (int,str) tab_replacement: tuple with tab character replace information. When
189189
(4, " ") will indicate a tab replacement of 4 spaces, defaults to 4 spaces by
190-
tab character"""
190+
tab character
191+
:param str label_direction: string defining the label text orientation. There are 5
192+
configurations possibles ``LTR``-Left-To-Right ``RTL``-Right-To-Left
193+
``TTB``-Top-To-Bottom ``UPR``-Upwards ``DWR``-Downwards. It defaults to ``LTR``"""
191194

192195
# pylint: disable=unused-argument, too-many-instance-attributes, too-many-locals, too-many-arguments
193196
def __init__(
@@ -211,6 +214,7 @@ def __init__(
211214
scale: int = 1,
212215
base_alignment: bool = False,
213216
tab_replacement: Tuple[int, str] = (4, " "),
217+
label_direction: str = "LTR",
214218
**kwargs,
215219
) -> None:
216220
super().__init__(max_size=1, x=x, y=y, scale=1)
@@ -230,6 +234,11 @@ def __init__(
230234
self.local_group = None
231235

232236
self._text = text
237+
238+
if label_direction not in ["LTR", "RTL", "UPR", "DWR", "TTB"]:
239+
raise RuntimeError("Please provide a valid text direction")
240+
self._label_direction = label_direction
241+
233242
self.baseline = -1.0
234243

235244
self.base_alignment = base_alignment
@@ -388,3 +397,19 @@ def _set_line_spacing(self, new_line_spacing: float) -> None:
388397
@line_spacing.setter
389398
def line_spacing(self, new_line_spacing: float) -> None:
390399
self._set_line_spacing(new_line_spacing)
400+
401+
@property
402+
def label_direction(self) -> str:
403+
"""Set the text direction of the label"""
404+
return self._label_direction
405+
406+
def _set_label_direction(self, new_label_direction: str) -> None:
407+
# subclass should override this.
408+
pass
409+
410+
@label_direction.setter
411+
def label_direction(self, new_label_direction: str) -> None:
412+
"""Set the text direction of the label"""
413+
if new_label_direction not in ["LTR", "RTL", "UPR", "DWR", "TTB"]:
414+
raise RuntimeError("Please provide a valid text direction")
415+
self._set_label_direction(new_label_direction)

adafruit_display_text/label.py

+170-30
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ class Label(LabelBase):
6666
This is helpful when two or more labels need to be aligned to the same baseline
6767
:param (int,str) tab_replacement: tuple with tab character replace information. When
6868
(4, " ") will indicate a tab replacement of 4 spaces, defaults to 4 spaces by
69-
tab character"""
69+
tab character
70+
:param str label_direction: string defining the label text orientation. There are 5
71+
configurations possibles ``LTR``-Left-To-Right ``RTL``-Right-To-Left
72+
``TTB``-Top-To-Bottom ``UPR``-Upwards ``DWR``-Downwards. It defaults to ``LTR``"""
7073

7174
# pylint: disable=too-many-instance-attributes, too-many-locals
7275
# This has a lot of getters/setters, maybe it needs cleanup.
@@ -122,6 +125,7 @@ def __init__(self, font, **kwargs) -> None:
122125
self._padding_left = kwargs.get("padding_left", 0)
123126
self._padding_right = kwargs.get("padding_right", 0)
124127
self.base_alignment = kwargs.get("base_alignment", False)
128+
self._label_direction = kwargs.get("label_direction", "LTR")
125129

126130
if text is not None:
127131
self._update_text(str(text))
@@ -136,7 +140,6 @@ def _create_background_box(self, lines: int, y_offset: int) -> None:
136140
:param y_offset: int y pixel bottom coordinate for the background_box"""
137141

138142
left = self._bounding_box[0]
139-
140143
if self._background_tight: # draw a tight bounding box
141144
box_width = self._bounding_box[2]
142145
box_height = self._bounding_box[3]
@@ -146,14 +149,33 @@ def _create_background_box(self, lines: int, y_offset: int) -> None:
146149
else: # draw a "loose" bounding box to include any ascenders/descenders.
147150
ascent, descent = self._get_ascent_descent()
148151

149-
box_width = self._bounding_box[2] + self._padding_left + self._padding_right
150-
x_box_offset = -self._padding_left
151-
box_height = (
152-
(ascent + descent)
153-
+ int((lines - 1) * self.height * self._line_spacing)
154-
+ self._padding_top
155-
+ self._padding_bottom
156-
)
152+
if (
153+
self._label_direction == "UPR"
154+
or self._label_direction == "DWR"
155+
or self._label_direction == "TTB"
156+
):
157+
box_height = (
158+
self._bounding_box[3] + self._padding_top + self._padding_bottom
159+
)
160+
x_box_offset = -self._padding_bottom
161+
box_width = (
162+
(ascent + descent)
163+
+ int((lines - 1) * self.width * self._line_spacing)
164+
+ self._padding_left
165+
+ self._padding_right
166+
)
167+
else:
168+
box_width = (
169+
self._bounding_box[2] + self._padding_left + self._padding_right
170+
)
171+
x_box_offset = -self._padding_left
172+
box_height = (
173+
(ascent + descent)
174+
+ int((lines - 1) * self.height * self._line_spacing)
175+
+ self._padding_top
176+
+ self._padding_bottom
177+
)
178+
157179
if self.base_alignment:
158180
y_box_offset = -ascent - self._padding_top
159181
else:
@@ -162,12 +184,25 @@ def _create_background_box(self, lines: int, y_offset: int) -> None:
162184
box_width = max(0, box_width) # remove any negative values
163185
box_height = max(0, box_height) # remove any negative values
164186

187+
if self._label_direction == "UPR":
188+
movx = left + x_box_offset
189+
movy = -box_height - x_box_offset
190+
elif self._label_direction == "DWR":
191+
movx = left + x_box_offset
192+
movy = x_box_offset
193+
elif self._label_direction == "TTB":
194+
movx = left + x_box_offset
195+
movy = x_box_offset
196+
else:
197+
movx = left + x_box_offset
198+
movy = y_box_offset
199+
165200
background_bitmap = displayio.Bitmap(box_width, box_height, 1)
166201
tile_grid = displayio.TileGrid(
167202
background_bitmap,
168203
pixel_shader=self._background_palette,
169-
x=left + x_box_offset,
170-
y=y_box_offset,
204+
x=movx,
205+
y=movy,
171206
)
172207

173208
return tile_grid
@@ -222,7 +257,7 @@ def _update_background_color(self, new_color: int) -> None:
222257
self._bounding_box[3] + self._padding_top + self._padding_bottom > 0
223258
)
224259
):
225-
self.local_group[0] = self._create_background_box(lines, y_offset)
260+
self.local_group[0] = self._create_background_box(lines, self._y_offset)
226261
else: # delete the existing bitmap
227262
self.local_group.pop(0)
228263
self._added_background_tilegrid = False
@@ -243,8 +278,15 @@ def _update_text(
243278
else:
244279
self._y_offset = self._get_ascent() // 2
245280

246-
right = top = bottom = 0
247-
left = None
281+
if self._label_direction == "RTL":
282+
left = top = bottom = 0
283+
right = None
284+
elif self._label_direction == "LTR":
285+
right = top = bottom = 0
286+
left = None
287+
else:
288+
top = right = left = 0
289+
bottom = 0
248290

249291
for character in new_text:
250292
if character == "\n":
@@ -254,17 +296,74 @@ def _update_text(
254296
glyph = self._font.get_glyph(ord(character))
255297
if not glyph:
256298
continue
257-
right = max(right, x + glyph.shift_x, x + glyph.width + glyph.dx)
258-
if x == 0:
259-
if left is None:
260-
left = glyph.dx
299+
300+
if self._label_direction == "LTR" or self._label_direction == "RTL":
301+
bottom = max(bottom, y - glyph.dy + self._y_offset)
302+
if y == 0: # first line, find the Ascender height
303+
top = min(top, -glyph.height - glyph.dy + self._y_offset)
304+
position_y = y - glyph.height - glyph.dy + self._y_offset
305+
306+
if self._label_direction == "LTR":
307+
right = max(right, x + glyph.shift_x, x + glyph.width + glyph.dx)
308+
if x == 0:
309+
if left is None:
310+
left = glyph.dx
311+
else:
312+
left = min(left, glyph.dx)
313+
position_x = x + glyph.dx
261314
else:
262-
left = min(left, glyph.dx)
263-
if y == 0: # first line, find the Ascender height
264-
top = min(top, -glyph.height - glyph.dy + self._y_offset)
265-
bottom = max(bottom, y - glyph.dy + self._y_offset)
266-
position_y = y - glyph.height - glyph.dy + self._y_offset
267-
position_x = x + glyph.dx
315+
left = max(
316+
left, abs(x) + glyph.shift_x, abs(x) + glyph.width + glyph.dx
317+
)
318+
if x == 0:
319+
if right is None:
320+
right = glyph.dx
321+
else:
322+
right = max(right, glyph.dx)
323+
position_x = x - glyph.width
324+
325+
if self._label_direction == "TTB":
326+
if x == 0:
327+
if left is None:
328+
left = glyph.dx
329+
else:
330+
left = min(left, glyph.dx)
331+
if y == 0:
332+
top = min(top, -glyph.dy)
333+
334+
bottom = max(bottom, y + glyph.height, y + glyph.height + glyph.dy)
335+
right = max(
336+
right, x + glyph.width + glyph.dx, x + glyph.shift_x + glyph.dx
337+
)
338+
position_y = y + glyph.dy
339+
position_x = x - glyph.width // 2 + self._y_offset
340+
341+
if self._label_direction == "UPR":
342+
if x == 0:
343+
if bottom is None:
344+
bottom = -glyph.dx
345+
346+
if y == 0: # first line, find the Ascender height
347+
bottom = min(bottom, -glyph.dy)
348+
left = min(left, x - glyph.height + self._y_offset)
349+
top = min(top, y - glyph.width - glyph.dx, y - glyph.shift_x)
350+
right = max(right, x + glyph.height, x + glyph.height - glyph.dy)
351+
position_y = y - glyph.width - glyph.dx
352+
position_x = x - glyph.height - glyph.dy + self._y_offset
353+
354+
if self._label_direction == "DWR":
355+
if y == 0:
356+
if top is None:
357+
top = -glyph.dx
358+
top = min(top, -glyph.dx)
359+
if x == 0:
360+
left = min(left, -glyph.dy)
361+
left = min(left, x, x - glyph.dy - self._y_offset)
362+
bottom = max(bottom, y + glyph.width + glyph.dx, y + glyph.shift_x)
363+
right = max(right, x + glyph.height)
364+
position_y = y + glyph.dx
365+
position_x = x + glyph.dy - self._y_offset
366+
268367
if glyph.width > 0 and glyph.height > 0:
269368
try:
270369
# pylint: disable=unexpected-keyword-arg
@@ -286,22 +385,58 @@ def _update_text(
286385
x=position_x,
287386
y=position_y,
288387
)
388+
389+
if self._label_direction == "UPR":
390+
face.transpose_xy = True
391+
face.flip_x = True
392+
if self._label_direction == "DWR":
393+
face.transpose_xy = True
394+
face.flip_y = True
395+
289396
if tilegrid_count < len(self.local_group):
290397
self.local_group[tilegrid_count] = face
291398
else:
292399
self.local_group.append(face)
293400
tilegrid_count += 1
294-
x += glyph.shift_x
401+
402+
if self._label_direction == "RTL":
403+
x = x - glyph.shift_x
404+
if self._label_direction == "TTB":
405+
if glyph.height < 2:
406+
y = y + glyph.shift_x
407+
else:
408+
y = y + glyph.height + 1
409+
if self._label_direction == "UPR":
410+
y = y - glyph.shift_x
411+
if self._label_direction == "DWR":
412+
y = y + glyph.shift_x
413+
if self._label_direction == "LTR":
414+
x = x + glyph.shift_x
415+
295416
i += 1
296-
# Remove the rest
297417

298-
if left is None:
418+
if self._label_direction == "LTR" and left is None:
299419
left = 0
420+
if self._label_direction == "RTL" and right is None:
421+
right = 0
422+
if self._label_direction == "TTB" and top is None:
423+
top = 0
300424

301425
while len(self.local_group) > tilegrid_count: # i:
302426
self.local_group.pop()
427+
# pylint: disable=invalid-unary-operand-type
428+
if self._label_direction == "RTL":
429+
self._bounding_box = (-left, top, left - right, bottom - top)
430+
if self._label_direction == "TTB":
431+
self._bounding_box = (left, top, right - left, bottom - top)
432+
if self._label_direction == "UPR":
433+
self._bounding_box = (left, top, right, bottom - top)
434+
if self._label_direction == "DWR":
435+
self._bounding_box = (left, top, right, bottom - top)
436+
if self._label_direction == "LTR":
437+
self._bounding_box = (left, top, right - left, bottom - top)
438+
303439
self._text = new_text
304-
self._bounding_box = (left, top, right - left, bottom - top)
305440

306441
if self.background_color is not None:
307442
self._update_background_color(self._background_color)
@@ -331,5 +466,10 @@ def _set_line_spacing(self, new_line_spacing: float) -> None:
331466
def _set_text(self, new_text: str, scale: int) -> None:
332467
self._reset_text(new_text)
333468

334-
def _set_background_color(self, new_color):
469+
def _set_background_color(self, new_color: int) -> None:
335470
self._update_background_color(new_color)
471+
472+
def _set_label_direction(self, new_label_direction: str) -> None:
473+
self._label_direction = new_label_direction
474+
old_text = self._text
475+
self._update_text(str(old_text))

0 commit comments

Comments
 (0)