Compare commits
No commits in common. "use-ascent-descent" and "master" have entirely different histories.
use-ascent
...
master
7 changed files with 210 additions and 584 deletions
|
|
@ -1,69 +0,0 @@
|
|||
"""
|
||||
Display Text module helper functions
|
||||
"""
|
||||
|
||||
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2020 Tim C for Adafruit Industries LLC
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
|
||||
def wrap_text_to_lines(string, max_chars):
|
||||
"""wrap_text_to_lines function
|
||||
A helper that will return a list of lines with word-break wrapping
|
||||
|
||||
:param str string: The text to be wrapped
|
||||
:param int max_chars: The maximum number of characters on a line before wrapping
|
||||
|
||||
:return list the_lines: A list of lines where each line is separated based on the amount
|
||||
of max_chars provided
|
||||
|
||||
"""
|
||||
|
||||
def chunks(lst, n):
|
||||
"""Yield successive n-sized chunks from lst."""
|
||||
for i in range(0, len(lst), n):
|
||||
yield lst[i : i + n]
|
||||
|
||||
string = string.replace("\n", "").replace("\r", "") # Strip confusing newlines
|
||||
words = string.split(" ")
|
||||
the_lines = []
|
||||
the_line = ""
|
||||
for w in words:
|
||||
if len(w) > max_chars:
|
||||
parts = []
|
||||
for part in chunks(w, max_chars - 1):
|
||||
parts.append("{}-".format(part))
|
||||
the_lines.extend(parts[:-1])
|
||||
the_line = parts[-1][:-1]
|
||||
continue
|
||||
|
||||
if len(the_line + " " + w) <= max_chars:
|
||||
the_line += " " + w
|
||||
else:
|
||||
the_lines.append(the_line)
|
||||
the_line = "" + w
|
||||
if the_line: # Last line remaining
|
||||
the_lines.append(the_line)
|
||||
# Remove first space from first line:
|
||||
if the_lines[0][0] == " ":
|
||||
the_lines[0] = the_lines[0][1:]
|
||||
return the_lines
|
||||
|
|
@ -50,12 +50,13 @@ class Label(displayio.Group):
|
|||
"""A label displaying a string of text that is stored in a bitmap.
|
||||
Note: This ``bitmap_label.py`` library utilizes a bitmap to display the text.
|
||||
This method is memory-conserving relative to ``label.py``.
|
||||
The ``max_glyphs`` parameter is ignored and is present
|
||||
For the bitmap_label library, the font, text, and line_spacing must be set at
|
||||
instancing and are immutable. The ``max_glyphs`` parameter is ignored and is present
|
||||
only for direct compatability with label.py.
|
||||
|
||||
For further reduction in memory usage, set ``save_text=False`` (text string will not
|
||||
be stored and ``line_spacing`` and ``font`` are immutable with ``save_text``
|
||||
set to ``False``).
|
||||
For use cases where text changes are required after the initial instancing, please
|
||||
use the `label.py` library.
|
||||
For further reduction in memory usage, set save_text to False (text string will not
|
||||
be stored).
|
||||
|
||||
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
|
||||
|
|
@ -66,28 +67,28 @@ class Label(displayio.Group):
|
|||
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)
|
||||
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
|
||||
surround text
|
||||
: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 (double,double) 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.)
|
||||
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.
|
||||
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.
|
||||
label structure. Set False to reduce memory use.
|
||||
"""
|
||||
|
||||
# pylint: disable=unused-argument, too-many-instance-attributes, too-many-locals, too-many-arguments
|
||||
# pylint: disable=too-many-branches, no-self-use, too-many-statements
|
||||
# pylint: disable=too-many-branches, no-self-use
|
||||
# Note: max_glyphs parameter is unnecessary, this is used for direct
|
||||
# compatibility with label.py
|
||||
|
||||
|
|
@ -110,217 +111,128 @@ class Label(displayio.Group):
|
|||
anchor_point=None,
|
||||
anchored_position=None,
|
||||
save_text=True, # can reduce memory use if save_text = False
|
||||
scale=1,
|
||||
**kwargs,
|
||||
**kwargs
|
||||
):
|
||||
|
||||
# instance the Group
|
||||
# self Group will contain a single local_group which contains a Group (self.local_group)
|
||||
# which contains a TileGrid (self.tilegrid) which contains the text bitmap (self.bitmap)
|
||||
super().__init__(
|
||||
max_size=1, x=x, y=y, scale=1, **kwargs,
|
||||
)
|
||||
# the self group scale should always remain at 1, the self.local_group will
|
||||
# be used to set the scale
|
||||
# **kwargs will pass any additional arguments provided to the Label
|
||||
|
||||
self.local_group = displayio.Group(
|
||||
max_size=1, scale=scale
|
||||
) # local_group holds the tileGrid and sets the scaling
|
||||
self.append(
|
||||
self.local_group
|
||||
) # the local_group will always stay in the self Group
|
||||
if text == "":
|
||||
raise RuntimeError(
|
||||
"Please provide text string, or use label.py for mutable text"
|
||||
)
|
||||
|
||||
self._font = font
|
||||
self._text = text
|
||||
|
||||
# Create the two-color palette
|
||||
self.palette = displayio.Palette(2)
|
||||
self.color = color
|
||||
self.background_color = background_color
|
||||
# Scale will be passed to Group using kwargs.
|
||||
if "scale" in kwargs.keys():
|
||||
self._scale = kwargs["scale"]
|
||||
else:
|
||||
self._scale = 1
|
||||
|
||||
self._anchor_point = anchor_point
|
||||
self._anchored_position = anchored_position
|
||||
|
||||
# call the text updater with all the arguments.
|
||||
self._reset_text(
|
||||
font=font,
|
||||
x=x,
|
||||
y=y,
|
||||
text=text,
|
||||
line_spacing=line_spacing,
|
||||
background_tight=background_tight,
|
||||
padding_top=padding_top,
|
||||
padding_bottom=padding_bottom,
|
||||
padding_left=padding_left,
|
||||
padding_right=padding_right,
|
||||
anchor_point=anchor_point,
|
||||
anchored_position=anchored_position,
|
||||
save_text=save_text,
|
||||
scale=scale,
|
||||
)
|
||||
|
||||
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,
|
||||
):
|
||||
|
||||
# Store all the instance variables
|
||||
if font is not None:
|
||||
self._font = font
|
||||
if x is not None:
|
||||
self.x = x
|
||||
if y is not None:
|
||||
self.y = y
|
||||
if line_spacing is not None:
|
||||
self._line_spacing = line_spacing
|
||||
if background_tight is not None:
|
||||
self._background_tight = background_tight
|
||||
if padding_top is not None:
|
||||
self._padding_top = max(0, padding_top)
|
||||
if padding_bottom is not None:
|
||||
self._padding_bottom = max(0, padding_bottom)
|
||||
if padding_left is not None:
|
||||
self._padding_left = max(0, padding_left)
|
||||
if padding_right is not None:
|
||||
self._padding_right = max(0, padding_right)
|
||||
if anchor_point is not None:
|
||||
self.anchor_point = anchor_point
|
||||
if anchored_position is not None:
|
||||
self._anchored_position = anchored_position
|
||||
if save_text is not None:
|
||||
self._save_text = save_text
|
||||
if (
|
||||
scale is not None
|
||||
): # Scale will be defined in local_group (Note: self should have scale=1)
|
||||
self.scale = scale # call the setter
|
||||
|
||||
# if text is not provided as a parameter (text is None), use the previous value.
|
||||
if (text is None) and self._save_text:
|
||||
text = self._text
|
||||
self._line_spacing = line_spacing
|
||||
self._save_text = save_text
|
||||
|
||||
if self._save_text: # text string will be saved
|
||||
self._text = text
|
||||
else:
|
||||
self._text = None # save a None value since text string is not saved
|
||||
|
||||
# Check for empty string
|
||||
if (text == "") or (
|
||||
text is None
|
||||
): # If empty string, just create a zero-sized bounding box and that's it.
|
||||
# limit padding to >= 0
|
||||
padding_top = max(0, padding_top)
|
||||
padding_bottom = max(0, padding_bottom)
|
||||
padding_left = max(0, padding_left)
|
||||
padding_right = max(0, padding_right)
|
||||
|
||||
self._bounding_box = (
|
||||
0,
|
||||
0,
|
||||
0, # zero width with text == ""
|
||||
0, # zero height with text == ""
|
||||
# Calculate the text bounding box
|
||||
|
||||
# Calculate tight box to provide bounding box dimensions to match label for
|
||||
# anchor_position calculations
|
||||
(
|
||||
tight_box_x,
|
||||
tight_box_y,
|
||||
tight_x_offset,
|
||||
tight_y_offset,
|
||||
) = self._text_bounding_box(
|
||||
text, font, self._line_spacing, background_tight=True,
|
||||
)
|
||||
|
||||
if background_tight:
|
||||
box_x = tight_box_x
|
||||
box_y = tight_box_y
|
||||
y_offset = tight_y_offset
|
||||
x_offset = tight_x_offset
|
||||
|
||||
else:
|
||||
(box_x, box_y, x_offset, y_offset) = self._text_bounding_box(
|
||||
text, font, self._line_spacing, background_tight=background_tight,
|
||||
)
|
||||
# Clear out any items in the self.local_group Group, in case this is an
|
||||
# update to the bitmap_label
|
||||
for _ in self.local_group:
|
||||
self.local_group.pop(0)
|
||||
# Calculate the background size including padding
|
||||
box_x = box_x + padding_left + padding_right
|
||||
box_y = box_y + padding_top + padding_bottom
|
||||
|
||||
else: # The text string is not empty, so create the Bitmap and TileGrid and
|
||||
# append to the self Group
|
||||
# Create the two-color palette
|
||||
self.palette = displayio.Palette(2)
|
||||
|
||||
# Calculate the text bounding box
|
||||
self.background_color = background_color
|
||||
self.color = color
|
||||
|
||||
# Calculate both "tight" and "loose" bounding box dimensions to match label for
|
||||
# anchor_position calculations
|
||||
(
|
||||
box_x,
|
||||
tight_box_y,
|
||||
x_offset,
|
||||
tight_y_offset,
|
||||
loose_box_y,
|
||||
loose_y_offset,
|
||||
) = self._text_bounding_box(
|
||||
text, self._font, self._line_spacing,
|
||||
) # calculate the box size for a tight and loose backgrounds
|
||||
# Create the bitmap and TileGrid
|
||||
self.bitmap = displayio.Bitmap(box_x, box_y, len(self.palette))
|
||||
|
||||
if self._background_tight:
|
||||
box_y = tight_box_y
|
||||
y_offset = tight_y_offset
|
||||
# Place the text into the Bitmap
|
||||
self._place_text(
|
||||
self.bitmap,
|
||||
text,
|
||||
font,
|
||||
self._line_spacing,
|
||||
padding_left - x_offset,
|
||||
padding_top + y_offset,
|
||||
)
|
||||
|
||||
else: # calculate the box size for a loose background
|
||||
box_y = loose_box_y
|
||||
y_offset = loose_y_offset
|
||||
label_position_yoffset = int( # To calibrate with label.py positioning
|
||||
(font.get_glyph(ord("M")).height) / 2
|
||||
)
|
||||
|
||||
# Calculate the background size including padding
|
||||
box_x = box_x + self._padding_left + self._padding_right
|
||||
box_y = box_y + self._padding_top + self._padding_bottom
|
||||
self.tilegrid = displayio.TileGrid(
|
||||
self.bitmap,
|
||||
pixel_shader=self.palette,
|
||||
width=1,
|
||||
height=1,
|
||||
tile_width=box_x,
|
||||
tile_height=box_y,
|
||||
default_tile=0,
|
||||
x=-padding_left + x_offset,
|
||||
y=label_position_yoffset - y_offset - padding_top,
|
||||
)
|
||||
|
||||
# Create the bitmap and TileGrid
|
||||
self.bitmap = displayio.Bitmap(box_x, box_y, len(self.palette))
|
||||
# instance the Group
|
||||
# this Group will contain just one TileGrid with one contained bitmap
|
||||
super().__init__(
|
||||
max_size=1, x=x, y=y, **kwargs
|
||||
) # this will include any arguments, including scale
|
||||
self.append(self.tilegrid) # add the bitmap's tilegrid to the group
|
||||
|
||||
# Place the text into the Bitmap
|
||||
self._place_text(
|
||||
self.bitmap,
|
||||
text,
|
||||
self._font,
|
||||
self._line_spacing,
|
||||
self._padding_left - x_offset,
|
||||
self._padding_top + y_offset,
|
||||
)
|
||||
# Update bounding_box values. Note: To be consistent with label.py,
|
||||
# this is the bounding box for the text only, not including the background.
|
||||
|
||||
label_position_yoffset = int( # To calibrate with label.py positioning
|
||||
(self._font.get_glyph(ord("M")).height) / 2
|
||||
)
|
||||
|
||||
self.tilegrid = displayio.TileGrid(
|
||||
self.bitmap,
|
||||
pixel_shader=self.palette,
|
||||
width=1,
|
||||
height=1,
|
||||
tile_width=box_x,
|
||||
tile_height=box_y,
|
||||
default_tile=0,
|
||||
x=-self._padding_left + x_offset,
|
||||
y=label_position_yoffset - y_offset - self._padding_top,
|
||||
)
|
||||
|
||||
# Clear out any items in the local_group Group, in case this is an update to
|
||||
# the bitmap_label
|
||||
for _ in self.local_group:
|
||||
self.local_group.pop(0)
|
||||
self.local_group.append(
|
||||
self.tilegrid
|
||||
) # add the bitmap's tilegrid to the group
|
||||
|
||||
# Update bounding_box values. Note: To be consistent with label.py,
|
||||
# this is the bounding box for the text only, not including the background.
|
||||
self._bounding_box = (
|
||||
self.tilegrid.x,
|
||||
self.tilegrid.y,
|
||||
box_x,
|
||||
tight_box_y,
|
||||
)
|
||||
self._bounding_box = (
|
||||
self.tilegrid.x,
|
||||
self.tilegrid.y,
|
||||
tight_box_x,
|
||||
tight_box_y,
|
||||
)
|
||||
|
||||
self._anchored_position = anchored_position
|
||||
self.anchor_point = anchor_point
|
||||
self.anchored_position = (
|
||||
self._anchored_position
|
||||
) # set the anchored_position with setter after bitmap is created, sets the
|
||||
# x,y positions of the label
|
||||
) # sets anchored_position with setter after bitmap is created
|
||||
|
||||
@staticmethod
|
||||
def _line_spacing_ypixels(font, line_spacing):
|
||||
# Note: Scaling is provided at the Group level
|
||||
# Note: Scale is not implemented at this time, any scaling is pushed up to 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, font, line_spacing, background_tight=False):
|
||||
|
||||
# This empirical approach checks several glyphs for maximum ascender and descender height
|
||||
# (consistent with label.py)
|
||||
|
|
@ -328,7 +240,7 @@ class Label(displayio.Group):
|
|||
# descender, will depend upon font used
|
||||
|
||||
try:
|
||||
font.load_glyphs(text + glyphs)
|
||||
self._font.load_glyphs(text + glyphs)
|
||||
except AttributeError:
|
||||
# ignore if font does not have load_glyphs
|
||||
pass
|
||||
|
|
@ -351,6 +263,8 @@ class Label(displayio.Group):
|
|||
top = bottom = y_start
|
||||
|
||||
y_offset_tight = int((font.get_glyph(ord("M")).height) / 2)
|
||||
# this needs to be reviewed (also in label.py), since it doesn't respond
|
||||
# properly to the number of newlines.
|
||||
|
||||
newline = False
|
||||
|
||||
|
|
@ -391,25 +305,17 @@ class Label(displayio.Group):
|
|||
left = 0
|
||||
|
||||
final_box_width = right - left
|
||||
if background_tight:
|
||||
final_box_height = bottom - top
|
||||
final_y_offset = -top + y_offset_tight
|
||||
|
||||
final_box_height_tight = bottom - top
|
||||
final_y_offset_tight = -top + y_offset_tight
|
||||
else:
|
||||
final_box_height = (lines - 1) * self._line_spacing_ypixels(
|
||||
font, line_spacing
|
||||
) + (ascender_max + descender_max)
|
||||
final_y_offset = ascender_max
|
||||
|
||||
final_box_height_loose = (lines - 1) * self._line_spacing_ypixels(
|
||||
font, line_spacing
|
||||
) + (ascender_max + descender_max)
|
||||
final_y_offset_loose = ascender_max
|
||||
|
||||
# return (final_box_width, final_box_height, left, final_y_offset)
|
||||
|
||||
return (
|
||||
final_box_width,
|
||||
final_box_height_tight,
|
||||
left,
|
||||
final_y_offset_tight,
|
||||
final_box_height_loose,
|
||||
final_y_offset_loose,
|
||||
)
|
||||
return (final_box_width, final_box_height, left, final_y_offset)
|
||||
|
||||
# pylint: disable=too-many-nested-blocks
|
||||
def _place_text(
|
||||
|
|
@ -422,13 +328,20 @@ class Label(displayio.Group):
|
|||
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
|
||||
# when copying glyph bitmaps (this is important for slanted text
|
||||
# where rectangulary glyph boxes overlap)
|
||||
print_only_pixels=True, # print_only_pixels = True: only update the bitmap where the glyph
|
||||
# pixel color is > 0. This is especially useful for script fonts where glyph
|
||||
# bounding boxes overlap
|
||||
# Set `print_only_pixels=False` to write all pixels
|
||||
):
|
||||
# placeText - Writes text into a bitmap at the specified location.
|
||||
#
|
||||
# Note: scale is pushed up to Group level
|
||||
# Verify paletteIndex is working properly with * operator, especially
|
||||
# if accommodating multicolored fonts
|
||||
#
|
||||
# Note: Scale is not implemented at this time, is pushed up to Group level
|
||||
|
||||
bitmap_width = bitmap.width
|
||||
bitmap_height = bitmap.height
|
||||
|
||||
x_start = xposition # starting x position (left margin)
|
||||
y_start = yposition
|
||||
|
|
@ -472,111 +385,47 @@ class Label(displayio.Group):
|
|||
) # for type BuiltinFont, this creates the x-offset in the glyph bitmap.
|
||||
# for BDF loaded fonts, this should equal 0
|
||||
|
||||
self._blit(
|
||||
bitmap,
|
||||
xposition + my_glyph.dx,
|
||||
yposition - my_glyph.height - my_glyph.dy,
|
||||
my_glyph.bitmap,
|
||||
x_1=glyph_offset_x,
|
||||
y_1=0,
|
||||
x_2=glyph_offset_x + my_glyph.width,
|
||||
y_2=0 + my_glyph.height,
|
||||
skip_index=skip_index, # do not copy over any 0 background pixels
|
||||
)
|
||||
for y in range(my_glyph.height):
|
||||
for x in range(my_glyph.width):
|
||||
x_placement = x + xposition + my_glyph.dx
|
||||
y_placement = y + yposition - my_glyph.height - my_glyph.dy
|
||||
|
||||
if (bitmap_width > x_placement >= 0) and (
|
||||
bitmap_height > y_placement >= 0
|
||||
):
|
||||
|
||||
# Allows for remapping the bitmap indexes using paletteIndex
|
||||
# for background and text.
|
||||
palette_indexes = (
|
||||
background_palette_index,
|
||||
text_palette_index,
|
||||
)
|
||||
|
||||
this_pixel_color = palette_indexes[
|
||||
my_glyph.bitmap[
|
||||
y * my_glyph.bitmap.width + x + glyph_offset_x
|
||||
]
|
||||
]
|
||||
|
||||
if not print_only_pixels or this_pixel_color > 0:
|
||||
# write all characters if printOnlyPixels = False,
|
||||
# or if thisPixelColor is > 0
|
||||
bitmap[
|
||||
y_placement * bitmap_width + x_placement
|
||||
] = this_pixel_color
|
||||
elif y_placement > bitmap_height:
|
||||
break
|
||||
|
||||
xposition = xposition + my_glyph.shift_x
|
||||
|
||||
return (left, top, right - left, bottom - top) # bounding_box
|
||||
|
||||
def _blit(
|
||||
self,
|
||||
bitmap, # target bitmap
|
||||
x, # target x upper left corner
|
||||
y, # 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
|
||||
# (for example: the background color of a glyph)
|
||||
):
|
||||
|
||||
if hasattr(bitmap, "blit"): # if bitmap has a built-in blit function, call it
|
||||
# this function should perform its own input checks
|
||||
bitmap.blit(
|
||||
x,
|
||||
y,
|
||||
source_bitmap,
|
||||
x1=x_1,
|
||||
y1=y_1,
|
||||
x2=x_2,
|
||||
y2=y_2,
|
||||
skip_index=skip_index,
|
||||
)
|
||||
|
||||
else: # perform pixel by pixel copy of the bitmap
|
||||
|
||||
# Perform input checks
|
||||
|
||||
if x_2 is None:
|
||||
x_2 = source_bitmap.width
|
||||
if y_2 is None:
|
||||
y_2 = source_bitmap.height
|
||||
|
||||
# Rearrange so that x_1 < x_2 and y1 < y2
|
||||
if x_1 > x_2:
|
||||
x_1, x_2 = x_2, x_1
|
||||
if y_1 > y_2:
|
||||
y_1, y_2 = y_2, y_1
|
||||
|
||||
# Ensure that x2 and y2 are within source bitmap size
|
||||
x_2 = min(x_2, source_bitmap.width)
|
||||
y_2 = min(y_2, source_bitmap.height)
|
||||
|
||||
for y_count in range(y_2 - y_1):
|
||||
for x_count in range(x_2 - x_1):
|
||||
x_placement = x + x_count
|
||||
y_placement = y + y_count
|
||||
|
||||
if (bitmap.width > x_placement >= 0) and (
|
||||
bitmap.height > y_placement >= 0
|
||||
): # ensure placement is within target bitmap
|
||||
|
||||
# get the palette index from the source bitmap
|
||||
this_pixel_color = source_bitmap[
|
||||
y_1
|
||||
+ (
|
||||
y_count * source_bitmap.width
|
||||
) # Direct index into a bitmap array is speedier than [x,y] tuple
|
||||
+ x_1
|
||||
+ x_count
|
||||
]
|
||||
|
||||
if (skip_index is None) or (this_pixel_color != skip_index):
|
||||
bitmap[ # Direct index into a bitmap array is speedier than [x,y] tuple
|
||||
y_placement * bitmap.width + x_placement
|
||||
] = this_pixel_color
|
||||
elif y_placement > bitmap.height:
|
||||
break
|
||||
|
||||
@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 self._bounding_box
|
||||
|
||||
@property
|
||||
def scale(self):
|
||||
"""Set the scaling of the label, in integer values"""
|
||||
return self._scale
|
||||
|
||||
@scale.setter
|
||||
def scale(self, new_scale):
|
||||
self.local_group.scale = new_scale
|
||||
self._scale = new_scale
|
||||
self.anchored_position = self._anchored_position # update the anchored_position
|
||||
|
||||
@property
|
||||
def line_spacing(self):
|
||||
"""The amount of space between lines of text, in multiples of the font's
|
||||
|
|
@ -585,10 +434,9 @@ class Label(displayio.Group):
|
|||
|
||||
@line_spacing.setter
|
||||
def line_spacing(self, new_line_spacing):
|
||||
if self._save_text:
|
||||
self._reset_text(line_spacing=new_line_spacing)
|
||||
else:
|
||||
raise RuntimeError("line_spacing is immutable when save_text is False")
|
||||
raise RuntimeError(
|
||||
"line_spacing is immutable for bitmap_label.py; use label.py for mutable line_spacing"
|
||||
)
|
||||
|
||||
@property
|
||||
def color(self):
|
||||
|
|
@ -625,22 +473,22 @@ class Label(displayio.Group):
|
|||
"""Text to displayed."""
|
||||
return self._text
|
||||
|
||||
@text.setter # Cannot set color or background color with text setter, use separate setter
|
||||
@text.setter
|
||||
def text(self, new_text):
|
||||
self._reset_text(text=new_text)
|
||||
raise RuntimeError(
|
||||
"text is immutable for bitmap_label.py; use label.py library for mutable text"
|
||||
)
|
||||
|
||||
@property
|
||||
def font(self):
|
||||
"""Font to use for text display."""
|
||||
return self._font
|
||||
return self.font
|
||||
|
||||
@font.setter
|
||||
def font(self, new_font):
|
||||
self._font = new_font
|
||||
if self._save_text:
|
||||
self._reset_text(font=new_font)
|
||||
else:
|
||||
raise RuntimeError("font is immutable when save_text is False")
|
||||
raise RuntimeError(
|
||||
"font is immutable for bitmap_label.py; use label.py library for mutable font"
|
||||
)
|
||||
|
||||
@property
|
||||
def anchor_point(self):
|
||||
|
|
@ -665,15 +513,16 @@ class Label(displayio.Group):
|
|||
@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._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)
|
||||
- (self._bounding_box[1] * self._scale)
|
||||
- round(self._anchor_point[1] * self._bounding_box[3] * self._scale)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -79,25 +79,18 @@ class Label(displayio.Group):
|
|||
padding_right=0,
|
||||
anchor_point=None,
|
||||
anchored_position=None,
|
||||
scale=1,
|
||||
**kwargs
|
||||
):
|
||||
if "scale" in kwargs.keys():
|
||||
self._scale = kwargs["scale"]
|
||||
else:
|
||||
self._scale = 1
|
||||
if not max_glyphs and not text:
|
||||
raise RuntimeError("Please provide a max size, or initial text")
|
||||
if not max_glyphs:
|
||||
max_glyphs = len(text)
|
||||
# add one to max_size for the background bitmap tileGrid
|
||||
|
||||
# instance the Group
|
||||
# self Group will contain a single local_group which contains a Group (self.local_group)
|
||||
# which contains a TileGrid
|
||||
super().__init__(
|
||||
max_size=1, scale=1, **kwargs
|
||||
) # The self scale should always be 1
|
||||
self.local_group = displayio.Group(
|
||||
max_size=max_glyphs + 1, scale=scale
|
||||
) # local_group will set the scale
|
||||
self.append(self.local_group)
|
||||
super().__init__(max_size=max_glyphs + 1, **kwargs)
|
||||
|
||||
self.width = max_glyphs
|
||||
self._font = font
|
||||
|
|
@ -129,8 +122,6 @@ class Label(displayio.Group):
|
|||
self._padding_left = padding_left
|
||||
self._padding_right = padding_right
|
||||
|
||||
self._scale = scale
|
||||
|
||||
if text is not None:
|
||||
self._update_text(str(text))
|
||||
if (anchored_position is not None) and (anchor_point is not None):
|
||||
|
|
@ -147,17 +138,28 @@ class Label(displayio.Group):
|
|||
y_box_offset = self._boundingbox[1]
|
||||
|
||||
else: # draw a "loose" bounding box to include any ascenders/descenders.
|
||||
ascent, descent = self._get_ascent_descent()
|
||||
|
||||
# check a few glyphs for maximum ascender and descender height
|
||||
# Enhancement: it would be preferred to access the font "FONT_ASCENT" and
|
||||
# "FONT_DESCENT" in the imported BDF file
|
||||
glyphs = "M j'" # choose glyphs with highest ascender and lowest
|
||||
# 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)
|
||||
|
||||
box_width = self._boundingbox[2] + self._padding_left + self._padding_right
|
||||
x_box_offset = -self._padding_left
|
||||
box_height = (
|
||||
(ascent + descent)
|
||||
(ascender_max + descender_max)
|
||||
+ int((lines - 1) * self.height * self._line_spacing)
|
||||
+ self._padding_top
|
||||
+ self._padding_bottom
|
||||
)
|
||||
y_box_offset = -ascent + y_offset - self._padding_top
|
||||
y_box_offset = -ascender_max + 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
|
||||
|
|
@ -172,35 +174,12 @@ class Label(displayio.Group):
|
|||
|
||||
return tile_grid
|
||||
|
||||
def _get_ascent_descent(self):
|
||||
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]
|
||||
|
||||
def _update_background_color(self, new_color):
|
||||
|
||||
if new_color is None:
|
||||
self._background_palette.make_transparent(0)
|
||||
if self._added_background_tilegrid:
|
||||
self.local_group.pop(0)
|
||||
self.pop(0)
|
||||
self._added_background_tilegrid = False
|
||||
else:
|
||||
self._background_palette.make_opaque(0)
|
||||
|
|
@ -208,7 +187,7 @@ class Label(displayio.Group):
|
|||
self._background_color = new_color
|
||||
|
||||
lines = self._text.rstrip("\n").count("\n") + 1
|
||||
y_offset = self._get_ascent() // 2
|
||||
y_offset = int((self._font.get_glyph(ord("M")).height) / 2)
|
||||
|
||||
if not self._added_background_tilegrid: # no bitmap is in the self Group
|
||||
# add bitmap if text is present and bitmap sizes > 0 pixels
|
||||
|
|
@ -221,16 +200,10 @@ class Label(displayio.Group):
|
|||
self._boundingbox[3] + self._padding_top + self._padding_bottom > 0
|
||||
)
|
||||
):
|
||||
if (
|
||||
len(self.local_group) > 0
|
||||
): # This can be simplified in CP v6.0, when group.append(0) bug is corrected
|
||||
self.local_group.insert(
|
||||
0, self._create_background_box(lines, y_offset)
|
||||
)
|
||||
if len(self) > 0:
|
||||
self.insert(0, self._create_background_box(lines, y_offset))
|
||||
else:
|
||||
self.local_group.append(
|
||||
self._create_background_box(lines, y_offset)
|
||||
)
|
||||
self.append(self._create_background_box(lines, y_offset))
|
||||
self._added_background_tilegrid = True
|
||||
|
||||
else: # a bitmap is present in the self Group
|
||||
|
|
@ -244,9 +217,9 @@ class Label(displayio.Group):
|
|||
self._boundingbox[3] + self._padding_top + self._padding_bottom > 0
|
||||
)
|
||||
):
|
||||
self.local_group[0] = self._create_background_box(lines, y_offset)
|
||||
self[0] = self._create_background_box(lines, y_offset)
|
||||
else: # delete the existing bitmap
|
||||
self.local_group.pop(0)
|
||||
self.pop(0)
|
||||
self._added_background_tilegrid = False
|
||||
|
||||
def _update_text(
|
||||
|
|
@ -260,7 +233,13 @@ class Label(displayio.Group):
|
|||
i = 0
|
||||
tilegrid_count = i
|
||||
|
||||
y_offset = self._get_ascent() // 2
|
||||
try:
|
||||
self._font.load_glyphs(new_text + "M")
|
||||
except AttributeError:
|
||||
# ignore if font does not have load_glyphs
|
||||
pass
|
||||
|
||||
y_offset = int((self._font.get_glyph(ord("M")).height) / 2)
|
||||
|
||||
right = top = bottom = 0
|
||||
left = None
|
||||
|
|
@ -305,10 +284,10 @@ class Label(displayio.Group):
|
|||
x=position_x,
|
||||
y=position_y,
|
||||
)
|
||||
if tilegrid_count < len(self.local_group):
|
||||
self.local_group[tilegrid_count] = face
|
||||
if tilegrid_count < len(self):
|
||||
self[tilegrid_count] = face
|
||||
else:
|
||||
self.local_group.append(face)
|
||||
self.append(face)
|
||||
tilegrid_count += 1
|
||||
x += glyph.shift_x
|
||||
i += 1
|
||||
|
|
@ -317,8 +296,8 @@ class Label(displayio.Group):
|
|||
if left is None:
|
||||
left = 0
|
||||
|
||||
while len(self.local_group) > tilegrid_count: # i:
|
||||
self.local_group.pop()
|
||||
while len(self) > tilegrid_count: # i:
|
||||
self.pop()
|
||||
self._text = new_text
|
||||
self._boundingbox = (left, top, right - left, bottom - top)
|
||||
|
||||
|
|
@ -340,7 +319,6 @@ class Label(displayio.Group):
|
|||
@line_spacing.setter
|
||||
def line_spacing(self, spacing):
|
||||
self._line_spacing = spacing
|
||||
self.text = self._text # redraw the box
|
||||
|
||||
@property
|
||||
def color(self):
|
||||
|
|
@ -380,18 +358,6 @@ class Label(displayio.Group):
|
|||
except RuntimeError as run_error:
|
||||
raise RuntimeError("Text length exceeds max_glyphs") from run_error
|
||||
|
||||
@property
|
||||
def scale(self):
|
||||
"""Set the scaling of the label, in integer values"""
|
||||
return self._scale
|
||||
|
||||
@scale.setter
|
||||
def scale(self, new_scale):
|
||||
current_anchored_position = self.anchored_position
|
||||
self._scale = new_scale
|
||||
self.local_group.scale = new_scale
|
||||
self.anchored_position = current_anchored_position
|
||||
|
||||
@property
|
||||
def font(self):
|
||||
"""Font to use for text display."""
|
||||
|
|
|
|||
|
|
@ -6,6 +6,3 @@
|
|||
|
||||
.. automodule:: adafruit_display_text.label
|
||||
:members:
|
||||
|
||||
.. automodule:: adafruit_display_text.bitmap_label
|
||||
:members:
|
||||
|
|
|
|||
|
|
@ -1,68 +0,0 @@
|
|||
"""
|
||||
Basic display_text.label example script
|
||||
adapted for use on MagTag.
|
||||
"""
|
||||
import time
|
||||
import board
|
||||
import displayio
|
||||
import terminalio
|
||||
from adafruit_display_text import label
|
||||
|
||||
# use built in display (PyPortal, PyGamer, PyBadge, CLUE, etc.)
|
||||
# see guide for setting up external displays (TFT / OLED breakouts, RGB matrices, etc.)
|
||||
# https://learn.adafruit.com/circuitpython-display-support-using-displayio/display-and-display-bus
|
||||
display = board.DISPLAY
|
||||
|
||||
# wait until we can draw
|
||||
time.sleep(display.time_to_refresh)
|
||||
|
||||
# main group to hold everything
|
||||
main_group = displayio.Group()
|
||||
|
||||
# white background. Scaled to save RAM
|
||||
bg_bitmap = displayio.Bitmap(display.width // 8, display.height // 8, 1)
|
||||
bg_palette = displayio.Palette(1)
|
||||
bg_palette[0] = 0xFFFFFF
|
||||
bg_sprite = displayio.TileGrid(bg_bitmap, x=0, y=0, pixel_shader=bg_palette)
|
||||
bg_group = displayio.Group(scale=8)
|
||||
bg_group.append(bg_sprite)
|
||||
main_group.append(bg_group)
|
||||
|
||||
# first example label
|
||||
TEXT = "Hello world"
|
||||
text_area = label.Label(
|
||||
terminalio.FONT,
|
||||
text=TEXT,
|
||||
color=0xFFFFFF,
|
||||
background_color=0x666666,
|
||||
padding_top=1,
|
||||
padding_bottom=3,
|
||||
padding_right=4,
|
||||
padding_left=4,
|
||||
)
|
||||
text_area.x = 10
|
||||
text_area.y = 14
|
||||
main_group.append(text_area)
|
||||
|
||||
# second example label
|
||||
another_text = label.Label(
|
||||
terminalio.FONT,
|
||||
scale=2,
|
||||
text="MagTag display_text\nexample",
|
||||
color=0x000000,
|
||||
background_color=0x999999,
|
||||
padding_top=1,
|
||||
padding_bottom=3,
|
||||
padding_right=4,
|
||||
padding_left=4,
|
||||
)
|
||||
# centered
|
||||
another_text.anchor_point = (0.5, 0.5)
|
||||
another_text.anchored_position = (display.width // 2, display.height // 2)
|
||||
main_group.append(another_text)
|
||||
|
||||
# show the main group and refresh.
|
||||
display.show(main_group)
|
||||
display.refresh()
|
||||
while True:
|
||||
pass
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
"""
|
||||
This example shows how to create a display_text label and show it
|
||||
with a Matrix Portal
|
||||
|
||||
Requires:
|
||||
adafruit_matrixportal - https://github.com/adafruit/Adafruit_CircuitPython_MatrixPortal
|
||||
|
||||
Copy it from the current libraries bundle into the lib folder on your device.
|
||||
"""
|
||||
import terminalio
|
||||
from adafruit_matrixportal.matrix import Matrix
|
||||
from adafruit_display_text import label
|
||||
|
||||
matrix = Matrix()
|
||||
display = matrix.display
|
||||
|
||||
text = "Hello\nworld"
|
||||
text_area = label.Label(terminalio.FONT, text=text)
|
||||
text_area.x = 1
|
||||
text_area.y = 4
|
||||
display.show(text_area)
|
||||
while True:
|
||||
pass
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
"""
|
||||
This example illustrates how to use the wrap_text_to_lines
|
||||
helper function.
|
||||
"""
|
||||
import board
|
||||
import terminalio
|
||||
from adafruit_display_text import label, wrap_text_to_lines
|
||||
|
||||
# use built in display (PyPortal, PyGamer, PyBadge, CLUE, etc.)
|
||||
# see guide for setting up external displays (TFT / OLED breakouts, RGB matrices, etc.)
|
||||
# https://learn.adafruit.com/circuitpython-display-support-using-displayio/display-and-display-bus
|
||||
display = board.DISPLAY
|
||||
|
||||
text = (
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, "
|
||||
"sed do eiusmod tempor incididunt ut labore et dolore magna "
|
||||
"aliqua. Ut enim ad minim veniam, quis nostrud exercitation "
|
||||
"ullamco laboris nisi ut aliquip ex ea commodo consequat."
|
||||
)
|
||||
text = "\n".join(wrap_text_to_lines(text, 28))
|
||||
text_area = label.Label(terminalio.FONT, text=text)
|
||||
text_area.x = 10
|
||||
text_area.y = 10
|
||||
display.show(text_area)
|
||||
while True:
|
||||
pass
|
||||
Loading…
Reference in a new issue