diff --git a/inkycal/custom/functions.py b/inkycal/custom/functions.py index 327095c..0bdb419 100644 --- a/inkycal/custom/functions.py +++ b/inkycal/custom/functions.py @@ -8,17 +8,17 @@ import logging import os import time import traceback +from typing import Tuple import arrow -import PIL import requests import tzlocal from PIL import Image from PIL import ImageDraw from PIL import ImageFont -logs = logging.getLogger(__name__) -logs.setLevel(level=logging.INFO) +logger = logging.getLogger(__name__) +logger.setLevel(level=logging.INFO) # Get the path to the Inkycal folder top_level = "/".join(os.path.dirname(os.path.abspath(os.path.dirname(__file__))).split("/")[:-1]) @@ -39,7 +39,7 @@ for path, dirs, files in os.walk(fonts_location): if _.endswith(".ttf"): name = _.split(".ttf")[0] fonts[name] = os.path.join(path, _) -logs.debug(f"Found fonts: {json.dumps(fonts, indent=4, sort_keys=True)}") +logger.debug(f"Found fonts: {json.dumps(fonts, indent=4, sort_keys=True)}") available_fonts = [key for key, values in fonts.items()] @@ -77,16 +77,16 @@ def get_system_tz() -> str: >>> import arrow >>> print(arrow.now()) # returns non-timezone-aware time - >>> print(arrow.now(tz=get_system_tz()) # prints timezone aware time. + >>> print(arrow.now(tz=get_system_tz())) # prints timezone aware time. """ try: local_tz = tzlocal.get_localzone().key - logs.debug(f"Local system timezone is {local_tz}.") + logger.debug(f"Local system timezone is {local_tz}.") except: - logs.error("System timezone could not be parsed!") - logs.error("Please set timezone manually!. Falling back to UTC...") + logger.error("System timezone could not be parsed!") + logger.error("Please set timezone manually!. Falling back to UTC...") local_tz = "UTC" - logs.debug(f"The time is {arrow.now(tz=local_tz).format('YYYY-MM-DD HH:mm:ss ZZ')}.") + logger.debug(f"The time is {arrow.now(tz=local_tz).format('YYYY-MM-DD HH:mm:ss ZZ')}.") return local_tz @@ -115,7 +115,7 @@ def auto_fontsize(font, max_height): return font -def write(image, xy, box_size, text, font=None, **kwargs): +def write(image: Image, xy: Tuple[int, int], box_size: Tuple[int, int], text: str, font=None, **kwargs): """Writes text on an image. Writes given text at given position on the specified image. @@ -165,7 +165,7 @@ def write(image, xy, box_size, text, font=None, **kwargs): text_bbox = font.getbbox(text) text_width = text_bbox[2] - text_bbox[0] text_bbox_height = font.getbbox("hg") - text_height = text_bbox_height[3] - text_bbox_height[1] + text_height = abs(text_bbox_height[3]) # - abs(text_bbox_height[1]) while text_width < int(box_width * fill_width) and text_height < int(box_height * fill_height): size += 1 @@ -173,23 +173,23 @@ def write(image, xy, box_size, text, font=None, **kwargs): text_bbox = font.getbbox(text) text_width = text_bbox[2] - text_bbox[0] text_bbox_height = font.getbbox("hg") - text_height = text_bbox_height[3] - text_bbox_height[1] + text_height = abs(text_bbox_height[3]) # - abs(text_bbox_height[1]) text_bbox = font.getbbox(text) text_width = text_bbox[2] - text_bbox[0] text_bbox_height = font.getbbox("hg") - text_height = text_bbox_height[3] - text_bbox_height[1] + text_height = abs(text_bbox_height[3]) # - abs(text_bbox_height[1]) # Truncate text if text is too long, so it can fit inside the box if (text_width, text_height) > (box_width, box_height): - logs.debug(("truncating {}".format(text))) + logger.debug(("truncating {}".format(text))) while (text_width, text_height) > (box_width, box_height): text = text[0:-1] text_bbox = font.getbbox(text) text_width = text_bbox[2] - text_bbox[0] text_bbox_height = font.getbbox("hg") - text_height = text_bbox_height[3] - text_bbox_height[1] - logs.debug(text) + text_height = abs(text_bbox_height[3]) # - abs(text_bbox_height[1]) + logger.debug(text) # Align text to desired position if alignment == "center" or None: @@ -199,10 +199,13 @@ def write(image, xy, box_size, text, font=None, **kwargs): elif alignment == "right": x = int(box_width - text_width) + # Vertical centering + y = int((box_height / 2) - (text_height / 2)) + # Draw the text in the text-box draw = ImageDraw.Draw(image) space = Image.new('RGBA', (box_width, box_height)) - ImageDraw.Draw(space).text((x, 0), text, fill=colour, font=font) + ImageDraw.Draw(space).text((x, y), text, fill=colour, font=font) # Uncomment following two lines, comment out above two lines to show # red text-box with white text (debugging purposes) @@ -217,7 +220,7 @@ def write(image, xy, box_size, text, font=None, **kwargs): image.paste(space, xy, space) -def text_wrap(text, font=None, max_width=None): +def text_wrap(text: str, font=None, max_width=None): """Splits a very long text into smaller parts Splits a long text to smaller lines which can fit in a line with max_width. @@ -253,7 +256,7 @@ def text_wrap(text, font=None, max_width=None): return lines -def internet_available(): +def internet_available() -> bool: """checks if the internet is available. Attempts to connect to google.com with a timeout of 5 seconds to check @@ -278,15 +281,16 @@ def internet_available(): return False -def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1, 0.1)): +def draw_border(image: Image, xy: Tuple[int, int], size: Tuple[int, int], radius: int = 5, thickness: int = 1, + shrinkage: Tuple[int, int] = (0.1, 0.1)) -> None: """Draws a border at given coordinates. Args: - image: The image on which the border should be drawn (usually im_black or - im_colour. + im_colour). - xy: Tuple representing the top-left corner of the border e.g. (32, 100) - where 32 is the x co-ordinate and 100 is the y-coordinate. + where 32 is the x-coordinate and 100 is the y-coordinate. - size: Size of the border as a tuple -> (width, height). @@ -324,6 +328,7 @@ def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1, 0.1)): c5, c6 = ((x + width) - diameter, (y + height) - diameter), (x + width, y + height) c7, c8 = (x, (y + height) - diameter), (x + diameter, y + height) + # Draw lines and arcs, creating a square with round corners draw = ImageDraw.Draw(image) draw.line((p1, p2), fill=colour, width=thickness) @@ -338,7 +343,7 @@ def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1, 0.1)): draw.arc((c7, c8), 90, 180, fill=colour, width=thickness) -def draw_border_2(im: PIL.Image, xy: tuple, size: tuple, radius: int): +def draw_border_2(im: Image, xy: Tuple[int, int], size: Tuple[int, int], radius: int): draw = ImageDraw.Draw(im) x, y = xy diff --git a/inkycal/modules/inkycal_calendar.py b/inkycal/modules/inkycal_calendar.py index 9c85984..c17582f 100755 --- a/inkycal/modules/inkycal_calendar.py +++ b/inkycal/modules/inkycal_calendar.py @@ -6,16 +6,16 @@ Copyright by aceinnolab # pylint: disable=logging-fstring-interpolation import calendar as cal -import arrow -from inkycal.modules.template import inkycal_module + from inkycal.custom import * +from inkycal.modules.template import inkycal_module logger = logging.getLogger(__name__) class Calendar(inkycal_module): """Calendar class - Create monthly calendar and show events from given icalendars + Create monthly calendar and show events from given iCalendars """ name = "Calendar - Show monthly calendar with events from iCalendars" @@ -39,12 +39,12 @@ class Calendar(inkycal_module): }, "date_format": { "label": "Use an arrow-supported token for custom date formatting " - + "see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. D MMM", + + "see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. D MMM", "default": "D MMM", }, "time_format": { "label": "Use an arrow-supported token for custom time formatting " - + "see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. HH:mm", + + "see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. HH:mm", "default": "HH:mm", }, } @@ -61,7 +61,7 @@ class Calendar(inkycal_module): self._days_with_events = None # optional parameters - self.weekstart = config['week_starts_on'] + self.week_start = config['week_starts_on'] self.show_events = config['show_events'] self.date_format = config["date_format"] self.time_format = config['time_format'] @@ -109,7 +109,7 @@ class Calendar(inkycal_module): # Allocate space for month-names, weekdays etc. month_name_height = int(im_height * 0.10) text_bbox_height = self.font.getbbox("hg") - weekdays_height = int((text_bbox_height[3] - text_bbox_height[1])* 1.25) + weekdays_height = int((abs(text_bbox_height[3]) + abs(text_bbox_height[1])) * 1.25) logger.debug(f"month_name_height: {month_name_height}") logger.debug(f"weekdays_height: {weekdays_height}") @@ -117,7 +117,7 @@ class Calendar(inkycal_module): logger.debug("Allocating space for events") calendar_height = int(im_height * 0.6) events_height = ( - im_height - month_name_height - weekdays_height - calendar_height + im_height - month_name_height - weekdays_height - calendar_height ) logger.debug(f'calendar-section size: {im_width} x {calendar_height} px') logger.debug(f'events-section size: {im_width} x {events_height} px') @@ -156,13 +156,13 @@ class Calendar(inkycal_module): now = arrow.now(tz=self.timezone) - # Set weekstart of calendar to specified weekstart - if self.weekstart == "Monday": + # Set week-start of calendar to specified week-start + if self.week_start == "Monday": cal.setfirstweekday(cal.MONDAY) - weekstart = now.shift(days=-now.weekday()) + week_start = now.shift(days=-now.weekday()) else: cal.setfirstweekday(cal.SUNDAY) - weekstart = now.shift(days=-now.isoweekday()) + week_start = now.shift(days=-now.isoweekday()) # Write the name of current month write( @@ -174,9 +174,9 @@ class Calendar(inkycal_module): autofit=True, ) - # Set up weeknames in local language and add to main section + # Set up week-names in local language and add to main section weekday_names = [ - weekstart.shift(days=+_).format('ddd', locale=self.language) + week_start.shift(days=+_).format('ddd', locale=self.language) for _ in range(7) ] logger.debug(f'weekday names: {weekday_names}') @@ -192,7 +192,7 @@ class Calendar(inkycal_module): fill_height=0.9, ) - # Create a calendar template and flatten (remove nestings) + # Create a calendar template and flatten (remove nesting) calendar_flat = self.flatten(cal.monthcalendar(now.year, now.month)) # logger.debug(f" calendar_flat: {calendar_flat}") @@ -281,7 +281,7 @@ class Calendar(inkycal_module): month_start = arrow.get(now.floor('month')) month_end = arrow.get(now.ceil('month')) - # fetch events from given icalendars + # fetch events from given iCalendars self.ical = iCalendar() parser = self.ical @@ -294,14 +294,12 @@ class Calendar(inkycal_module): month_events = parser.get_events(month_start, month_end, self.timezone) parser.sort() self.month_events = month_events - + # Initialize days_with_events as an empty list days_with_events = [] # Handle multi-day events by adding all days between start and end for event in month_events: - start_date = event['begin'].date() - end_date = event['end'].date() # Convert start and end dates to arrow objects with timezone start = arrow.get(event['begin'].date(), tzinfo=self.timezone) @@ -325,8 +323,6 @@ class Calendar(inkycal_module): grid[days], (icon_width, icon_height), radius=6, - thickness=1, - shrinkage=(0.4, 0.2), ) # Filter upcoming events until 4 weeks in the future @@ -345,13 +341,13 @@ class Calendar(inkycal_module): date_width = int(max(( self.font.getlength(events['begin'].format(self.date_format, locale=lang)) - for events in upcoming_events))* 1.1 - ) + for events in upcoming_events)) * 1.1 + ) time_width = int(max(( self.font.getlength(events['begin'].format(self.time_format, locale=lang)) - for events in upcoming_events))* 1.1 - ) + for events in upcoming_events)) * 1.1 + ) text_bbox_height = self.font.getbbox("hg") line_height = text_bbox_height[3] + line_spacing @@ -369,7 +365,8 @@ class Calendar(inkycal_module): event_duration = (event['end'] - event['begin']).days if event_duration > 1: # Format the duration using Arrow's localization - days_translation = arrow.get().shift(days=event_duration).humanize(only_distance=True, locale=lang) + days_translation = arrow.get().shift(days=event_duration).humanize(only_distance=True, + locale=lang) the_name = f"{event['title']} ({days_translation})" else: the_name = event['title'] diff --git a/tests/test_functions.py b/tests/test_functions.py index b624978..0d8a6dd 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -1,12 +1,22 @@ """ Test the functions in the functions module. """ +import unittest + from PIL import Image, ImageFont -from inkycal.custom import write, fonts + +from inkycal.custom import write, fonts, get_system_tz -def test_write(): - im = Image.new("RGB", (500, 200), "white") - font = ImageFont.truetype(fonts['NotoSans-SemiCondensed'], size = 40) - write(im, (125,75), (250, 50), "Hello World", font) - # im.show() +class TestIcalendar(unittest.TestCase): + + def test_write(self): + im = Image.new("RGB", (500, 200), "white") + font = ImageFont.truetype(fonts['NotoSans-SemiCondensed'], size=40) + write(im, (125, 75), (250, 50), "Hello World", font) + # im.show() + + def test_get_system_tz(self): + tz = get_system_tz() + assert isinstance(tz, str) + diff --git a/tests/test_inkycal_calendar.py b/tests/test_inkycal_calendar.py index cb28b9a..434d5d8 100755 --- a/tests/test_inkycal_calendar.py +++ b/tests/test_inkycal_calendar.py @@ -20,7 +20,7 @@ tests = [ { "name": "Calendar", "config": { - "size": [500, 500], + "size": [500, 600], "week_starts_on": "Monday", "show_events": True, "ical_urls": sample_url,