import os import os.path import logging from tempfile import NamedTemporaryFile import sys from PIL import Image, ImageDraw, ImageFont class Config: def __init__(self, cache_timeout, max_cache_timeout, cals_file_list=".cals"): self.icons_dir = os.path.join(os.path.dirname(sys.argv[0]), "icons") self.fonts_dir = os.path.join(os.path.dirname(sys.argv[0]), "fonts") self.cache_timeout = cache_timeout self.max_cache_timeout = max_cache_timeout if os.path.exists('/usr/share/fonts/source-pro/'): self.fnt_R_path = "/usr/share/fonts/source-pro/SourceSansPro-Regular.otf" self.fnt_RI_path = "/usr/share/fonts/source-pro/SourceSansPro-It.otf" self.fnt_RB_path = "/usr/share/fonts/source-pro/SourceSansPro-Bold.otf" elif os.path.exists('/usr/share/fonts/source-sans/'): self.fnt_R_path = "/usr/share/fonts/source-sans/SourceSans3-Regular.otf" self.fnt_RI_path = "/usr/share/fonts/source-sans/SourceSans3-It.otf" self.fnt_RB_path = "/usr/share/fonts/source-sans/SourceSans3-Bold.otf" elif os.path.exists('/usr/share/fonts/libertine'): self.fnt_R_path = "/usr/share/fonts/libertine/LinBiolinum_Rah.ttf" self.fnt_RI_path = "/usr/share/fonts/libertine/LinBiolinum_RIah.ttf" self.fnt_RB_path = "/usr/share/fonts/libertine/LinBiolinum_RBah.ttf" else: self.fnt_R_path = "/usr/share/fonts/TTF/LinBiolinum_Rah.ttf" self.fnt_RI_path = "/usr/share/fonts/TTF/LinBiolinum_RIah.ttf" self.fnt_RB_path = "/usr/share/fonts/TTF/LinBiolinum_RBah.ttf" try: with open(cals_file_list) as f: self.cals = [c for c in f.read().split("\n") if len(c) > 0] except: print("No calendar found. Please create a file " + cals_file_list) self.cals = [] self.css_overlay = NamedTemporaryFile() with open(self.css_overlay.name, 'w') as f: f.write(''' {{ id }} .bound { font-size: 0.9em; } {{ id }} .gauge-background { fill: white; stroke: black; } {{ id }} .gauge path { fill-opacity: 1 !important; } {{ id }} .series .line { stroke-width: 2 !important; } {{ id }} .x .guides .line { stroke-width: 0 !important; } {{ id }} .y .guides .line { stroke: #555; stroke-width: 0.7; } {{ id }} .y .guides .line.axis { stroke: #000; stroke-width: 1; } ''') import pygal self.pygal_config = pygal.Config() self.pygal_config.css.append('file://' + self.css_overlay.name) from pygal.style import Style self.pygal_custom_style = Style( background='white', foreground='black', font_family='Source Code Pro', gauge_background="red", colors=('#000', '#777')) self.charts_opts = {'style': self.pygal_custom_style, 'show_legend': False, 'margin_right': 7, 'margin_left': 2, 'margin_bottom': 4, 'margin_top': 7} class RuleModule: def __init__(self): self.coeff = 0.92 def draw_module(self, config, width, height): image = Image.new('RGB', (width, height), 'white') draw = ImageDraw.Draw(image) draw.rectangle((int((1-self.coeff) * width), 0, int(self.coeff * width), height), fill="black") return image class AlertsModule: def __init__(self, config, alerts=[], ignore_module=[]): self._config = config self.icon_size = 50 self.alerts = alerts self.ignore_module = ignore_module def draw_alert(self, alert, width, image, draw, fnt_R, fnt_B, align, font_size): if "icon" in alert and alert["icon"] is not None: if callable(alert["icon"]): icon_img = alert["icon"](size=int(font_size*self.icon_size/16)) image.paste(icon_img, (int(self.icon_size / 2 - font_size*self.icon_size/32), align)) else: color_img = Image.new('RGB', (self.icon_size, self.icon_size), "#fff") icon_img = Image.open(os.path.join(self._config.icons_dir, alert["icon"])).resize((self.icon_size, self.icon_size)) image.paste(color_img, (0, align - 5), icon_img) if "title" in alert: draw.text( ((self.icon_size if alert["icon"] is not None else 0) - 5, align + 12), alert["title"], fill="white", anchor="ls", font=fnt_B ) if "subtitle" in alert and alert["subtitle"]: draw.text( ((self.icon_size if alert["icon"] is not None else 0) + (fnt_B.getlength(alert["title"]) if "title" in alert else 0), align + 12), alert["subtitle"], fill="white", anchor="ls", font=fnt_R ) if "title" in alert: align += fnt_B.getbbox(alert["title"])[3] align += display_longtext( draw, (self.icon_size - 5, align), alert["description"], fill="white", font=fnt_R, maxwidth=width-self.icon_size-5, maxlines=1 if "oneline" in alert and alert["oneline"] else 99, ) return align + 7 def draw_module(self, config, width, height, font_size=16): image = Image.new('RGBA', (width, height), "#000") draw = ImageDraw.Draw(image) align = 0 more_alerts = 0 if len(self.alerts) > 0: fnt_R = ImageFont.truetype(config.fnt_R_path, font_size) fnt_B = ImageFont.truetype(config.fnt_RB_path, font_size) align = 7 for alert in self.alerts: args_module = [] args_func = [] if isinstance(alert, dict) and "module" in alert: args_module = alert["args_module"] if "args_module" in alert else [] args_func = alert["args_func"] if "args_func" in alert else [] alert = alert["module"] if isinstance(alert, type): if alert.__name__ in self.ignore_module: logging.info("Skip module " + alert.__name__ + ", as requested by arguments") continue try: for alert in alert(*args_module).gen_alerts(*args_func): align = self.draw_alert(alert, width, image, draw, fnt_R, fnt_B, align, font_size) except BaseException as e: logging.exception(e) align = self.draw_alert({ "title": "Impossible de générer les alertes de " + alert.__name__, "description": type(e).__name__ + ": " + (e.message if hasattr(e, 'message') else str(e)), "icon": "weather/wi-earthquake.png", }, width, image, draw, fnt_R, fnt_B, align, font_size) else: align = self.draw_alert(alert, width, image, draw, fnt_R, fnt_B, align, font_size) if align > height: more_alerts += 1 image = image.crop((0,0,width, min(align, height))) if more_alerts > 0 and font_size > 12: return self.draw_module(config, width, height, font_size-1) elif more_alerts > 0: draw = ImageDraw.Draw(image) draw.rectangle((0, height-font_size, width, height), fill="black") draw.text( (width - 3, height), ("%d alertes supplémentaires" if more_alerts > 1 else "%d alerte supplémentaire") % more_alerts, fill="white", anchor="rb", font=fnt_B ) return image, more_alerts def display_longtext(draw, pos, text, font, anchor="lt", maxwidth=9999, maxlines=999, **kwargs): x,y = pos lines = [] mainlines = text.split("\n") for line in mainlines: words = line.split(" ") cur = "" while len(words) > 0: added = (" " if len(cur) > 0 else "") + words[0] width = font.getlength(cur + added) if width < maxwidth: words.pop(0) cur += added else: lines.append(cur) cur = words.pop(0) if len(cur) > 0: lines.append(cur) if len(lines) >= maxlines: lines = lines[0:maxlines] lines[-1] += "…" break line_height = font.getbbox("test")[3] if anchor[1] == "m": y -= line_height * len(lines) / 2 elif anchor[1] == "b": y -= line_height * len(lines) for line in lines: draw.text((x,y), line, font=font, anchor=anchor, **kwargs) y += line_height return line_height * len(lines)