#! /usr/bin/env python3 import argparse import io from PIL import Image, ImageDraw, ImageFont import csv import datetime as dt from dateutil import relativedelta from cairosvg import svg2png CURRENT_TIME = dt.date.today() NEXT_MONTH = CURRENT_TIME + relativedelta.relativedelta(months=1, day=1) DAYS_OF_WEEK_SR = ("PON", "UTO", "SRE", "ČET", "PET", "SUB", "NED") DAYS_OF_WEEK_EN = ("MON", "TUE", "WED", "THU", "FRI", "SAT", "SUn") MONTHS_SR = ("Januar", "Februar", "Mart", "April", "Maj", "Jun", "Jul", "Avgust", "Septembar", "Oktobar", "Novembar", "Decembar") MONTHS_EN = ("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December") HEADER_SR = "Plan za {}" SUBHEADER_SR = """ Ponedeljak u 19h u XECUT Jovana Ćirilova 15 Utorak u 18h u JAG3 Vatroslava Jagića 5 Svi dogadjaji su uvek besplatni """ HEADER_EN = "Plan for {}" SUBHEADER_EN = """ Mondays at 19h at XECUT Jovana Ćirilova 15 Tuesdays at 18h at JAG3 Vatroslava Jagića 5 All events are always free """ def parseArgs(parser): """ Parse all arguments and return the list of argument values """ parser.add_argument("month", metavar="MM", help="two digit number representing the month for which to generate poster", default="empty", nargs="?") return parser.parse_args() def load_events(csv_path: str, month: int) -> list[dict]: monthafter = month + relativedelta.relativedelta(months=1, day=1) events = [] with open(csv_path) as csv_file: csv_reader = csv.reader(csv_file) next(csv_reader, None) for event in csv_reader: event_date = event[0] event_date_parsed = dt.datetime.strptime( event_date, "%d-%m-%Y").date() event_time = event[1] event_title = event[3] event_title_en = event[3] if len(event) > 6: event_title_en = event[6] current_event = {"date": event_date_parsed, "time": event_time, "title": event_title.strip(), "title_en": event_title_en.strip()} if event_date_parsed >= month and event_date_parsed < monthafter: events.append(current_event) return events def drawMesh(draw, img, fg, bg, font, W, H): def drawCircle(x, y): r = 50 draw.ellipse((x - r, y - r, x + r, y+r), fill=fg, outline=(0, 0, 0), width=0) LCX = 415 # logo center x LCY = 4350 # logo center y d = 190 # delta drawCircle(LCX - d, LCY) drawCircle(LCX, LCY) drawCircle(LCX, LCY - d) drawCircle(LCX, LCY + d) drawCircle(LCX + d, LCY) draw.line([(LCX - d, LCY), (LCX + d, LCY)], fill=fg, width=20, joint=None) draw.line([(LCX, LCY), (LCX, LCY + d), (LCX + d, LCY), (LCX, LCY - d)], fill=fg, width=20, joint=None) draw.text((LCX - 1.7*d, LCY + 1.5*d), "dmz.rs", font=font, fill=fg) mesh_svg = svg2png(url='site/img/mesh-light.svg') mesh_svg_bytes = io.BytesIO(mesh_svg) mesh_img = Image.open(mesh_svg_bytes) if bg == (0, 0, 0): pixdata = mesh_img.load() for y in range(mesh_img.size[1]): for x in range(mesh_img.size[0]): if pixdata[x, y] != (0, 0, 0, 0): pixdata[x, y] = (0, 100, 0, 255) mesh_img = mesh_img.resize((W, H)) mesh_img.thumbnail((W, H), Image.Resampling.LANCZOS) mesh_w, mesh_h = mesh_img.size mesh_position = (W - mesh_w, H - mesh_h) img.paste(mesh_img, mesh_position, mesh_img) def drawPoster(events, bg, fg, month: int, en: bool): fontFacade = ImageFont.truetype('./site/font/Facade-Sud.woff', size=365) fontIosevka = ImageFont.truetype( './site/font/iosevka-regular.woff', size=200) fontIosevkaSmall = ImageFont.truetype( './site/font/iosevka-regular.woff', size=150) W = 3508 H = 4960 img = Image.new('RGB', (W, H), bg) draw = ImageDraw.Draw(img) drawMesh(draw, img, fg, bg, fontIosevka, W, H) title = "DECENTRALA" _, _, w, _ = draw.textbbox((0, 0), title, font=fontFacade) draw.text(((W-w)/2, 165), title, font=fontFacade, fill=fg) header = HEADER_EN if en else HEADER_SR months = MONTHS_EN if en else MONTHS_SR header = header.format(months[month.month - 1]) _, _, w, _ = draw.textbbox((0, 0), header, font=fontIosevka) draw.text(((W-w)/2, 560), header, font=fontIosevka, fill=fg) height = 890 sub_header = SUBHEADER_EN if en else SUBHEADER_SR draw.text((165, height), sub_header, font=fontIosevkaSmall, fill=fg) height += 800 # Write list of events to sperate text file as well textfile = open(f"poster{"_en" if en else ""}.txt", "w") sub_header = SUBHEADER_EN if en else SUBHEADER_SR textfile.write(sub_header.format(months[month.month - 1])) days_of_week = DAYS_OF_WEEK_EN if en else DAYS_OF_WEEK_SR # Loop to write events both to poster image and text file for event in events: # Add event to image poster date = days_of_week[event["date"].weekday()] day = event["date"].day title = event["title_en"] if en else event["title"] pad = " " if event["date"].day < 10 else "" eventText = f"{date} {day}. {pad}{title}" draw.text((165, height), eventText, font=fontIosevkaSmall, fill=fg) height += 200 # Add event to textfile textfile.write(eventText + "\n") textfile.close() return img def main(): # Parse arguments parser = argparse.ArgumentParser( description="Generate images of the poster") args = parseArgs(parser) # Set month based on user input month = NEXT_MONTH if args.month.isdigit(): month = dt.date(CURRENT_TIME.year, int(args.month), 1) elif args.month != "empty": print("Month has to be specified as a number. I will use next month as the default") # Load events and draw a poseter events = load_events("dogadjaji.csv", month) img = drawPoster(events, (0, 0, 0), (20, 250, 50), month, False) img.save('poster_dark.png') img = drawPoster(events, (255, 255, 255), (0, 0, 0), month, False) img.save('poster_light.png') img = drawPoster(events, (0, 0, 0), (20, 250, 50), month, True) img.save('poster_dark_en.png') img = drawPoster(events, (255, 255, 255), (0, 0, 0), month, True) img.save('poster_light_en.png') if __name__ == "__main__": main()