def color_morph_vector_shape( *, color_start, color_end, vector_shape, position, **kwargs, ): """Performs color morphing for a vector shape, with color between the ``color_start`` and ``color_end`` values based on the position parameter. :param int color_start: the starting color :param int color_end: the ending_color :param vectorio.VectorShape vector_shape: the VectorShape whose palette color index 1 should be morphed. :param float position: float position: a linear interpolation of the current frame's position between ``frame_start`` and ``frame_end``. If using `Animation.execute_frame()` the ``position`` parameter will be included automatically. """ morphed_color = _color_fade(color_start, color_end, position) palette = Palette(2) palette[1] = morphed_color palette.make_transparent(0) vector_shape.pixel_shader = palette
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, 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, " "), label_direction: str = "LTR", **kwargs, ) -> None: super().__init__(max_size=1, x=x, y=y, scale=1) self._font = font self.palette = Palette(2) self._color = color self._background_color = background_color self._bounding_box = None self._anchor_point = anchor_point self._anchored_position = anchored_position # 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 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 if self.base_alignment: self._y_offset = 0 else: self._y_offset = self._get_ascent() // 2
def __init__(self, font: Union[BuiltinFont, BDF, PCF], **kwargs) -> None: self._background_palette = Palette(1) self._added_background_tilegrid = False super().__init__(font, **kwargs) text = self._replace_tabs(self._text) self._width = len(text) self._height = self._font.get_bounding_box()[1] # Create the two-color text palette self._palette[0] = 0 self._palette.make_transparent(0) if text is not None: self._reset_text(str(text))
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, " "), theme=None, **kwargs, ): super().__init__(max_size=1, x=x, y=y, scale=1) self._font = font self.palette = Palette(2) self._color = color if theme == "MAGTAG": self._background_color = 0x990099 else: self._background_color = background_color self._bounding_box = None self._anchor_point = anchor_point self._anchored_position = anchored_position # 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 self._text = text
def init_class(cls, display=None, max_scale=1.5, max_icon_size=(80, 80), max_color_depth=256): """ Initializes the IconAnimated Class variables, including preallocating memory buffers for the icon zoom bitmap and icon zoom palette. .. Note:: The `init_class` class function must be called before instancing any IconAnimated widgets. Usage example: ``IconAnimated.init_class(display=board.DISPLAY, max_scale=1.5, max_icon_size=(80,80), max_color_depth=256)`` :param displayio.Display display: The display where the icons will be displayed. :param float max_scale: The maximum zoom of the any of the icons, should be >= 1.0, (default: 1.5) :param max_icon_size: The maximum (x,y) pixel dimensions of any `IconAnimated` bitmap size that will be created (default: (80,80)). Note: This is the original pixel size, before scaling :type max_icon_size: Tuple[int,int] :param int max_color_depth: The maximum color depth of any `IconAnimated` bitmap that will be created (default: 256) """ if display is None: raise ValueError( "IconAninmated.init_class: Must provide display parameter for IconAnimated." ) if (isinstance(max_icon_size, tuple) and len(max_icon_size) == 2 # validate max_icon_size input and isinstance(max_icon_size[0], int) and isinstance(max_icon_size[1], int)): pass else: raise ValueError( "IconAninmated.init_class: max_icon_size must be an (x,y) " "tuple of integer pixel sizes.") cls.display = display if max_scale < 1.0: print("Warning: IconAnimated.init_class - max_scale value was " "constrained to minimum of 1.0") cls.max_scale = max(1.0, max_scale) cls.bitmap_buffer = Bitmap( round(cls.max_scale * max_icon_size[0]), round(cls.max_scale * max_icon_size[1]), max_color_depth + 1, ) cls.palette_buffer = Palette(max_color_depth + 1)
class LabelBase(Group): """Super class that all other types of labels will extend. This contains all of the properties and functions that work the same way in all labels. subclasses should implement _set_text, _set_font, and _set_line_spacing to have the correct behavior fo rthat type of label. :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 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 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 :param int padding_left: Additional pixels added to background bounding box at left :param int padding_right: Additional pixels added to background bounding box at right :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.) :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""" # pylint: disable=unused-argument, too-many-instance-attributes, too-many-locals, too-many-arguments 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, " "), **kwargs, ): super().__init__(max_size=1, x=x, y=y, scale=1) self._font = font self.palette = Palette(2) self._color = color self._background_color = background_color self._bounding_box = None self._anchor_point = anchor_point self._anchored_position = anchored_position # 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 self._text = text def _get_ascent_descent(self): """ Private function to calculate ascent and descent font values """ if hasattr(self.font, "ascent"): return self.font.ascent, self.font.descent # check a few glyphs for maximum ascender and descender height glyphs = "M j'" # choose glyphs with highest ascender and lowest try: self._font.load_glyphs(glyphs) except AttributeError: # Builtin font doesn't have or need load_glyphs pass # descender, will depend upon font used ascender_max = descender_max = 0 for char in glyphs: this_glyph = self._font.get_glyph(ord(char)) if this_glyph: ascender_max = max(ascender_max, this_glyph.height + this_glyph.dy) descender_max = max(descender_max, -this_glyph.dy) return ascender_max, descender_max def _get_ascent(self): return self._get_ascent_descent()[0] @property def font(self): """Font to use for text display.""" return self._font def _set_font(self, new_font): # subclasses should override this pass @font.setter def font(self, new_font): self._set_font(new_font) @property def color(self): """Color of the text as an RGB hex number.""" return self._color @color.setter def color(self, new_color): self._color = new_color if new_color is not None: self.palette[1] = new_color self.palette.make_opaque(1) else: self.palette[1] = 0 self.palette.make_transparent(1) @property def background_color(self): """Color of the background as an RGB hex number.""" return self._background_color def _set_background_color(self, new_color): # subclasses should override this pass @background_color.setter def background_color(self, new_color): self._set_background_color(new_color) @property def anchor_point(self): """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 self.anchored_position = ( self._anchored_position ) # update the anchored_position using setter @property def anchored_position(self): """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): self._anchored_position = new_position # Set anchored_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)) ) 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): """Set the scaling of the label, in integer values""" return self.local_group.scale @scale.setter def scale(self, new_scale): self.local_group.scale = new_scale self.anchored_position = self._anchored_position # update the anchored_position def _set_text(self, new_text, scale): # subclasses should override this pass @property def text(self): """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): self._set_text(new_text, self.scale) @property def bounding_box(self): """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): """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): # subclass should override this. pass @line_spacing.setter def line_spacing(self, new_line_spacing): self._set_line_spacing(new_line_spacing)
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, it will try to have it be center-left as close as possible. :param font: A font class that has ``get_bounding_box`` and ``get_glyph``. Must include a capital M for measuring character size. :type font: ~BuiltinFont, ~BDF, or ~PCF :param str text: Text to 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 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 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 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 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.) :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 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``""" def __init__(self, font: Union[BuiltinFont, BDF, PCF], **kwargs) -> None: self._background_palette = Palette(1) self._added_background_tilegrid = False super().__init__(font, **kwargs) text = self._replace_tabs(self._text) self._width = len(text) self._height = self._font.get_bounding_box()[1] # Create the two-color text palette self._palette[0] = 0 self._palette.make_transparent(0) if text is not None: self._reset_text(str(text)) 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""" 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] x_box_offset = 0 y_box_offset = self._bounding_box[1] else: # draw a "loose" bounding box to include any ascenders/descenders. ascent, descent = self._ascent, self._descent 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) + 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: y_box_offset = -ascent + y_offset - self._padding_top 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 = Bitmap(box_width, box_height, 1) tile_grid = TileGrid( background_bitmap, pixel_shader=self._background_palette, x=movx, y=movy, ) return tile_grid def _set_background_color(self, new_color: Optional[int]) -> None: """Private class function that allows updating the font box background color :param int new_color: Color as an RGB hex number, setting to None makes it transparent """ if new_color is None: self._background_palette.make_transparent(0) if self._added_background_tilegrid: self._local_group.pop(0) self._added_background_tilegrid = False else: self._background_palette.make_opaque(0) self._background_palette[0] = new_color self._background_color = new_color 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 ( (len(self._text) > 0) and ( self._bounding_box[2] + self._padding_left + self._padding_right > 0 ) and ( self._bounding_box[3] + self._padding_top + self._padding_bottom > 0 ) ): 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 # update bitmap if text is present and bitmap sizes > 0 pixels if ( (len(self._text) > 0) and ( self._bounding_box[2] + self._padding_left + self._padding_right > 0 ) and ( self._bounding_box[3] + self._padding_top + self._padding_bottom > 0 ) ): 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 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: i = 1 else: i = 0 tilegrid_count = i if self._base_alignment: self._y_offset = 0 else: self._y_offset = self._ascent // 2 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": y += int(self._height * self._line_spacing) x = 0 continue glyph = self._font.get_glyph(ord(character)) if not glyph: continue 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) 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 = 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 elif 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 elif 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 elif 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: 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 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 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 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() 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) 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 if self._background_color is not None: self._set_background_color(self._background_color) def _reset_text(self, new_text: str) -> None: 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: Union[BuiltinFont, BDF, PCF]) -> 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._update_text(str(old_text)) self.anchored_position = current_anchored_position 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: str, scale: int) -> None: self._reset_text(new_text) def _set_label_direction(self, new_label_direction: str) -> None: self._label_direction = new_label_direction self._update_text(str(self._text)) def _get_valid_label_directions(self) -> Tuple[str, ...]: return "LTR", "RTL", "UPR", "DWR", "TTB"
def __init__( self, font: Union[BuiltinFont, BDF, PCF], x: int = 0, y: int = 0, text: str = "", 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, scale: int = 1, base_alignment: bool = False, tab_replacement: Tuple[int, str] = (4, " "), label_direction: str = "LTR", **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._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] if "max_glyphs" in kwargs: print( "Please update your code: 'max_glyphs' is not needed anymore.") self._ascent, self._descent = self._get_ascent_descent() self._bounding_box = None self.color = color self.background_color = background_color # 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._baseline = -1.0 if self._base_alignment: self._y_offset = 0 else: self._y_offset = self._ascent // 2
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. **Note:** This should be treated as an abstract base class. Subclasses should implement ``_set_text``, ``_set_font``, and ``_set_line_spacing`` to have the correct behavior for that type of label. :param font: A font class that has ``get_bounding_box`` and ``get_glyph``. Must include a capital M for measuring character size. :type font: ~BuiltinFont, ~BDF, or ~PCF :param str text: Text to 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 :param int padding_bottom: Additional pixels added to background bounding box at bottom :param int padding_left: Additional pixels added to background bounding box at left :param int padding_right: Additional pixels added to background bounding box at right :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.) :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 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. See the subclass documentation for the possible values.""" def __init__( self, font: Union[BuiltinFont, BDF, PCF], x: int = 0, y: int = 0, text: str = "", 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, scale: int = 1, base_alignment: bool = False, tab_replacement: Tuple[int, str] = (4, " "), label_direction: str = "LTR", **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._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] if "max_glyphs" in kwargs: print( "Please update your code: 'max_glyphs' is not needed anymore.") self._ascent, self._descent = self._get_ascent_descent() self._bounding_box = None self.color = color self.background_color = background_color # 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._baseline = -1.0 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") and hasattr(self.font, "descent"): return self.font.ascent, self.font.descent # check a few glyphs for maximum ascender and descender height glyphs = "M j'" # choose glyphs with highest ascender and lowest try: self._font.load_glyphs(glyphs) except AttributeError: # Builtin font doesn't have or need load_glyphs pass # descender, will depend upon font used ascender_max = descender_max = 0 for char in glyphs: this_glyph = self._font.get_glyph(ord(char)) if this_glyph: ascender_max = max(ascender_max, this_glyph.height + this_glyph.dy) descender_max = max(descender_max, -this_glyph.dy) return ascender_max, descender_max @property def font(self) -> Union[BuiltinFont, BDF, PCF]: """Font to use for text display.""" return self._font def _set_font(self, new_font: Union[BuiltinFont, BDF, PCF]) -> None: raise NotImplementedError("{} MUST override '_set_font'".format( type(self))) @font.setter def font(self, new_font: Union[BuiltinFont, BDF, PCF]) -> None: self._set_font(new_font) @property def color(self) -> int: """Color of the text as an RGB hex number.""" return self._color @color.setter 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) else: self._palette[1] = 0 self._palette.make_transparent(1) @property def background_color(self) -> int: """Color of the background as an RGB hex number.""" return self._background_color def _set_background_color(self, new_color): raise NotImplementedError( "{} MUST override '_set_background_color'".format(type(self))) @background_color.setter def background_color(self, new_color: int) -> None: self._set_background_color(new_color) @property 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: 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 # update the anchored_position using setter self.anchored_position = self._anchored_position @property 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: Tuple[int, int]) -> None: self._anchored_position = new_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: 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) -> int: """Set the scaling of the label, in integer values""" return self._local_group.scale @scale.setter 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: str, scale: int) -> None: raise NotImplementedError("{} MUST override '_set_text'".format( type(self))) @property 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: str) -> None: self._set_text(new_text, self.scale) @property 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 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 bounding-box height. (E.g. 1.0 is the bounding-box height)""" return self._line_spacing def _set_line_spacing(self, new_line_spacing: float) -> None: raise NotImplementedError( "{} MUST override '_set_line_spacing'".format(type(self))) @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: 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 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: str) -> str: return self._tab_text.join(text.split("\t"))
WHITE = 0xFFFFFF BLACK = 0x000000 RED = 0xFF0000 ORANGE = 0xFFA500 YELLOW = 0xFFFF00 GREEN = 0x00FF00 BLUE = 0x0000FF PURPLE = 0x800080 PINK = 0xFFC0CB colors = (BLACK, RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE, WHITE) print("Building sample bitmap and palette") bitmap = Bitmap(16, 16, 9) palette = Palette(len(colors)) for i, c in enumerate(colors): palette[i] = c for x in range(16): for y in range(16): if x == 0 or y == 0 or x == 15 or y == 15: bitmap[x, y] = 1 elif x == y: bitmap[x, y] = 4 elif x == 15 - y: bitmap[x, y] = 5 else: bitmap[x, y] = 0 print("Saving bitmap")
def __init__(self, color_list, iter_step_size=1): self.color_list = color_list self.palette = Palette(self.num_colors) for i in range(self.num_colors): self.palette[i] = color_list[i] self.iter_step_size = iter_step_size