diff --git a/adafruit_display_text/__init__.py b/adafruit_display_text/__init__.py index c0ce501..2a63aee 100644 --- a/adafruit_display_text/__init__.py +++ b/adafruit_display_text/__init__.py @@ -5,10 +5,16 @@ """ Display Text module helper functions """ +try: + from typing import Tuple +except ImportError: + pass from displayio import Group, Palette -def wrap_text_to_pixels(string, max_width, font=None, indent0="", indent1=""): +def wrap_text_to_pixels( + string: str, max_width: int, font=None, indent0: str = "", indent1: str = "" +) -> None: """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 @@ -26,7 +32,7 @@ def wrap_text_to_pixels(string, max_width, font=None, indent0="", indent1=""): input text at max_width pixels size """ - # pylint: disable=too-many-locals too-many-branches + # pylint: disable=too-many-locals, too-many-branches if font is None: def measure(string): @@ -173,9 +179,9 @@ class LabelBase(Group): :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. + :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 + :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""" @@ -183,27 +189,26 @@ class LabelBase(Group): def __init__( self, font, - x=0, - y=0, - text="", - max_glyphs=None, - # with label.py - color=0xFFFFFF, - background_color=None, - line_spacing=1.25, - background_tight=False, - padding_top=0, - padding_bottom=0, - padding_left=0, - padding_right=0, - anchor_point=None, - anchored_position=None, - save_text=True, # can reduce memory use if save_text = False - scale=1, - base_alignment=False, - tab_replacement=(4, " "), + x: int = 0, + y: int = 0, + text: str = "", + max_glyphs: int = None, + color: int = 0xFFFFFF, + background_color: int = None, + line_spacing: float = 1.25, + background_tight: bool = False, + padding_top: int = 0, + padding_bottom: int = 0, + padding_left: int = 0, + 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, " "), **kwargs, - ): + ) -> None: super().__init__(max_size=1, x=x, y=y, scale=1) self._font = font @@ -221,8 +226,16 @@ def __init__( self.local_group = None self._text = text + self.baseline = -1.0 - def _get_ascent_descent(self): + self.base_alignment = base_alignment + + if self.base_alignment: + self._y_offset = 0 + else: + self._y_offset = self._get_ascent() // 2 + + def _get_ascent_descent(self) -> Tuple[int, int]: """ Private function to calculate ascent and descent font values """ if hasattr(self.font, "ascent"): return self.font.ascent, self.font.descent @@ -243,29 +256,29 @@ def _get_ascent_descent(self): descender_max = max(descender_max, -this_glyph.dy) return ascender_max, descender_max - def _get_ascent(self): + def _get_ascent(self) -> int: return self._get_ascent_descent()[0] @property - def font(self): + def font(self) -> None: """Font to use for text display.""" return self._font - def _set_font(self, new_font): + def _set_font(self, new_font) -> None: # subclasses should override this pass @font.setter - def font(self, new_font): + def font(self, new_font) -> None: self._set_font(new_font) @property - def color(self): + def color(self) -> int: """Color of the text as an RGB hex number.""" return self._color @color.setter - def color(self, new_color): + def color(self, new_color: int): self._color = new_color if new_color is not None: self.palette[1] = new_color @@ -275,7 +288,7 @@ def color(self, new_color): self.palette.make_transparent(1) @property - def background_color(self): + def background_color(self) -> int: """Color of the background as an RGB hex number.""" return self._background_color @@ -284,31 +297,34 @@ def _set_background_color(self, new_color): pass @background_color.setter - def background_color(self, new_color): + def background_color(self, new_color: int) -> None: self._set_background_color(new_color) @property - def anchor_point(self): + def anchor_point(self) -> Tuple[float, float]: """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.)""" return self._anchor_point @anchor_point.setter - def anchor_point(self, new_anchor_point): - self._anchor_point = new_anchor_point + def anchor_point(self, new_anchor_point: Tuple[float, float]) -> None: + 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 @property - def anchored_position(self): + def anchored_position(self) -> Tuple[int, int]: """Position relative to the anchor_point. Tuple containing x,y pixel coordinates.""" return self._anchored_position @anchored_position.setter - def anchored_position(self, new_position): + def anchored_position(self, new_position: Tuple[int, int]) -> None: self._anchored_position = new_position # Set anchored_position if (self._anchor_point is not None) and (self._anchored_position is not None): @@ -317,51 +333,54 @@ def anchored_position(self, new_position): - (self._bounding_box[0] * self.scale) - round(self._anchor_point[0] * (self._bounding_box[2] * self.scale)) ) - self.y = int( - new_position[1] - - (self._bounding_box[1] * self.scale) - - round(self._anchor_point[1] * self._bounding_box[3] * self.scale) - ) + if self._anchor_point[1] == self.baseline: + self.y = int(new_position[1] - (self._y_offset * self.scale)) + else: + self.y = int( + new_position[1] + - (self._bounding_box[1] * self.scale) + - round(self._anchor_point[1] * self._bounding_box[3] * self.scale) + ) @property - def scale(self): + def scale(self) -> int: """Set the scaling of the label, in integer values""" return self.local_group.scale @scale.setter - def scale(self, new_scale): + def scale(self, new_scale: int) -> None: self.local_group.scale = new_scale self.anchored_position = self._anchored_position # update the anchored_position - def _set_text(self, new_text, scale): + def _set_text(self, new_text: str, scale: int) -> None: # subclasses should override this pass @property - def text(self): + def text(self) -> str: """Text to be displayed.""" return self._text @text.setter # Cannot set color or background color with text setter, use separate setter - def text(self, new_text): + def text(self, new_text: str) -> None: self._set_text(new_text, self.scale) @property - def bounding_box(self): + def bounding_box(self) -> Tuple[int, int]: """An (x, y, w, h) tuple that completely covers all glyphs. The first two numbers are offset from the x, y origin of this group""" return tuple(self._bounding_box) @property - def line_spacing(self): + def line_spacing(self) -> float: """The amount of space between lines of text, in multiples of the font's bounding-box height. (E.g. 1.0 is the bounding-box height)""" return self._line_spacing - def _set_line_spacing(self, new_line_spacing): + def _set_line_spacing(self, new_line_spacing: float) -> None: # subclass should override this. pass @line_spacing.setter - def line_spacing(self, new_line_spacing): + def line_spacing(self, new_line_spacing: float) -> None: self._set_line_spacing(new_line_spacing) diff --git a/adafruit_display_text/bitmap_label.py b/adafruit_display_text/bitmap_label.py index 9d9f072..b459388 100755 --- a/adafruit_display_text/bitmap_label.py +++ b/adafruit_display_text/bitmap_label.py @@ -22,7 +22,10 @@ https://github.com/adafruit/circuitpython/releases """ - +try: + from typing import Tuple +except ImportError: + pass import displayio __version__ = "0.0.0-auto.0" @@ -69,9 +72,9 @@ class Label(LabelBase): :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. + :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 + :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""" @@ -80,7 +83,7 @@ class Label(LabelBase): # Note: max_glyphs parameter is unnecessary, this is used for direct # compatibility with label.py - def __init__(self, font, **kwargs): + def __init__(self, font, **kwargs) -> None: super().__init__(font, **kwargs) @@ -126,22 +129,22 @@ def __init__(self, font, **kwargs): def _reset_text( self, font=None, - x=None, - y=None, - text=None, - line_spacing=None, - background_tight=None, - padding_top=None, - padding_bottom=None, - padding_left=None, - padding_right=None, - anchor_point=None, - anchored_position=None, - save_text=None, - scale=None, - base_alignment=None, - tab_replacement=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, + ) -> None: # Store all the instance variables if font is not None: @@ -288,12 +291,14 @@ def _reset_text( # x,y positions of the label @staticmethod - def _line_spacing_ypixels(font, line_spacing): + def _line_spacing_ypixels(font, line_spacing: float) -> int: # Note: Scaling is provided at the Group level return_value = int(line_spacing * font.get_bounding_box()[1]) return return_value - def _text_bounding_box(self, text, font, line_spacing): + def _text_bounding_box( + self, text: str, font, line_spacing: float + ) -> Tuple[int, int, int, int, int, int]: ascender_max, descender_max = self._get_ascent_descent() lines = 1 @@ -371,17 +376,17 @@ def _text_bounding_box(self, text, font, line_spacing): def _place_text( self, bitmap, - text, + text: str, font, - line_spacing, - xposition, - yposition, - text_palette_index=1, - background_palette_index=0, - skip_index=0, # set to None to write all pixels, other wise skip this palette index + 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) - ): + ) -> Tuple[int, int, int, int]: # placeText - Writes text into a bitmap at the specified location. # # Note: scale is pushed up to Group level @@ -469,16 +474,16 @@ def _place_text( def _blit( self, bitmap, # target bitmap - x, # target x upper left corner - y, # target y upper left corner + x: int, # target x upper left corner + y: int, # target y upper left corner source_bitmap, # source bitmap - x_1=0, # source x start - y_1=0, # source y start - x_2=None, # source x end - y_2=None, # source y end - skip_index=None, # palette index that will not be copied + x_1: int = 0, # source x start + y_1: int = 0, # source y start + x_2: int = None, # source x end + y_2: int = None, # source y end + skip_index: int = None, # palette index that will not be copied # (for example: the background color of a glyph) - ): + ) -> None: if hasattr(bitmap, "blit"): # if bitmap has a built-in blit function, call it # this function should perform its own input checks @@ -538,20 +543,20 @@ def _blit( elif y_placement > bitmap.height: break - def _set_line_spacing(self, new_line_spacing): + def _set_line_spacing(self, new_line_spacing: float) -> None: if self._save_text: self._reset_text(line_spacing=new_line_spacing, scale=self.scale) else: raise RuntimeError("line_spacing is immutable when save_text is False") - def _set_font(self, new_font): + def _set_font(self, new_font) -> None: self._font = new_font if self._save_text: self._reset_text(font=new_font, scale=self.scale) else: raise RuntimeError("font is immutable when save_text is False") - def _set_text(self, new_text, scale): + 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) diff --git a/adafruit_display_text/label.py b/adafruit_display_text/label.py index dadc7f0..5c4472c 100755 --- a/adafruit_display_text/label.py +++ b/adafruit_display_text/label.py @@ -64,14 +64,14 @@ class Label(LabelBase): :param int scale: Integer value of the pixel scaling :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 + :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""" # 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): + def __init__(self, font, **kwargs) -> None: super().__init__(font, **kwargs) max_glyphs = kwargs.get("max_glyphs", None) @@ -130,7 +130,7 @@ def __init__(self, font, **kwargs): ): self.anchored_position = kwargs.get("anchored_position", None) - def _create_background_box(self, lines, y_offset): + def _create_background_box(self, lines: int, y_offset: int) -> None: """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""" @@ -172,7 +172,7 @@ def _create_background_box(self, lines, y_offset): return tile_grid - def _update_background_color(self, new_color): + def _update_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.""" @@ -227,9 +227,10 @@ def _update_background_color(self, new_color): self.local_group.pop(0) self._added_background_tilegrid = False + # pylint: disable = too-many-branches, too-many-statements def _update_text( - self, new_text - ): # pylint: disable=too-many-locals ,too-many-branches, too-many-statements + self, new_text: str + ) -> None: # pylint: disable=too-many-locals ,too-many-branches, too-many-statements x = 0 y = 0 if self._added_background_tilegrid: @@ -238,9 +239,9 @@ def _update_text( i = 0 tilegrid_count = i if self.base_alignment: - y_offset = 0 + self._y_offset = 0 else: - y_offset = self._get_ascent() // 2 + self._y_offset = self._get_ascent() // 2 right = top = bottom = 0 left = None @@ -260,9 +261,9 @@ def _update_text( else: left = min(left, glyph.dx) if y == 0: # first line, find the Ascender height - 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 + 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 if glyph.width > 0 and glyph.height > 0: try: @@ -305,7 +306,7 @@ def _update_text( if self.background_color is not None: self._update_background_color(self._background_color) - def _reset_text(self, new_text): + 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 @@ -314,7 +315,7 @@ def _reset_text(self, new_text): except RuntimeError as run_error: raise RuntimeError("Text length exceeds max_glyphs") from run_error - def _set_font(self, new_font): + def _set_font(self, new_font) -> None: old_text = self._text current_anchored_position = self.anchored_position self._text = "" @@ -323,11 +324,11 @@ def _set_font(self, new_font): self._update_text(str(old_text)) self.anchored_position = current_anchored_position - def _set_line_spacing(self, new_line_spacing): + def _set_line_spacing(self, new_line_spacing: float) -> None: self._line_spacing = new_line_spacing self.text = self._text # redraw the box - def _set_text(self, new_text, scale): + def _set_text(self, new_text: str, scale: int) -> None: self._reset_text(new_text) def _set_background_color(self, new_color):