full code documentation with epydoc

git-svn-id: https://callirhoe.googlecode.com/svn/branches/next@47 81c8bb96-aa45-f2e2-0eef-c4fa4a15c6df
This commit is contained in:
geortz@gmail.com 2014-09-03 12:56:19 +00:00
parent e31fa29316
commit d7ec93602d
19 changed files with 384 additions and 101 deletions

View File

@ -16,7 +16,10 @@
# --- geom.default ---
"""module defining the default geometry"""
class dom:
"""day of month geometry"""
size = (0.5,0.5,0.8,0.5) # short 0-1, long 2-3
mw_split = (0.7,0.2)
@ -27,6 +30,7 @@ class dom:
hf_vsplit = (0.5,0.4)
class month:
"""month geometry"""
symmetric = False
sloppy_rot = 0
sloppy_dx = 0

View File

@ -16,6 +16,8 @@
# --- geom.sloppy ---
"""module defining the sloppy geometry"""
import default
class dom(default.dom): pass

View File

@ -30,4 +30,4 @@ ce|39||Ascension|off
ce|50||Whit Monday|off
d|20130223-20130310|winter vacations (B)||multi
f|20140222-20140309|winter vacations (B)||multi
d|20140222-20140309|winter vacations (B)||multi

View File

@ -3,7 +3,7 @@
# callirhoe - high quality calendar rendering
# Copyright (C) 2012 George M. Tzoumas
# German language data
""" German language definition file"""
# Copyright (C) 2013 Neels Hofmeyr
# This program is free software: you can redistribute it and/or modify

View File

@ -16,6 +16,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see http://www.gnu.org/licenses/
"""Greek language definition file"""
long_day_name = [ u'Δευτέρα', u'Τρίτη', u'Τετάρτη',
u'Πέμπτη', u'Παρασκευή', u'Σάββατο', u'Κυριακή' ]

View File

@ -16,8 +16,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see http://www.gnu.org/licenses/
"""English language definition file"""
long_day_name = [ u'Monday', u'Tuesday', u'Wednesday',
u'Thursday', u'Friday', u'Saturday', u'Sunday' ]
u'Thursday', u'Friday', u'Saturday', u'Sunday' ]
short_day_name = [ u'Mo', u'Tu', u'We', u'Th', u'Fr', u'Sa', u'Su' ]

View File

@ -16,6 +16,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see http://www.gnu.org/licenses/
"""French language definition file"""
long_day_name = [ u'Lundi', u'Mardi', u'Mercredi',
u'Jeudi', u'Vendredi', u'Samedi', u'Dimanche' ]

View File

