1 #!/usr/bin/env python
3 '''
4 calendar.py
5 A calendar generator plugin for Inkscape, but also can be used as a standalone
6 command line application.
8 Copyright (C) 2008 Aurelio A. Heckert <aurium(a)gmail.com>
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 '''
25 __version__ = "0.2"
27 import inkex, simplestyle, re, calendar
28 from datetime import *
30 class SVGCalendar (inkex.Effect):
32 def __init__(self):
33 inkex.Effect.__init__(self)
34 self.OptionParser.add_option("--tab",
35 action="store", type="string",
36 dest="tab")
37 self.OptionParser.add_option("--month",
38 action="store", type="int",
39 dest="month", default=0,
40 help="Month to be generated. If 0, then the entry year will be generated.")
41 self.OptionParser.add_option("--year",
42 action="store", type="int",
43 dest="year", default=0,
44 help="Year to be generated. If 0, then the current year will be generated.")
45 self.OptionParser.add_option("--fill-empty-day-boxes",
46 action="store", type="inkbool",
47 dest="fill_edb", default=True,
48 help="Fill empty day boxes with next month days.")
49 self.OptionParser.add_option("--start-day",
50 action="store", type="string",
51 dest="start_day", default="sun",
52 help='Week start day. ("sun" or "mon")')
53 self.OptionParser.add_option("--weekend",
54 action="store", type="string",
55 dest="weekend", default="sat+sun",
56 help='Define the weekend days. ("sat+sun" or "sat" or "sun")')
57 self.OptionParser.add_option("--auto-organize",
58 action="store", type="inkbool",
59 dest="auto_organize", default=True,
60 help='Authomaticaly set the size and positions.')
61 self.OptionParser.add_option("--months-per-line",
62 action="store", type="int",
63 dest="months_per_line", default=3,
64 help='Number of months side by side.')
65 self.OptionParser.add_option("--month-width",
66 action="store", type="string",
67 dest="month_width", default="6cm",
68 help='The width of the month days box.')
69 self.OptionParser.add_option("--month-margin",
70 action="store", type="string",
71 dest="month_margin", default="1cm",
72 help='The space between the month boxes.')
73 self.OptionParser.add_option("--color-year",
74 action="store", type="string",
75 dest="color_year", default="#888",
76 help='Color for the year header.')
77 self.OptionParser.add_option("--color-month",
78 action="store", type="string",
79 dest="color_month", default="#666",
80 help='Color for the month name header.')
81 self.OptionParser.add_option("--color-day-name",
82 action="store", type="string",
83 dest="color_day_name", default="#999",
84 help='Color for the week day names header.')
85 self.OptionParser.add_option("--color-day",
86 action="store", type="string",
87 dest="color_day", default="#000",
88 help='Color for the common day box.')
89 self.OptionParser.add_option("--color-weekend",
90 action="store", type="string",
91 dest="color_weekend", default="#777",
92 help='Color for the weekend days.')
93 self.OptionParser.add_option("--color-nmd",
94 action="store", type="string",
95 dest="color_nmd", default="#BBB",
96 help='Color for the next month day, in enpty day boxes.')
97 self.OptionParser.add_option("--month-names",
98 action="store", type="string",
99 dest="month_names", default='January February March April May June '+\
100 'July August September October November December',
101 help='The month names for localization.')
102 self.OptionParser.add_option("--day-names",
103 action="store", type="string",
104 dest="day_names", default='Sun Mon Tue Wed Thu Fri Sat',
105 help='The week day names for localization.')
106 self.OptionParser.add_option("--encoding",
107 action="store", type="string",
108 dest="input_encode", default='utf-8',
109 help='The input encoding of the names.')
111 def validate_options(self):
112 #inkex.errormsg( self.options.input_encode )
113 # Convert string names lists in real lists:
114 m = re.match( '\s*(.*[^\s])\s*', self.options.month_names )
115 self.options.month_names = re.split( '\s+', m.group(1) )
116 m = re.match( '\s*(.*[^\s])\s*', self.options.day_names )
117 self.options.day_names = re.split( '\s+', m.group(1) )
118 # Validate names lists:
119 if len(self.options.month_names) != 12:
120 inkex.errormsg('The month name list "'+
121 str(self.options.month_names)+
122 '" is invalid. Using default.')
123 self.options.month_names = ['January','February','March',
124 'April', 'May', 'June',
125 'July', 'August', 'September',
126 'October','November','December']
127 if len(self.options.day_names) != 7:
128 inkex.errormsg('The day name list "'+
129 str(self.options.day_names)+
130 '" is invalid. Using default.')
131 self.options.day_names = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']
132 # Convert year 0 to current year:
133 if self.options.year == 0: self.options.year = datetime.today().year
134 # Set the calendar start day:
135 if self.options.start_day=='sun':
136 calendar.setfirstweekday(6)
137 else:
138 calendar.setfirstweekday(0)
139 # Convert string numbers with unit to user space float numbers:
140 self.options.month_width = inkex.unittouu( self.options.month_width )
141 self.options.month_margin = inkex.unittouu( self.options.month_margin )
143 # initial values:
144 month_x_pos = 0
145 month_y_pos = 0
147 def calculate_size_and_positions(self):
148 #month_margin month_width months_per_line auto_organize
149 self.doc_w = inkex.unittouu(self.document.getroot().get('width'))
150 self.doc_h = inkex.unittouu(self.document.getroot().get('height'))
151 if self.options.auto_organize:
152 if self.doc_h > self.doc_w:
153 self.months_per_line = 3
154 else:
155 self.months_per_line = 4
156 else:
157 self.months_per_line = self.options.months_per_line
158 #self.month_w = self.doc_w / self.months_per_line
159 if self.options.auto_organize:
160 self.month_w = (self.doc_w * 0.8) / self.months_per_line
161 self.month_margin = self.month_w / 10
162 else:
163 self.month_w = self.options.month_width
164 self.month_margin = self.options.month_margin
165 self.day_w = self.month_w / 7
166 self.day_h = self.month_w / 9
167 self.month_h = self.day_w * 7
168 if self.options.month == 0:
169 self.year_margin = (( self.doc_w + self.day_w -
170 (self.month_w*self.months_per_line) -
171 (self.month_margin*(self.months_per_line-1))
172 ) / 2 ) #- self.month_margin
173 else:
174 self.year_margin = ( self.doc_w - self.month_w ) / 2
175 self.style_day = {
176 'font-size': str( self.day_w / 2 ),
177 'font-family': 'arial',
178 'text-anchor': 'middle',
179 'text-align': 'center',
180 'fill': self.options.color_day
181 }
182 self.style_weekend = self.style_day.copy()
183 self.style_weekend['fill'] = self.options.color_weekend
184 self.style_nmd = self.style_day.copy()
185 self.style_nmd['fill'] = self.options.color_nmd
186 self.style_month = self.style_day.copy()
187 self.style_month['fill'] = self.options.color_month
188 self.style_month['font-size'] = str( self.day_w / 1.5 )
189 self.style_month['font-weight'] = 'bold'
190 self.style_day_name = self.style_day.copy()
191 self.style_day_name['fill'] = self.options.color_day_name
192 self.style_day_name['font-size'] = str( self.day_w / 3 )
193 self.style_year = self.style_day.copy()
194 self.style_year['fill'] = self.options.color_year
195 self.style_year['font-size'] = str( self.day_w * 2 )
196 self.style_year['font-weight'] = 'bold'
198 def is_weekend(self, pos):
199 # weekend values: "sat+sun" or "sat" or "sun"
200 if self.options.start_day=='sun':
201 if self.options.weekend=='sat+sun' and pos==0: return True
202 if self.options.weekend=='sat+sun' and pos==6: return True
203 if self.options.weekend=='sat' and pos==6: return True
204 if self.options.weekend=='sun' and pos==0: return True
205 else:
206 if self.options.weekend=='sat+sun' and pos==5: return True
207 if self.options.weekend=='sat+sun' and pos==6: return True
208 if self.options.weekend=='sat' and pos==5: return True
209 if self.options.weekend=='sun' and pos==6: return True
210 return False
212 def in_line_month(self, cal):
213 cal2 = []
214 for week in cal:
215 for day in week:
216 if day != 0:
217 cal2.append(day)
218 return cal2
220 def write_month_header(self, g, m):
221 txt_atts = {'style': simplestyle.formatStyle(self.style_month),
222 'x': str( (self.month_w - self.day_w) / 2 ),
223 'y': str( self.day_h / 5 ) }
224 try:
225 inkex.etree.SubElement(g, 'text', txt_atts).text = unicode(self.options.month_names[m-1], self.options.input_encode)
226 except:
227 inkex.errormsg('You must select your correct system encode.')
228 exit(1)
229 gw = inkex.etree.SubElement(g, 'g')
230 week_x = 0
231 if self.options.start_day=='sun':
232 for wday in self.options.day_names:
233 txt_atts = {'style': simplestyle.formatStyle(self.style_day_name),
234 'x': str( self.day_w * week_x ),
235 'y': str( self.day_h ) }
236 try:
237 inkex.etree.SubElement(gw, 'text', txt_atts).text = unicode(wday, self.options.input_encode)
238 except:
239 inkex.errormsg('You must select your correct system encode.')
240 exit(1)
241 week_x += 1
242 else:
243 w2 = self.options.day_names[1:]
244 w2.append(self.options.day_names[0])
245 for wday in w2:
246 txt_atts = {'style': simplestyle.formatStyle(self.style_day_name),
247 'x': str( self.day_w * week_x ),
248 'y': str( self.day_h ) }
249 try:
250 inkex.etree.SubElement(gw, 'text', txt_atts).text = unicode(wday, self.options.input_encode)
251 except:
252 inkex.errormsg('You must select your correct system encode.')
253 exit(1)
254 week_x += 1
256 def create_month(self, m):
257 txt_atts = {
258 'transform': 'translate('+str(self.year_margin +
259 (self.month_w + self.month_margin) *
260 self.month_x_pos) +
261 ','+str((self.day_h * 4) +
262 (self.month_h * self.month_y_pos))+')',
263 'id': 'month_'+str(m)+'_'+str(self.options.year) }
264 g = inkex.etree.SubElement(self.year_g, 'g', txt_atts)
265 self.write_month_header(g, m)
266 gdays = inkex.etree.SubElement(g, 'g')
267 cal = calendar.monthcalendar(self.options.year,m)
268 if m == 1:
269 before_month = \
270 self.in_line_month( calendar.monthcalendar(self.options.year-1, 12) )
271 else:
272 before_month = \
273 self.in_line_month( calendar.monthcalendar(self.options.year, m-1) )
274 if m == 12:
275 next_month = \
276 self.in_line_month( calendar.monthcalendar(self.options.year+1, 1) )
277 else:
278 next_month = \
279 self.in_line_month( calendar.monthcalendar(self.options.year, m+1) )
280 if len(cal) < 6: # add a line after the last week
281 cal.append([0,0,0,0,0,0,0])
282 if len(cal) < 6: # add a line before the first week (Feb 2009)
283 cal.reverse()
284 cal.append([0,0,0,0,0,0,0])
285 cal.reverse()
286 # How mutch before month days will be showed:
287 bmd = cal[0].count(0) + cal[1].count(0)
288 before = True
289 week_y = 0
290 for week in cal:
291 week_x = 0
292 for day in week:
293 style = self.style_day
294 if self.is_weekend(week_x): style = self.style_weekend
295 if day == 0: style = self.style_nmd
296 txt_atts = {'style': simplestyle.formatStyle(style),
297 'x': str( self.day_w * week_x ),
298 'y': str( self.day_h * (week_y+2) ) }
299 if day==0 and not self.options.fill_edb:
300 pass # draw nothing
301 elif day==0:
302 if before:
303 inkex.etree.SubElement(gdays, 'text', txt_atts).text = str( before_month[-bmd] )
304 bmd -= 1
305 else:
306 inkex.etree.SubElement(gdays, 'text', txt_atts).text = str( next_month[bmd] )
307 bmd += 1
308 else:
309 inkex.etree.SubElement(gdays, 'text', txt_atts).text = str(day)
310 before = False
311 week_x += 1
312 week_y += 1
313 self.month_x_pos += 1
314 if self.month_x_pos >= self.months_per_line:
315 self.month_x_pos = 0
316 self.month_y_pos += 1
318 def effect(self):
319 self.validate_options()
320 self.calculate_size_and_positions()
321 parent = self.document.getroot()
322 txt_atts = {
323 'id': 'year_'+str(self.options.year) }
324 self.year_g = inkex.etree.SubElement(parent, 'g', txt_atts)
325 txt_atts = {'style': simplestyle.formatStyle(self.style_year),
326 'x': str( self.doc_w / 2 ),
327 'y': str( self.day_w * 1.5 ) }
328 inkex.etree.SubElement(self.year_g, 'text', txt_atts).text = str(self.options.year)
329 if self.options.month == 0:
330 for m in range(1,13):
331 self.create_month(m)
332 else:
333 self.create_month(self.options.month)
336 if __name__ == '__main__': #pragma: no cover
337 e = SVGCalendar()
338 e.affect()