#! /usr/bin/python ''' Copyright (C) 2017 Artem Synytsyn a.synytsyn@gmail.com #TODO: Code cleaning and refactoring This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ''' import inkex import sys from lxml import etree from math import * import re from typing import Any import wikipedia import openpyxl import math, decimal, datetime dec = decimal.Decimal ## __ ___ _ _ ____ _ _ ## \ \ / (_) | _(_) ___| __ _(_)_ __ | |_ ___ ## \ \ /\ / /| | |/ / \___ \ / _` | | '_ \| __/ __| ## \ V V / | | <| |___) | (_| | | | | | |_\__ \ ## \_/\_/ |_|_|\_\_|____/ \__,_|_|_| |_|\__|___/ ## class WikiSaints: def __init__(self, get_from_wiki=False): if get_from_wiki: self.saints_per_month = self.get_data() else: self.saints_per_month = {} self.it_month_name = [ "Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"] def get_data(self): wikipedia.set_lang("it") lines = wikipedia.page('Calendario_dei_santi').content.split('\n') # print(lines) reg = re.compile(r'(==)*(==)') cat = {} month_name = self.it_month_name month_strg = [ "== "+m+" ==" for m in month_name] d = None for n,l in enumerate(lines): if reg.search(l): cat[l] = [] d = cat[l] elif d is not None: d.append(l) clean_cat = {} for m in month_name: clean_cat[m] = cat[f"== {m} =="] return clean_cat def save(self, file_name): wb = openpyxl.Workbook(file_name) for k,data in self.saints_per_month.items(): ws = wb.create_sheet(k) # add column headings. NB. these must be strings ws.append(["Giorno", "Santo", "altri santi"]) for i,s in enumerate(data): try: g = s.split(":")[0] S = s.split(":")[1].split(";")[0] o = s.split(":")[1].split(";")[1] except: g = S = o = "" ws.append([i+1, S, o]) wb.save(file_name) def open(self, file_name): wb = openpyxl.open(file_name) for ws in wb.worksheets: self.saints_per_month[ws.title] = [] for i,row in enumerate(ws.rows): if i==0: continue # skip header elements = [ el.value for el in row][1:] if len(elements) and elements[0] is not None: self.saints_per_month[ws.title].append(elements) def print_month(self, month: str): try: m = self.saints_per_month[month] for i,el in enumerate(m): print(i+1, el) except: pass def get_saint(self, month : str, day: int): try: return self.saints_per_month[month][day-1] except: return None def get_date(self, day : int, month : str, year : int = None) -> str : if year is None: year = datetime.date.today().year month = self.it_month_name.index(month) return datetime.date(year, month+1, day) def get_weekday(self, date) -> str : wday = date.weekday() it_wday = [ 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato', 'Domenica' ] it_wday_short = ['Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab', 'Dom'] return it_wday[wday] def get_moon_position(self, date=None): if date is None: date = datetime.date.today() diff = date - datetime.date(2001, 1, 1) days = dec(diff.days) + (dec(diff.seconds) / dec(86400)) lunations = dec("0.20439731") + (days * dec("0.03386319269")) return lunations % dec(1) def get_moon_phase(self, date=None, phases=4): index = ((self.get_moon_position(date)+dec(0.26))%dec(1) * dec(phases))# + dec("0.5") if index - math.floor(index) < phases/30: index = math.floor(index) return int(index) else: return None def _get_easter(self, year): """ Use easter date algorithm to calculate the day of easter for a given year. If the year given is in a special range(special_years) then subtract 7 from the final date. return easter as offset from March 1 -> goes in April if day > 31 """ special_years = ['1954', '1981', '2049', '2076'] specyr_sub = 7 a = year % 19 b = year % 4 c = year % 7 d = (19 * a + 24) % 30 e = ((2 * b) + (4 * c) + (6 * d) + 5) % 7 if year in special_years: dateofeaster = (22 + d + e) - specyr_sub else: dateofeaster = 22 + d + e return dateofeaster def get_mobile_fest(self, date): """ ### https://it.cathopedia.org/wiki/Calcolo_delle_feste_mobili_legate_alla_Pasqua """ mobile_fests = { 'easter' : "Resurrezione del Signore", 'mc' : "Mercoledì delle Ceneri", # -46 'dc' : "Domenica delle Palme", # -7 'as' : "Ascensione di nostro Signore", # 42 'cd' : "Solennità del Corpus Domini", # 63 (dal 1978) 'cr' : "Festa del Ctristo Re", # prima di avvento ( metti a mano ) } # easter eday = self._get_easter(date.year) emonth = 3 if eday > 31: eday -= 31 emonth = 4 easter = datetime.date(date.year, emonth, eday) if date == easter: return mobile_fests['easter'] if date == easter - datetime.timedelta(days=46): return mobile_fests['mc'] # mercoledi delle ceneri if date == easter - datetime.timedelta(days=7): return mobile_fests['dc'] # domenica delle palme if date == easter + datetime.timedelta(days=42): return mobile_fests['as'] # ascensione if date == easter + datetime.timedelta(days=63): return mobile_fests['cd'] # corpus domini # AVVENTO ( da rifare ) # xm = datetime.date(date.year, 12, 25) # a4 = xm - datetime.timedelta(days=7) # a3 = a4 - datetime.timedelta(days=7) # a2 = a3 - datetime.timedelta(days=7) # a1 = a2 - datetime.timedelta(days=7) # if date == a1: return mobile_fests['a1'] # if date == a2: return mobile_fests['a2'] # if date == a3: return mobile_fests['a3'] # if date == a4: return mobile_fests['a4'] return None ## ____ _ _ _ ____ _ _ ## / ___|__ _| | ___ _ __ __| | __ _ _ __(_) ___/ ___| __ _ _ __ | |_(_) ## | | / _` | |/ _ \ '_ \ / _` |/ _` | '__| |/ _ \___ \ / _` | '_ \| __| | ## | |__| (_| | | __/ | | | (_| | (_| | | | | (_) |__) | (_| | | | | |_| | ## \____\__,_|_|\___|_| |_|\__,_|\__,_|_| |_|\___/____/ \__,_|_| |_|\__|_| ## class _Spacing(): size = 0. padding = 0. margin_top = 0. margin_bottom = 0. margin_left = 0. margin_right = 0. class CalendarioSanti(inkex.Effect): def __init__(self): inkex.Effect.__init__(self) # General settings self.arg_parser.add_argument("--x", type=float, dest="x", default=0.0, help="Center X") self.arg_parser.add_argument("--y", type=float, dest="y", default=0.0, help="Center Y") self.arg_parser.add_argument("--columnwidth", type=float, dest="columnwidth", default=300, help="Calendar Column width") self.arg_parser.add_argument("--linewidth", type=float, dest="linewidth", default=1, help="day separation line width") self.arg_parser.add_argument("--rowheight", type=float, dest="rowheight", default=12, help="row height") self.arg_parser.add_argument("-u", "--units", type=str, dest="units", default="mm", help="units to measure size of knob") # Dummy self.arg_parser.add_argument("--tab") # Label settings self.arg_parser.add_argument("--saints_file", type=str, dest="saints_file", default='', help="xlsx file to load saints") self.arg_parser.add_argument("--font_family", type=str, dest="font_family", default="Apple Garamond", help="Font family to use for days and labels") self.arg_parser.add_argument("--year", type=int, dest="year", default=1970, help="Calendar Year") self.arg_parser.add_argument("-m", "--month", type=str, dest="month", default="Gennaio", help="month to render") self.arg_parser.add_argument("--dayfrom", type=int, dest="dayfrom", default=1, help="Range to render day from") self.arg_parser.add_argument("--dayto", type=int, dest="dayto", default=31, help="Range to render day to") self.arg_parser.add_argument("--day_text_size", type=float, dest="day_text_size", default=12, help="Text size of day") self.arg_parser.add_argument("--saint_text_size", type=float, dest="saint_text_size", default=8, help="Text size of saint") # spacing self.__elements = ['day', 'wday', 'saint', 'moon'] self.__spacing = { 'size' : 8, 'padding' : 0, 'margin-top' : 0, 'margin-bottom' : 0, 'margin-left' : 0, 'margin-right' : 0 } for el in self.__elements: for sk in self.__spacing.keys(): sk = sk.replace('-','_') self.arg_parser.add_argument("--" + el + "_" + sk, type=float, dest=el + "_" + sk, help="Spacing " + el + " " + sk) def draw_text(self, textvalue, pos, text_size, parent, bold=False, italic=False ): # Create text element x,y = pos font = str(self.options.font_family) font_specs = str(self.options.font_family) if bold: font_specs += ", Bold" else: font_specs += ", Normal" # Center text horizontally with CSS style. style = { 'text-align' : 'right', 'text-anchor': 'start', 'alignment-baseline' : 'baseline', 'font-family' : f"'{font}'", '-inkscape-font-specification' : f"'{font_specs}'", 'font-weight' : 'bold' if bold else 'normal', 'font-size' : str(text_size), 'vertical-align' : 'middle' } attribs = {'style' : str(inkex.Style(style)), 'x' : str(x), 'y' : str(y), 'text' : str(textvalue) } text = etree.SubElement(parent, inkex.addNS('text','svg'), attribs ) text.text = textvalue text.set('style', str(inkex.Style(style))) # parent.append(text) return text def draw_moon_text(self, phase, pos, text_size, parent): moon_phase = "○◑●◐" textvalue = moon_phase[(phase+1)%4] return self.draw_text(textvalue, pos, text_size, parent) def draw_hline(self, pos, length, parent): x1,y1 = pos x2,y2 = pos[0] + length, pos[1] line_style = { 'stroke': '#000000', 'stroke-width': str(self.spacing['linewidth']), 'fill': 'none' } line_attribs = {'style' : str(inkex.Style(line_style)), inkex.addNS('label','inkscape') : "none", 'd' : 'M '+str(x1) +',' + str(y1) +' L '+str(x2) +','+str(y2) } line = etree.SubElement(parent, inkex.addNS('path','svg'), line_attribs ) return line def draw_day(self, cal_date, pos, parent, fest = False): date = self._wss.get_date(cal_date['day'], cal_date['month'], cal_date['year']) bold = date.weekday() == 6 # Domenica # import feste fisse da Excel if cal_date['fest'] != 'None': fest = True saint_textvalue = cal_date['fest'] if fest else str(cal_date['saint']) # calcolo feste mobili mobile_fest = self._wss.get_mobile_fest(date) if mobile_fest is not None: fest = True saint_textvalue += " - " + mobile_fest if fest: bold = True wday = self._wss.get_weekday(date) mphs = self._wss.get_moon_phase(date) day_text = self.draw_text(str(cal_date['day']), pos['day'], self.spacing['day'].size, parent, bold=bold) wdl_text = self.draw_text(str(wday), pos['wday'], self.spacing['wday'].size, parent, bold=bold) ssn_text = self.draw_text(saint_textvalue, pos['saint'], self.spacing['saint'].size, parent, bold=bold) if mphs is not None: mph_text = self.draw_moon_text( mphs, pos['moon'], self.spacing['moon'].size, parent) h_line = self.draw_hline( pos['hline'], self.spacing['columnwidth'],parent) pass def _compute_spacing(self, x, y, it): d = self.spacing['day'] w = self.spacing['wday'] s = self.spacing['saint'] m = self.spacing['moon'] dbb = 1.5*d.size + 2*d.padding + d.margin_left + d.margin_right, \ d.size + 2*d.padding + d.margin_top + d.margin_bottom sby = s.size + 2*s.padding + s.margin_top + s.margin_bottom wby = w.size + 2*w.padding + w.margin_top + w.margin_bottom lw = self.spacing['linewidth'] cw = self.spacing['columnwidth'] rh = self.spacing['rowheight'] y = y + max(rh, dbb[1]+lw) * it ans = { 'day' : (x+ d.padding+d.margin_left , y+ dbb[1]-d.padding-d.margin_bottom), 'wday' : (x+ dbb[0] + w.padding+w.margin_left , y+ dbb[1]-sby-w.padding-w.margin_bottom), 'saint': (x+ dbb[0] + s.padding+s.margin_left , y+ dbb[1]-s.padding-s.margin_bottom), 'hline': (x , y+ dbb[1]), 'moon' : (x + cw - m.size-2*m.padding-m.margin_right, y+ m.size+2*m.padding+m.margin_top), } return ans def effect(self): parent = self.svg.get_current_layer() selected = self.svg.selected if selected: bbox = selected.bounding_box() self.x_offset = bbox.left self.y_offset = bbox.top else: self.x_offset = self.svg.unittouu(str(self.options.x) + self.options.units) self.y_offset = self.svg.unittouu(str(self.options.y) + self.options.units) # populating spacing with options self.spacing = {} for el in self.__elements: self.spacing[el] = _Spacing() for sk in self.__spacing.keys(): sk = sk.replace('-','_') self.spacing[el].__dict__[sk] = self.svg.unittouu( str(getattr(self.options ,el+'_'+sk)) + self.options.units) self.spacing['linewidth'] = getattr(self.options ,'linewidth') self.spacing['columnwidth'] = self.svg.unittouu( str(getattr(self.options ,'columnwidth')) + self.options.units) self.spacing['rowheight'] = self.svg.unittouu( str(getattr(self.options ,'rowheight')) + self.options.units) if len(self.options.saints_file): ss = WikiSaints() ss.open(str(self.options.saints_file)) else: ss = WikiSaints(get_from_wiki=True) self._wss = ss compute_date = lambda d: { 'day' : d, 'month' : self.options.month, 'year': self.options.year, 'saint' : str(ss.get_saint(self.options.month, d)[0]), 'fest' : str(ss.get_saint(self.options.month, d)[2]) } for n,d in enumerate(range(self.options.dayfrom, self.options.dayto + 1)): cal_date = compute_date(d) pos = self._compute_spacing(self.x_offset, self.y_offset, n) self.draw_day(cal_date, pos, parent) if __name__ == '__main__': e = CalendarioSanti() e.run()