@ -3,7 +3,7 @@
# callirhoe - high quality calendar rendering
# Copyright (C) 2012 George M. Tzoumas
# Turkish language data
""" Turkish language definition file"""
# Copyright (C) 2013 Ece Neslihan Aybeke
# This program is free software: you can redistribute it and/or modify
@ -24,7 +24,7 @@ long_day_name = [ u'Pazartesi', u'Salı', u' Çarşamba ',
short_day_name = [ u'Pt', u'Sa', u'Ça', u'Pe', u'Cu', u'Ct', u'Pa' ]
long_month_name = [ '', u'Ocak', 'Şubat', u'Mart', u'Nisan',
long_month_name = [ '', u'Ocak', u'Şubat', u'Mart', u'Nisan',
u'Mayıs', u'Haziran', u'Temmuz', u'Ağustos',
u'Eylül', u'Ekim', u'Kasım', u'Aralık' ]

View File

@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see http://www.gnu.org/licenses/
# --- layouts._base ---
"""base layout module -- others may inherit from this one"""
import optparse
from lib.xcairo import *
@ -23,6 +23,10 @@ from lib.geom import *
from math import floor, ceil, sqrt
def get_parser(layout_name):
"""get the parser object for the layout command-line arguments
@param layout_name: corresponding python module (.py file)
"""
lname = layout_name.split(".")[1]
parser = optparse.OptionParser(usage="%prog (...) --layout " + lname + " [options] (...)",add_help_option=False)
parser.add_option("--rows", type="int", default=0, help="force grid rows [%default]")
@ -67,6 +71,16 @@ def get_parser(layout_name):
class DayCell(object):
"""class Holding a day cell to be drawn
@type day: int
@ivar day: day of week
@ivar header: header string
@ivar footer: footer string
@ivar theme: (Style,Geometry,Language) tuple
@type show_day_name: bool
@ivar show_day_name: whether day name is displayed
"""
def __init__(self, day, header, footer, theme, show_day_name):
self.day = day
self.header = header
@ -75,6 +89,7 @@ class DayCell(object):
self.show_day_name = show_day_name
def _draw_short(self, cr, rect):
"""render the day cell in short mode"""
S,G,L = self.theme
x, y, w, h = rect
day_of_month, day_of_week = self.day
@ -86,24 +101,25 @@ class DayCell(object):
Rdom = R
valign = 0 if self.show_day_name else 2
# draw day of month (number)
draw_str(cr, text = str(day_of_month), rect = Rdom, stretch = -1, stroke_rgba = S.fg,
draw_str(cr, text = str(day_of_month), rect = Rdom, scaling = -1, stroke_rgba = S.fg,
align = (2,valign), font = S.font, measure = "88")
# draw name of day
if self.show_day_name:
draw_str(cr, text = L.day_name[day_of_week][0], rect = Rdow, stretch = -1, stroke_rgba = S.fg,
draw_str(cr, text = L.day_name[day_of_week][0], rect = Rdow, scaling = -1, stroke_rgba = S.fg,
align = (2,valign), font = S.font, measure = "88")
# draw header
if self.header:
R = rect_rel_scale(rect, G.header_size[0], G.header_size[1], 0, -1.0 + G.header_align)
draw_str(cr, text = self.header, rect = R, stretch = -1, stroke_rgba = S.header,
draw_str(cr, text = self.header, rect = R, scaling = -1, stroke_rgba = S.header,
font = S.header_font) # , measure = "MgMgMgMgMgMg"
# draw footer
if self.footer:
R = rect_rel_scale(rect, G.footer_size[0], G.footer_size[1], 0, 1.0 - G.footer_align)
draw_str(cr, text = self.footer, rect = R, stretch = -1, stroke_rgba = S.footer,
draw_str(cr, text = self.footer, rect = R, scaling = -1, stroke_rgba = S.footer,
font = S.footer_font)
def _draw_long(self, cr, rect):
"""render the day cell in long mode"""
S,G,L = self.theme
x, y, w, h = rect
day_of_month, day_of_week = self.day
@ -116,23 +132,27 @@ class DayCell(object):
Rdom = rect_rel_scale(R1, G.size[0], G.size[1])
valign = 0 if self.show_day_name else 2
# draw day of month (number)
draw_str(cr, text = str(day_of_month), rect = Rdom, stretch = -1, stroke_rgba = S.fg,
draw_str(cr, text = str(day_of_month), rect = Rdom, scaling = -1, stroke_rgba = S.fg,
align = (2,valign), font = S.font, measure = "88")
# draw name of day
if self.show_day_name:
draw_str(cr, text = L.day_name[day_of_week], rect = Rdow, stretch = -1, stroke_rgba = S.fg,
draw_str(cr, text = L.day_name[day_of_week], rect = Rdow, scaling = -1, stroke_rgba = S.fg,
align = (0,valign), font = S.font, measure = "M")
Rh, Rf = rect_vsplit(Rhf, *G.hf_vsplit)
# draw header
if self.header:
draw_str(cr, text = self.header, rect = Rh, stretch = -1, stroke_rgba = S.header, align = (1,2),
draw_str(cr, text = self.header, rect = Rh, scaling = -1, stroke_rgba = S.header, align = (1,2),
font = S.header_font)
# draw footer
if self.footer:
draw_str(cr, text = self.footer, rect = Rf, stretch = -1, stroke_rgba = S.footer, align = (1,2),
draw_str(cr, text = self.footer, rect = Rf, scaling = -1, stroke_rgba = S.footer, align = (1,2),
font = S.footer_font)
def draw(self, cr, rect, short_thres):
"""automatically render a short or long day cell depending on threshold I{short_thres}
If C{rect} ratio is less than C{short_thres} then short mode is chosen, otherwise long mode.
"""
if rect_ratio(rect) < short_thres:
self._draw_short(cr, rect)
else:
@ -140,6 +160,17 @@ class DayCell(object):
class CalendarRenderer(object):
"""base monthly calendar renderer - others inherit from this
@ivar Outfile: output file
@ivar Year: year of first month
@ivar Month: first month
@ivar MonthSpan: month span
@ivar Theme: (Style,Geometry,Language) tuple
@ivar holiday_provider: L{HolidayProvider} object
@ivar version_string: callirhoe version string
@ivar options: parser options object
"""
def __init__(self, Outfile, Year, Month, MonthSpan, Theme, holiday_provider, version_string, options):
self.Outfile = Outfile
self.Year = Year
@ -151,6 +182,14 @@ class CalendarRenderer(object):
self.options = options
def _draw_month(self, cr, rect, month, year, daycell_thres):
"""this method renders a calendar month, it B{should be overridden} in any subclass
@param cr: cairo context
@param rect: rendering rect
@param month: month
@param year: year
@param daycell_thres: short/long day cell threshold
"""
raise NotImplementedError("base _draw_month() should be overridden")
#1 1 1
@ -171,6 +210,7 @@ class CalendarRenderer(object):
#rows = 0
#cols = 0
def render(self):
"""main calendar rendering routine"""
S,G,L = self.Theme
rows, cols = self.options.rows, self.options.cols
@ -186,7 +226,7 @@ class CalendarRenderer(object):
S.month.color_map_fg = (S.month.color_map_fg[1], S.month.color_map_fg[0])
try:
page = PageWriter(self.Outfile, G.landscape, G.pagespec, G.border, not self.options.opaque)
page = PageWriter(self.Outfile, G.pagespec, not self.options.opaque, G.landscape, G.border)
except InvalidFormat as e:
print >> sys.stderr, "invalid output format", e.args[0]
sys.exit(1)
@ -253,11 +293,11 @@ class CalendarRenderer(object):
if (y > yy[-1]): yy.append(y)
if not self.options.month_with_year and not self.options.no_footer:
year_str = str(yy[0]) if yy[0] == yy[-1] else "%s %s" % (yy[0],yy[-1])
draw_str(page.cr, text = year_str, rect = Rc, stroke_rgba = (0,0,0,0.5), stretch = -1,
draw_str(page.cr, text = year_str, rect = Rc, stroke_rgba = (0,0,0,0.5), scaling = -1,
align = (0,0), font = (extract_font_name(S.month.font),0,0))
if not self.options.no_footer:
draw_str(page.cr, text = "rendered by Callirhoe ver. %s" % self.version_string,
rect = Rc, stroke_rgba = (0,0,0,0.5), stretch = -1, align = (1,0),
rect = Rc, stroke_rgba = (0,0,0,0.5), scaling = -1, align = (1,0),
font = (extract_font_name(S.month.font),1,0))
num_pages_written += 1
page.end_page()

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# callirhoe - high quality calendar rendering
# Copyright (C) 2012-2013 George M. Tzoumas
# Copyright (C) 2012-2014 George M. Tzoumas
# 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
@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see http://www.gnu.org/licenses/
# --- layouts.bars ---
"""bars layout"""
from lib.xcairo import *
from lib.geom import *
@ -29,10 +29,11 @@ import _base
parser = _base.get_parser(__name__)
class CalendarRenderer(_base.CalendarRenderer):
"""bars layout class"""
#default thres = 2.5
def _draw_month(self, cr, rect, month, year, daycell_thres):
S,G,L = self.Theme
apply_rect(cr, rect, G.month.sloppy_dx, G.month.sloppy_dy, G.month.sloppy_rot)
make_sloppy_rect(cr, rect, G.month.sloppy_dx, G.month.sloppy_dy, G.month.sloppy_rot)
day, span = calendar.monthrange(year, month)
mmeasure = 'A'*max(map(len,L.month_name))
@ -79,7 +80,7 @@ class CalendarRenderer(_base.CalendarRenderer):
mshad = (f,-f) if G.landscape else (f,f)
title_str = L.month_name[month]
if self.options.month_with_year: title_str += ' ' + str(year)
draw_str(cr, text = title_str, rect = R_text, stretch = -1, stroke_rgba = mcolor_fg,
draw_str(cr, text = title_str, rect = R_text, scaling = -1, stroke_rgba = mcolor_fg,
align = (2,0), font = S.month.font, measure = mmeasure, shadow = mshad)
cr.restore()

View File

@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see http://www.gnu.org/licenses/
# --- layouts.classic ---
"""classic layout"""
from lib.xcairo import *
from lib.geom import *
@ -28,6 +28,7 @@ import _base
parser = _base.get_parser(__name__)
def _weekrows_of_month(year, month):
"""returns the number of Monday-Sunday ranges (or subsets of) that a month contains, which are 4, 5 or 6"""
day,span = calendar.monthrange(year, month)
if day == 0 and span == 28: return 4
if day == 5 and span == 31: return 6
@ -35,10 +36,11 @@ def _weekrows_of_month(year, month):
return 5
class CalendarRenderer(_base.CalendarRenderer):
"""classic tiles layout class"""
#default thres = 2.5
def _draw_month(self, cr, rect, month, year, daycell_thres):
S,G,L = self.Theme
apply_rect(cr, rect, G.month.sloppy_dx, G.month.sloppy_dy, G.month.sloppy_rot)
make_sloppy_rect(cr, rect, G.month.sloppy_dx, G.month.sloppy_dy, G.month.sloppy_rot)
day, span = calendar.monthrange(year, month)
weekrows = 6 if G.month.symmetric else _weekrows_of_month(year, month)
@ -67,7 +69,7 @@ class CalendarRenderer(_base.CalendarRenderer):
fill_rgba = S.dom.bg if col < 5 else S.dom_weekend.bg,
stroke_width = mm_to_dots(S.dow.frame_thickness))
R_text = rect_rel_scale(R, 1, 0.5)
draw_str(cr, text = L.day_name[col], rect = R_text, stretch = -1, stroke_rgba = S.dow.fg,
draw_str(cr, text = L.day_name[col], rect = R_text, scaling = -1, stroke_rgba = S.dow.fg,
align = (2,0), font = S.dow.font, measure = wmeasure)
# draw day cells
@ -100,7 +102,7 @@ class CalendarRenderer(_base.CalendarRenderer):
mshad = (f,-f) if G.landscape else (f,f)
title_str = L.month_name[month]
if self.options.month_with_year: title_str += ' ' + str(year)
draw_str(cr, text = title_str, rect = R_text, stretch = -1, stroke_rgba = mcolor_fg,
draw_str(cr, text = title_str, rect = R_text, scaling = -1, stroke_rgba = mcolor_fg,
align = (2,0), font = S.month.font, measure = mmeasure, shadow = mshad)
cr.restore()

View File

@ -170,7 +170,7 @@ class HLayout(VLayout):
t = super(HLayout,self).item(i)
return (t[1], t[0], t[3], t[2])
class GLayout:
class GLayout(object):
"""grid layout manager
@ivar vrep: internal L{VLayout} for row computations

View File

@ -25,7 +25,7 @@
from datetime import date, timedelta
def _get_orthodox_easter(year):
"""Compute date of orthodox easter."""
"""compute date of orthodox easter"""
y1, y2, y3 = year % 4 , year % 7, year % 19
a = 19*y3 + 15
y4 = a % 30
@ -37,7 +37,7 @@ def _get_orthodox_easter(year):
# return res
def _get_catholic_easter(year):
"""Compute date of catholic easter."""
"""compute date of catholic easter"""
a, b, c = year % 19, year // 100, year % 100
d, e = divmod(b,4)
f = (b + 8) // 25
@ -49,11 +49,23 @@ def _get_catholic_easter(year):
emonth,edate = divmod(h + l - 7*m + 114,31)
return date(year, emonth, edate+1)
class Holiday(object):
"""class holding a Holiday object (date is I{not} stored!)
def _strip_empty(sl):
"""strip empty strings from list I{sl}"""
return filter(lambda z: z, sl) if sl else []
@ivar header: string for header (primary text)
@ivar footer: string for footer (secondary text)
def _flatten(sl):
"""join list I{sl} into a comma-separated string"""
if not sl: return None
res = sl[0]
for s in sl[1:]:
res += ', ' + s
return res
class Holiday(object):
"""class holding a Holiday object (date is I{not} stored, use L{HolidayProvider} for that)
@ivar header_list: string list for header (primary text)
@ivar footer_list: string list for footer (secondary text)
@ivar flags: bit combination of {OFF=1, MULTI=2, REMINDER=4}
I{OFF}: day off (real holiday)
@ -73,27 +85,31 @@ class Holiday(object):
MULTI = 2
REMINDER = 4
def __init__(self, header = [], footer = [], flags_str = None):
self.header_list = self._strip_empty(header)
self.footer_list = self._strip_empty(footer)
self.header_list = _strip_empty(header)
self.footer_list = _strip_empty(footer)
self.flags = self._parse_flags(flags_str)
def merge_with(self, hol_list):
"""Merge a list of holiday objects into this object."""
"""merge a list of holiday objects into this object"""
for hol in hol_list:
self.header_list.extend(hol.header_list)
self.footer_list.extend(hol.footer_list)
self.flags |= hol.flags
def header(self):
return self._flatten(self.header_list)
"""return a comma-separated string for L{header_list}"""
return _flatten(self.header_list)
def footer(self):
return self._flatten(self.footer_list)
"""return a comma-separated string for L{footer_list}"""
return _flatten(self.footer_list)
def __repr__(self):
def __str__(self):
"""string representation for debugging purposes"""
return str(self.footer()) + ':' + str(self.header()) + ':' + str(self.flags)
def _parse_flags(self, fstr):
"""return a bit combination of flags, from a comma-separated string list"""
if not fstr: return 0
fs = fstr.split(',')
val = 0
@ -104,18 +120,20 @@ class Holiday(object):
elif 'reminder'.startswith(s): val |= Holiday.REMINDER
return val
def _strip_empty(self, sl):
return filter(lambda z: z, sl) if sl else []
def _flatten(self, sl):
if not sl: return None
res = sl[0]
for s in sl[1:]:
res += ', ' + s
return res
def _decode_date_str(ddef):
"""decode a date definition string into a I{(year,month,day)} tuple
@param ddef: date definition string of length 2, 4 or 8
If C{ddef} is of the form "DD" then tuple (0,0,DD) is returned, which
stands for any year - any month - day DD.
If C{ddef} is of the form "MMDD" then tuple (0,MM,DD) is returned, which
stands for any year - month MM - day DD.
If C{ddef} is of the form "YYYYMMDD" then tuple (YYYY,MM,DD) is returned, which
stands for year YYYY - month MM - day DD.
"""
if len(ddef) == 2:
return (0,0,int(ddef))
if len(ddef) == 4:
@ -125,7 +143,36 @@ def _decode_date_str(ddef):
raise ValueError("invalid date definition '%s'" % ddef)
class HolidayProvider(object):
def __init__(self, s_normal, s_weekend, s_holiday, s_weekend_holiday, s_multi, s_weekend_multi, verbose=True):
"""class holding the holidays throught the year(s)
@ivar annual: dict of events occuring annually, indexed by tuple I{(day,month)}. Note
each dict entry is actually a list of L{Holiday} objects. This is also true for the other
instance variables: L{monthly}, L{fixed}, L{orth_easter}, L{george}, L{cath_easter}.
@ivar monthly: event occuring monthly, indexed by int I{day}
@ivar fixed: fixed date events, indexed by a C{date()} object
@ivar orth_easter: dict of events relative to the orthodox easter Sunday, indexed by
an integer days offset
@ivar george: events occuring on St George's day (orthodox calendar special computation)
@ivar cath_easter: dict of events relative to the catholic easter Sunday, indexed by
an integer days offset
@ivar cache: for each year requested, all holidays occuring
within that year (annual, monthly, easter-based etc.) are precomputed and stored into
dict C{cache}, indexed by a C{date()} object
@ivar ycache: set holding cached years; each new year requested, triggers a cache-fill
operation
"""
def __init__(self, s_normal, s_weekend, s_holiday, s_weekend_holiday, s_multi, s_weekend_multi, multiday_markers=True):
"""initialize a C{HolidayProvider} object
@param s_normal: style class object for normal (weekday) day cells
@param s_weekend: style for weekend day cells
@param s_holiday: style for holiday day cells
@param s_weekend_holiday: style for holiday cells on weekends
@param s_multi: style for multi-day holiday weekday cells
@param s_weekend_multi: style for multi-day holiday weekend cells
@param multiday_markers: if C{True}, then use end-of-multiday-holiday markers and range markers (with dots),
otherwise only first day and first-day-of-month are marked
"""
self.annual = dict() # key = (d,m)
self.monthly = dict() # key = d
self.fixed = dict() # key = date()
@ -140,9 +187,9 @@ class HolidayProvider(object):
self.s_weekend_holiday = s_weekend_holiday
self.s_multi = s_multi
self.s_weekend_multi = s_weekend_multi
self.verbose = verbose
self.multiday_markers = multiday_markers
def parse_day_record(self, fields):
def _parse_day_record(self, fields):
"""return tuple (etype,ddef,footer,header,flags)
@note: I{ddef} is one of the following:
@ -189,17 +236,22 @@ class HolidayProvider(object):
res = int(fields[1])
return (fields[0],res,fields[2],fields[3],fields[4])
def multi_holiday_tuple(self, date1, date2, header, footer, flags):
"""Returns Holiday objects for (beginning, end, first_dom, rest)"""
def _multi_holiday_tuple(self, header, footer, flags):
"""returns a 4-tuple of L{Holiday} objects representing (beginning, end, first-day-of-month, rest)
@param header: passed as C{[header]} of the generated L{Holiday} object
@param footer: passed as C{[footer]} of the generated L{Holiday} object
@param flags: C{flags} of the generated L{Holiday} object
"""
if header:
if self.verbose:
if self.multiday_markers:
header_tuple = (header+'..', '..'+header, '..'+header+'..', None)
else:
header_tuple = (header, None, header, None)
else:
header_tuple = (None, None, None, None)
if footer:
if self.verbose:
if self.multiday_markers:
footer_tuple = (footer+'..', '..'+footer, '..'+footer+'..', None)
else:
footer_tuple = (footer, None, footer, None)
@ -208,29 +260,47 @@ class HolidayProvider(object):
return tuple(map(lambda k: Holiday([header_tuple[k]], [footer_tuple[k]], flags),
range(4)))
# File Format:
# type|DATE*span|footer|header|flags
# type|DATE1-DATE2|footer|header|flags
# type|DATE|footer|header|flags
#
# type:
# d: event occurs annually fixed day/month: MMDD
# d: event occurs monthly, fixed day: DD
# d: fixed day/month/year combination (e.g. deadline, trip, etc.): YYYYMMDD
# oe: Orthodox Easter-dependent holiday, annually
# ge: Georgios' name day, Orthodox Easter dependent holiday, annually
# ce: Catholic Easter holiday
#
# DATE*span and DATE1-DATE2 supported only for YYYYMMDD
# flags = {off, multi}
def load_holiday_file(self, filename):
"""load a holiday file into the C{HolidayProvider} object
B{File Format:}
- C{type|date*span|footer|header|flags}
- C{type|date1-date2|footer|header|flags}
- C{type|date|footer|header|flags}
I{type:}
- C{d}: event occurs annually fixed day/month; I{date}=MMDD
- C{d}: event occurs monthly, fixed day; I{date}=DD
- C{d}: fixed day/month/year combination (e.g. deadline, trip, etc.); I{date}=YYYYMMDD
- C{oe}: Orthodox Easter-dependent holiday, annually; I{date}=integer offset in days
- C{ge}: Georgios' name day, Orthodox Easter dependent holiday, annually; I{date} field is ignored
- C{ce}: Catholic Easter holiday; I{date}=integer offset in days
I{date*span} and range I{date1-date2} supported only for I{date}=YYYYMMDD (fixed) events
I{flags:} comma-separated list of the following:
1. off
2. multi
3. reminder (or any prefix of it)
B{Example}::
d|0101||New year's|off
d|0501||Labour day|off
ce|-2||Good Friday|
ce|0||Easter|off
ce|1||Easter Monday|off
d|20130223-20130310|winter vacations (B)||multi
@param filename: file to be loaded
"""
with open(filename, 'r') as f:
for line in f:
line = line.strip()
if not line: continue
if line[0] == '#': continue
fields = line.split('|')
etype,ddef,footer,header,flags = self.parse_day_record(fields)
etype,ddef,footer,header,flags = self._parse_day_record(fields)
hol = Holiday([header], [footer], flags)
if etype == 'd':
if len(ddef) == 1:
@ -249,7 +319,7 @@ class HolidayProvider(object):
self.fixed[dt1].append(hol)
else:
# properly annotate multi-day events
hols = self.multi_holiday_tuple(dt1, dt2, header, footer, flags)
hols = self._multi_holiday_tuple(header, footer, flags)
dt = dt1
while dt <= dt2:
if dt not in self.fixed: self.fixed[dt] = []
@ -273,6 +343,11 @@ class HolidayProvider(object):
self.cath_easter[d].append(hol)
def get_holiday(self, y, m, d):
"""return a L{Holiday} object for the specified date (y,m,d) or C{None} if no holiday is defined
@note: If year I{y} has not been requested before, the cache is updated first
with all holidays that belong in I{y}, indexed by C{date()} objects.
"""
if y not in self.ycache:
# fill-in events for year y
# annual
@ -315,6 +390,11 @@ class HolidayProvider(object):
return self.cache[dt] if dt in self.cache else None
def get_style(self, flags, dow):
"""return appropriate style object, depending on I{flags} and I{dow}
@param flags: bit combination of holiday flags
@param dow: day of week
"""
if flags & Holiday.OFF:
return self.s_weekend_holiday if dow >= 5 else self.s_holiday
if flags & Holiday.MULTI:
@ -322,7 +402,7 @@ class HolidayProvider(object):
return self.s_weekend if dow >= 5 else self.s_normal
def __call__(self, year, month, dom, dow):
"""Returns (header,footer,day_style)
"""returns (header,footer,day_style)
@param month: month (0-12)
@param dom: day of month (1-31)
@ -337,6 +417,8 @@ class HolidayProvider(object):
if __name__ == '__main__':
import sys
hp = HolidayProvider('n', 'w', 'h', 'wh', 'm', 'wm')
if len(sys.argv) < 3:
raise SystemExit("Usage: %s YEAR holiday_file ..." % sys.argv[0]);
y = int(sys.argv[1])
for f in sys.argv[2:]:
hp.load_holiday_file(f)

View File

@ -29,16 +29,30 @@ from os.path import splitext
from geom import *
XDPI = 72.0
"""dots per inch of output device"""
# decreasing order
# [1188, 840, 594, 420, 297, 210, 148, 105, 74, 52, 37]
ISOPAGE = map(lambda x: int(210*math.sqrt(2)**x+0.5), range(5,-6,-1))
"""ISO page height list, index k for height of Ak paper"""
def page_spec(spec = None):
"""return tuple of page dimensions (width,height) in mm for I{spec}
@param spec: paper type.
Paper type can be an ISO paper type (a0..a9 or a0w..a9w) or of the
form W:H; positive values correspond to W or H mm, negative values correspond to
-W or -H pixels; 'w' suffix swaps width & height; None defaults to A4 paper
"""
if not spec:
return (ISOPAGE[5], ISOPAGE[4])
if len(spec) == 2 and spec[0].lower() == 'a':
k = int(spec[1])
if k > 9: k = 9
return (ISOPAGE[k+1], ISOPAGE[k])
if len(spec) == 3 and spec[0].lower() == 'a' and spec[2].lower() == 'w':
k = int(spec[1])
if k > 9: k = 9
return (ISOPAGE[k], ISOPAGE[k+1])
if ':' in spec:
s = spec.split(':')
@ -48,13 +62,38 @@ def page_spec(spec = None):
return (w,h)
def mm_to_dots(mm):
"""convert millimeters to dots"""
return mm/25.4 * XDPI
def dots_to_mm(dots):
"""convert dots to millimeters"""
return dots*25.4/XDPI
class Page(object):
"""class holding Page properties
@type Size_mm: tuple (width,height)
@ivar Size_mm: page dimensions in mm
@type landscape: bool
@ivar landscape: landscape mode (for landscape, Size_mm will have swapped elements)
@type Size: tuple (width,height)
@ivar Size: page size in dots/pixels
@type Margins: tuple (top,left,bottom,right)
@ivar Margins: page margins in pixels
@type Text_rect: tuple (x,y,w,h)
@ivar Text_rect: text rectangle
"""
def __init__(self, landscape, w, h, b, raster):
"""initialize Page properties object
@type landscape: bool
@param landscape: landscape mode
@param w: page physical width in mm
@param h: page physical height in mm, M{h>w}, even in landscape mode
@param b: page border in mm (uniform)
@type raster: bool
@param raster: raster mode (not vector)
"""
if not landscape:
self.Size_mm = (w, h) # (width, height) in mm
else:
@ -67,12 +106,37 @@ class Page(object):
self.Size[1] - self.Margins[0] - self.Margins[2])
self.Text_rect = (self.Margins[1], self.Margins[0], txs[0], txs[1])
class InvalidFormat(Exception): pass
class InvalidFormat(Exception):
"""exception thrown when an invalid output format is requested"""
pass
class PageWriter(Page):
"""class to output multiple pages in raster (png) or vector (pdf) format
@ivar base: out filename (without extension)
@ivar ext: filename extension (with dot)
@type curpage: int
@ivar curpage: current page
@ivar format: output format: L{PDF} or L{PNG}
@type keep_transparency: bool
@ivar keep_transparency: C{True} to use transparent instead of white fill color
@ivar img_format: C{cairo.FORMAT_ARGB32} or C{cairo.FORMAT_RGB24} depending on
L{keep_transparency}
@ivar Surface: cairo surface (set by L{_setup_surface_and_context})
@ivar cr: cairo context (set by L{_setup_surface_and_context})
"""
PDF = 0
PNG = 1
def __init__(self, filename, landscape = False, pagespec = None, b = 0.0, keep_transparency = True):
def __init__(self, filename, pagespec = None, keep_transparency = True, landscape = False, b = 0.0):
"""initialize PageWriter object
see also L{Page.__init__}
@param filename: output filename (extension determines format PDF or PNG)
@param pagespec: iso page spec, see L{page_spec}
@param keep_transparency: see L{keep_transparency}
"""
self.base,self.ext = splitext(filename)
self.filename = filename
self.curpage = 1
@ -90,19 +154,15 @@ class PageWriter(Page):
w, h = h, w
landscape = False
super(PageWriter,self).__init__(landscape, w, h, b, self.format == PageWriter.PNG)
self.setup_surface_and_context()
self._setup_surface_and_context()
def setup_surface_and_context(self):
if not self.landscape:
if self.format == PageWriter.PDF:
self.Surface = cairo.PDFSurface(self.filename, self.Size[0], self.Size[1])
else:
self.Surface = cairo.ImageSurface(self.img_format, int(self.Size[0]), int(self.Size[1]))
def _setup_surface_and_context(self):
"""setup cairo surface taking into account raster mode, transparency and landscape mode"""
z = int(self.landscape)
if self.format == PageWriter.PDF:
self.Surface = cairo.PDFSurface(self.filename, self.Size[z], self.Size[1-z])
else:
if self.format == PageWriter.PDF:
self.Surface = cairo.PDFSurface(self.filename, self.Size[1], self.Size[0])
else:
self.Surface = cairo.ImageSurface(self.img_format, int(self.Size[1]), int(self.Size[0]))
self.Surface = cairo.ImageSurface(self.img_format, int(self.Size[z]), int(self.Size[1-z]))
self.cr = cairo.Context(self.Surface)
if self.landscape:
@ -118,11 +178,13 @@ class PageWriter(Page):
self.cr.fill()
def end_page(self):
"""in PNG mode, output a separate file for each page"""
if self.format == PageWriter.PNG:
outfile = self.filename if self.curpage < 2 else self.base + "%02d" % (self.curpage) + self.ext
self.Surface.write_to_png(outfile)
def new_page(self):
"""setup next page"""
if self.format == PageWriter.PDF:
self.cr.show_page()
else:
@ -131,15 +193,29 @@ class PageWriter(Page):
def set_color(cr, rgba):
"""set stroke color
@param cr: cairo context
@type rgba: tuple
@param rgba: (r,g,b) or (r,g,b,a)
"""
if len(rgba) == 3:
cr.set_source_rgb(*rgba)
else:
cr.set_source_rgba(*rgba)
def extract_font_name(f):
"extract the font name from a string or from a tuple (fontname, slant, weight)"""
return f if type(f) is str else f[0]
def apply_rect(cr, rect, sdx = 0.0, sdy = 0.0, srot = 0.0):
def make_sloppy_rect(cr, rect, sdx = 0.0, sdy = 0.0, srot = 0.0):
"""slightly rotate and translate a rect to give it a sloppy look
@param cr: cairo context
@param sdx: maximum x-offset, true offset will be uniformly distibuted
@param sdy: maximum y-offset
@param sdy: maximum rotation
"""
x, y, w, h = rect
cr.save()
cr.translate(x,y)
@ -150,6 +226,15 @@ def apply_rect(cr, rect, sdx = 0.0, sdy = 0.0, srot = 0.0):
cr.translate(-w/2.0, -h/2.0)
def draw_shadow(cr, rect, thickness = None, shadow_color = (0,0,0,0.3)):
"""draw a shadow at the bottom-right corner of a rect
@param cr: cairo context
@param rect: tuple (x,y,w,h)
@param thickness: if C{None} nothing is drawn
@param shadow_color: shadow color
"""
if thickness is None: return
fx = mm_to_dots(thickness[0])
fy = mm_to_dots(thickness[1])
@ -172,6 +257,13 @@ def draw_shadow(cr, rect, thickness = None, shadow_color = (0,0,0,0.3)):
cr.close_path(); cr.fill();
def draw_line(cr, rect, stroke_rgba = None, stroke_width = 1.0):
"""draw a line from (x,y) to (x+w,y+h), where rect=(x,y,w,h)
@param cr: cairo context
@param rect: tuple (x,y,w,h)
@param stroke_rgba: stroke color
@param stroke_width: stroke width, if <= 0 nothing is drawn
"""
if (stroke_width <= 0): return
x, y, w, h = rect
cr.move_to(x, y)
@ -183,6 +275,15 @@ def draw_line(cr, rect, stroke_rgba = None, stroke_width = 1.0):
cr.stroke()
def draw_box(cr, rect, stroke_rgba = None, fill_rgba = None, stroke_width = 1.0, shadow = None):
"""draw a box (rectangle) with optional shadow
@param cr: cairo context
@param rect: box rectangle as tuple (x,y,w,h)
@param stroke_rgba: stroke color (set if not C{None})
@param fill_rgba: fill color (set if not C{None})
@param stroke_width: stroke width
@param shadow: shadow thickness
"""
if (stroke_width <= 0): return
draw_shadow(cr, rect, shadow)
x, y, w, h = rect
@ -199,8 +300,34 @@ def draw_box(cr, rect, stroke_rgba = None, fill_rgba = None, stroke_width = 1.0,
cr.set_line_width(stroke_width)
cr.stroke()
def draw_str(cr, text, rect, stretch = -1, stroke_rgba = None, align = (2,0), bbox = False,
def draw_str(cr, text, rect, scaling = -1, stroke_rgba = None, align = (2,0), bbox = False,
font = "Times", measure = None, shadow = None):
"""draw text
@param cr: cairo context
@param text: text string to be drawn
@type scaling: int
@param scaling: text scaling mode
- -1: auto select x-scaling or y-scaling (whatever fills the rect)
- 0: no scaling
- 1: x-scaling, scale so that text fills rect horizontally, preserving ratio
- 2: y-scaling, scale so that text fills rect vertically, preserving ratio
- 3: xy-scaling, stretch so that text fills rect completely, does not preserve ratio
@param stroke_rgba: stroke color
@type align: tuple
@param align: alignment mode as (int,int) tuple for horizontal/vertical alignment
- 0: left/top alignment
- 1: right/bottom alignment
- 2: center/middle alignment
@param bbox: draw text bounding box (for debugging)
@param font: font name as string or (font,slant,weight) tuple
@param measure: use this string for measurement instead of C{text}
@param shadow: draw text shadow as tuple (dx,dy)
"""
x, y, w, h = rect
cr.save()
slant = weight = 0
@ -216,13 +343,13 @@ def draw_str(cr, text, rect, stretch = -1, stroke_rgba = None, align = (2,0), bb
mw = 5.
if mh < 5:
mh = 5.
ratio, tratio = w*1.0/h, mw*1.0/mh;
#ratio, tratio = w*1.0/h, mw*1.0/mh;
xratio, yratio = mw*1.0/w, mh*1.0/h;
if stretch < 0: stretch = 1 if xratio >= yratio else 2
if stretch == 0: crs = (1,1)
elif stretch == 1: crs = (1.0/xratio, 1.0/xratio)
elif stretch == 2: crs = (1.0/yratio, 1.0/yratio)
elif stretch == 3: crs = (1.0/xratio, 1.0/yratio)
if scaling < 0: scaling = 1 if xratio >= yratio else 2
if scaling == 0: crs = (1,1)
elif scaling == 1: crs = (1.0/xratio, 1.0/xratio)
elif scaling == 2: crs = (1.0/yratio, 1.0/yratio)
elif scaling == 3: crs = (1.0/xratio, 1.0/yratio)
te = cr.text_extents(text)
tw,th = te[2], te[3]
tw *= crs[0]

View File

@ -16,15 +16,17 @@
# --- style.bw ---
# day of week
"""module defining the black-& white style"""
class dow:
"""day of week style"""
fg = (.33,.33,.33)
frame_thickness = 0.1
frame = (0.8,0.8,0.8)
font = "Arial"
# day of month
class dom:
"""day of month style"""
bg = (1,1,1)
frame = (0.8,0.8,0.8)
frame_thickness = 0.1
@ -35,27 +37,33 @@ class dom:
header_font = footer_font = "Arial"
class dom_weekend(dom):
"""day of month style (weekend)"""
bg = (0.95,0.95,0.95)
fg = (0,0,0)
font = ("Times New Roman", 0, 1)
class dom_holiday(dom):
"""day of month (holiday, indicated by the OFF flag in the holiday file)"""
fg = (0,0,0)
bg = (0.95,0.95,0.95)
header = (0,0,0)
font = ("Times New Roman", 0, 1)
class dom_weekend_holiday(dom_holiday):
"""day of month (weekend & holiday)"""
fg = (0,0,0)
bg = (0.95,0.95,0.95)
class dom_multi(dom_holiday):
"""day of month (multi-day holiday)"""
pass
class dom_weekend_multi(dom_weekend_holiday):
"""day of month (weekend in multi-day holiday)"""
pass
class month:
"""month style"""
font = ("Times New Roman", 0, 1)
frame = (0,0,0)
frame_thickness = 0.2

View File

@ -16,15 +16,17 @@
# --- style.default ---
# day of week
"""module defining the default style"""
class dow:
"""day of week style"""
fg = (0,0,1)
frame_thickness = 0.1
frame = (0.75,0.75,0.75)
font = "Arial"
# day of month
class dom:
"""day of month style"""
bg = (1,1,1)
frame = (0.75,0.75,0.75)
frame_thickness = 0.1
@ -35,25 +37,30 @@ class dom:
header_font = footer_font = "Arial"
class dom_weekend(dom):
"""day of month style (weekend)"""
bg = (0.7,1,1)
fg = (0,0,1)
# OFF flag
class dom_holiday(dom):
"""day of month (holiday, indicated by the OFF flag in the holiday file)"""
bg = (0.7,1,1)
fg = (1,0,0)
header = (1,0,0)
class dom_weekend_holiday(dom_holiday):
"""day of month (weekend & holiday)"""
pass
class dom_multi(dom):
"""day of month (multi-day holiday)"""
bg = (0.7,1,1)
class dom_weekend_multi(dom_multi):
"""day of month (weekend in multi-day holiday)"""
pass
class month:
"""month style"""
font = ("Times New Roman", 0, 1)
frame = (0,0,0)
frame_thickness = 0.2

View File

@ -14,7 +14,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see http://www.gnu.org/licenses/
# --- style.gfs (Greek Font Society fonts) ---
# --- style.gfs ---
"""module defining Greek Font Society fonts for default style"""
import default

View File

@ -16,6 +16,7 @@
# --- style.rainbow ---
"""module defining rainbow color & gfs style"""
import gfs

View File

@ -16,6 +16,7 @@
# --- style.rainbow ---
"""module defining rainbow color style"""
import default