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))
Ejemplo n.º 4
0
    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
Ejemplo n.º 5
0
    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")
Ejemplo n.º 11
0
 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