diff --git a/adafruit_display_text/__init__.py b/adafruit_display_text/__init__.py index ad10da8..e7e91be 100644 --- a/adafruit_display_text/__init__.py +++ b/adafruit_display_text/__init__.py @@ -17,6 +17,8 @@ def wrap_text_to_pixels( string: str, max_width: int, font=None, indent0: str = "", indent1: str = "" ) -> List[str]: + # pylint: disable=too-many-branches, too-many-locals + """wrap_text_to_pixels function A helper that will return a list of lines with word-break wrapping. Leading and trailing whitespace in your string will be removed. If @@ -34,18 +36,17 @@ def wrap_text_to_pixels( :rtype: List[str] """ - # pylint: disable=too-many-locals, too-many-branches if font is None: - def measure(string): - return len(string) + def measure(text): + return len(text) else: if hasattr(font, "load_glyphs"): font.load_glyphs(string) - def measure(string): - return sum(font.get_glyph(ord(c)).shift_x for c in string) + def measure(text): + return sum(font.get_glyph(ord(c)).shift_x for c in text) lines = [] partial = [indent0] @@ -153,6 +154,8 @@ def chunks(lst, n): class LabelBase(Group): + # pylint: disable=too-many-instance-attributes + """Superclass that all other types of labels will extend. This contains all of the properties and functions that work the same way in all labels. @@ -164,8 +167,6 @@ class LabelBase(Group): :param Font font: A font class that has ``get_bounding_box`` and ``get_glyph``. Must include a capital M for measuring character size. :param str text: Text to display - :param int max_glyphs: Unnecessary parameter (provided only for direct compability - with :py:func:`~adafruit_display_text.label.Label`) :param int color: Color of all text in RGB hex :param int background_color: Color of the background, use `None` for transparent :param float line_spacing: Line spacing of text to display @@ -181,25 +182,20 @@ class LabelBase(Group): :param (int,int) anchored_position: Position relative to the anchor_point. Tuple containing x,y pixel coordinates. :param int scale: Integer value of the pixel scaling - :param bool save_text: Set True to save the text string as a constant in the - label structure. Set False to reduce memory use. :param bool base_alignment: when True allows to align text label to the baseline. 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 - :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_direction: string defining the label text orientation. See the + subclass documentation for the possible values.""" - # pylint: disable=unused-argument, too-many-instance-attributes, too-many-locals, too-many-arguments def __init__( self, font, x: int = 0, y: int = 0, text: str = "", - max_glyphs: int = None, color: int = 0xFFFFFF, background_color: int = None, line_spacing: float = 1.25, @@ -210,46 +206,59 @@ def __init__( padding_right: int = 0, anchor_point: Tuple[float, float] = None, anchored_position: Tuple[int, int] = None, - save_text: bool = True, # can reduce memory use if save_text = False scale: int = 1, base_alignment: bool = False, tab_replacement: Tuple[int, str] = (4, " "), label_direction: str = "LTR", - **kwargs, + **kwargs, # pylint: disable=unused-argument ) -> None: + # pylint: disable=too-many-arguments, too-many-locals + super().__init__(x=x, y=y, scale=1) self._font = font - self._ascent, self._descent = self._get_ascent_descent() - self.palette = Palette(2) - self._color = color - self._background_color = background_color - - self._bounding_box = None + self._text = text + self._palette = Palette(2) + self._color = 0xFFFFFF + self._background_color = None + self._line_spacing = line_spacing + self._background_tight = background_tight + self._padding_top = padding_top + self._padding_bottom = padding_bottom + self._padding_left = padding_left + self._padding_right = padding_right self._anchor_point = anchor_point self._anchored_position = anchored_position + self._base_alignment = base_alignment + self._label_direction = label_direction + self._tab_replacement = tab_replacement + self._tab_text = self._tab_replacement[1] * self._tab_replacement[0] - # local group will hold background and text - # the self group scale should always remain at 1, the self.local_group will - # be used to set the scale of the label - self.local_group = None + if "max_glyphs" in kwargs: + print("Please update your code: 'max_glyphs' is not needed anymore.") - self._text = text + self._ascent, self._descent = self._get_ascent_descent() + self._bounding_box = None - self._label_direction = label_direction + self.color = color + self.background_color = background_color - self.baseline = -1.0 + # local group will hold background and text + # the self group scale should always remain at 1, the self._local_group will + # be used to set the scale of the label + self._local_group = Group(scale=scale) + self.append(self._local_group) - self.base_alignment = base_alignment + self._baseline = -1.0 - if self.base_alignment: + if self._base_alignment: self._y_offset = 0 else: self._y_offset = self._ascent // 2 def _get_ascent_descent(self) -> Tuple[int, int]: """ Private function to calculate ascent and descent font values """ - if hasattr(self.font, "ascent"): + if hasattr(self.font, "ascent") and hasattr(self.font, "descent"): return self.font.ascent, self.font.descent # check a few glyphs for maximum ascender and descender height @@ -274,8 +283,7 @@ def font(self) -> None: return self._font def _set_font(self, new_font) -> None: - # subclasses should override this - pass + raise NotImplementedError("{} MUST override '_set_font'".format(type(self))) @font.setter def font(self, new_font) -> None: @@ -290,11 +298,11 @@ def color(self) -> int: def color(self, new_color: int): self._color = new_color if new_color is not None: - self.palette[1] = new_color - self.palette.make_opaque(1) + self._palette[1] = new_color + self._palette.make_opaque(1) else: - self.palette[1] = 0 - self.palette.make_transparent(1) + self._palette[1] = 0 + self._palette.make_transparent(1) @property def background_color(self) -> int: @@ -302,8 +310,9 @@ def background_color(self) -> int: return self._background_color def _set_background_color(self, new_color): - # subclasses should override this - pass + raise NotImplementedError( + "{} MUST override '_set_background_color'".format(type(self)) + ) @background_color.setter def background_color(self, new_color: int) -> None: @@ -318,13 +327,13 @@ def anchor_point(self) -> Tuple[float, float]: @anchor_point.setter def anchor_point(self, new_anchor_point: Tuple[float, float]) -> None: - if new_anchor_point[1] == self.baseline: + if new_anchor_point[1] == self._baseline: self._anchor_point = (new_anchor_point[0], -1.0) else: self._anchor_point = new_anchor_point - self.anchored_position = ( - self._anchored_position - ) # update the anchored_position using setter + + # update the anchored_position using setter + self.anchored_position = self._anchored_position @property def anchored_position(self) -> Tuple[int, int]: @@ -335,14 +344,14 @@ def anchored_position(self) -> Tuple[int, int]: @anchored_position.setter def anchored_position(self, new_position: Tuple[int, int]) -> None: self._anchored_position = new_position - # Set anchored_position + # Calculate (x,y) position if (self._anchor_point is not None) and (self._anchored_position is not None): self.x = int( new_position[0] - (self._bounding_box[0] * self.scale) - round(self._anchor_point[0] * (self._bounding_box[2] * self.scale)) ) - if self._anchor_point[1] == self.baseline: + if self._anchor_point[1] == self._baseline: self.y = int(new_position[1] - (self._y_offset * self.scale)) else: self.y = int( @@ -354,16 +363,15 @@ def anchored_position(self, new_position: Tuple[int, int]) -> None: @property def scale(self) -> int: """Set the scaling of the label, in integer values""" - return self.local_group.scale + return self._local_group.scale @scale.setter def scale(self, new_scale: int) -> None: - self.local_group.scale = new_scale + self._local_group.scale = new_scale self.anchored_position = self._anchored_position # update the anchored_position def _set_text(self, new_text: str, scale: int) -> None: - # subclasses should override this - pass + raise NotImplementedError("{} MUST override '_set_text'".format(type(self))) @property def text(self) -> str: @@ -380,6 +388,16 @@ def bounding_box(self) -> Tuple[int, int]: first two numbers are offset from the x, y origin of this group""" return tuple(self._bounding_box) + @property + def height(self) -> int: + """The height of the label determined from the bounding box.""" + return self._bounding_box[3] - self._bounding_box[1] + + @property + def width(self) -> int: + """The width of the label determined from the bounding box.""" + return self._bounding_box[2] - self._bounding_box[0] + @property def line_spacing(self) -> float: """The amount of space between lines of text, in multiples of the font's @@ -387,8 +405,9 @@ def line_spacing(self) -> float: return self._line_spacing def _set_line_spacing(self, new_line_spacing: float) -> None: - # subclass should override this. - pass + raise NotImplementedError( + "{} MUST override '_set_line_spacing'".format(type(self)) + ) @line_spacing.setter def line_spacing(self, new_line_spacing: float) -> None: @@ -400,12 +419,21 @@ def label_direction(self) -> str: return self._label_direction def _set_label_direction(self, new_label_direction: str) -> None: - # subclass should override this. - pass + raise NotImplementedError( + "{} MUST override '_set_label_direction'".format(type(self)) + ) + + def _get_valid_label_directions(self) -> Tuple[str, ...]: + raise NotImplementedError( + "{} MUST override '_get_valid_label_direction'".format(type(self)) + ) @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"]: + if new_label_direction not in self._get_valid_label_directions(): raise RuntimeError("Please provide a valid text direction") self._set_label_direction(new_label_direction) + + def _replace_tabs(self, text): + return self._tab_text.join(text.split("\t")) diff --git a/adafruit_display_text/bitmap_label.py b/adafruit_display_text/bitmap_label.py index f603fab..a613039 100755 --- a/adafruit_display_text/bitmap_label.py +++ b/adafruit_display_text/bitmap_label.py @@ -22,14 +22,17 @@ https://circuitpython.org/downloads """ + +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git" + + try: from typing import Tuple except ImportError: pass -import displayio -__version__ = "0.0.0-auto.0" -__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git" +import displayio from adafruit_display_text import LabelBase @@ -38,8 +41,6 @@ class Label(LabelBase): """A label displaying a string of text that is stored in a bitmap. Note: This ``bitmap_label.py`` library utilizes a bitmap to display the text. This method is memory-conserving relative to ``label.py``. - The ``max_glyphs`` parameter is ignored and is present - only for direct compatibility with label.py. For further reduction in memory usage, set ``save_text=False`` (text string will not be stored and ``line_spacing`` and ``font`` are immutable with ``save_text`` @@ -53,12 +54,10 @@ class Label(LabelBase): :param Font font: A font class that has ``get_bounding_box`` and ``get_glyph``. Must include a capital M for measuring character size. :param str text: Text to display - :param int max_glyphs: Unnecessary parameter (provided only for direct compatibility - with label.py) :param int color: Color of all text in RGB hex :param int background_color: Color of the background, use `None` for transparent - :param double line_spacing: Line spacing of text to display - :param boolean background_tight: Set `True` only if you want background box to tightly + :param float line_spacing: Line spacing of text to display + :param bool background_tight: Set `True` only if you want background box to tightly surround text. When set to 'True' Padding parameters will be ignored. :param int padding_top: Additional pixels added to background bounding box at top :param int padding_bottom: Additional pixels added to background bounding box at bottom @@ -81,121 +80,47 @@ class Label(LabelBase): configurations possibles ``LTR``-Left-To-Right ``RTL``-Right-To-Left ``UPD``-Upside Down ``UPR``-Upwards ``DWR``-Downwards. It defaults to ``LTR``""" - # pylint: disable=unused-argument, too-many-instance-attributes, too-many-locals, too-many-arguments - # pylint: disable=too-many-branches, no-self-use, too-many-statements - # Note: max_glyphs parameter is unnecessary, this is used for direct - # compatibility with label.py + def __init__(self, font, save_text=True, **kwargs) -> None: - def __init__(self, font, **kwargs) -> None: + self._bitmap = None super().__init__(font, **kwargs) - self.local_group = displayio.Group( - scale=kwargs.get("scale", 1) - ) # local_group holds the tileGrid and sets the scaling - self.append( - self.local_group - ) # the local_group will always stay in the self Group - - self._tab_replacement = kwargs.get("tab_replacement", (4, " ")) - self._tab_text = self._tab_replacement[1] * self._tab_replacement[0] - text = kwargs.get("text", "") - self._text = self._tab_text.join(text.split("\t")) - - # Create the two-color palette - - self.color = kwargs.get("color", 0xFFFFFF) - self.background_color = kwargs.get("background_color", None) - self._label_direction = kwargs.get("label_direction", "LTR") - - if self._label_direction not in ["LTR", "RTL", "UPD", "UPR", "DWR"]: - raise RuntimeError("Please provide a valid text direction") + self._save_text = save_text + self._text = self._replace_tabs(self._text) if self._label_direction == "RTL": self._text = "".join(reversed(self._text)) - self.base_alignment = kwargs.get("base_alignment", False) - # call the text updater with all the arguments. self._reset_text( font=font, - x=kwargs.get("x", 0), - y=kwargs.get("y", 0), - text=kwargs.get("text", ""), - line_spacing=kwargs.get("line_spacing", 1.25), - background_tight=kwargs.get("background_tight", False), - padding_top=kwargs.get("padding_top", 0), - padding_bottom=kwargs.get("padding_bottom", 0), - padding_left=kwargs.get("padding_left", 0), - padding_right=kwargs.get("padding_right", 0), - anchor_point=kwargs.get("anchor_point", None), - anchored_position=kwargs.get("anchored_position", None), - save_text=kwargs.get("save_text", True), - scale=kwargs.get("scale", 1), - base_alignment=kwargs.get("base_alignment", False), - tab_replacement=kwargs.get("tab_replacement", (4, " ")), - label_direction=kwargs.get("label_direction", "LTR"), + text=self._text, + line_spacing=self._line_spacing, + scale=self.scale, ) def _reset_text( self, font=None, - x: int = None, - y: int = None, text: str = None, line_spacing: float = None, - background_tight: bool = None, - padding_top: int = None, - padding_bottom: int = None, - padding_left: int = None, - padding_right: int = None, - anchor_point: Tuple[float, float] = None, - anchored_position: Tuple[int, int] = None, - save_text: bool = None, scale: int = None, - base_alignment: bool = None, - tab_replacement: Tuple[int, str] = None, - label_direction: str = "LTR", ) -> None: + # pylint: disable=too-many-branches, too-many-statements # Store all the instance variables if font is not None: self._font = font - if x is not None: - self.x = x - if y is not None: - self.y = y if line_spacing is not None: self._line_spacing = line_spacing - if background_tight is not None: - self._background_tight = background_tight - if padding_top is not None: - self._padding_top = max(0, padding_top) - if padding_bottom is not None: - self._padding_bottom = max(0, padding_bottom) - if padding_left is not None: - self._padding_left = max(0, padding_left) - if padding_right is not None: - self._padding_right = max(0, padding_right) - if anchor_point is not None: - self._anchor_point = anchor_point - if anchored_position is not None: - self._anchored_position = anchored_position - if save_text is not None: - self._save_text = save_text - if base_alignment is not None: - self.base_alignment = base_alignment - if tab_replacement is not None: - self._tab_replacement = tab_replacement - if label_direction is not None: - self._label_direction = label_direction # if text is not provided as a parameter (text is None), use the previous value. if (text is None) and self._save_text: text = self._text if self._save_text: # text string will be saved - self._text = self._tab_text.join(text.split("\t")) + self._text = self._replace_tabs(text) if self._label_direction == "RTL": self._text = "".join(reversed(self._text)) else: @@ -212,10 +137,10 @@ def _reset_text( 0, # zero width with text == "" 0, # zero height with text == "" ) - # Clear out any items in the self.local_group Group, in case this is an + # Clear out any items in the self._local_group Group, in case this is an # update to the bitmap_label - for _ in self.local_group: - self.local_group.pop(0) + for _ in self._local_group: + self._local_group.pop(0) else: # The text string is not empty, so create the Bitmap and TileGrid and # append to the self Group @@ -234,7 +159,6 @@ def _reset_text( ) = self._text_bounding_box( self._text, self._font, - self._line_spacing, ) # calculate the box size for a tight and loose backgrounds if self._background_tight: @@ -250,26 +174,25 @@ def _reset_text( box_y = box_y + self._padding_top + self._padding_bottom # Create the bitmap and TileGrid - self.bitmap = displayio.Bitmap(box_x, box_y, len(self.palette)) + self._bitmap = displayio.Bitmap(box_x, box_y, len(self._palette)) # Place the text into the Bitmap self._place_text( - self.bitmap, + self._bitmap, self._text, self._font, - self._line_spacing, self._padding_left - x_offset, self._padding_top + y_offset, ) - if self.base_alignment: + if self._base_alignment: label_position_yoffset = 0 else: label_position_yoffset = self._ascent // 2 - self.tilegrid = displayio.TileGrid( - self.bitmap, - pixel_shader=self.palette, + self._tilegrid = displayio.TileGrid( + self._bitmap, + pixel_shader=self._palette, width=1, height=1, tile_width=box_x, @@ -280,36 +203,36 @@ def _reset_text( ) if self._label_direction == "UPR": - self.tilegrid.transpose_xy = True - self.tilegrid.flip_x = True + self._tilegrid.transpose_xy = True + self._tilegrid.flip_x = True if self._label_direction == "DWR": - self.tilegrid.transpose_xy = True - self.tilegrid.flip_y = True + self._tilegrid.transpose_xy = True + self._tilegrid.flip_y = True if self._label_direction == "UPD": - self.tilegrid.flip_x = True - self.tilegrid.flip_y = True + self._tilegrid.flip_x = True + self._tilegrid.flip_y = True # Clear out any items in the local_group Group, in case this is an update to # the bitmap_label - for _ in self.local_group: - self.local_group.pop(0) - self.local_group.append( - self.tilegrid + for _ in self._local_group: + self._local_group.pop(0) + self._local_group.append( + self._tilegrid ) # add the bitmap's tilegrid to the group # Update bounding_box values. Note: To be consistent with label.py, # this is the bounding box for the text only, not including the background. - if label_direction == "UPR" or self._label_direction == "DWR": + if self._label_direction in ("UPR", "DWR"): self._bounding_box = ( - self.tilegrid.x, - self.tilegrid.y, + self._tilegrid.x, + self._tilegrid.y, tight_box_y, box_x, ) else: self._bounding_box = ( - self.tilegrid.x, - self.tilegrid.y, + self._tilegrid.x, + self._tilegrid.y, box_x, tight_box_y, ) @@ -319,10 +242,9 @@ def _reset_text( ): # Scale will be defined in local_group (Note: self should have scale=1) self.scale = scale # call the setter - self.anchored_position = ( - self._anchored_position - ) # set the anchored_position with setter after bitmap is created, sets the + # set the anchored_position with setter after bitmap is created, sets the # x,y positions of the label + self.anchored_position = self._anchored_position @staticmethod def _line_spacing_ypixels(font, line_spacing: float) -> int: @@ -331,8 +253,10 @@ def _line_spacing_ypixels(font, line_spacing: float) -> int: return return_value def _text_bounding_box( - self, text: str, font, line_spacing: float + self, text: str, font ) -> Tuple[int, int, int, int, int, int]: + # pylint: disable=too-many-locals + ascender_max, descender_max = self._ascent, self._descent lines = 1 @@ -348,6 +272,7 @@ def _text_bounding_box( y_offset_tight = self._ascent // 2 newline = False + line_spacing = self._line_spacing for char in text: @@ -406,21 +331,19 @@ def _text_bounding_box( final_y_offset_loose, ) - # pylint: disable=too-many-nested-blocks def _place_text( self, bitmap, text: str, font, - line_spacing: float, xposition: int, yposition: int, - text_palette_index: int = 1, - background_palette_index: int = 0, skip_index: int = 0, # set to None to write all pixels, other wise skip this palette index # when copying glyph bitmaps (this is important for slanted text - # where rectangulary glyph boxes overlap) + # where rectangular glyph boxes overlap) ) -> Tuple[int, int, int, int]: + # pylint: disable=too-many-arguments, too-many-locals + # placeText - Writes text into a bitmap at the specified location. # # Note: scale is pushed up to Group level @@ -431,6 +354,7 @@ def _place_text( left = None right = x_start top = bottom = y_start + line_spacing = self._line_spacing for char in text: @@ -472,8 +396,8 @@ def _place_text( # Clip glyph y-direction if outside the font ascent/descent metrics. # Note: bitmap.blit will automatically clip the bottom of the glyph. y_clip = 0 - if (y_blit_target) < 0: - y_clip = -(y_blit_target) # clip this amount from top of bitmap + if y_blit_target < 0: + y_clip = -y_blit_target # clip this amount from top of bitmap y_blit_target = 0 # draw the clipped bitmap at y=0 print( @@ -503,7 +427,8 @@ def _place_text( xposition = xposition + my_glyph.shift_x - return (left, top, right - left, bottom - top) # bounding_box + # bounding_box + return left, top, right - left, bottom - top def _blit( self, @@ -518,6 +443,7 @@ def _blit( skip_index: int = None, # palette index that will not be copied # (for example: the background color of a glyph) ) -> None: + # pylint: disable=no-self-use, too-many-arguments if hasattr(bitmap, "blit"): # if bitmap has a built-in blit function, call it # this function should perform its own input checks @@ -591,14 +517,20 @@ def _set_font(self, new_font) -> None: raise RuntimeError("font is immutable when save_text is False") def _set_text(self, new_text: str, scale: int) -> None: - new_text = self._tab_text.join(new_text.split("\t")) - self._reset_text(text=new_text, scale=self.scale) + self._reset_text(text=self._replace_tabs(new_text), scale=self.scale) def _set_background_color(self, new_color): self._background_color = new_color if new_color is not None: - self.palette[0] = new_color - self.palette.make_opaque(0) + self._palette[0] = new_color + self._palette.make_opaque(0) else: - self.palette[0] = 0 - self.palette.make_transparent(0) + self._palette[0] = 0 + self._palette.make_transparent(0) + + def _set_label_direction(self, new_label_direction: str) -> None: + self._label_direction = new_label_direction + self._reset_text(text=str(self._text)) # Force a recalculation + + def _get_valid_label_directions(self) -> Tuple[str, ...]: + return "LTR", "RTL", "UPD", "UPR", "DWR" diff --git a/adafruit_display_text/label.py b/adafruit_display_text/label.py index bb2fd2a..943755f 100755 --- a/adafruit_display_text/label.py +++ b/adafruit_display_text/label.py @@ -22,15 +22,23 @@ """ -import displayio - __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git" + +try: + from typing import Tuple +except ImportError: + pass + +from displayio import Bitmap, Palette, TileGrid + from adafruit_display_text import LabelBase class Label(LabelBase): + # pylint: disable=too-many-instance-attributes + """A label displaying a string of text. The origin point set by ``x`` and ``y`` properties will be the left edge of the bounding box, and in the center of a M glyph (if its one line), or the (number of lines * linespacing + M)/2. That is, @@ -39,23 +47,23 @@ class Label(LabelBase): :param Font font: A font class that has ``get_bounding_box`` and ``get_glyph``. Must include a capital M for measuring character size. :param str text: Text to display - :param int max_glyphs: The largest quantity of glyphs we will display :param int color: Color of all text in RGB hex + :param int background_color: Color of the background, use `None` for transparent :param float line_spacing: Line spacing of text to display :param bool background_tight: Set `True` only if you want background box to tightly surround text. When set to 'True' Padding parameters will be ignored. :param int padding_top: Additional pixels added to background bounding box at top. - This parameter could be negative indicating additional pixels subtracted to background - bounding box. + This parameter could be negative indicating additional pixels subtracted from the + background bounding box. :param int padding_bottom: Additional pixels added to background bounding box at bottom. - This parameter could be negative indicating additional pixels subtracted to background - bounding box. + This parameter could be negative indicating additional pixels subtracted from the + background bounding box. :param int padding_left: Additional pixels added to background bounding box at left. - This parameter could be negative indicating additional pixels subtracted to background - bounding box. + This parameter could be negative indicating additional pixels subtracted from the + background bounding box. :param int padding_right: Additional pixels added to background bounding box at right. - This parameter could be negative indicating additional pixels subtracted to background - bounding box. + This parameter could be negative indicating additional pixels subtracted from the + background bounding box. :param (float,float) anchor_point: Point that anchored_position moves relative to. Tuple with decimal percentage of width and height. (E.g. (0,0) is top left, (1.0, 0.5): is middle right.) @@ -71,70 +79,25 @@ class Label(LabelBase): 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. - def __init__(self, font, **kwargs) -> None: - super().__init__(font, **kwargs) - - max_glyphs = kwargs.get("max_glyphs", None) - text = kwargs.get("text", "") - - if not max_glyphs and not text: - raise RuntimeError("Please provide a max_glyphs, or initial text") - self._tab_replacement = kwargs.get("tab_replacement", (4, " ")) - self._tab_text = self._tab_replacement[1] * self._tab_replacement[0] - text = self._tab_text.join(text.split("\t")) - if not max_glyphs: - max_glyphs = len(text) - - # local_group will set the scale - self.local_group = displayio.Group(scale=kwargs.get("scale", 1)) - self.append(self.local_group) + self._background_palette = Palette(1) + self._added_background_tilegrid = False - self.width = max_glyphs - self._font = font - self._text = None - self._anchor_point = kwargs.get("anchor_point", None) - self.x = kwargs.get("x", 0) - self.y = kwargs.get("y", 0) + super().__init__(font, **kwargs) - self.height = self._font.get_bounding_box()[1] - self._line_spacing = kwargs.get("line_spacing", 1.25) - self._bounding_box = None + text = self._replace_tabs(self._text) - self._background_tight = kwargs.get( - "background_tight", False - ) # sets padding status for text background box + self._width = len(text) + self._height = self._font.get_bounding_box()[1] # Create the two-color text palette - self.palette = displayio.Palette(2) - self.palette[0] = 0 - self.palette.make_transparent(0) - self.color = kwargs.get("color", 0xFFFFFF) - - self._background_color = kwargs.get("background_color", None) - self._background_palette = displayio.Palette(1) - self._added_background_tilegrid = False - - self._padding_top = kwargs.get("padding_top", 0) - self._padding_bottom = kwargs.get("padding_bottom", 0) - 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 self._label_direction not in ["LTR", "RTL", "UPR", "DWR", "TTB"]: - raise RuntimeError("Please provide a valid text direction") + self._palette[0] = 0 + self._palette.make_transparent(0) if text is not None: - self._update_text(str(text)) - if (kwargs.get("anchored_position", None) is not None) and ( - kwargs.get("anchor_point", None) is not None - ): - self.anchored_position = kwargs.get("anchored_position", None) + self._reset_text(str(text)) - def _create_background_box(self, lines: int, y_offset: int) -> None: + def _create_background_box(self, lines: int, y_offset: int) -> TileGrid: """Private Class function to create a background_box :param lines: int number of lines :param y_offset: int y pixel bottom coordinate for the background_box""" @@ -149,18 +112,14 @@ 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._ascent, self._descent - if ( - self._label_direction == "UPR" - or self._label_direction == "DWR" - or self._label_direction == "TTB" - ): + if self._label_direction in ("UPR", "DWR", "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) + + int((lines - 1) * self._width * self._line_spacing) + self._padding_left + self._padding_right ) @@ -171,12 +130,12 @@ def _create_background_box(self, lines: int, y_offset: int) -> None: x_box_offset = -self._padding_left box_height = ( (ascent + descent) - + int((lines - 1) * self.height * self._line_spacing) + + int((lines - 1) * self._height * self._line_spacing) + self._padding_top + self._padding_bottom ) - if self.base_alignment: + if self._base_alignment: y_box_offset = -ascent - self._padding_top else: y_box_offset = -ascent + y_offset - self._padding_top @@ -197,8 +156,8 @@ def _create_background_box(self, lines: int, y_offset: int) -> None: movx = left + x_box_offset movy = y_box_offset - background_bitmap = displayio.Bitmap(box_width, box_height, 1) - tile_grid = displayio.TileGrid( + background_bitmap = Bitmap(box_width, box_height, 1) + tile_grid = TileGrid( background_bitmap, pixel_shader=self._background_palette, x=movx, @@ -207,14 +166,14 @@ def _create_background_box(self, lines: int, y_offset: int) -> None: return tile_grid - def _update_background_color(self, new_color: int) -> None: + def _set_background_color(self, new_color: int) -> None: """Private class function that allows updating the font box background color - :param new_color: int color as an RGB hex number.""" + :param int new_color: color as an RGB hex number.""" if new_color is None: self._background_palette.make_transparent(0) if self._added_background_tilegrid: - self.local_group.pop(0) + self._local_group.pop(0) self._added_background_tilegrid = False else: self._background_palette.make_opaque(0) @@ -224,6 +183,10 @@ def _update_background_color(self, new_color: int) -> None: lines = self._text.rstrip("\n").count("\n") + 1 y_offset = self._ascent // 2 + if self._bounding_box is None: + # Still in initialization + return + if not self._added_background_tilegrid: # no bitmap is in the self Group # add bitmap if text is present and bitmap sizes > 0 pixels if ( @@ -235,15 +198,9 @@ def _update_background_color(self, new_color: int) -> None: self._bounding_box[3] + self._padding_top + self._padding_bottom > 0 ) ): - # This can be simplified in CP v6.0, when group.append(0) bug is corrected - if len(self.local_group) > 0: - self.local_group.insert( - 0, self._create_background_box(lines, y_offset) - ) - else: - self.local_group.append( - self._create_background_box(lines, y_offset) - ) + self._local_group.insert( + 0, self._create_background_box(lines, y_offset) + ) self._added_background_tilegrid = True else: # a bitmap is present in the self Group @@ -257,15 +214,16 @@ 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, self._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._local_group.pop(0) self._added_background_tilegrid = False - # pylint: disable = too-many-branches, too-many-statements - def _update_text( - self, new_text: str - ) -> None: # pylint: disable=too-many-locals ,too-many-branches, too-many-statements + def _update_text(self, new_text: str) -> None: + # pylint: disable=too-many-branches,too-many-statements + x = 0 y = 0 if self._added_background_tilegrid: @@ -273,7 +231,7 @@ def _update_text( else: i = 0 tilegrid_count = i - if self.base_alignment: + if self._base_alignment: self._y_offset = 0 else: self._y_offset = self._ascent // 2 @@ -290,14 +248,16 @@ def _update_text( for character in new_text: if character == "\n": - y += int(self.height * self._line_spacing) + y += int(self._height * self._line_spacing) x = 0 continue glyph = self._font.get_glyph(ord(character)) if not glyph: continue - if self._label_direction == "LTR" or self._label_direction == "RTL": + position_x, position_y = 0, 0 + + if self._label_direction in ("LTR", "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) @@ -322,7 +282,7 @@ def _update_text( right = max(right, glyph.dx) position_x = x - glyph.width - if self._label_direction == "TTB": + elif self._label_direction == "TTB": if x == 0: if left is None: left = glyph.dx @@ -338,7 +298,7 @@ def _update_text( position_y = y + glyph.dy position_x = x - glyph.width // 2 + self._y_offset - if self._label_direction == "UPR": + elif self._label_direction == "UPR": if x == 0: if bottom is None: bottom = -glyph.dx @@ -351,7 +311,7 @@ def _update_text( position_y = y - glyph.width - glyph.dx position_x = x - glyph.height - glyph.dy + self._y_offset - if self._label_direction == "DWR": + elif self._label_direction == "DWR": if y == 0: if top is None: top = -glyph.dx @@ -365,26 +325,15 @@ def _update_text( position_x = x + glyph.dy - self._y_offset if glyph.width > 0 and glyph.height > 0: - try: - # pylint: disable=unexpected-keyword-arg - face = displayio.TileGrid( - glyph.bitmap, - pixel_shader=self.palette, - default_tile=glyph.tile_index, - tile_width=glyph.width, - tile_height=glyph.height, - position=(position_x, position_y), - ) - except TypeError: - face = displayio.TileGrid( - glyph.bitmap, - pixel_shader=self.palette, - default_tile=glyph.tile_index, - tile_width=glyph.width, - tile_height=glyph.height, - x=position_x, - y=position_y, - ) + face = TileGrid( + glyph.bitmap, + pixel_shader=self._palette, + default_tile=glyph.tile_index, + tile_width=glyph.width, + tile_height=glyph.height, + x=position_x, + y=position_y, + ) if self._label_direction == "UPR": face.transpose_xy = True @@ -393,10 +342,10 @@ def _update_text( face.transpose_xy = True face.flip_y = True - if tilegrid_count < len(self.local_group): - self.local_group[tilegrid_count] = face + if tilegrid_count < len(self._local_group): + self._local_group[tilegrid_count] = face else: - self.local_group.append(face) + self._local_group.append(face) tilegrid_count += 1 if self._label_direction == "RTL": @@ -422,10 +371,12 @@ def _update_text( 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 + while len(self._local_group) > tilegrid_count: # i: + self._local_group.pop() + if self._label_direction == "RTL": + # pylint: disable=invalid-unary-operand-type + # type-checkers think left can be None self._bounding_box = (-left, top, left - right, bottom - top) if self._label_direction == "TTB": self._bounding_box = (left, top, right - left, bottom - top) @@ -438,24 +389,20 @@ def _update_text( self._text = new_text - if self.background_color is not None: - self._update_background_color(self._background_color) + if self._background_color is not None: + self._set_background_color(self._background_color) def _reset_text(self, new_text: str) -> None: - new_text = self._tab_text.join(new_text.split("\t")) - try: - current_anchored_position = self.anchored_position - self._update_text(str(new_text)) - self.anchored_position = current_anchored_position - except RuntimeError as run_error: - raise RuntimeError("Text length exceeds max_glyphs") from run_error + current_anchored_position = self.anchored_position + self._update_text(str(self._replace_tabs(new_text))) + self.anchored_position = current_anchored_position def _set_font(self, new_font) -> None: old_text = self._text current_anchored_position = self.anchored_position self._text = "" self._font = new_font - self.height = self._font.get_bounding_box()[1] + self._height = self._font.get_bounding_box()[1] self._update_text(str(old_text)) self.anchored_position = current_anchored_position @@ -466,10 +413,9 @@ 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: 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)) + self._update_text(str(self._text)) + + def _get_valid_label_directions(self) -> Tuple[str, ...]: + return "LTR", "RTL", "UPR", "DWR", "TTB" diff --git a/examples/display_text_advance_example.py b/examples/display_text_advance_example.py index 393c976..750728a 100644 --- a/examples/display_text_advance_example.py +++ b/examples/display_text_advance_example.py @@ -28,22 +28,25 @@ main_group.append(horizontal_line) bitmap = displayio.Bitmap(display.width, 4, 2) -vertica_line = displayio.TileGrid(bitmap, pixel_shader=palette, x=0, y=110) -main_group.append(vertica_line) +vertical_line = displayio.TileGrid(bitmap, pixel_shader=palette, x=0, y=110) +main_group.append(vertical_line) + # Tests -text_area = label.Label(terminalio.FONT, text="Circuit Python", max_glyphs=40) +text_area = label.Label(terminalio.FONT, text="Circuit Python") main_group.append(text_area) display.show(main_group) time.sleep(TIME_PAUSE) + # Testing position setter text_area.x = 10 text_area.y = 10 display.show(main_group) time.sleep(TIME_PAUSE) + # Testing creating label with initial position text_area.text = "Testing initiating without text" try: - text_middle = label.Label(terminalio.FONT, max_glyphs=40) + text_middle = label.Label(terminalio.FONT) except SyntaxError: print("Fail setting-up label without text") warning_text = label.Label( @@ -64,17 +67,20 @@ main_group.append(text_middle) display.show(main_group) time.sleep(TIME_PAUSE) + # Testing Text Setter text_area.text = "Testing Changing Text" text_middle.text = "Python" display.show(main_group) time.sleep(TIME_PAUSE) + # Testing a and y getter and setter text_area.text = "Testing Changing Position" text_middle.x = text_middle.x - 50 text_middle.y = text_middle.y - 50 display.show(main_group) time.sleep(TIME_PAUSE) + # Testing font Getter and setter text_area.text = "Testing Changing FONT" if isinstance(text_middle.font, fontio.BuiltinFont): @@ -84,6 +90,7 @@ # Once this working we create another label with all the initial specs main_group.pop() + # Testing Color text_area.text = "Testing Color" text_initial_specs = label.Label( @@ -95,10 +102,12 @@ main_group.append(text_initial_specs) display.show(main_group) time.sleep(TIME_PAUSE) + text_initial_specs.color = 0x004400 display.show(main_group) time.sleep(TIME_PAUSE) main_group.pop() + # Testing Background Color text_area.text = "Testing Background Color" text_initial_specs = label.Label( @@ -111,17 +120,18 @@ main_group.append(text_initial_specs) display.show(main_group) time.sleep(TIME_PAUSE) + text_initial_specs.background_color = 0x990099 display.show(main_group) time.sleep(TIME_PAUSE) main_group.pop() + # Testing Background Color text_area.text = "Testing Background Tight" text_initial_specs = label.Label( BIG_FONT, text="aaaaq~", x=0, - max_glyphs=6, y=display.height // 2, color=0xFFFFFF, background_color=0x990099, @@ -132,7 +142,6 @@ BIG_FONT, text="aaaaq~", x=90, - max_glyphs=6, y=display.height // 2, color=0xFFFFFF, background_color=0x990099, @@ -179,7 +188,8 @@ ) main_group.append(text_initial_specs) display.show(main_group) -time.sleep(1) +time.sleep(TIME_PAUSE) + try: text_initial_specs.anchored_position = (100, 100) text_initial_specs.anchor_point = (0.5, 0.5) @@ -219,6 +229,7 @@ main_group.append(text_initial_specs) display.show(main_group) time.sleep(TIME_PAUSE) + text_initial_specs.scale = 2 display.show(main_group) time.sleep(TIME_PAUSE) @@ -272,6 +283,7 @@ display.show(main_group) time.sleep(TIME_PAUSE) main_group.pop() + text_area.text = "Testing Direction-DWR" text_initial_specs = label.Label( MEDIUM_FONT, @@ -292,6 +304,7 @@ display.show(main_group) time.sleep(TIME_PAUSE) main_group.pop() + text_area.text = "Testing Direction-TTB" text_initial_specs = label.Label( MEDIUM_FONT, @@ -312,6 +325,7 @@ display.show(main_group) time.sleep(TIME_PAUSE) main_group.pop() + text_area.text = "Testing Direction-RTL" text_initial_specs = label.Label( MEDIUM_FONT, @@ -338,7 +352,7 @@ # Testing creating label with initial position display.show(main_group) time.sleep(TIME_PAUSE) -text_area = bitmap_label.Label(terminalio.FONT, text="Circuit Python", max_glyphs=40) +text_area = bitmap_label.Label(terminalio.FONT, text="Circuit Python") main_group.append(text_area) display.show(main_group) time.sleep(TIME_PAUSE) @@ -349,7 +363,7 @@ time.sleep(TIME_PAUSE) text_area.text = "Testing initiating without text" try: - text_middle = label.Label(terminalio.FONT, max_glyphs=40) + text_middle = label.Label(terminalio.FONT) except TypeError: print("Fail setting-up label without text") warning_text = label.Label( @@ -369,26 +383,31 @@ main_group.append(text_middle) display.show(main_group) time.sleep(TIME_PAUSE) + # Testing Text Setter text_area.text = "Testing Changing Text" text_middle.text = "Python" display.show(main_group) time.sleep(TIME_PAUSE) + # Testing a and y getter and setter text_area.text = "Testing Changing Position" text_middle.x = text_middle.x - 50 text_middle.y = text_middle.y - 50 display.show(main_group) time.sleep(TIME_PAUSE) + # Testing font Getter and setter text_area.text = "Testing Changing FONT" if isinstance(text_middle.font, fontio.BuiltinFont): + print("Font was BuiltinFont") text_middle.font = MEDIUM_FONT display.show(main_group) time.sleep(TIME_PAUSE) # Once this working we create another label with all the initial specs main_group.pop() + # Testing Color text_area.text = "Testing Color" text_initial_specs = bitmap_label.Label( @@ -400,10 +419,12 @@ main_group.append(text_initial_specs) display.show(main_group) time.sleep(TIME_PAUSE) + text_initial_specs.color = 0x004400 display.show(main_group) time.sleep(TIME_PAUSE) main_group.pop() + # Testing Background Color text_area.text = "Testing Background Color" text_initial_specs = bitmap_label.Label( @@ -416,17 +437,18 @@ main_group.append(text_initial_specs) display.show(main_group) time.sleep(TIME_PAUSE) + text_initial_specs.background_color = 0x990099 display.show(main_group) time.sleep(TIME_PAUSE) main_group.pop() + # Testing Background Color text_area.text = "Testing Background Tight" text_initial_specs = bitmap_label.Label( BIG_FONT, text="aaaaq~", x=0, - max_glyphs=6, y=display.height // 2, color=0xFFFFFF, background_color=0x990099, @@ -437,7 +459,6 @@ BIG_FONT, text="aaaaq~", x=90, - max_glyphs=6, y=display.height // 2, color=0xFFFFFF, background_color=0x990099, @@ -484,7 +505,8 @@ ) main_group.append(text_initial_specs) display.show(main_group) -time.sleep(1) +time.sleep(TIME_PAUSE) + try: text_initial_specs.anchored_position = (100, 100) text_initial_specs.anchor_point = (0.5, 0.5) @@ -524,6 +546,7 @@ main_group.append(text_initial_specs) display.show(main_group) time.sleep(TIME_PAUSE) + text_initial_specs.scale = 2 display.show(main_group) time.sleep(TIME_PAUSE) @@ -577,6 +600,7 @@ display.show(main_group) time.sleep(TIME_PAUSE) main_group.pop() + text_area.text = "Testing Direction-DWR" text_initial_specs = bitmap_label.Label( MEDIUM_FONT, @@ -597,6 +621,7 @@ display.show(main_group) time.sleep(TIME_PAUSE) main_group.pop() + text_area.text = "Testing Direction-UPD" text_initial_specs = bitmap_label.Label( MEDIUM_FONT, @@ -617,6 +642,7 @@ display.show(main_group) time.sleep(TIME_PAUSE) main_group.pop() + text_area.text = "Testing Direction-RTL" text_initial_specs = bitmap_label.Label( MEDIUM_FONT, @@ -637,3 +663,6 @@ display.show(main_group) time.sleep(TIME_PAUSE) main_group.pop() + +text_area.text = "Finished" +print("Tests finished") diff --git a/examples/display_text_label_vs_bitmap_label_comparison.py b/examples/display_text_label_vs_bitmap_label_comparison.py index 29f01f4..69fba87 100644 --- a/examples/display_text_label_vs_bitmap_label_comparison.py +++ b/examples/display_text_label_vs_bitmap_label_comparison.py @@ -143,7 +143,6 @@ font=fontToUse, text=long_string, color=0x000000, - max_glyphs=len(long_string), background_color=0xFFFF00, padding_bottom=label2_padding, padding_left=0, @@ -192,7 +191,6 @@ font=fontToUse, text=long_string, color=0x000000, - max_glyphs=len(long_string), background_color=0xFFFF00, padding_bottom=label2_padding, padding_left=0, diff --git a/examples/display_text_pyportal.py b/examples/display_text_pyportal.py index edcfabb..3691ec8 100644 --- a/examples/display_text_pyportal.py +++ b/examples/display_text_pyportal.py @@ -14,10 +14,13 @@ from adafruit_bitmap_font import bitmap_font from adafruit_display_text.label import Label +FONT_DIR = "/fonts/" fonts = list( - filter(lambda x: x.endswith("bdf") and not x.startswith("."), os.listdir("/")) + filter(lambda x: x.endswith("bdf") and not x.startswith("."), os.listdir(FONT_DIR)) ) -fonts = [bitmap_font.load_font(x) for x in fonts] +fonts = [bitmap_font.load_font(FONT_DIR + x) for x in fonts] +if len(fonts) == 0: + print("No fonts found in '{}'".format(FONT_DIR)) print("fade up") # Fade up the backlight @@ -30,12 +33,17 @@ splash = displayio.Group() board.DISPLAY.show(splash) max_y = 0 -y = 2 +y = 0 for demo_text in demos: for font in fonts: + if y >= board.DISPLAY.height: + y = 0 + while len(splash): + splash.pop() print("Font load {}".format(font.name)) - area = Label(font, text=demo_text) - area.y = y + area = Label( + font, text=demo_text, anchor_point=(0, 0), anchored_position=(0, y) + ) splash.append(area) y += area.height @@ -46,8 +54,8 @@ except AttributeError: board.DISPLAY.wait_for_frame() -# Wait for 10 minutes (600 seconds) -time.sleep(600) +# Wait for 1 minute (60 seconds) +time.sleep(60) # Fade down the backlight for b in range(100, -1, -1):