diff --git a/.gitignore b/.gitignore
index 5d381cc..466070a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,8 +15,6 @@ dist/
downloads/
eggs/
.eggs/
-lib/
-lib64/
parts/
sdist/
var/
diff --git a/E_Ink_Test/code.py b/E_Ink_Test/code.py
new file mode 100644
index 0000000..7653a96
--- /dev/null
+++ b/E_Ink_Test/code.py
@@ -0,0 +1,88 @@
+import time
+import board
+import displayio
+#import fourwire
+import adafruit_ssd1680
+import busio
+import terminalio
+from adafruit_display_text import label
+
+BLACK = 0x000000
+WHITE = 0xFFFFFF
+RED = 0xFF0000
+
+FOREGROUND_COLOR = BLACK
+BACKGROUND_COLOR = WHITE
+
+# Create the display object - the third color is red (0xff0000)
+DISPLAY_WIDTH = 296
+DISPLAY_HEIGHT = 127
+
+# For 8.x.x and 9.x.x. When 8.x.x is discontinued as a stable release, change this.
+try:
+ from fourwire import FourWire
+except ImportError:
+ from displayio import FourWire
+
+displayio.release_displays()
+
+# Pin order defined based on WeAct Header order
+edp_busy = board.IO0
+epd_reset = board.IO1
+epd_dc = board.IO2
+epd_cs = board.IO3
+edp_clk = board.IO4
+edp_mosi = board.IO5
+#edp_miso =
+spi = busio.SPI(clock=edp_clk, MOSI=edp_mosi)
+
+display_bus = FourWire(
+ spi, command=epd_dc, chip_select=epd_cs, reset=epd_reset, baudrate=1000000
+)
+
+time.sleep(1)
+
+display = adafruit_ssd1680.SSD1680(
+ display_bus,
+ colstart=1,
+ width=DISPLAY_WIDTH,
+ height=DISPLAY_HEIGHT,
+ busy_pin=edp_busy,
+ highlight_color=FOREGROUND_COLOR,
+ rotation=270,
+)
+
+# Create a display group for our screen objects
+g = displayio.Group()
+
+# Set a background
+background_bitmap = displayio.Bitmap(DISPLAY_WIDTH, DISPLAY_HEIGHT, 1)
+# Map colors in a palette
+palette = displayio.Palette(1)
+palette[0] = BACKGROUND_COLOR
+
+# Create a Tilegrid with the background and put in the displayio group
+t = displayio.TileGrid(background_bitmap, pixel_shader=palette)
+g.append(t)
+
+# Draw simple text using the built-in font into a displayio group
+text_group = displayio.Group(scale=3, x=20, y=80)
+text = "Hello World2!"
+text_area = label.Label(terminalio.FONT, text=text, color=FOREGROUND_COLOR)
+text_group.append(text_area) # Add this text to the text group
+g.append(text_group)
+
+# Place the display group on the screen
+display.root_group = g
+
+# Refresh the display to have everything show on the display
+# NOTE: Do not refresh eInk displays more often than 180 seconds!
+display.refresh()
+
+time.sleep(120)
+
+while True:
+ pass
+
+
+
diff --git a/E_Ink_Test/lib/adafruit_display_text/__init__.py b/E_Ink_Test/lib/adafruit_display_text/__init__.py
new file mode 100644
index 0000000..270e25f
--- /dev/null
+++ b/E_Ink_Test/lib/adafruit_display_text/__init__.py
@@ -0,0 +1,476 @@
+# SPDX-FileCopyrightText: 2020 Tim C, 2021 Jeff Epler for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+
+"""
+`adafruit_display_text`
+=======================
+"""
+
+__version__ = "0.0.0+auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git"
+
+from displayio import Group, Palette
+
+try:
+ from typing import Optional, List, Tuple
+ from fontio import FontProtocol
+except ImportError:
+ pass
+
+
+def wrap_text_to_pixels(
+ string: str,
+ max_width: int,
+ font: Optional[FontProtocol] = None,
+ indent0: str = "",
+ indent1: str = "",
+) -> List[str]:
+ # pylint: disable=too-many-branches, too-many-locals, too-many-nested-blocks, too-many-statements
+
+ """wrap_text_to_pixels function
+ A helper that will return a list of lines with word-break wrapping.
+ Leading and trailing whitespace in your string will be removed. If
+ you wish to use leading whitespace see ``indent0`` and ``indent1``
+ parameters.
+
+ :param str string: The text to be wrapped.
+ :param int max_width: The maximum number of pixels on a line before wrapping.
+ :param font: The font to use for measuring the text.
+ :type font: ~fontio.FontProtocol
+ :param str indent0: Additional character(s) to add to the first line.
+ :param str indent1: Additional character(s) to add to all other lines.
+
+ :return: A list of the lines resulting from wrapping the
+ input text at ``max_width`` pixels size
+ :rtype: List[str]
+
+ """
+ if font is None:
+
+ def measure(text):
+ return len(text)
+
+ else:
+ if hasattr(font, "load_glyphs"):
+ font.load_glyphs(string)
+
+ def measure(text):
+ total_len = 0
+ for char in text:
+ this_glyph = font.get_glyph(ord(char))
+ if this_glyph:
+ total_len += this_glyph.shift_x
+ return total_len
+
+ lines = []
+ partial = [indent0]
+ width = measure(indent0)
+ swidth = measure(" ")
+ firstword = True
+ for line_in_input in string.split("\n"):
+ newline = True
+ for index, word in enumerate(line_in_input.split(" ")):
+ wwidth = measure(word)
+ word_parts = []
+ cur_part = ""
+
+ if wwidth > max_width:
+ for char in word:
+ if newline:
+ extraspace = 0
+ leadchar = ""
+ else:
+ extraspace = swidth
+ leadchar = " "
+ if (
+ measure("".join(partial))
+ + measure(cur_part)
+ + measure(char)
+ + measure("-")
+ + extraspace
+ > max_width
+ ):
+ if cur_part:
+ word_parts.append(
+ "".join(partial) + leadchar + cur_part + "-"
+ )
+
+ else:
+ word_parts.append("".join(partial))
+ cur_part = char
+ partial = [indent1]
+ newline = True
+ else:
+ cur_part += char
+ if cur_part:
+ word_parts.append(cur_part)
+ for line in word_parts[:-1]:
+ lines.append(line)
+ partial.append(word_parts[-1])
+ width = measure(word_parts[-1])
+ if firstword:
+ firstword = False
+ else:
+ if firstword:
+ partial.append(word)
+ firstword = False
+ width += wwidth
+ elif width + swidth + wwidth < max_width:
+ if index > 0:
+ partial.append(" ")
+ partial.append(word)
+ width += wwidth + swidth
+ else:
+ lines.append("".join(partial))
+ partial = [indent1, word]
+ width = measure(indent1) + wwidth
+ if newline:
+ newline = False
+
+ lines.append("".join(partial))
+ partial = [indent1]
+ width = measure(indent1)
+
+ return lines
+
+
+def wrap_text_to_lines(string: str, max_chars: int) -> List[str]:
+ """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: A list of lines where each line is separated based on the amount
+ of ``max_chars`` provided
+ :rtype: List[str]
+ """
+
+ 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:
+ if the_line: # add what we had stored
+ the_lines.append(the_line)
+ 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
+ elif not the_line and len(w) == max_chars:
+ the_lines.append(w)
+ else:
+ the_lines.append(the_line)
+ the_line = "" + w
+ if the_line: # Last line remaining
+ the_lines.append(the_line)
+ # Remove any blank lines
+ while not the_lines[0]:
+ del the_lines[0]
+ # Remove first space from first line:
+ if the_lines[0][0] == " ":
+ the_lines[0] = the_lines[0][1:]
+ return the_lines
+
+
+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: ~fontio.FontProtocol
+ :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.
+ :param bool verbose: print debugging information in some internal functions. Default to False
+ """
+
+ def __init__(
+ self,
+ font: FontProtocol,
+ 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",
+ verbose: bool = False,
+ **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]
+ self._verbose = verbose
+
+ 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) -> FontProtocol:
+ """Font to use for text display."""
+ return self._font
+
+ def _set_font(self, new_font: FontProtocol) -> None:
+ raise NotImplementedError("{} MUST override '_set_font'".format(type(self)))
+
+ @font.setter
+ def font(self, new_font: FontProtocol) -> 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]
+
+ @property
+ def width(self) -> int:
+ """The width of the label determined from the bounding box."""
+ return self._bounding_box[2]
+
+ @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 text if text.find("\t") < 0 else self._tab_text.join(text.split("\t"))
diff --git a/E_Ink_Test/lib/adafruit_display_text/bitmap_label.py b/E_Ink_Test/lib/adafruit_display_text/bitmap_label.py
new file mode 100644
index 0000000..ad6ea5f
--- /dev/null
+++ b/E_Ink_Test/lib/adafruit_display_text/bitmap_label.py
@@ -0,0 +1,594 @@
+# SPDX-FileCopyrightText: 2020 Kevin Matocha
+#
+# SPDX-License-Identifier: MIT
+
+"""
+`adafruit_display_text.bitmap_label`
+================================================================================
+
+Text graphics handling for CircuitPython, including text boxes
+
+
+* Author(s): Kevin Matocha
+
+Implementation Notes
+--------------------
+
+**Hardware:**
+
+**Software and Dependencies:**
+
+* Adafruit CircuitPython firmware for the supported boards:
+ https://circuitpython.org/downloads
+
+"""
+
+__version__ = "0.0.0+auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git"
+
+import displayio
+from adafruit_display_text import LabelBase
+
+try:
+ import bitmaptools
+except ImportError:
+ # We have a slower fallback for bitmaptools
+ pass
+
+try:
+ from typing import Optional, Tuple
+ from fontio import FontProtocol
+except ImportError:
+ pass
+
+
+# pylint: disable=too-many-instance-attributes
+class Label(LabelBase):
+ """A label displaying a string of text that is stored in a bitmap.
+ Note: This ``bitmap_label.py`` library utilizes a :py:class:`~displayio.Bitmap`
+ to display the text. This method is memory-conserving relative to ``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``).
+
+ 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: ~fontio.FontProtocol
+ :param str text: Text to display
+ :param int|Tuple(int, int, int) color: Color of all text in HEX or RGB
+ :param int|Tuple(int, int, int)|None 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 Tuple(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 Tuple(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 Tuple(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
+ ``UPD``-Upside Down ``UPR``-Upwards ``DWR``-Downwards. It defaults to ``LTR``
+ :param bool verbose: print debugging information in some internal functions. Default to False
+
+ """
+
+ # This maps label_direction to TileGrid's transpose_xy, flip_x, flip_y
+ _DIR_MAP = {
+ "UPR": (True, True, False),
+ "DWR": (True, False, True),
+ "UPD": (False, True, True),
+ "LTR": (False, False, False),
+ "RTL": (False, False, False),
+ }
+
+ def __init__(self, font: FontProtocol, save_text: bool = True, **kwargs) -> None:
+ self._bitmap = None
+ self._tilegrid = None
+ self._prev_label_direction = None
+
+ super().__init__(font, **kwargs)
+
+ self._save_text = save_text
+ self._text = self._replace_tabs(self._text)
+
+ # call the text updater with all the arguments.
+ self._reset_text(
+ font=font,
+ text=self._text,
+ line_spacing=self._line_spacing,
+ scale=self.scale,
+ )
+
+ def _reset_text(
+ self,
+ font: Optional[FontProtocol] = None,
+ text: Optional[str] = None,
+ line_spacing: Optional[float] = None,
+ scale: Optional[int] = None,
+ ) -> None:
+ # pylint: disable=too-many-branches, too-many-statements, too-many-locals
+
+ # Store all the instance variables
+ if font is not None:
+ self._font = font
+ if line_spacing is not None:
+ self._line_spacing = line_spacing
+
+ # if text is not provided as a parameter (text is None), use the previous value.
+ if (text is None) and self._save_text:
+ text = self._text
+
+ if self._save_text: # text string will be saved
+ self._text = self._replace_tabs(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.
+ self._bounding_box = (
+ 0,
+ 0,
+ 0, # zero width with text == ""
+ 0, # zero height with text == ""
+ )
+ # Clear out any items in the self._local_group Group, in case this is an
+ # update to the bitmap_label
+ for _ in self._local_group:
+ self._local_group.pop(0)
+
+ # Free the bitmap and tilegrid since they are removed
+ self._bitmap = None
+ self._tilegrid = None
+
+ else: # The text string is not empty, so create the Bitmap and TileGrid and
+ # append to the self Group
+
+ # Calculate the text bounding box
+
+ # 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,
+ ) # calculate the box size for a tight and loose backgrounds
+
+ if self._background_tight:
+ box_y = tight_box_y
+ y_offset = tight_y_offset
+ self._padding_left = 0
+ self._padding_right = 0
+ self._padding_top = 0
+ self._padding_bottom = 0
+
+ else: # calculate the box size for a loose background
+ box_y = loose_box_y
+ y_offset = loose_y_offset
+
+ # Calculate the background size including padding
+ tight_box_x = box_x
+ box_x = box_x + self._padding_left + self._padding_right
+ box_y = box_y + self._padding_top + self._padding_bottom
+
+ # Create the Bitmap unless it can be reused
+ new_bitmap = None
+ if (
+ self._bitmap is None
+ or self._bitmap.width != box_x
+ or self._bitmap.height != box_y
+ ):
+ new_bitmap = displayio.Bitmap(box_x, box_y, len(self._palette))
+ self._bitmap = new_bitmap
+ else:
+ self._bitmap.fill(0)
+
+ # Place the text into the Bitmap
+ self._place_text(
+ self._bitmap,
+ text if self._label_direction != "RTL" else "".join(reversed(text)),
+ self._font,
+ self._padding_left - x_offset,
+ self._padding_top + y_offset,
+ )
+
+ if self._base_alignment:
+ label_position_yoffset = 0
+ else:
+ label_position_yoffset = self._ascent // 2
+
+ # Create the TileGrid if not created bitmap unchanged
+ if self._tilegrid is None or new_bitmap:
+ 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
+
+ # Set TileGrid properties based on label_direction
+ if self._label_direction != self._prev_label_direction:
+ tg1 = self._tilegrid
+ tg1.transpose_xy, tg1.flip_x, tg1.flip_y = self._DIR_MAP[
+ self._label_direction
+ ]
+
+ # Update bounding_box values. Note: To be consistent with label.py,
+ # this is the bounding box for the text only, not including the background.
+ if self._label_direction in ("UPR", "DWR"):
+ if self._label_direction == "UPR":
+ top = self._padding_right
+ left = self._padding_top
+ if self._label_direction == "DWR":
+ top = self._padding_left
+ left = self._padding_bottom
+ self._bounding_box = (
+ self._tilegrid.x + left,
+ self._tilegrid.y + top,
+ tight_box_y,
+ tight_box_x,
+ )
+ else:
+ self._bounding_box = (
+ self._tilegrid.x + self._padding_left,
+ self._tilegrid.y + self._padding_top,
+ tight_box_x,
+ tight_box_y,
+ )
+
+ if (
+ scale is not None
+ ): # Scale will be defined in local_group (Note: self should have scale=1)
+ self.scale = scale # call the setter
+
+ # set the anchored_position with setter after bitmap is created, sets the
+ # x,y positions of the label
+ self.anchored_position = self._anchored_position
+
+ @staticmethod
+ def _line_spacing_ypixels(font: FontProtocol, line_spacing: float) -> int:
+ # Note: Scaling is provided at the Group level
+ return_value = int(line_spacing * font.get_bounding_box()[1])
+ return return_value
+
+ def _text_bounding_box(
+ self, text: str, font: FontProtocol
+ ) -> Tuple[int, int, int, int, int, int]:
+ # pylint: disable=too-many-locals
+
+ ascender_max, descender_max = self._ascent, self._descent
+
+ lines = 1
+
+ xposition = (
+ x_start
+ ) = yposition = y_start = 0 # starting x and y position (left margin)
+
+ left = None
+ right = x_start
+ top = bottom = y_start
+
+ y_offset_tight = self._ascent // 2
+
+ newlines = 0
+ line_spacing = self._line_spacing
+
+ for char in text:
+ if char == "\n": # newline
+ newlines += 1
+
+ else:
+ my_glyph = font.get_glyph(ord(char))
+
+ if my_glyph is None: # Error checking: no glyph found
+ print("Glyph not found: {}".format(repr(char)))
+ else:
+ if newlines:
+ xposition = x_start # reset to left column
+ yposition += (
+ self._line_spacing_ypixels(font, line_spacing) * newlines
+ ) # Add the newline(s)
+ lines += newlines
+ newlines = 0
+ if xposition == x_start:
+ if left is None:
+ left = 0
+ else:
+ left = min(left, my_glyph.dx)
+ xright = xposition + my_glyph.width + my_glyph.dx
+ xposition += my_glyph.shift_x
+
+ right = max(right, xposition, xright)
+
+ if yposition == y_start: # first line, find the Ascender height
+ top = min(top, -my_glyph.height - my_glyph.dy + y_offset_tight)
+ bottom = max(bottom, yposition - my_glyph.dy + y_offset_tight)
+
+ if left is None:
+ left = 0
+
+ final_box_width = right - left
+
+ final_box_height_tight = bottom - top
+ final_y_offset_tight = -top + y_offset_tight
+
+ 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,
+ )
+
+ # pylint: disable = too-many-branches
+ def _place_text(
+ self,
+ bitmap: displayio.Bitmap,
+ text: str,
+ font: FontProtocol,
+ xposition: int,
+ yposition: int,
+ skip_index: int = 0, # set to None to write all pixels, other wise skip this palette index
+ # when copying glyph bitmaps (this is important for slanted text
+ # where rectangular glyph boxes overlap)
+ ) -> Tuple[int, int, int, int]:
+ # pylint: disable=too-many-arguments, too-many-locals
+
+ # placeText - Writes text into a bitmap at the specified location.
+ #
+ # Note: scale is pushed up to Group level
+
+ x_start = xposition # starting x position (left margin)
+ y_start = yposition
+
+ left = None
+ right = x_start
+ top = bottom = y_start
+ line_spacing = self._line_spacing
+
+ for char in text:
+ if char == "\n": # newline
+ xposition = x_start # reset to left column
+ yposition = yposition + self._line_spacing_ypixels(
+ font, line_spacing
+ ) # Add a newline
+
+ else:
+ my_glyph = font.get_glyph(ord(char))
+
+ if my_glyph is None: # Error checking: no glyph found
+ print("Glyph not found: {}".format(repr(char)))
+ else:
+ if xposition == x_start:
+ if left is None:
+ left = 0
+ else:
+ left = min(left, my_glyph.dx)
+
+ right = max(
+ right,
+ xposition + my_glyph.shift_x,
+ xposition + my_glyph.width + my_glyph.dx,
+ )
+ if yposition == y_start: # first line, find the Ascender height
+ top = min(top, -my_glyph.height - my_glyph.dy)
+ bottom = max(bottom, yposition - my_glyph.dy)
+
+ glyph_offset_x = (
+ my_glyph.tile_index * my_glyph.width
+ ) # for type BuiltinFont, this creates the x-offset in the glyph bitmap.
+ # for BDF loaded fonts, this should equal 0
+
+ y_blit_target = yposition - my_glyph.height - my_glyph.dy
+
+ # Clip glyph y-direction if outside the font ascent/descent metrics.
+ # Note: bitmap.blit will automatically clip the bottom of the glyph.
+ y_clip = 0
+ if y_blit_target < 0:
+ y_clip = -y_blit_target # clip this amount from top of bitmap
+ y_blit_target = 0 # draw the clipped bitmap at y=0
+ if self._verbose:
+ print(
+ 'Warning: Glyph clipped, exceeds Ascent property: "{}"'.format(
+ char
+ )
+ )
+
+ if (y_blit_target + my_glyph.height) > bitmap.height:
+ if self._verbose:
+ print(
+ 'Warning: Glyph clipped, exceeds descent property: "{}"'.format(
+ char
+ )
+ )
+
+ self._blit(
+ bitmap,
+ max(xposition + my_glyph.dx, 0),
+ y_blit_target,
+ my_glyph.bitmap,
+ x_1=glyph_offset_x,
+ y_1=y_clip,
+ x_2=glyph_offset_x + my_glyph.width,
+ y_2=my_glyph.height,
+ skip_index=skip_index, # do not copy over any 0 background pixels
+ )
+
+ xposition = xposition + my_glyph.shift_x
+
+ # bounding_box
+ return left, top, right - left, bottom - top
+
+ def _blit(
+ self,
+ bitmap: displayio.Bitmap, # target bitmap
+ x: int, # target x upper left corner
+ y: int, # target y upper left corner
+ source_bitmap: displayio.Bitmap, # source bitmap
+ x_1: int = 0, # source x start
+ y_1: int = 0, # source y start
+ x_2: int = None, # source x end
+ y_2: int = None, # source y end
+ skip_index: int = None, # palette index that will not be copied
+ # (for example: the background color of a glyph)
+ ) -> None:
+ # pylint: disable=no-self-use, too-many-arguments
+
+ if hasattr(bitmap, "blit"): # if bitmap has a built-in blit function, call it
+ # this function should perform its own input checks
+ bitmap.blit(
+ x,
+ y,
+ source_bitmap,
+ x1=x_1,
+ y1=y_1,
+ x2=x_2,
+ y2=y_2,
+ skip_index=skip_index,
+ )
+ elif hasattr(bitmaptools, "blit"):
+ bitmaptools.blit(
+ bitmap,
+ source_bitmap,
+ x,
+ y,
+ x1=x_1,
+ y1=y_1,
+ x2=x_2,
+ y2=y_2,
+ skip_source_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
+
+ def _set_line_spacing(self, new_line_spacing: float) -> None:
+ if self._save_text:
+ self._reset_text(line_spacing=new_line_spacing, scale=self.scale)
+ else:
+ raise RuntimeError("line_spacing is immutable when save_text is False")
+
+ def _set_font(self, new_font: FontProtocol) -> None:
+ self._font = new_font
+ if self._save_text:
+ self._reset_text(font=new_font, scale=self.scale)
+ else:
+ raise RuntimeError("font is immutable when save_text is False")
+
+ def _set_text(self, new_text: str, scale: int) -> None:
+ self._reset_text(text=self._replace_tabs(new_text), scale=self.scale)
+
+ def _set_background_color(self, new_color: Optional[int]):
+ self._background_color = new_color
+ if new_color is not None:
+ self._palette[0] = new_color
+ self._palette.make_opaque(0)
+ else:
+ self._palette[0] = 0
+ self._palette.make_transparent(0)
+
+ def _set_label_direction(self, new_label_direction: str) -> None:
+ # Only make changes if new direction is different
+ # to prevent errors in the _reset_text() direction checks
+ if self._label_direction != new_label_direction:
+ self._prev_label_direction = self._label_direction
+ self._label_direction = new_label_direction
+ self._reset_text(text=str(self._text)) # Force a recalculation
+
+ def _get_valid_label_directions(self) -> Tuple[str, ...]:
+ return "LTR", "RTL", "UPD", "UPR", "DWR"
+
+ @property
+ def bitmap(self) -> displayio.Bitmap:
+ """
+ The Bitmap object that the text and background are drawn into.
+
+ :rtype: displayio.Bitmap
+ """
+ return self._bitmap
diff --git a/E_Ink_Test/lib/adafruit_display_text/label.py b/E_Ink_Test/lib/adafruit_display_text/label.py
new file mode 100644
index 0000000..775845c
--- /dev/null
+++ b/E_Ink_Test/lib/adafruit_display_text/label.py
@@ -0,0 +1,447 @@
+# SPDX-FileCopyrightText: 2019 Scott Shawcroft for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+
+"""
+`adafruit_display_text.label`
+====================================================
+
+Displays text labels using CircuitPython's displayio.
+
+* Author(s): Scott Shawcroft
+
+Implementation Notes
+--------------------
+
+**Hardware:**
+
+**Software and Dependencies:**
+
+* Adafruit CircuitPython firmware for the supported boards:
+ https://circuitpython.org/downloads
+
+"""
+
+__version__ = "0.0.0+auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git"
+
+
+from displayio import Bitmap, Palette, TileGrid
+from adafruit_display_text import LabelBase
+
+try:
+ from typing import Optional, Tuple
+ from fontio import FontProtocol
+except ImportError:
+ pass
+
+
+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: ~fontio.FontProtocol
+ :param str text: Text to display
+ :param int|Tuple(int, int, int) color: Color of all text in HEX or RGB
+ :param int|Tuple(int, int, int)|None 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 Tuple(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 Tuple(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 Tuple(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: FontProtocol, **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))
+
+ # pylint: disable=too-many-branches
+ 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 ("DWR", "UPR"):
+ box_height = (
+ self._bounding_box[3] + self._padding_right + self._padding_left
+ )
+ x_box_offset = -self._padding_left
+ box_width = (
+ (ascent + descent)
+ + int((lines - 1) * self._width * self._line_spacing)
+ + self._padding_top
+ + self._padding_bottom
+ )
+ elif self._label_direction == "TTB":
+ box_height = (
+ self._bounding_box[3] + self._padding_top + self._padding_bottom
+ )
+ x_box_offset = -self._padding_left
+ box_width = (
+ (ascent + descent)
+ + int((lines - 1) * self._height * self._line_spacing)
+ + self._padding_right
+ + self._padding_left
+ )
+ 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._label_direction == "DWR":
+ padding_to_use = self._padding_bottom
+ elif self._label_direction == "TTB":
+ padding_to_use = self._padding_top
+ y_offset = 0
+ ascent = 0
+ else:
+ padding_to_use = self._padding_top
+
+ if self._base_alignment:
+ y_box_offset = -ascent - padding_to_use
+ else:
+ y_box_offset = -ascent + y_offset - padding_to_use
+
+ 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 = y_box_offset
+ movy = -box_height - x_box_offset
+ elif self._label_direction == "DWR":
+ movx = y_box_offset
+ movy = x_box_offset
+ elif self._label_direction == "TTB":
+ movx = x_box_offset
+ movy = y_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
+
+ # pylint: enable=too-many-branches
+ 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 = 0
+ 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 = 0
+ else:
+ right = max(right, glyph.dx)
+ position_x = x - glyph.width
+
+ elif self._label_direction == "TTB":
+ if x == 0:
+ if left is None:
+ left = 0
+ 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: FontProtocol) -> 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"
diff --git a/E_Ink_Test/lib/adafruit_display_text/outlined_label.py b/E_Ink_Test/lib/adafruit_display_text/outlined_label.py
new file mode 100644
index 0000000..1c66a99
--- /dev/null
+++ b/E_Ink_Test/lib/adafruit_display_text/outlined_label.py
@@ -0,0 +1,188 @@
+# SPDX-FileCopyrightText: 2023 Tim C
+#
+# SPDX-License-Identifier: MIT
+
+"""
+`adafruit_display_text.outlined_label`
+====================================================
+
+Subclass of BitmapLabel that adds outline color and stroke size
+functionalities.
+
+* Author(s): Tim Cocks
+
+Implementation Notes
+--------------------
+
+**Hardware:**
+
+**Software and Dependencies:**
+
+* Adafruit CircuitPython firmware for the supported boards:
+ https://circuitpython.org/downloads
+
+"""
+__version__ = "0.0.0+auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git"
+
+import bitmaptools
+from displayio import Palette, Bitmap
+from adafruit_display_text import bitmap_label
+
+try:
+ from typing import Optional, Tuple, Union
+ from fontio import FontProtocol
+except ImportError:
+ pass
+
+
+class OutlinedLabel(bitmap_label.Label):
+ """
+ OutlinedLabel - A BitmapLabel subclass that includes arguments and properties for specifying
+ outline_size and outline_color to get drawn as a stroke around the text.
+
+ :param Union[Tuple, int] outline_color: The color of the outline stroke as RGB tuple, or hex.
+ :param int outline_size: The size in pixels of the outline stroke.
+
+ """
+
+ # pylint: disable=too-many-arguments
+ def __init__(
+ self,
+ font,
+ outline_color: Union[int, Tuple] = 0x999999,
+ outline_size: int = 1,
+ padding_top: Optional[int] = None,
+ padding_bottom: Optional[int] = None,
+ padding_left: Optional[int] = None,
+ padding_right: Optional[int] = None,
+ **kwargs
+ ):
+ if padding_top is None:
+ padding_top = outline_size + 0
+ if padding_bottom is None:
+ padding_bottom = outline_size + 2
+ if padding_left is None:
+ padding_left = outline_size + 0
+ if padding_right is None:
+ padding_right = outline_size + 0
+
+ super().__init__(
+ font,
+ padding_top=padding_top,
+ padding_bottom=padding_bottom,
+ padding_left=padding_left,
+ padding_right=padding_right,
+ **kwargs
+ )
+
+ _background_color = self._palette[0]
+ _foreground_color = self._palette[1]
+ _background_is_transparent = self._palette.is_transparent(0)
+ self._palette = Palette(3)
+ self._palette[0] = _background_color
+ self._palette[1] = _foreground_color
+ self._palette[2] = outline_color
+ if _background_is_transparent:
+ self._palette.make_transparent(0)
+
+ self._outline_size = outline_size
+ self._stamp_source = Bitmap((outline_size * 2) + 1, (outline_size * 2) + 1, 3)
+ self._stamp_source.fill(2)
+
+ self._bitmap = None
+
+ self._reset_text(
+ font=font,
+ text=self._text,
+ line_spacing=self._line_spacing,
+ scale=self.scale,
+ )
+
+ def _add_outline(self):
+ """
+ Blit the outline into the labels Bitmap. We will stamp self._stamp_source for each
+ pixel of the foreground color but skip the foreground color when we blit.
+ :return: None
+ """
+ if hasattr(self, "_stamp_source"):
+ for y in range(self.bitmap.height):
+ for x in range(self.bitmap.width):
+ if self.bitmap[x, y] == 1:
+ try:
+ bitmaptools.blit(
+ self.bitmap,
+ self._stamp_source,
+ x - self._outline_size,
+ y - self._outline_size,
+ skip_dest_index=1,
+ )
+ except ValueError as value_error:
+ raise ValueError(
+ "Padding must be big enough to fit outline_size "
+ "all the way around the text. "
+ "Try using either larger padding sizes, or smaller outline_size."
+ ) from value_error
+
+ def _place_text(
+ self,
+ bitmap: Bitmap,
+ text: str,
+ font: FontProtocol,
+ xposition: int,
+ yposition: int,
+ skip_index: int = 0, # set to None to write all pixels, other wise skip this palette index
+ # when copying glyph bitmaps (this is important for slanted text
+ # where rectangular glyph boxes overlap)
+ ) -> Tuple[int, int, int, int]:
+ """
+ Copy the glpyphs that represent the value of the string into the labels Bitmap.
+ :param bitmap: The bitmap to place text into
+ :param text: The text to render
+ :param font: The font to render the text in
+ :param xposition: x location of the starting point within the bitmap
+ :param yposition: y location of the starting point within the bitmap
+ :param skip_index: Color index to skip during rendering instead of covering up
+ :return Tuple bounding_box: tuple with x, y, width, height values of the bitmap
+ """
+ parent_result = super()._place_text(
+ bitmap, text, font, xposition, yposition, skip_index=skip_index
+ )
+
+ self._add_outline()
+
+ return parent_result
+
+ @property
+ def outline_color(self):
+ """Color of the outline to draw around the text."""
+ return self._palette[2]
+
+ @outline_color.setter
+ def outline_color(self, new_outline_color):
+ self._palette[2] = new_outline_color
+
+ @property
+ def outline_size(self):
+ """Stroke size of the outline to draw around the text."""
+ return self._outline_size
+
+ @outline_size.setter
+ def outline_size(self, new_outline_size):
+ self._outline_size = new_outline_size
+
+ self._padding_top = new_outline_size + 0
+ self._padding_bottom = new_outline_size + 2
+ self._padding_left = new_outline_size + 0
+ self._padding_right = new_outline_size + 0
+
+ self._stamp_source = Bitmap(
+ (new_outline_size * 2) + 1, (new_outline_size * 2) + 1, 3
+ )
+ self._stamp_source.fill(2)
+ self._reset_text(
+ font=self._font,
+ text=self._text,
+ line_spacing=self._line_spacing,
+ scale=self.scale,
+ )
diff --git a/E_Ink_Test/lib/adafruit_display_text/scrolling_label.py b/E_Ink_Test/lib/adafruit_display_text/scrolling_label.py
new file mode 100644
index 0000000..2031c5d
--- /dev/null
+++ b/E_Ink_Test/lib/adafruit_display_text/scrolling_label.py
@@ -0,0 +1,158 @@
+# SPDX-FileCopyrightText: 2019 Scott Shawcroft for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+
+"""
+`adafruit_display_text.scrolling_label`
+====================================================
+
+Displays text into a fixed-width label that scrolls leftward
+if the full_text is large enough to need it.
+
+* Author(s): Tim Cocks
+
+Implementation Notes
+--------------------
+
+**Hardware:**
+
+**Software and Dependencies:**
+
+* Adafruit CircuitPython firmware for the supported boards:
+ https://circuitpython.org/downloads
+
+"""
+
+__version__ = "0.0.0+auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git"
+
+import adafruit_ticks
+from adafruit_display_text import bitmap_label
+
+try:
+ from typing import Optional
+ from fontio import FontProtocol
+except ImportError:
+ pass
+
+
+class ScrollingLabel(bitmap_label.Label):
+ """ScrollingLabel - A fixed-width label that will scroll to the left
+ in order to show the full text if it's larger than the fixed-width.
+
+ :param font: The font to use for the label.
+ :type: ~fontio.FontProtocol
+ :param int max_characters: The number of characters that sets the fixed-width. Default is 10.
+ :param str text: The full text to show in the label. If this is longer than
+ ``max_characters`` then the label will scroll to show everything.
+ :param float animate_time: The number of seconds in between scrolling animation
+ frames. Default is 0.3 seconds.
+ :param int current_index: The index of the first visible character in the label.
+ Default is 0, the first character. Will increase while scrolling."""
+
+ # pylint: disable=too-many-arguments
+ def __init__(
+ self,
+ font: FontProtocol,
+ max_characters: int = 10,
+ text: Optional[str] = "",
+ animate_time: Optional[float] = 0.3,
+ current_index: Optional[int] = 0,
+ **kwargs
+ ) -> None:
+ super().__init__(font, **kwargs)
+ self.animate_time = animate_time
+ self._current_index = current_index
+ self._last_animate_time = -1
+ self.max_characters = max_characters
+
+ if text[-1] != " ":
+ text = "{} ".format(text)
+ self._full_text = text
+
+ self.update()
+
+ def update(self, force: bool = False) -> None:
+ """Attempt to update the display. If ``animate_time`` has elapsed since
+ previews animation frame then move the characters over by 1 index.
+ Must be called in the main loop of user code.
+
+ :param bool force: whether to ignore ``animation_time`` and force the update.
+ Default is False.
+ :return: None
+ """
+ _now = adafruit_ticks.ticks_ms()
+ if force or adafruit_ticks.ticks_less(
+ self._last_animate_time + int(self.animate_time * 1000), _now
+ ):
+ if len(self.full_text) <= self.max_characters:
+ super()._set_text(self.full_text, self.scale)
+ self._last_animate_time = _now
+ return
+
+ if self.current_index + self.max_characters <= len(self.full_text):
+ _showing_string = self.full_text[
+ self.current_index : self.current_index + self.max_characters
+ ]
+ else:
+ _showing_string_start = self.full_text[self.current_index :]
+ _showing_string_end = "{}".format(
+ self.full_text[
+ : (self.current_index + self.max_characters)
+ % len(self.full_text)
+ ]
+ )
+
+ _showing_string = "{}{}".format(
+ _showing_string_start, _showing_string_end
+ )
+ super()._set_text(_showing_string, self.scale)
+ self.current_index += 1
+ self._last_animate_time = _now
+
+ return
+
+ @property
+ def current_index(self) -> int:
+ """Index of the first visible character.
+
+ :return int: The current index
+ """
+ return self._current_index
+
+ @current_index.setter
+ def current_index(self, new_index: int) -> None:
+ if new_index < len(self.full_text):
+ self._current_index = new_index
+ else:
+ self._current_index = new_index % len(self.full_text)
+
+ @property
+ def full_text(self) -> str:
+ """The full text to be shown. If it's longer than ``max_characters`` then
+ scrolling will occur as needed.
+
+ :return str: The full text of this label.
+ """
+ return self._full_text
+
+ @full_text.setter
+ def full_text(self, new_text: str) -> None:
+ if new_text[-1] != " ":
+ new_text = "{} ".format(new_text)
+ self._full_text = new_text
+ self.current_index = 0
+ self.update()
+
+ @property
+ def text(self):
+ """The full text to be shown. If it's longer than ``max_characters`` then
+ scrolling will occur as needed.
+
+ :return str: The full text of this label.
+ """
+ return self.full_text
+
+ @text.setter
+ def text(self, new_text):
+ self.full_text = new_text
diff --git a/E_Ink_Test/lib/adafruit_framebuf.py b/E_Ink_Test/lib/adafruit_framebuf.py
new file mode 100644
index 0000000..b86c54a
--- /dev/null
+++ b/E_Ink_Test/lib/adafruit_framebuf.py
@@ -0,0 +1,639 @@
+# SPDX-FileCopyrightText: 2018 Kattni Rembor, Melissa LeBlanc-Williams
+# and Tony DiCola, for Adafruit Industries.
+# Original file created by Damien P. George
+#
+# SPDX-License-Identifier: MIT
+
+"""
+`adafruit_framebuf`
+====================================================
+
+CircuitPython pure-python framebuf module, based on the micropython framebuf module.
+
+Implementation Notes
+--------------------
+
+**Hardware:**
+
+* `Adafruit SSD1306 OLED displays `_
+* `Adafruit HT16K33 Matrix displays `_
+
+**Software and Dependencies:**
+
+* Adafruit CircuitPython firmware for the supported boards:
+ https://github.com/adafruit/circuitpython/releases
+
+"""
+
+__version__ = "0.0.0+auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_framebuf.git"
+
+import os
+import struct
+
+# Framebuf format constants:
+MVLSB = 0 # Single bit displays (like SSD1306 OLED)
+RGB565 = 1 # 16-bit color displays
+GS4_HMSB = 2 # Unimplemented!
+MHMSB = 3 # Single bit displays like the Sharp Memory
+RGB888 = 4 # Neopixels and Dotstars
+GS2_HMSB = 5 # 2-bit color displays like the HT16K33 8x8 Matrix
+
+
+class GS2HMSBFormat:
+ """GS2HMSBFormat"""
+
+ @staticmethod
+ def set_pixel(framebuf, x, y, color):
+ """Set a given pixel to a color."""
+ index = (y * framebuf.stride + x) >> 2
+ pixel = framebuf.buf[index]
+
+ shift = (x & 0b11) << 1
+ mask = 0b11 << shift
+ color = (color & 0b11) << shift
+
+ framebuf.buf[index] = color | (pixel & (~mask))
+
+ @staticmethod
+ def get_pixel(framebuf, x, y):
+ """Get the color of a given pixel"""
+ index = (y * framebuf.stride + x) >> 2
+ pixel = framebuf.buf[index]
+
+ shift = (x & 0b11) << 1
+ return (pixel >> shift) & 0b11
+
+ @staticmethod
+ def fill(framebuf, color):
+ """completely fill/clear the buffer with a color"""
+ if color:
+ bits = color & 0b11
+ fill = (bits << 6) | (bits << 4) | (bits << 2) | (bits << 0)
+ else:
+ fill = 0x00
+
+ framebuf.buf = [fill for i in range(len(framebuf.buf))]
+
+ @staticmethod
+ def rect(framebuf, x, y, width, height, color):
+ """Draw the outline of a rectangle at the given location, size and color."""
+ # pylint: disable=too-many-arguments
+ for _x in range(x, x + width):
+ for _y in range(y, y + height):
+ if _x in [x, x + width] or _y in [y, y + height]:
+ GS2HMSBFormat.set_pixel(framebuf, _x, _y, color)
+
+ @staticmethod
+ def fill_rect(framebuf, x, y, width, height, color):
+ """Draw the outline and interior of a rectangle at the given location, size and color."""
+ # pylint: disable=too-many-arguments
+ for _x in range(x, x + width):
+ for _y in range(y, y + height):
+ GS2HMSBFormat.set_pixel(framebuf, _x, _y, color)
+
+
+class MHMSBFormat:
+ """MHMSBFormat"""
+
+ @staticmethod
+ def set_pixel(framebuf, x, y, color):
+ """Set a given pixel to a color."""
+ index = (y * framebuf.stride + x) // 8
+ offset = 7 - x & 0x07
+ framebuf.buf[index] = (framebuf.buf[index] & ~(0x01 << offset)) | (
+ (color != 0) << offset
+ )
+
+ @staticmethod
+ def get_pixel(framebuf, x, y):
+ """Get the color of a given pixel"""
+ index = (y * framebuf.stride + x) // 8
+ offset = 7 - x & 0x07
+ return (framebuf.buf[index] >> offset) & 0x01
+
+ @staticmethod
+ def fill(framebuf, color):
+ """completely fill/clear the buffer with a color"""
+ if color:
+ fill = 0xFF
+ else:
+ fill = 0x00
+ for i in range(len(framebuf.buf)): # pylint: disable=consider-using-enumerate
+ framebuf.buf[i] = fill
+
+ @staticmethod
+ def fill_rect(framebuf, x, y, width, height, color):
+ """Draw a rectangle at the given location, size and color. The ``fill_rect`` method draws
+ both the outline and interior."""
+ # pylint: disable=too-many-arguments
+ for _x in range(x, x + width):
+ offset = 7 - _x & 0x07
+ for _y in range(y, y + height):
+ index = (_y * framebuf.stride + _x) // 8
+ framebuf.buf[index] = (framebuf.buf[index] & ~(0x01 << offset)) | (
+ (color != 0) << offset
+ )
+
+
+class MVLSBFormat:
+ """MVLSBFormat"""
+
+ @staticmethod
+ def set_pixel(framebuf, x, y, color):
+ """Set a given pixel to a color."""
+ index = (y >> 3) * framebuf.stride + x
+ offset = y & 0x07
+ framebuf.buf[index] = (framebuf.buf[index] & ~(0x01 << offset)) | (
+ (color != 0) << offset
+ )
+
+ @staticmethod
+ def get_pixel(framebuf, x, y):
+ """Get the color of a given pixel"""
+ index = (y >> 3) * framebuf.stride + x
+ offset = y & 0x07
+ return (framebuf.buf[index] >> offset) & 0x01
+
+ @staticmethod
+ def fill(framebuf, color):
+ """completely fill/clear the buffer with a color"""
+ if color:
+ fill = 0xFF
+ else:
+ fill = 0x00
+ for i in range(len(framebuf.buf)): # pylint: disable=consider-using-enumerate
+ framebuf.buf[i] = fill
+
+ @staticmethod
+ def fill_rect(framebuf, x, y, width, height, color):
+ """Draw a rectangle at the given location, size and color. The ``fill_rect`` method draws
+ both the outline and interior."""
+ # pylint: disable=too-many-arguments
+ while height > 0:
+ index = (y >> 3) * framebuf.stride + x
+ offset = y & 0x07
+ for w_w in range(width):
+ framebuf.buf[index + w_w] = (
+ framebuf.buf[index + w_w] & ~(0x01 << offset)
+ ) | ((color != 0) << offset)
+ y += 1
+ height -= 1
+
+
+class RGB565Format:
+ """
+ This class implements the RGB565 format
+ It assumes a little-endian byte order in the frame buffer
+ """
+
+ @staticmethod
+ def color_to_rgb565(color):
+ """Convert a color in either tuple or 24 bit integer form to RGB565,
+ and return as two bytes"""
+ if isinstance(color, tuple):
+ hibyte = (color[0] & 0xF8) | (color[1] >> 5)
+ lobyte = ((color[1] << 5) & 0xE0) | (color[2] >> 3)
+ else:
+ hibyte = ((color >> 16) & 0xF8) | ((color >> 13) & 0x07)
+ lobyte = ((color >> 5) & 0xE0) | ((color >> 3) & 0x1F)
+ return bytes([lobyte, hibyte])
+
+ def set_pixel(self, framebuf, x, y, color):
+ """Set a given pixel to a color."""
+ index = (y * framebuf.stride + x) * 2
+ framebuf.buf[index : index + 2] = self.color_to_rgb565(color)
+
+ @staticmethod
+ def get_pixel(framebuf, x, y):
+ """Get the color of a given pixel"""
+ index = (y * framebuf.stride + x) * 2
+ lobyte, hibyte = framebuf.buf[index : index + 2]
+ r = hibyte & 0xF8
+ g = ((hibyte & 0x07) << 5) | ((lobyte & 0xE0) >> 5)
+ b = (lobyte & 0x1F) << 3
+ return (r << 16) | (g << 8) | b
+
+ def fill(self, framebuf, color):
+ """completely fill/clear the buffer with a color"""
+ rgb565_color = self.color_to_rgb565(color)
+ for i in range(0, len(framebuf.buf), 2):
+ framebuf.buf[i : i + 2] = rgb565_color
+
+ def fill_rect(self, framebuf, x, y, width, height, color):
+ """Draw a rectangle at the given location, size and color. The ``fill_rect`` method draws
+ both the outline and interior."""
+ # pylint: disable=too-many-arguments
+ rgb565_color = self.color_to_rgb565(color)
+ for _y in range(2 * y, 2 * (y + height), 2):
+ offset2 = _y * framebuf.stride
+ for _x in range(2 * x, 2 * (x + width), 2):
+ index = offset2 + _x
+ framebuf.buf[index : index + 2] = rgb565_color
+
+
+class RGB888Format:
+ """RGB888Format"""
+
+ @staticmethod
+ def set_pixel(framebuf, x, y, color):
+ """Set a given pixel to a color."""
+ index = (y * framebuf.stride + x) * 3
+ if isinstance(color, tuple):
+ framebuf.buf[index : index + 3] = bytes(color)
+ else:
+ framebuf.buf[index : index + 3] = bytes(
+ ((color >> 16) & 255, (color >> 8) & 255, color & 255)
+ )
+
+ @staticmethod
+ def get_pixel(framebuf, x, y):
+ """Get the color of a given pixel"""
+ index = (y * framebuf.stride + x) * 3
+ return (
+ (framebuf.buf[index] << 16)
+ | (framebuf.buf[index + 1] << 8)
+ | framebuf.buf[index + 2]
+ )
+
+ @staticmethod
+ def fill(framebuf, color):
+ """completely fill/clear the buffer with a color"""
+ fill = (color >> 16) & 255, (color >> 8) & 255, color & 255
+ for i in range(0, len(framebuf.buf), 3):
+ framebuf.buf[i : i + 3] = bytes(fill)
+
+ @staticmethod
+ def fill_rect(framebuf, x, y, width, height, color):
+ """Draw a rectangle at the given location, size and color. The ``fill_rect`` method draws
+ both the outline and interior."""
+ # pylint: disable=too-many-arguments
+ fill = (color >> 16) & 255, (color >> 8) & 255, color & 255
+ for _x in range(x, x + width):
+ for _y in range(y, y + height):
+ index = (_y * framebuf.stride + _x) * 3
+ framebuf.buf[index : index + 3] = bytes(fill)
+
+
+class FrameBuffer:
+ """FrameBuffer object.
+
+ :param buf: An object with a buffer protocol which must be large enough to contain every
+ pixel defined by the width, height and format of the FrameBuffer.
+ :param width: The width of the FrameBuffer in pixel
+ :param height: The height of the FrameBuffer in pixel
+ :param buf_format: Specifies the type of pixel used in the FrameBuffer; permissible values
+ are listed under Constants below. These set the number of bits used to
+ encode a color value and the layout of these bits in ``buf``. Where a
+ color value c is passed to a method, c is a small integer with an encoding
+ that is dependent on the format of the FrameBuffer.
+ :param stride: The number of pixels between each horizontal line of pixels in the
+ FrameBuffer. This defaults to ``width`` but may need adjustments when
+ implementing a FrameBuffer within another larger FrameBuffer or screen. The
+ ``buf`` size must accommodate an increased step size.
+
+ """
+
+ def __init__(self, buf, width, height, buf_format=MVLSB, stride=None):
+ # pylint: disable=too-many-arguments
+ self.buf = buf
+ self.width = width
+ self.height = height
+ self.stride = stride
+ self._font = None
+ if self.stride is None:
+ self.stride = width
+ if buf_format == MVLSB:
+ self.format = MVLSBFormat()
+ elif buf_format == MHMSB:
+ self.format = MHMSBFormat()
+ elif buf_format == RGB888:
+ self.format = RGB888Format()
+ elif buf_format == RGB565:
+ self.format = RGB565Format()
+ elif buf_format == GS2_HMSB:
+ self.format = GS2HMSBFormat()
+ else:
+ raise ValueError("invalid format")
+ self._rotation = 0
+
+ @property
+ def rotation(self):
+ """The rotation setting of the display, can be one of (0, 1, 2, 3)"""
+ return self._rotation
+
+ @rotation.setter
+ def rotation(self, val):
+ if not val in (0, 1, 2, 3):
+ raise RuntimeError("Bad rotation setting")
+ self._rotation = val
+
+ def fill(self, color):
+ """Fill the entire FrameBuffer with the specified color."""
+ self.format.fill(self, color)
+
+ def fill_rect(self, x, y, width, height, color):
+ """Draw a rectangle at the given location, size and color. The ``fill_rect`` method draws
+ both the outline and interior."""
+ # pylint: disable=too-many-arguments, too-many-boolean-expressions
+ self.rect(x, y, width, height, color, fill=True)
+
+ def pixel(self, x, y, color=None):
+ """If ``color`` is not given, get the color value of the specified pixel. If ``color`` is
+ given, set the specified pixel to the given color."""
+ if self.rotation == 1:
+ x, y = y, x
+ x = self.width - x - 1
+ if self.rotation == 2:
+ x = self.width - x - 1
+ y = self.height - y - 1
+ if self.rotation == 3:
+ x, y = y, x
+ y = self.height - y - 1
+
+ if x < 0 or x >= self.width or y < 0 or y >= self.height:
+ return None
+ if color is None:
+ return self.format.get_pixel(self, x, y)
+ self.format.set_pixel(self, x, y, color)
+ return None
+
+ def hline(self, x, y, width, color):
+ """Draw a horizontal line up to a given length."""
+ self.rect(x, y, width, 1, color, fill=True)
+
+ def vline(self, x, y, height, color):
+ """Draw a vertical line up to a given length."""
+ self.rect(x, y, 1, height, color, fill=True)
+
+ def circle(self, center_x, center_y, radius, color):
+ """Draw a circle at the given midpoint location, radius and color.
+ The ```circle``` method draws only a 1 pixel outline."""
+ x = radius - 1
+ y = 0
+ d_x = 1
+ d_y = 1
+ err = d_x - (radius << 1)
+ while x >= y:
+ self.pixel(center_x + x, center_y + y, color)
+ self.pixel(center_x + y, center_y + x, color)
+ self.pixel(center_x - y, center_y + x, color)
+ self.pixel(center_x - x, center_y + y, color)
+ self.pixel(center_x - x, center_y - y, color)
+ self.pixel(center_x - y, center_y - x, color)
+ self.pixel(center_x + y, center_y - x, color)
+ self.pixel(center_x + x, center_y - y, color)
+ if err <= 0:
+ y += 1
+ err += d_y
+ d_y += 2
+ if err > 0:
+ x -= 1
+ d_x += 2
+ err += d_x - (radius << 1)
+
+ def rect(self, x, y, width, height, color, *, fill=False):
+ """Draw a rectangle at the given location, size and color. The ```rect``` method draws only
+ a 1 pixel outline."""
+ # pylint: disable=too-many-arguments
+ if self.rotation == 1:
+ x, y = y, x
+ width, height = height, width
+ x = self.width - x - width
+ if self.rotation == 2:
+ x = self.width - x - width
+ y = self.height - y - height
+ if self.rotation == 3:
+ x, y = y, x
+ width, height = height, width
+ y = self.height - y - height
+
+ # pylint: disable=too-many-boolean-expressions
+ if (
+ width < 1
+ or height < 1
+ or (x + width) <= 0
+ or (y + height) <= 0
+ or y >= self.height
+ or x >= self.width
+ ):
+ return
+ x_end = min(self.width - 1, x + width - 1)
+ y_end = min(self.height - 1, y + height - 1)
+ x = max(x, 0)
+ y = max(y, 0)
+ if fill:
+ self.format.fill_rect(self, x, y, x_end - x + 1, y_end - y + 1, color)
+ else:
+ self.format.fill_rect(self, x, y, x_end - x + 1, 1, color)
+ self.format.fill_rect(self, x, y, 1, y_end - y + 1, color)
+ self.format.fill_rect(self, x, y_end, x_end - x + 1, 1, color)
+ self.format.fill_rect(self, x_end, y, 1, y_end - y + 1, color)
+
+ def line(self, x_0, y_0, x_1, y_1, color):
+ # pylint: disable=too-many-arguments
+ """Bresenham's line algorithm"""
+ d_x = abs(x_1 - x_0)
+ d_y = abs(y_1 - y_0)
+ x, y = x_0, y_0
+ s_x = -1 if x_0 > x_1 else 1
+ s_y = -1 if y_0 > y_1 else 1
+ if d_x > d_y:
+ err = d_x / 2.0
+ while x != x_1:
+ self.pixel(x, y, color)
+ err -= d_y
+ if err < 0:
+ y += s_y
+ err += d_x
+ x += s_x
+ else:
+ err = d_y / 2.0
+ while y != y_1:
+ self.pixel(x, y, color)
+ err -= d_x
+ if err < 0:
+ x += s_x
+ err += d_y
+ y += s_y
+ self.pixel(x, y, color)
+
+ def blit(self):
+ """blit is not yet implemented"""
+ raise NotImplementedError()
+
+ def scroll(self, delta_x, delta_y):
+ """shifts framebuf in x and y direction"""
+ if delta_x < 0:
+ shift_x = 0
+ xend = self.width + delta_x
+ dt_x = 1
+ else:
+ shift_x = self.width - 1
+ xend = delta_x - 1
+ dt_x = -1
+ if delta_y < 0:
+ y = 0
+ yend = self.height + delta_y
+ dt_y = 1
+ else:
+ y = self.height - 1
+ yend = delta_y - 1
+ dt_y = -1
+ while y != yend:
+ x = shift_x
+ while x != xend:
+ self.format.set_pixel(
+ self, x, y, self.format.get_pixel(self, x - delta_x, y - delta_y)
+ )
+ x += dt_x
+ y += dt_y
+
+ # pylint: disable=too-many-arguments
+ def text(self, string, x, y, color, *, font_name="font5x8.bin", size=1):
+ """Place text on the screen in variables sizes. Breaks on \n to next line.
+
+ Does not break on line going off screen.
+ """
+ # determine our effective width/height, taking rotation into account
+ frame_width = self.width
+ frame_height = self.height
+ if self.rotation in (1, 3):
+ frame_width, frame_height = frame_height, frame_width
+
+ for chunk in string.split("\n"):
+ if not self._font or self._font.font_name != font_name:
+ # load the font!
+ self._font = BitmapFont(font_name)
+ width = self._font.font_width
+ height = self._font.font_height
+ for i, char in enumerate(chunk):
+ char_x = x + (i * (width + 1)) * size
+ if (
+ char_x + (width * size) > 0
+ and char_x < frame_width
+ and y + (height * size) > 0
+ and y < frame_height
+ ):
+ self._font.draw_char(char, char_x, y, self, color, size=size)
+ y += height * size
+
+ # pylint: enable=too-many-arguments
+
+ def image(self, img):
+ """Set buffer to value of Python Imaging Library image. The image should
+ be in 1 bit mode and a size equal to the display size."""
+ # determine our effective width/height, taking rotation into account
+ width = self.width
+ height = self.height
+ if self.rotation in (1, 3):
+ width, height = height, width
+
+ if isinstance(self.format, (RGB565Format, RGB888Format)) and img.mode != "RGB":
+ raise ValueError("Image must be in mode RGB.")
+ if isinstance(self.format, (MHMSBFormat, MVLSBFormat)) and img.mode != "1":
+ raise ValueError("Image must be in mode 1.")
+
+ imwidth, imheight = img.size
+ if imwidth != width or imheight != height:
+ raise ValueError(
+ f"Image must be same dimensions as display ({width}x{height})."
+ )
+ # Grab all the pixels from the image, faster than getpixel.
+ pixels = img.load()
+ # Clear buffer
+ for i in range(len(self.buf)): # pylint: disable=consider-using-enumerate
+ self.buf[i] = 0
+ # Iterate through the pixels
+ for x in range(width): # yes this double loop is slow,
+ for y in range(height): # but these displays are small!
+ if img.mode == "RGB":
+ self.pixel(x, y, pixels[(x, y)])
+ elif pixels[(x, y)]:
+ self.pixel(x, y, 1) # only write if pixel is true
+
+
+# MicroPython basic bitmap font renderer.
+# Author: Tony DiCola
+# License: MIT License (https://opensource.org/licenses/MIT)
+class BitmapFont:
+ """A helper class to read binary font tiles and 'seek' through them as a
+ file to display in a framebuffer. We use file access so we dont waste 1KB
+ of RAM on a font!"""
+
+ def __init__(self, font_name="font5x8.bin"):
+ # Specify the drawing area width and height, and the pixel function to
+ # call when drawing pixels (should take an x and y param at least).
+ # Optionally specify font_name to override the font file to use (default
+ # is font5x8.bin). The font format is a binary file with the following
+ # format:
+ # - 1 unsigned byte: font character width in pixels
+ # - 1 unsigned byte: font character height in pixels
+ # - x bytes: font data, in ASCII order covering all 255 characters.
+ # Each character should have a byte for each pixel column of
+ # data (i.e. a 5x8 font has 5 bytes per character).
+ self.font_name = font_name
+
+ # Open the font file and grab the character width and height values.
+ # Note that only fonts up to 8 pixels tall are currently supported.
+ try:
+ self._font = open( # pylint: disable=consider-using-with
+ self.font_name, "rb"
+ )
+ self.font_width, self.font_height = struct.unpack("BB", self._font.read(2))
+ # simple font file validation check based on expected file size
+ if 2 + 256 * self.font_width != os.stat(font_name)[6]:
+ raise RuntimeError("Invalid font file: " + font_name)
+ except OSError:
+ print("Could not find font file", font_name)
+ raise
+ except OverflowError:
+ # os.stat can throw this on boards without long int support
+ # just hope the font file is valid and press on
+ pass
+
+ def deinit(self):
+ """Close the font file as cleanup."""
+ self._font.close()
+
+ def __enter__(self):
+ """Initialize/open the font file"""
+ self.__init__()
+ return self
+
+ def __exit__(self, exception_type, exception_value, traceback):
+ """cleanup on exit"""
+ self.deinit()
+
+ def draw_char(
+ self, char, x, y, framebuffer, color, size=1
+ ): # pylint: disable=too-many-arguments
+ """Draw one character at position (x,y) to a framebuffer in a given color"""
+ size = max(size, 1)
+ # Don't draw the character if it will be clipped off the visible area.
+ # if x < -self.font_width or x >= framebuffer.width or \
+ # y < -self.font_height or y >= framebuffer.height:
+ # return
+ # Go through each column of the character.
+ for char_x in range(self.font_width):
+ # Grab the byte for the current column of font data.
+ self._font.seek(2 + (ord(char) * self.font_width) + char_x)
+ try:
+ line = struct.unpack("B", self._font.read(1))[0]
+ except RuntimeError:
+ continue # maybe character isnt there? go to next
+ # Go through each row in the column byte.
+ for char_y in range(self.font_height):
+ # Draw a pixel for each bit that's flipped on.
+ if (line >> char_y) & 0x1:
+ framebuffer.fill_rect(
+ x + char_x * size, y + char_y * size, size, size, color
+ )
+
+ def width(self, text):
+ """Return the pixel width of the specified text message."""
+ return len(text) * (self.font_width + 1)
+
+
+class FrameBuffer1(FrameBuffer): # pylint: disable=abstract-method
+ """FrameBuffer1 object. Inherits from FrameBuffer."""
diff --git a/E_Ink_Test/lib/adafruit_ssd1680.py b/E_Ink_Test/lib/adafruit_ssd1680.py
new file mode 100644
index 0000000..a534e36
--- /dev/null
+++ b/E_Ink_Test/lib/adafruit_ssd1680.py
@@ -0,0 +1,110 @@
+# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
+# SPDX-FileCopyrightText: Copyright (c) 2021 Melissa LeBlanc-Williams for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+"""
+`adafruit_ssd1680`
+================================================================================
+
+CircuitPython `displayio` driver for SSD1680-based ePaper displays
+
+
+* Author(s): Melissa LeBlanc-Williams
+
+Implementation Notes
+--------------------
+
+**Hardware:**
+
+* `Adafruit 2.13" Tri-Color eInk Display Breakout `_
+* `Adafruit 2.13" Tri-Color eInk Display FeatherWing `_
+* `Adafruit 2.13" Mono eInk Display FeatherWing `_
+
+
+**Software and Dependencies:**
+
+* Adafruit CircuitPython firmware for the supported boards:
+ https://github.com/adafruit/circuitpython/releases
+
+"""
+
+try:
+ from epaperdisplay import EPaperDisplay
+ from fourwire import FourWire
+except ImportError:
+ from displayio import EPaperDisplay
+ from displayio import FourWire
+
+
+__version__ = "0.0.0+auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_SSD1680.git"
+
+_START_SEQUENCE = (
+ b"\x12\x80\x14" # soft reset and wait 20ms
+ b"\x11\x01\x03" # Ram data entry mode
+ b"\x3C\x01\x05" # border color
+ b"\x2c\x01\x36" # Set vcom voltage
+ b"\x03\x01\x17" # Set gate voltage
+ b"\x04\x03\x41\x00\x32" # Set source voltage
+ b"\x4e\x01\x01" # ram x count
+ b"\x4f\x02\x00\x00" # ram y count
+ b"\x01\x03\x00\x00\x00" # set display size
+ b"\x22\x01\xf4" # display update mode
+)
+
+_STOP_SEQUENCE = b"\x10\x81\x01\x64" # Deep Sleep
+
+
+# pylint: disable=too-few-public-methods
+class SSD1680(EPaperDisplay):
+ r"""SSD1680 driver
+
+ :param bus: The data bus the display is on
+ :param \**kwargs:
+ See below
+
+ :Keyword Arguments:
+ * *width* (``int``) --
+ Display width
+ * *height* (``int``) --
+ Display height
+ * *rotation* (``int``) --
+ Display rotation
+ """
+
+ def __init__(self, bus: FourWire, column_correction=1,**kwargs) -> None:
+ if "colstart" not in kwargs:
+ kwargs["colstart"] = 8
+ stop_sequence = bytearray(_STOP_SEQUENCE)
+ try:
+ bus.reset()
+ except RuntimeError:
+ # No reset pin defined, so no deep sleeping
+ stop_sequence = b""
+
+ start_sequence = bytearray(_START_SEQUENCE)
+ width = kwargs["width"]
+ height = kwargs["height"]
+ if "rotation" in kwargs and kwargs["rotation"] % 180 != 90:
+ width, height = height, width
+ start_sequence[29] = (width - 1) & 0xFF
+ start_sequence[30] = ((width - 1) >> 8) & 0xFF
+
+ super().__init__(
+ bus,
+ start_sequence,
+ stop_sequence,
+ **kwargs,
+ ram_width=250,
+ ram_height=296,
+ busy_state=True,
+ write_black_ram_command=0x24,
+ write_color_ram_command=0x26,
+ set_column_window_command=0x44,
+ set_row_window_command=0x45,
+ set_current_column_command=0x4E,
+ set_current_row_command=0x4F,
+ refresh_display_command=0x20,
+ always_toggle_chip_select=False,
+ address_little_endian=True
+ )
diff --git a/E_Ink_Test/lib/display_ruler.bmp b/E_Ink_Test/lib/display_ruler.bmp
new file mode 100644
index 0000000..726b5e0
Binary files /dev/null and b/E_Ink_Test/lib/display_ruler.bmp differ
diff --git a/E_Ink_Test/lib/neopixel.py b/E_Ink_Test/lib/neopixel.py
new file mode 100644
index 0000000..8cd2a36
--- /dev/null
+++ b/E_Ink_Test/lib/neopixel.py
@@ -0,0 +1,180 @@
+# SPDX-FileCopyrightText: 2016 Damien P. George
+# SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries
+# SPDX-FileCopyrightText: 2019 Carter Nelson
+# SPDX-FileCopyrightText: 2019 Roy Hooper
+#
+# SPDX-License-Identifier: MIT
+
+"""
+`neopixel` - NeoPixel strip driver
+====================================================
+
+* Author(s): Damien P. George, Scott Shawcroft, Carter Nelson, Rose Hooper
+"""
+
+import sys
+import board
+import digitalio
+from neopixel_write import neopixel_write
+
+import adafruit_pixelbuf
+
+try:
+ # Used only for typing
+ from typing import Optional, Type
+ from types import TracebackType
+ import microcontroller
+except ImportError:
+ pass
+
+
+__version__ = "0.0.0+auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_NeoPixel.git"
+
+
+# Pixel color order constants
+RGB = "RGB"
+"""Red Green Blue"""
+GRB = "GRB"
+"""Green Red Blue"""
+RGBW = "RGBW"
+"""Red Green Blue White"""
+GRBW = "GRBW"
+"""Green Red Blue White"""
+
+
+class NeoPixel(adafruit_pixelbuf.PixelBuf):
+ """
+ A sequence of neopixels.
+
+ :param ~microcontroller.Pin pin: The pin to output neopixel data on.
+ :param int n: The number of neopixels in the chain
+ :param int bpp: Bytes per pixel. 3 for RGB and 4 for RGBW pixels.
+ :param float brightness: Brightness of the pixels between 0.0 and 1.0 where 1.0 is full
+ brightness
+ :param bool auto_write: True if the neopixels should immediately change when set. If False,
+ `show` must be called explicitly.
+ :param str pixel_order: Set the pixel color channel order. GRBW is set by default.
+
+ Example for Circuit Playground Express:
+
+ .. code-block:: python
+
+ import neopixel
+ from board import *
+
+ RED = 0x100000 # (0x10, 0, 0) also works
+
+ pixels = neopixel.NeoPixel(NEOPIXEL, 10)
+ for i in range(len(pixels)):
+ pixels[i] = RED
+
+ Example for Circuit Playground Express setting every other pixel red using a slice:
+
+ .. code-block:: python
+
+ import neopixel
+ from board import *
+ import time
+
+ RED = 0x100000 # (0x10, 0, 0) also works
+
+ # Using ``with`` ensures pixels are cleared after we're done.
+ with neopixel.NeoPixel(NEOPIXEL, 10) as pixels:
+ pixels[::2] = [RED] * (len(pixels) // 2)
+ time.sleep(2)
+
+ .. py:method:: NeoPixel.show()
+
+ Shows the new colors on the pixels themselves if they haven't already
+ been autowritten.
+
+ The colors may or may not be showing after this function returns because
+ it may be done asynchronously.
+
+ .. py:method:: NeoPixel.fill(color)
+
+ Colors all pixels the given ***color***.
+
+ .. py:attribute:: brightness
+
+ Overall brightness of the pixel (0 to 1.0)
+
+ """
+
+ def __init__(
+ self,
+ pin: microcontroller.Pin,
+ n: int,
+ *,
+ bpp: int = 3,
+ brightness: float = 1.0,
+ auto_write: bool = True,
+ pixel_order: str = None
+ ):
+ if not pixel_order:
+ pixel_order = GRB if bpp == 3 else GRBW
+ elif isinstance(pixel_order, tuple):
+ order_list = [RGBW[order] for order in pixel_order]
+ pixel_order = "".join(order_list)
+
+ self._power = None
+ if (
+ sys.implementation.version[0] >= 7
+ and getattr(board, "NEOPIXEL", None) == pin
+ ):
+ power = getattr(board, "NEOPIXEL_POWER_INVERTED", None)
+ polarity = power is None
+ if not power:
+ power = getattr(board, "NEOPIXEL_POWER", None)
+ if power:
+ try:
+ self._power = digitalio.DigitalInOut(power)
+ self._power.switch_to_output(value=polarity)
+ except ValueError:
+ pass
+
+ super().__init__(
+ n, brightness=brightness, byteorder=pixel_order, auto_write=auto_write
+ )
+
+ self.pin = digitalio.DigitalInOut(pin)
+ self.pin.direction = digitalio.Direction.OUTPUT
+
+ def deinit(self) -> None:
+ """Blank out the NeoPixels and release the pin."""
+ self.fill(0)
+ self.show()
+ self.pin.deinit()
+ if self._power:
+ self._power.deinit()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(
+ self,
+ exception_type: Optional[Type[BaseException]],
+ exception_value: Optional[BaseException],
+ traceback: Optional[TracebackType],
+ ):
+ self.deinit()
+
+ def __repr__(self):
+ return "[" + ", ".join([str(x) for x in self]) + "]"
+
+ @property
+ def n(self) -> int:
+ """
+ The number of neopixels in the chain (read-only)
+ """
+ return len(self)
+
+ def write(self) -> None:
+ """.. deprecated: 1.0.0
+
+ Use ``show`` instead. It matches Micro:Bit and Arduino APIs."""
+ self.show()
+
+ def _transmit(self, buffer: bytearray) -> None:
+ neopixel_write(self.pin, buffer)