Skip to content

Commit

Permalink
Merge pull request #345 from aceinnolab/hotfix/#326
Browse files Browse the repository at this point in the history
fix an issue where the text would not be vertically centered
  • Loading branch information
aceisace committed Jun 22, 2024
2 parents e6ebbb2 + acc4fb7 commit 90948d2
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 56 deletions.
51 changes: 28 additions & 23 deletions inkycal/custom/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@
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])
Expand All @@ -39,7 +39,7 @@
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()]


Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -165,31 +165,31 @@ 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
font = ImageFont.truetype(font.path, size)
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:
Expand All @@ -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)
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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).
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
49 changes: 23 additions & 26 deletions inkycal/modules/inkycal_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
# 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"
Expand All @@ -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",
},
}
Expand All @@ -61,7 +61,7 @@ def __init__(self, config):
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']
Expand Down Expand Up @@ -109,15 +109,15 @@ def generate_image(self):
# 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}")

if self.show_events:
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')
Expand Down Expand Up @@ -156,13 +156,13 @@ def generate_image(self):

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(
Expand All @@ -174,9 +174,9 @@ def generate_image(self):
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}')
Expand All @@ -192,7 +192,7 @@ def generate_image(self):
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}")

Expand Down Expand Up @@ -281,7 +281,7 @@ def generate_image(self):
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

Expand All @@ -294,14 +294,12 @@ def generate_image(self):
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)
Expand All @@ -325,8 +323,6 @@ def generate_image(self):
grid[days],
(icon_width, icon_height),
radius=6,
thickness=1,
shrinkage=(0.4, 0.2),
)

# Filter upcoming events until 4 weeks in the future
Expand All @@ -345,13 +341,13 @@ def generate_image(self):

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
Expand All @@ -369,7 +365,8 @@ def generate_image(self):
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']
Expand Down
22 changes: 16 additions & 6 deletions tests/test_functions.py
Original file line number Diff line number Diff line change
@@ -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


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)

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()
2 changes: 1 addition & 1 deletion tests/test_inkycal_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
{
"name": "Calendar",
"config": {
"size": [500, 500],
"size": [500, 600],
"week_starts_on": "Monday",
"show_events": True,
"ical_urls": sample_url,
Expand Down

0 comments on commit 90948d2

Please sign in to comment.