481 lines
18 KiB
Python
Executable File
481 lines
18 KiB
Python
Executable File
#! /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),
|
|
'bbox' : (dbb[0], dbb[1]),
|
|
}
|
|
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['rowheight'] = self.svg.unittouu( str(getattr(self.options ,'rowheight')) + self.options.units)
|
|
|
|
if selected:
|
|
self.spacing['columnwidth'] = bbox.width
|
|
else:
|
|
self.spacing['columnwidth'] = self.svg.unittouu( str(getattr(self.options ,'columnwidth')) + 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]) }
|
|
|
|
if selected:
|
|
day_bbox = self._compute_spacing(self.x_offset, self.y_offset, 0)['bbox']
|
|
available_days = int(ceil(bbox.height / day_bbox[1])) -1
|
|
self.options.dayto = min(self.options.dayfrom + available_days - 1, self.options.dayto)
|
|
|
|
for n,d in enumerate(range(self.options.dayfrom, self.options.dayto)):
|
|
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()
|
|
|