diff --git a/calendario_dei_santi.inx b/calendario_dei_santi.inx new file mode 100644 index 0000000..fd24696 --- /dev/null +++ b/calendario_dei_santi.inx @@ -0,0 +1,94 @@ + + + CalendarioDeiSanti + com.calendario_dei_santi + calendario_dei_santi.py + + + + 0 + 0 + + px + mm + + + + + + Apple Garamond + 1970 + + Gennaio + Febbraio + Marzo + Aprile + Maggio + Giugno + Luglio + Agosto + Settembre + Ottobre + Novembre + Dicembre + + 1 + 31 + + 1 + 500 + 12 + + + + + + 16 + 10 + 0 + 0 + 0 + 0 + + + + 14 + 0 + 0 + 0 + 0 + 0 + + + + + + 9 + 0 + 0 + 0 + 0 + 0 + + + + 21 + 0 + 0 + 0 + 0 + 0 + + + + + + all + + + + + + diff --git a/calendario_dei_santi.py b/calendario_dei_santi.py new file mode 100755 index 0000000..32e4e7d --- /dev/null +++ b/calendario_dei_santi.py @@ -0,0 +1,469 @@ +#! /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() + diff --git a/santi.xlsx b/santi.xlsx new file mode 100644 index 0000000..05fa27c Binary files /dev/null and b/santi.xlsx differ