mirror of
https://github.com/oDinZu/callirhoe.git
synced 2025-02-22 00:04:52 -05:00
0.3.0 release
holiday support (with Orthodox & Catholic Easter based holidays) fully documented code code refactoring, better layout separation sparse layout and minor patches {Neels} more languages: german, turkish git-svn-id: https://callirhoe.googlecode.com/svn/trunk@51 81c8bb96-aa45-f2e2-0eef-c4fa4a15c6df
This commit is contained in:
commit
ab38ed51f6
372
callirhoe.py
372
callirhoe.py
@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# callirhoe - high quality calendar rendering
|
||||
# Copyright (C) 2012 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
|
||||
@ -17,17 +17,24 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see http://www.gnu.org/licenses/
|
||||
|
||||
"""high quality calendar rendering"""
|
||||
|
||||
# TODO:
|
||||
|
||||
# default values for rows/cols depending on layout (classic/bars)
|
||||
# fix auto-measure rendering (cairo)
|
||||
# fix plugin loading (without global vars)
|
||||
# week markers selectable
|
||||
# test layouts
|
||||
|
||||
# allow to change background color (fill), other than white
|
||||
# page spec parse errors
|
||||
# mobile themes (e.g. 800x480)
|
||||
# photo support
|
||||
# implement DATA SOURCES
|
||||
# python source documentation
|
||||
# photo support (like ImageMagick's polaroid effect)
|
||||
# .callirhoe/config : default values for plugins (styles/layouts/lang...) and cmdline
|
||||
|
||||
# MAYBE-TODO:
|
||||
# implement various data sources
|
||||
# auto-landscape? should aim for matrix or bars?
|
||||
# allow /usr/bin/date-like formatting %x...
|
||||
# improve file matching with __init__ when lang known
|
||||
@ -38,19 +45,35 @@
|
||||
|
||||
# CANNOT UPGRADE TO argparse !!! -- how to handle [[month] year] form?
|
||||
|
||||
_version = "0.2.1.r15"
|
||||
_version = "0.2.2"
|
||||
|
||||
import calendar
|
||||
import sys
|
||||
import time
|
||||
import optparse
|
||||
import lib.xcairo as xcairo
|
||||
import lib.holiday as holiday
|
||||
|
||||
from lib.plugin import *
|
||||
# TODO: SEE IF IT CAN BE MOVED INTO lib.plugin ...
|
||||
def import_plugin(cat, longcat, longcat2, listopt, preset):
|
||||
def import_plugin(plugin_paths, cat, longcat, longcat2, listopt, preset):
|
||||
"""import a plugin making it visible
|
||||
|
||||
I{Example:}
|
||||
|
||||
>>> Language = import_plugin(get_plugin_paths(), "lang", "language", "languages", "--list-languages", "EN")
|
||||
|
||||
@param plugin_paths: list of plugin search paths
|
||||
@param cat: short category name (for folder name)
|
||||
@param longcat: long category name
|
||||
@param longcat2: long category name in plural form
|
||||
@param listopt: option name
|
||||
@param preset: default value
|
||||
|
||||
@note: Aimed for internal use with I{lang}, I{style}, I{geom}, I{layouts}.
|
||||
"""
|
||||
try:
|
||||
found = available_files(plugin_path[0], cat, preset) + available_files(plugin_path[1], cat, preset)
|
||||
found = available_files(plugin_paths[0], cat, preset) + available_files(plugin_paths[1], cat, preset)
|
||||
if len(found) == 0: raise IOError
|
||||
old = sys.path[0];
|
||||
sys.path[0] = found[0][1]
|
||||
@ -65,44 +88,19 @@ def import_plugin(cat, longcat, longcat2, listopt, preset):
|
||||
print >> sys.stderr, "error loading %s definition `%s'" % (longcat, preset)
|
||||
sys.exit(1)
|
||||
|
||||
parser = optparse.OptionParser(usage="usage: %prog [options] [[MONTH[-MONTH2|:SPAN]] YEAR] FILE",
|
||||
description="""High quality calendar rendering with vector graphics.
|
||||
By default, a calendar of the current year in pdf format is written to FILE.
|
||||
Alternatively, you can select a specific YEAR (0=current),
|
||||
and a month range from MONTH (0-12, 0=current) to MONTH2 or for SPAN months.
|
||||
""", version="callirhoe " + _version)
|
||||
parser.add_option("-l", "--lang", dest="lang", default="EN",
|
||||
help="choose language [%default]")
|
||||
parser.add_option("-t", "--layout", dest="layout", default="classic",
|
||||
help="choose layout [%default]")
|
||||
parser.add_option("-H", "--layout-help", dest="layouthelp", action="store_true", default=False,
|
||||
help="show layout-specific help")
|
||||
parser.add_option("--examples", dest="examples", action="store_true",
|
||||
help="display some usage examples")
|
||||
parser.add_option("-s", "--style", dest="style", default="default",
|
||||
help="choose style [%default]")
|
||||
parser.add_option("-g", "--geometry", dest="geom", default="default",
|
||||
help="choose geometry [%default]")
|
||||
parser.add_option("--landscape", action="store_true", dest="landscape", default=False,
|
||||
help="landscape mode")
|
||||
parser.add_option("--dpi", type="float", default=72.0,
|
||||
help="set DPI (for raster output) [%default]")
|
||||
parser.add_option("--paper", default="a4",
|
||||
help="set paper type; PAPER 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 [%default]")
|
||||
parser.add_option("--border", type="float", default=3,
|
||||
help="set border size (in mm) [%default]")
|
||||
|
||||
def print_examples():
|
||||
"""print usage examples"""
|
||||
print """Examples:
|
||||
|
||||
Create a calendar of the current year (by default in a 4x3 grid):
|
||||
$ callirhoe my_calendar.pdf
|
||||
|
||||
Same as above, but in landscape mode (3x4):
|
||||
Same as above, but in landscape mode (3x4) (for printing):
|
||||
$ callirhoe --landscape my_calendar.pdf
|
||||
|
||||
Landscape via rotation (for screen):
|
||||
$ callirhoe --paper=a4w --rows=3 my_calendar.pdf
|
||||
|
||||
Forcing 1 row only, we get month bars instead of boxes:
|
||||
$ callirhoe --landscape --rows=1 my_calendar.pdf
|
||||
|
||||
@ -120,95 +118,15 @@ and do some magic with ImageMagick! ;)
|
||||
"""
|
||||
|
||||
def add_list_option(parser, opt):
|
||||
"""add a --list-I{plugins} option to parser
|
||||
|
||||
@note: To be used with I{languages}, I{layouts}, I{styles} and I{geometries}.
|
||||
"""
|
||||
parser.add_option("--list-%s" % opt, action="store_true", dest="list_%s" % opt, default=False,
|
||||
help="list available %s" % opt)
|
||||
for x in ["languages", "layouts", "styles", "geometries"]:
|
||||
add_list_option(parser, x)
|
||||
|
||||
parser.add_option("--lang-var", action="append", dest="lang_assign",
|
||||
help="modify a language variable")
|
||||
parser.add_option("--style-var", action="append", dest="style_assign",
|
||||
help="modify a style variable, e.g. dom.frame_thickness=0")
|
||||
parser.add_option("--geom-var", action="append", dest="geom_assign",
|
||||
help="modify a geometry variable")
|
||||
|
||||
argv1 = []
|
||||
argv2 = []
|
||||
for x in sys.argv:
|
||||
y = x[0:x.find('=')] if '=' in x else x
|
||||
if x[0] == '-' and not parser.has_option(y):
|
||||
argv2.append(x)
|
||||
else:
|
||||
argv1.append(x)
|
||||
sys.argv = argv1
|
||||
|
||||
(options,args) = parser.parse_args()
|
||||
|
||||
if options.list_languages:
|
||||
for x in plugin_list("lang"): print x[0],
|
||||
print
|
||||
if options.list_styles:
|
||||
for x in plugin_list("style"): print x[0],
|
||||
print
|
||||
if options.list_geometries:
|
||||
for x in plugin_list("geom"): print x[0],
|
||||
print
|
||||
if options.list_layouts:
|
||||
for x in plugin_list("layouts"): print x[0],
|
||||
print
|
||||
if (options.list_languages or options.list_styles or
|
||||
options.list_geometries or options.list_layouts): sys.exit(0)
|
||||
|
||||
Language = import_plugin("lang", "language", "languages", "--list-languages", options.lang)
|
||||
Style = import_plugin("style", "style", "styles", "--list-styles", options.style)
|
||||
Geometry = import_plugin("geom", "geometry", "geometries", "--list-geometries", options.geom)
|
||||
Layout = import_plugin("layouts", "layout", "layouts", "--list-layouts", options.layout)
|
||||
|
||||
for x in argv2:
|
||||
if '=' in x: x = x[0:x.find('=')]
|
||||
if not Layout.parser.has_option(x):
|
||||
parser.error("invalid option %s; use --help (-h) or --layout-help (-H) to see available options" % x)
|
||||
|
||||
(Layout.options,largs) = Layout.parser.parse_args(argv2)
|
||||
if options.layouthelp:
|
||||
#print "Help for layout:", options.layout
|
||||
Layout.parser.print_help()
|
||||
sys.exit(0)
|
||||
|
||||
if options.examples:
|
||||
print_examples()
|
||||
sys.exit(0)
|
||||
|
||||
# we can put it separately together with Layout; but we load Layout *after* lang,style,geom
|
||||
if len(args) < 1 or len(args) > 3:
|
||||
parser.print_help()
|
||||
sys.exit(0)
|
||||
|
||||
#if (len(args[-1]) == 4 and args[-1].isdigit()):
|
||||
# print "WARNING: file name `%s' looks like a year, writing anyway..." % args[-1]
|
||||
|
||||
# the usual "beware of exec()" crap applies here... but come on,
|
||||
# this is a SCRIPTING language, you can always hack the source code!!!
|
||||
if options.lang_assign:
|
||||
for x in options.lang_assign: exec "Language.%s" % x
|
||||
if options.style_assign:
|
||||
for x in options.style_assign: exec "Style.%s" % x
|
||||
if options.geom_assign:
|
||||
for x in options.geom_assign: exec "Geometry.%s" % x
|
||||
|
||||
calendar.month_name = Language.month_name
|
||||
calendar.day_name = Language.day_name
|
||||
|
||||
def get_orthodox_easter(y):
|
||||
y1, y2, y3 = y - y//4 * 4, y - y//7 * 7, y - y//19 * 19
|
||||
a = 19*y3 + 15
|
||||
y4 = a - a//30 * 30
|
||||
b = 2*y1 + 4*y2 + 6*(y4 + 1)
|
||||
y5 = b - b/7 * 7
|
||||
r = 1 + 3 + y4 + y5;
|
||||
return (5, r - 30) if r > 30 else (4,r)
|
||||
|
||||
def itoa(s):
|
||||
"""convert integer to string, exiting on error (for cmdline parsing)"""
|
||||
try:
|
||||
k = int(s);
|
||||
except ValueError as e:
|
||||
@ -216,46 +134,192 @@ def itoa(s):
|
||||
return k
|
||||
|
||||
def parse_month(mstr):
|
||||
"""get a month value (0-12) from I{mstr}, exiting on error (for cmdline parsing)"""
|
||||
m = itoa(mstr)
|
||||
if m < 1: m = time.localtime()[1]
|
||||
elif m > 12: sys.exit("invalid month value `" + str(mstr) + "'")
|
||||
if m == 0: m = time.localtime()[1]
|
||||
elif m > 12 or m < 0: sys.exit("invalid month value `" + str(mstr) + "'")
|
||||
return m
|
||||
|
||||
def parse_year(ystr):
|
||||
"""get a year value (>=0) from I{ystr}, exiting on error (for cmdline parsing)"""
|
||||
y = itoa(ystr)
|
||||
if y == 0: y = time.localtime()[0]
|
||||
elif y < 0: sys.exit("invalid year value `" + str(ystr) + "'")
|
||||
return y
|
||||
|
||||
if len(args) == 1:
|
||||
Year = time.localtime()[0]
|
||||
Month, MonthSpan = 1, 12
|
||||
Outfile = args[0]
|
||||
elif len(args) == 2:
|
||||
Year = parse_year(args[0])
|
||||
Month, MonthSpan = 1, 12
|
||||
Outfile = args[1]
|
||||
elif len(args) == 3:
|
||||
if ':' in args[0]:
|
||||
t = args[0].split(':')
|
||||
if len(t) != 2: sys.exit("invalid month range `" + args[0] + "'")
|
||||
Month = parse_month(t[0])
|
||||
MonthSpan = itoa(t[1])
|
||||
if MonthSpan < 0: sys.exit("invalid month range `" + args[0] + "'")
|
||||
elif '-' in args[0]:
|
||||
t = args[0].split('-')
|
||||
if len(t) != 2: sys.exit("invalid month range `" + args[0] + "'")
|
||||
Month = parse_month(t[0])
|
||||
MonthSpan = itoa(t[1]) - Month + 1
|
||||
if MonthSpan < 0: sys.exit("invalid month range `" + args[0] + "'")
|
||||
if __name__ == "__main__":
|
||||
parser = optparse.OptionParser(usage="usage: %prog [options] [[MONTH[-MONTH2|:SPAN]] YEAR] FILE",
|
||||
description="""High quality calendar rendering with vector graphics.
|
||||
By default, a calendar of the current year in pdf format is written to FILE.
|
||||
Alternatively, you can select a specific YEAR (0=current),
|
||||
and a month range from MONTH (0-12, 0=current) to MONTH2 or for SPAN months.
|
||||
""", version="callirhoe " + _version)
|
||||
parser.add_option("-l", "--lang", dest="lang", default="EN",
|
||||
help="choose language [%default]")
|
||||
parser.add_option("-t", "--layout", dest="layout", default="classic",
|
||||
help="choose layout [%default]")
|
||||
parser.add_option("-?", "--layout-help", dest="layouthelp", action="store_true", default=False,
|
||||
help="show layout-specific help")
|
||||
parser.add_option("--examples", dest="examples", action="store_true",
|
||||
help="display some usage examples")
|
||||
parser.add_option("-s", "--style", dest="style", default="default",
|
||||
help="choose style [%default]")
|
||||
parser.add_option("-g", "--geometry", dest="geom", default="default",
|
||||
help="choose geometry [%default]")
|
||||
parser.add_option("--landscape", action="store_true", dest="landscape", default=False,
|
||||
help="landscape mode")
|
||||
parser.add_option("--dpi", type="float", default=72.0,
|
||||
help="set DPI (for raster output) [%default]")
|
||||
parser.add_option("--paper", default="a4",
|
||||
help="set paper type; PAPER 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 [%default]")
|
||||
parser.add_option("--border", type="float", default=3,
|
||||
help="set border size (in mm) [%default]")
|
||||
parser.add_option("-H", "--with-holidays", action="append", dest="holidays",
|
||||
help="load holiday file (can be used multiple times)")
|
||||
parser.add_option("--short-monthnames", action="store_true", default=False,
|
||||
help="user the short version of month names (defined in language file) [%default]")
|
||||
parser.add_option("--long-daynames", action="store_true", default=False,
|
||||
help="user the long version of day names (defined in language file) [%default]")
|
||||
parser.add_option("-T", "--terse-holidays", action="store_false", dest="multiday_holidays",
|
||||
default=True, help="do not print holiday end markers and omit dots")
|
||||
|
||||
for x in ["languages", "layouts", "styles", "geometries"]:
|
||||
add_list_option(parser, x)
|
||||
|
||||
parser.add_option("--lang-var", action="append", dest="lang_assign",
|
||||
help="modify a language variable")
|
||||
parser.add_option("--style-var", action="append", dest="style_assign",
|
||||
help="modify a style variable, e.g. dom.frame_thickness=0")
|
||||
parser.add_option("--geom-var", action="append", dest="geom_assign",
|
||||
help="modify a geometry variable")
|
||||
|
||||
argv1 = []
|
||||
argv2 = []
|
||||
for x in sys.argv:
|
||||
y = x[0:x.find('=')] if '=' in x else x
|
||||
if x[0] == '-' and not parser.has_option(y):
|
||||
argv2.append(x)
|
||||
else:
|
||||
argv1.append(x)
|
||||
sys.argv = argv1
|
||||
|
||||
(options,args) = parser.parse_args()
|
||||
|
||||
list_and_exit = False
|
||||
if options.list_languages:
|
||||
for x in plugin_list("lang"): print x[0],
|
||||
print
|
||||
list_and_exit = True
|
||||
if options.list_styles:
|
||||
for x in plugin_list("style"): print x[0],
|
||||
print
|
||||
list_and_exit = True
|
||||
if options.list_geometries:
|
||||
for x in plugin_list("geom"): print x[0],
|
||||
print
|
||||
list_and_exit = True
|
||||
if options.list_layouts:
|
||||
for x in plugin_list("layouts"): print x[0],
|
||||
print
|
||||
list_and_exit = True
|
||||
if list_and_exit: sys.exit(0)
|
||||
|
||||
plugin_paths = get_plugin_paths()
|
||||
Language = import_plugin(plugin_paths, "lang", "language", "languages", "--list-languages", options.lang)
|
||||
Style = import_plugin(plugin_paths, "style", "style", "styles", "--list-styles", options.style)
|
||||
Geometry = import_plugin(plugin_paths, "geom", "geometry", "geometries", "--list-geometries", options.geom)
|
||||
Layout = import_plugin(plugin_paths, "layouts", "layout", "layouts", "--list-layouts", options.layout)
|
||||
|
||||
for x in argv2:
|
||||
if '=' in x: x = x[0:x.find('=')]
|
||||
if not Layout.parser.has_option(x):
|
||||
parser.error("invalid option %s; use --help (-h) or --layout-help (-?) to see available options" % x)
|
||||
|
||||
(loptions,largs) = Layout.parser.parse_args(argv2)
|
||||
|
||||
if options.layouthelp:
|
||||
#print "Help for layout:", options.layout
|
||||
Layout.parser.print_help()
|
||||
sys.exit(0)
|
||||
|
||||
if options.examples:
|
||||
print_examples()
|
||||
sys.exit(0)
|
||||
|
||||
# we can put it separately together with Layout; but we load Layout *after* lang,style,geom
|
||||
if len(args) < 1 or len(args) > 3:
|
||||
parser.print_help()
|
||||
sys.exit(0)
|
||||
|
||||
#if (len(args[-1]) == 4 and args[-1].isdigit()):
|
||||
# print "WARNING: file name `%s' looks like a year, writing anyway..." % args[-1]
|
||||
|
||||
# the usual "beware of exec()" crap applies here... but come on,
|
||||
# this is a SCRIPTING language, you can always hack the source code!!!
|
||||
if options.lang_assign:
|
||||
for x in options.lang_assign: exec "Language.%s" % x
|
||||
if options.style_assign:
|
||||
for x in options.style_assign: exec "Style.%s" % x
|
||||
if options.geom_assign:
|
||||
for x in options.geom_assign: exec "Geometry.%s" % x
|
||||
|
||||
calendar.long_month_name = Language.long_month_name
|
||||
calendar.long_day_name = Language.long_day_name
|
||||
calendar.short_month_name = Language.short_month_name
|
||||
calendar.short_day_name = Language.short_day_name
|
||||
|
||||
if len(args) == 1:
|
||||
Year = time.localtime()[0]
|
||||
Month, MonthSpan = 1, 12
|
||||
Outfile = args[0]
|
||||
elif len(args) == 2:
|
||||
Year = parse_year(args[0])
|
||||
Month, MonthSpan = 1, 12
|
||||
Outfile = args[1]
|
||||
elif len(args) == 3:
|
||||
if ':' in args[0]:
|
||||
t = args[0].split(':')
|
||||
if len(t) != 2: sys.exit("invalid month range `" + args[0] + "'")
|
||||
Month = parse_month(t[0])
|
||||
MonthSpan = itoa(t[1])
|
||||
if MonthSpan < 0: sys.exit("invalid month range `" + args[0] + "'")
|
||||
elif '-' in args[0]:
|
||||
t = args[0].split('-')
|
||||
if len(t) != 2: sys.exit("invalid month range `" + args[0] + "'")
|
||||
Month = parse_month(t[0])
|
||||
MonthSpan = itoa(t[1]) - Month + 1
|
||||
if MonthSpan < 0: sys.exit("invalid month range `" + args[0] + "'")
|
||||
else:
|
||||
Month = parse_month(args[0])
|
||||
MonthSpan = 1
|
||||
Year = parse_year(args[1])
|
||||
Outfile = args[2]
|
||||
|
||||
Geometry.landscape = options.landscape
|
||||
xcairo.XDPI = options.dpi
|
||||
Geometry.pagespec = options.paper
|
||||
Geometry.border = options.border
|
||||
|
||||
hprovider = holiday.HolidayProvider(Style.dom, Style.dom_weekend,
|
||||
Style.dom_holiday, Style.dom_weekend_holiday,
|
||||
Style.dom_multi, Style.dom_weekend_multi, options.multiday_holidays)
|
||||
|
||||
if options.holidays:
|
||||
for f in options.holidays:
|
||||
hprovider.load_holiday_file(f)
|
||||
|
||||
if options.long_daynames:
|
||||
Language.day_name = Language.long_day_name
|
||||
else:
|
||||
Month = parse_month(args[0])
|
||||
MonthSpan = 1
|
||||
Year = parse_year(args[1])
|
||||
Outfile = args[2]
|
||||
Language.day_name = Language.short_day_name
|
||||
|
||||
Geometry.landscape = options.landscape
|
||||
xcairo.XDPI = options.dpi
|
||||
Geometry.pagespec = options.paper
|
||||
Geometry.border = options.border
|
||||
if options.short_monthnames:
|
||||
Language.month_name = Language.short_month_name
|
||||
else:
|
||||
Language.month_name = Language.long_month_name
|
||||
|
||||
Layout.draw_calendar(Outfile, Year, Month, MonthSpan, (Style,Geometry), _version)
|
||||
renderer = Layout.CalendarRenderer(Outfile, Year, Month, MonthSpan,
|
||||
(Style,Geometry,Language), hprovider, _version, loptions)
|
||||
renderer.render()
|
||||
|
@ -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
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
# --- geom.sloppy ---
|
||||
|
||||
"""module defining the sloppy geometry"""
|
||||
|
||||
import default
|
||||
|
||||
class dom(default.dom): pass
|
||||
|
33
holidays/french_holidays.EN.dat
Normal file
33
holidays/french_holidays.EN.dat
Normal file
@ -0,0 +1,33 @@
|
||||
# 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}
|
||||
|
||||
d|0101||New year's|off
|
||||
d|0501||Labour day|off
|
||||
d|0508||Victory in Europe|off
|
||||
d|0714||Bastille|off
|
||||
d|0815||Assumption of Mary|off
|
||||
d|1101||All Saints|off
|
||||
d|1111||Armistice|off
|
||||
d|1225||Christmas|off
|
||||
d|1226||St. Stephen's|off
|
||||
|
||||
ce|-2||Good Friday|
|
||||
ce|0||Easter|off
|
||||
ce|1||Easter Monday|off
|
||||
ce|39||Ascension|off
|
||||
ce|50||Whit Monday|off
|
||||
|
||||
d|20130223-20130310|winter vacations (B)||multi
|
||||
d|20140222-20140309|winter vacations (B)||multi
|
33
holidays/french_holidays.FR.dat
Normal file
33
holidays/french_holidays.FR.dat
Normal file
@ -0,0 +1,33 @@
|
||||
# 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}
|
||||
|
||||
d|0101||Nouvel an|off
|
||||
d|0501||Fête du Travail|off
|
||||
d|0508||Fête de la Victoire|off
|
||||
d|0714||Fête nationale|off
|
||||
d|0815||Assomption|off
|
||||
d|1101||Toussaint|off
|
||||
d|1111||Armistice de 1918|off
|
||||
d|1225||Noël|off
|
||||
d|1226||Saint-Étienne|off
|
||||
|
||||
ce|-2||Vendredi saint|
|
||||
ce|0||Pâques|off
|
||||
ce|1||Lundi de Pâques|off
|
||||
ce|39||Ascension|off
|
||||
ce|50||Lundi de Pentecôte|off
|
||||
|
||||
d|20130223-20130310|vacances d'hiver (B)||multi
|
||||
d|20140222-20140309|vacances d'hiver (B)||multi
|
20
holidays/generic_holidays.EN.dat
Normal file
20
holidays/generic_holidays.EN.dat
Normal file
@ -0,0 +1,20 @@
|
||||
# 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}
|
||||
|
||||
d|0101||New year's|off
|
||||
d|0501||Labour day|off
|
||||
d|1225||Christmas|off
|
||||
ce|0||Easter|off
|
||||
oe|0||Orthodox Easter|off
|
39
holidays/greek_holidays.EL.dat
Normal file
39
holidays/greek_holidays.EL.dat
Normal file
@ -0,0 +1,39 @@
|
||||
# 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}
|
||||
|
||||
d|0101||ΠΡΩΤΟΧΡΟΝΙΑ|off
|
||||
d|0106||ΘΕΟΦΑΝΕΙΑ|off
|
||||
d|0325||ΕΥΑΓΓΕΛΙΣΜΟΣ|off
|
||||
d|0501||ΠΡΩΤΟΜΑΓΙΑ|off
|
||||
d|0815||ΚΟΙΜΗΣΗ ΘΕΟΤΟΚΟΥ|off
|
||||
d|1028||ΕΠΕΤΕΙΟΣ ΤΟΥ «ΟΧΙ»|off
|
||||
d|1117||ΠΟΛΥΤΕΧΝΕΙΟ|
|
||||
d|1225||ΧΡΙΣΤΟΥΓΕΝΝΑ|off
|
||||
d|1226||2η ΜΕΡΑ ΧΡΙΣΤΟΥΓ.|off
|
||||
|
||||
oe|-70||Αρχή Τριωδίου|off
|
||||
oe|-59||Τσικνοπέμπτη|
|
||||
oe|-56||Της Απόκρεω|
|
||||
oe|-49||Της Τυροφάγου|off
|
||||
oe|-48||ΚΑΘΑΡΑ ΔΕΥΤΕΡΑ|off
|
||||
oe|-8||Σαβ. Λαζάρου|
|
||||
oe|-7||Κυρ. Βαϊων|
|
||||
oe|-2||ΜΕΓ. ΠΑΡΑΣΚΕΥΗ|off
|
||||
oe|0||ΑΓΙΟ ΠΑΣΧΑ|off
|
||||
oe|1||2η ΜΕΡΑ ΠΑΣΧΑ|off
|
||||
oe|49||ΠΕΝΤΗΚΟΣΤΗ|
|
||||
oe|50||ΑΓΙΟΥ ΠΝΕΥΜΑΤΟΣ|off
|
||||
|
||||
#ce|0|Καθολικό Πάσχα||
|
70
holidays/greek_namedays.EL.dat
Normal file
70
holidays/greek_namedays.EL.dat
Normal file
@ -0,0 +1,70 @@
|
||||
# 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}
|
||||
|
||||
d|0101|Βασίλης||
|
||||
d|0106|Φώτης, Φάνης, Ιορδάνης||
|
||||
d|0107|Ιωάννης Πρόδρομος||
|
||||
d|0117|Αντώνης, Γιώργος||
|
||||
d|0118|Αθανάσιος, Κύριλλος||
|
||||
d|0120|Ευθύμιος||
|
||||
d|0125|Γρηγόρης||
|
||||
d|0201|Τρύφων, Αναστάσιος||
|
||||
d|0207|Λουκάς||
|
||||
d|0210|Χαράλαμπος, Χαρίκλεια, Ζήνων||
|
||||
d|0214|Βαλεντίνος||
|
||||
d|0325|Ευάγγελος||
|
||||
d|0507|Ειρήνη||
|
||||
d|0509|Χριστόφορος||
|
||||
d|0521|Κωνσταντίνος, Ελένη||
|
||||
d|0630|Απόστολος||
|
||||
d|0701|Κοσμάς, Διαμιανός, Ανάργυρος||
|
||||
d|0711|Όλγα, Ευφημία||
|
||||
d|0717|Μαρίνα||
|
||||
d|0720|Ηλίας||
|
||||
d|0726|Παρασκευή||
|
||||
d|0727|Παντελεήμων||
|
||||
d|0806|Σωτήρης||
|
||||
d|0815|Παναγιώτης, Μαρία, Δέσποινα||
|
||||
d|0830|Αλέξανδρος||
|
||||
d|0914|Σταύρος||
|
||||
d|0917|Σοφία, Αγάπη, Ελπίδα||
|
||||
d|0920|Ευστάθιος||
|
||||
d|1026|Δημήτριος||
|
||||
d|1101|Κοσμάς, Δαμιανός, Ανάργυρος||
|
||||
d|1108|Άγγελος, Μιχ, Γαβρ, Ταξιάρ.||
|
||||
d|1109|Νεκτάριος||
|
||||
d|1110|Ορέστης||
|
||||
d|1114|Φίλιππος||
|
||||
d|1121|Εισόδια Θεοτόκου (Μαρία)||
|
||||
d|1125|Αικατερίνη, Μερκούριος||
|
||||
d|1126|Στυλιανός||
|
||||
d|1130|Ανδρέας||
|
||||
d|1204|Βαρβάρα||
|
||||
d|1205|Σάββας, Διογένης||
|
||||
d|1206|Νικόλαος||
|
||||
d|1209|Άννα||
|
||||
d|1212|Σπυρίδων||
|
||||
d|1215|Ελευθέριος, Ελευθερία||
|
||||
d|1222|Αναστασία||
|
||||
d|1224|Ευγενία||
|
||||
d|1225|Χρήστος, Χριστίνα||
|
||||
d|1227|Στέφανος||
|
||||
|
||||
oe|-43|Αγίων Θεοδώρων||
|
||||
oe|0|Αναστάσιος, Πασχάλης, Λάμπρος||
|
||||
oe|-8|Λάζαρος||
|
||||
oe|-7|Βάιος||
|
||||
oe|7|Θωμάς||
|
||||
oe|56|Αγίων Πάντων||
|
36
lang/DE.py
Normal file
36
lang/DE.py
Normal file
@ -0,0 +1,36 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# callirhoe - high quality calendar rendering
|
||||
# Copyright (C) 2012 George M. Tzoumas
|
||||
|
||||
""" German language definition file"""
|
||||
# Copyright (C) 2013 Neels Hofmeyr
|
||||
|
||||
# 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 3 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, see http://www.gnu.org/licenses/
|
||||
|
||||
long_day_name = [ u'Montag', u'Dienstag', u'Mittwoch',
|
||||
u'Donnerstag', u'Freitag', u'Samstag', u'Sonntag' ]
|
||||
|
||||
short_day_name = [ u'Mo', u'Di', u'Mi', u'Do', u'Fr', u'Sa', u'So' ]
|
||||
|
||||
long_month_name = [ '',
|
||||
u'Januar', u'Februar', u'März', u'April',
|
||||
u'Mai', u'Juni', u'Juli', u'August',
|
||||
u'September', u'Oktober', u'November', u'Dezember' ]
|
||||
|
||||
short_month_name = [ '',
|
||||
u'Jan', u'Feb', u'Mrz', u'Apr', u'Mai', u'Jun',
|
||||
u'Jul', u'Aug', u'Sep', u'Okt', u'Nov', u'Dez' ]
|
||||
|
||||
week_of_year_prefix = u'W'
|
10
lang/EL.py
10
lang/EL.py
@ -16,14 +16,18 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see http://www.gnu.org/licenses/
|
||||
|
||||
day_name = [ u'Δευτέρα', u'Τρίτη', u'Τετάρτη',
|
||||
"""Greek language definition file"""
|
||||
|
||||
long_day_name = [ u'Δευτέρα', u'Τρίτη', u'Τετάρτη',
|
||||
u'Πέμπτη', u'Παρασκευή', u'Σάββατο', u'Κυριακή' ]
|
||||
|
||||
short_day_name = [ u'Δευ', u'Τρι', u'Τετ', u'Πεμ', u'Παρ', u'Σαβ', u'Κυρ' ]
|
||||
short_day_name = [ u'Δε', u'Τρ', u'Τε', u'Πε', u'Πα', u'Σα', u'Κυ' ]
|
||||
|
||||
month_name = [ '', u'Ιανουάριος', u'Φεβρουάριος', u'Μάρτιος', u'Απρίλιος',
|
||||
long_month_name = [ '', u'Ιανουάριος', u'Φεβρουάριος', u'Μάρτιος', u'Απρίλιος',
|
||||
u'Μάιος', u'Ιούνιος', u'Ιούλιος', u'Αύγουστος',
|
||||
u'Σεπτέμβριος', u'Οκτώβριος', u'Νοέμβριος', u'Δεκέμβριος' ]
|
||||
|
||||
short_month_name = [ '', u'Ιαν', u'Φεβ', u'Μαρ', u'Απρ', u'Μαϊ', u'Ιον', u'Ιολ',
|
||||
u'Αυγ', u'Σεπ', u'Οκτ', u'Νοε', u'Δεκ' ]
|
||||
|
||||
week_of_year_prefix = u'Ε'
|
||||
|
12
lang/EN.py
12
lang/EN.py
@ -16,12 +16,14 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see http://www.gnu.org/licenses/
|
||||
|
||||
day_name = [ u'Monday', u'Tuesday', u'Wednesday',
|
||||
u'Thursday', u'Friday', u'Saturday', u'Sunday' ]
|
||||
"""English language definition file"""
|
||||
|
||||
short_day_name = [ u'Mon', u'Tue', u'Wed', u'Thu', u'Fri', u'Sat', u'Sun' ]
|
||||
long_day_name = [ u'Monday', u'Tuesday', u'Wednesday',
|
||||
u'Thursday', u'Friday', u'Saturday', u'Sunday' ]
|
||||
|
||||
month_name = [ '',
|
||||
short_day_name = [ u'Mo', u'Tu', u'We', u'Th', u'Fr', u'Sa', u'Su' ]
|
||||
|
||||
long_month_name = [ '',
|
||||
u'January', u'February', u'March', u'April',
|
||||
u'May', u'June', u'July', u'August',
|
||||
u'September', u'October', u'November', u'December' ]
|
||||
@ -29,3 +31,5 @@ month_name = [ '',
|
||||
short_month_name = [ '',
|
||||
u'Jan', u'Feb', u'Mar', u'Apr', u'May', u'Jun',
|
||||
u'Jul', u'Aug', u'Sep', u'Oct', u'Nov', u'Dec' ]
|
||||
|
||||
week_of_year_prefix = u'W'
|
||||
|
10
lang/FR.py
10
lang/FR.py
@ -16,15 +16,19 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see http://www.gnu.org/licenses/
|
||||
|
||||
day_name = [ u'Lundi', u'Mardi', u'Mercredi',
|
||||
"""French language definition file"""
|
||||
|
||||
long_day_name = [ u'Lundi', u'Mardi', u'Mercredi',
|
||||
u'Jeudi', u'Vendredi', u'Samedi', u'Dimanche' ]
|
||||
|
||||
short_day_name = [ u'Lun', u'Mar', u'Mer', u'Jeu', u'Ven', u'Sam', u'Dim' ]
|
||||
short_day_name = [ u'Lu', u'Ma', u'Me', u'Je', u'Ve', u'Sa', u'Di' ]
|
||||
|
||||
month_name = [ '',
|
||||
long_month_name = [ '',
|
||||
u'Janvier', u'Février', u'Mars', u'Avril',
|
||||
u'Mai', u'Juin', u'Juillet', u'Août',
|
||||
u'Septembre', u'Octobre', u'Novembre', u'Décembre' ]
|
||||
|
||||
short_month_name = [ '', u'Jan', u'Fév', u'Mar', u'Avr', u'Mai', u'Jun', u'Jul',
|
||||
u'Aoû', u'Sep', u'Oct', u'Nov', u'Déc' ]
|
||||
|
||||
week_of_year_prefix = u'S'
|
||||
|
33
lang/TR.py
Normal file
33
lang/TR.py
Normal file
@ -0,0 +1,33 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# callirhoe - high quality calendar rendering
|
||||
# Copyright (C) 2012 George M. Tzoumas
|
||||
|
||||
""" Turkish language definition file"""
|
||||
# Copyright (C) 2013 Ece Neslihan Aybeke
|
||||
|
||||
# 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 3 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, see http://www.gnu.org/licenses/
|
||||
|
||||
long_day_name = [ u'Pazartesi', u'Salı', u' Çarşamba ',
|
||||
u'Perşembe', u'Cuma', u'Cumartesi', u'Pazar' ]
|
||||
|
||||
short_day_name = [ u'Pt', u'Sa', u'Ça', u'Pe', u'Cu', u'Ct', u'Pa' ]
|
||||
|
||||
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' ]
|
||||
|
||||
short_month_name = long_month_name
|
||||
|
||||
week_of_year_prefix = u'H'
|
304
layouts/_base.py
Normal file
304
layouts/_base.py
Normal file
@ -0,0 +1,304 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# callirhoe - high quality calendar rendering
|
||||
# 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
|
||||
# the Free Software Foundation, either version 3 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, see http://www.gnu.org/licenses/
|
||||
|
||||
"""base layout module -- others may inherit from this one"""
|
||||
|
||||
import optparse
|
||||
from lib.xcairo import *
|
||||
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]")
|
||||
parser.add_option("--cols", type="int", default=0,
|
||||
help="force grid columns [%default]; if ROWS and COLS are both non-zero, "
|
||||
"calendar will span multiple pages as needed; if one value is zero, it "
|
||||
"will be computed automatically in order to fill exactly 1 page")
|
||||
parser.add_option("--grid-order", choices=["row","column"],default="row",
|
||||
help="either `row' or `column' to set grid placing order row-wise or column-wise [%default]")
|
||||
parser.add_option("--z-order", choices=["auto", "increasing", "decreasing"], default="auto",
|
||||
help="either `increasing' or `decreasing' to set whether next month (in grid order) "
|
||||
"lies above or below the previously drawn month; this affects shadow casting, "
|
||||
"since rendering is always performed in increasing z-order; specifying `auto' "
|
||||
"selects increasing order if and only if sloppy boxes are enabled [%default]")
|
||||
parser.add_option("--month-with-year", action="store_true", default=False,
|
||||
help="displays year together with month name, e.g. January 1980; suppresses year from footer line")
|
||||
parser.add_option("--long-daycells", action="store_const", const=0.0, dest="short_daycell_ratio",
|
||||
help="force use of only long daycells")
|
||||
parser.add_option("--short-daycells", action="store_const", const=1.0e6, dest="short_daycell_ratio",
|
||||
help="force use of only short daycells")
|
||||
parser.add_option("--short-daycell-ratio", type="float", default=2.5,
|
||||
help="ratio threshold for day cells below which short version is drawn [%default]")
|
||||
parser.add_option("--no-footer", action="store_true", default=False,
|
||||
help="disable footer line (with year and rendered-by message)")
|
||||
parser.add_option("--symmetric", action="store_true", default=False,
|
||||
help="force symmetric mode (equivalent to --geom-var=month.symmetric=1). "
|
||||
"In symmetric mode, day cells are equally sized and all month boxes contain "
|
||||
"the same number of (possibly empty) cells, independently of how many days or "
|
||||
"weeks per month. In asymmetric mode, empty rows are eliminated, by slightly "
|
||||
"resizing day cells, in order to have uniform month boxes.")
|
||||
parser.add_option("--padding", type="float", default=None,
|
||||
help="set month box padding (equivalent to --geom-var=month.padding=PADDING); "
|
||||
"month bars look better with smaller padding, while matrix mode looks better with "
|
||||
"larger padding")
|
||||
parser.add_option("--no-shadow", action="store_true", default=None,
|
||||
help="disable box shadows")
|
||||
parser.add_option("--opaque", action="store_true", default=False,
|
||||
help="make background opaque (white fill)")
|
||||
parser.add_option("--swap-colors", action="store_true", default=None,
|
||||
help="swap month colors for even/odd years")
|
||||
return parser
|
||||
|
||||
|
||||
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 class,Geometry class,Language module) 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
|
||||
self.footer = footer
|
||||
self.theme = theme
|
||||
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
|
||||
draw_box(cr, rect, S.frame, S.bg, mm_to_dots(S.frame_thickness))
|
||||
R = rect_rel_scale(rect, G.size[0], G.size[1])
|
||||
if self.show_day_name:
|
||||
Rdom, Rdow = rect_hsplit(R, *G.mw_split)
|
||||
else:
|
||||
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, 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, 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, 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, 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
|
||||
draw_box(cr, rect, S.frame, S.bg, mm_to_dots(S.frame_thickness))
|
||||
R1, Rhf = rect_hsplit(rect, *G.hf_hsplit)
|
||||
if self.show_day_name:
|
||||
R = rect_rel_scale(R1, G.size[2], G.size[3])
|
||||
Rdom, Rdow = rect_hsplit(R, *G.mw_split)
|
||||
else:
|
||||
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, 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, 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, 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, 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:
|
||||
self._draw_long(cr, rect)
|
||||
|
||||
|
||||
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 module,Geometry module,Language module) 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
|
||||
self.Month = Month
|
||||
self.MonthSpan = MonthSpan
|
||||
self.Theme = Theme
|
||||
self.holiday_provider = holiday_provider
|
||||
self.version_string = version_string
|
||||
self.options = options
|
||||
|
||||
def _draw_month(self, cr, rect, month, year):
|
||||
"""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
|
||||
"""
|
||||
raise NotImplementedError("base _draw_month() should be overridden")
|
||||
|
||||
#1 1 1
|
||||
#2 2 1
|
||||
#3 3 1
|
||||
|
||||
#4 2 2
|
||||
#5 3 2
|
||||
#6 3 2
|
||||
#7 4 2
|
||||
#8 4 2
|
||||
|
||||
#9 3 3
|
||||
#10 4 3
|
||||
#11 4 3
|
||||
#12 4 3
|
||||
|
||||
#rows = 0
|
||||
#cols = 0
|
||||
def render(self):
|
||||
"""main calendar rendering routine"""
|
||||
S,G,L = self.Theme
|
||||
rows, cols = self.options.rows, self.options.cols
|
||||
|
||||
if self.options.symmetric:
|
||||
G.month.symmetric = True
|
||||
if self.options.padding is not None:
|
||||
G.month.padding = self.options.padding
|
||||
if self.options.no_shadow == True:
|
||||
S.month.box_shadow = False
|
||||
if self.Year % 2: self.options.swap_colors = not self.options.swap_colors
|
||||
if self.options.swap_colors:
|
||||
S.month.color_map_bg = (S.month.color_map_bg[1], S.month.color_map_bg[0])
|
||||
S.month.color_map_fg = (S.month.color_map_fg[1], S.month.color_map_fg[0])
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
if rows == 0 and cols == 0:
|
||||
# if MonthSpan < 4:
|
||||
# cols = 1; rows = MonthSpan
|
||||
# elif MonthSpan < 9:
|
||||
# cols = 2; rows = int(math.ceil(MonthSpan/2.0))
|
||||
# else:
|
||||
# TODO: improve this heuristic
|
||||
cols = int(floor(sqrt(self.MonthSpan)))
|
||||
rows = cols
|
||||
if rows*cols < self.MonthSpan: rows += 1
|
||||
if rows*cols < self.MonthSpan: rows += 1
|
||||
if rows*cols < self.MonthSpan: cols += 1; rows -= 1
|
||||
if G.landscape: rows, cols = cols, rows
|
||||
elif rows == 0:
|
||||
rows = int(ceil(self.MonthSpan*1.0/cols))
|
||||
elif cols == 0:
|
||||
cols = int(ceil(self.MonthSpan*1.0/rows))
|
||||
G.landscape = page.landscape # PNG is pseudo-landscape (portrait with width>height)
|
||||
|
||||
if not self.options.no_footer:
|
||||
V0 = VLayout(page.Text_rect, 40, (1,)*4)
|
||||
Rcal = V0.item_span(39,0)
|
||||
Rc = rect_rel_scale(V0.item(39),1,0.5,0,0)
|
||||
else:
|
||||
Rcal = page.Text_rect
|
||||
|
||||
grid = GLayout(Rcal, rows, cols, pad = (mm_to_dots(G.month.padding),)*4)
|
||||
mpp = grid.count() # months per page
|
||||
num_pages = int(ceil(self.MonthSpan*1.0/mpp))
|
||||
cur_month = self.Month
|
||||
cur_year = self.Year
|
||||
num_placed = 0
|
||||
page_layout = []
|
||||
for k in xrange(num_pages):
|
||||
page_layout.append([])
|
||||
for i in xrange(mpp):
|
||||
page_layout[k].append((cur_month,cur_year))
|
||||
num_placed += 1
|
||||
cur_month += 1
|
||||
if cur_month > 12: cur_month = 1; cur_year += 1
|
||||
if num_placed >= self.MonthSpan: break
|
||||
|
||||
num_pages_written = 0
|
||||
|
||||
z_order = self.options.z_order
|
||||
if z_order == "auto":
|
||||
if G.month.sloppy_dx != 0 or G.month.sloppy_dy != 0 or G.month.sloppy_rot != 0:
|
||||
z_order = "decreasing"
|
||||
else:
|
||||
z_order = "increasing"
|
||||
for p in page_layout:
|
||||
num_placed = 0
|
||||
yy = [p[0][1]]
|
||||
if z_order == "decreasing": p.reverse()
|
||||
for (m,y) in p:
|
||||
k = len(p) - num_placed - 1 if z_order == "decreasing" else num_placed
|
||||
self._draw_month(page.cr, grid.item_seq(k, self.options.grid_order == "column"),
|
||||
month=m, year=y)
|
||||
num_placed += 1
|
||||
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), 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), scaling = -1, align = (1,0),
|
||||
font = (extract_font_name(S.month.font),1,0))
|
||||
num_pages_written += 1
|
||||
page.end_page()
|
||||
if num_pages_written < num_pages:
|
||||
page.new_page()
|
85
layouts/bars.py
Normal file
85
layouts/bars.py
Normal file
@ -0,0 +1,85 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# callirhoe - high quality calendar rendering
|
||||
# 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
|
||||
# the Free Software Foundation, either version 3 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, see http://www.gnu.org/licenses/
|
||||
|
||||
"""bars layout"""
|
||||
|
||||
from lib.xcairo import *
|
||||
from lib.geom import *
|
||||
import calendar
|
||||
import optparse
|
||||
import sys
|
||||
from datetime import date, timedelta
|
||||
|
||||
import _base
|
||||
|
||||
parser = _base.get_parser(__name__)
|
||||
|
||||
class CalendarRenderer(_base.CalendarRenderer):
|
||||
"""bars layout class"""
|
||||
def _draw_month(self, cr, rect, month, year):
|
||||
S,G,L = self.Theme
|
||||
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))
|
||||
if self.options.month_with_year:
|
||||
mmeasure += 'A'*(len(str(year))+1)
|
||||
|
||||
rows = 31 if G.month.symmetric else span
|
||||
grid = VLayout(rect_from_origin(rect), 32) # title bar always symmetric
|
||||
dom_grid = VLayout(grid.item_span(31,1), rows)
|
||||
|
||||
# draw box shadow
|
||||
if S.month.box_shadow:
|
||||
f = S.month.box_shadow_size
|
||||
shad = (f,-f) if G.landscape else (f,f)
|
||||
draw_shadow(cr, rect_from_origin(rect), shad)
|
||||
|
||||
# draw day cells
|
||||
for dom in range(1,rows+1):
|
||||
R = dom_grid.item(dom-1)
|
||||
if dom <= span:
|
||||
holiday_tuple = self.holiday_provider(year, month, dom, day)
|
||||
day_style = holiday_tuple[2]
|
||||
dcell = _base.DayCell(day = (dom, day), header = holiday_tuple[0], footer = holiday_tuple[1],
|
||||
theme = (day_style, G.dom, L), show_day_name = True)
|
||||
dcell.draw(cr, R, self.options.short_daycell_ratio)
|
||||
else:
|
||||
day_style = S.dom
|
||||
draw_box(cr, rect = R, stroke_rgba = day_style.frame, fill_rgba = day_style.bg,
|
||||
stroke_width = mm_to_dots(day_style.frame_thickness))
|
||||
day = (day + 1) % 7
|
||||
|
||||
# draw month title (name)
|
||||
mcolor = S.month.color_map_bg[year%2][month]
|
||||
mcolor_fg = S.month.color_map_fg[year%2][month]
|
||||
R_mb = grid.item(0)
|
||||
draw_box(cr, rect = R_mb, stroke_rgba = S.month.frame, fill_rgba = mcolor,
|
||||
stroke_width = mm_to_dots(S.month.frame_thickness)) # title box
|
||||
draw_box(cr, rect = rect_from_origin(rect), stroke_rgba = S.month.frame, fill_rgba = (),
|
||||
stroke_width = mm_to_dots(S.month.frame_thickness)) # full box
|
||||
R_text = rect_rel_scale(R_mb, 1, 0.5)
|
||||
mshad = None
|
||||
if S.month.text_shadow:
|
||||
f = S.month.text_shadow_size
|
||||
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, scaling = -1, stroke_rgba = mcolor_fg,
|
||||
align = (2,0), font = S.month.font, measure = mmeasure, shadow = mshad)
|
||||
cr.restore()
|
||||
|
@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# callirhoe - high quality calendar rendering
|
||||
# Copyright (C) 2012 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,357 +15,93 @@
|
||||
# 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 *
|
||||
from math import floor, ceil, sqrt
|
||||
import calendar
|
||||
import optparse
|
||||
import sys
|
||||
from datetime import date, timedelta
|
||||
|
||||
parser = optparse.OptionParser(usage="%prog (...) --layout classic [options] (...)",add_help_option=False)
|
||||
parser.add_option("--rows", type="int", default=0, help="force grid rows [%default]")
|
||||
parser.add_option("--cols", type="int", default=0,
|
||||
help="force grid columns [%default]; if ROWS and COLS are both non-zero, "
|
||||
"calendar will span multiple pages as needed; if one value is zero, it "
|
||||
"will be computed automatically in order to fill exactly 1 page")
|
||||
parser.add_option("--grid-order", choices=["row","column"],default="row",
|
||||
help="either `row' or `column' to set grid placing order row-wise or column-wise [%default]")
|
||||
parser.add_option("--z-order", choices=["auto", "increasing", "decreasing"], default="auto",
|
||||
help="either `increasing' or `decreasing' to set whether next month (in grid order) "
|
||||
"lies above or below the previously drawn month; this affects shadow casting, "
|
||||
"since rendering is always performed in increasing z-order; specifying `auto' "
|
||||
"selects increasing order if and only if sloppy boxes are enabled [%default]")
|
||||
parser.add_option("--month-with-year", action="store_true", default=False,
|
||||
help="displays year together with month name, e.g. January 1980; suppresses year from footer line")
|
||||
parser.add_option("--long-daycells", action="store_const", const=0.0, dest="short_daycell_ratio",
|
||||
help="force use of only long daycells")
|
||||
parser.add_option("--short-daycells", action="store_const", const=1.0e6, dest="short_daycell_ratio",
|
||||
help="force use of only short daycells")
|
||||
parser.add_option("--bar", action="store_const", const=1.0e6, dest="month_bar_ratio",
|
||||
help="force month drawing in bar mode")
|
||||
parser.add_option("--matrix", action="store_const", const=0, dest="month_bar_ratio",
|
||||
help="force month drawing in matrix mode")
|
||||
parser.add_option("--short-daycell-ratio", type="float", default=2.5,
|
||||
help="ratio threshold for day cells below which short version is drawn [%default]")
|
||||
parser.add_option("--month-bar-ratio", type="float", default=0.7,
|
||||
help="ratio threshold for month box, below which bar is drawn [%default]")
|
||||
parser.add_option("--no-footer", action="store_true", default=False,
|
||||
help="disable footer line (with year and rendered-by message)")
|
||||
parser.add_option("--symmetric", action="store_true", default=False,
|
||||
help="force symmetric mode (equivalent to --geom-var=month.symmetric=1). "
|
||||
"In symmetric mode, day cells are equally sized and all month boxes contain "
|
||||
"the same number of (possibly empty) cells, independently of how many days or "
|
||||
"weeks per month. In asymmetric mode, empty rows are eliminated, by slightly "
|
||||
"resizing day cells, in order to have uniform month boxes.")
|
||||
parser.add_option("--padding", type="float", default=None,
|
||||
help="set month box padding (equivalent to --geom-var=month.padding=PADDING); "
|
||||
"month bars look better with smaller padding, while matrix mode looks better with "
|
||||
"larger padding")
|
||||
parser.add_option("--no-shadow", action="store_true", default=None,
|
||||
help="disable box shadows")
|
||||
parser.add_option("--opaque", action="store_true", default=False,
|
||||
help="make background opaque (white fill)")
|
||||
parser.add_option("--swap-colors", action="store_true", default=None,
|
||||
help="swap month colors for even/odd years")
|
||||
import _base
|
||||
|
||||
parser = _base.get_parser(__name__)
|
||||
|
||||
def weekrows_of_month(year, month):
|
||||
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
|
||||
if day == 6 and span >= 30: return 6
|
||||
return 5
|
||||
|
||||
def _draw_day_cell_short(cr, rect, day, header, footer, theme, show_day_name):
|
||||
S,G = theme
|
||||
x, y, w, h = rect
|
||||
day_of_month, day_of_week = day
|
||||
draw_box(cr, rect, S.frame, S.bg, mm_to_dots(S.frame_thickness))
|
||||
R = rect_rel_scale(rect, G.size[0], G.size[1])
|
||||
if show_day_name:
|
||||
Rdom, Rdow = rect_hsplit(R, *G.mw_split)
|
||||
else:
|
||||
Rdom = R
|
||||
valign = 0 if 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,
|
||||
align = (2,valign), font = S.font, measure = "88")
|
||||
# draw name of day
|
||||
if show_day_name:
|
||||
draw_str(cr, text = calendar.day_name[day_of_week][0], rect = Rdow, stretch = -1, stroke_rgba = S.fg,
|
||||
align = (2,valign), font = S.font, measure = "88")
|
||||
# draw header
|
||||
if header:
|
||||
R = rect_rel_scale(rect, G.header_size[0], G.header_size[1], 0, -1.0 + G.header_align)
|
||||
draw_str(cr, text = header, rect = R, stretch = -1, stroke_rgba = S.header, font = S.header_font)
|
||||
# draw footer
|
||||
if footer:
|
||||
R = rect_rel_scale(rect, G.footer_size[0], G.footer_size[1], 0, 1.0 - G.footer_align)
|
||||
draw_str(cr, text = footer, rect = R, stretch = -1, stroke_rgba = S.footer, font = S.footer_font)
|
||||
class CalendarRenderer(_base.CalendarRenderer):
|
||||
"""classic tiles layout class"""
|
||||
def _draw_month(self, cr, rect, month, year):
|
||||
S,G,L = self.Theme
|
||||
make_sloppy_rect(cr, rect, G.month.sloppy_dx, G.month.sloppy_dy, G.month.sloppy_rot)
|
||||
|
||||
def _draw_day_cell_long(cr, rect, day, header, footer, theme, show_day_name):
|
||||
S,G = theme
|
||||
x, y, w, h = rect
|
||||
day_of_month, day_of_week = day
|
||||
draw_box(cr, rect, S.frame, S.bg, mm_to_dots(S.frame_thickness))
|
||||
R1, Rhf = rect_hsplit(rect, *G.hf_hsplit)
|
||||
if show_day_name:
|
||||
R = rect_rel_scale(R1, G.size[2], G.size[3])
|
||||
Rdom, Rdow = rect_hsplit(R, *G.mw_split)
|
||||
else:
|
||||
Rdom = rect_rel_scale(R1, G.size[0], G.size[1])
|
||||
valign = 0 if 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,
|
||||
align = (2,valign), font = S.font, measure = "88")
|
||||
# draw name of day
|
||||
if show_day_name:
|
||||
draw_str(cr, text = calendar.day_name[day_of_week][0], rect = Rdow, stretch = -1, stroke_rgba = S.fg,
|
||||
align = (2,valign), font = S.font, measure = "M")
|
||||
Rh, Rf = rect_vsplit(Rhf, *G.hf_vsplit)
|
||||
# draw header
|
||||
if header:
|
||||
draw_str(cr, text = header, rect = Rh, stretch = -1, stroke_rgba = S.header, align = (1,2),
|
||||
font = S.header_font)
|
||||
# draw footer
|
||||
if footer:
|
||||
draw_str(cr, text = footer, rect = Rf, stretch = -1, stroke_rgba = S.footer, align = (1,2),
|
||||
font = S.footer_font)
|
||||
day, span = calendar.monthrange(year, month)
|
||||
weekrows = 6 if G.month.symmetric else _weekrows_of_month(year, month)
|
||||
dom = -day + 1;
|
||||
wmeasure = 'A'*max(map(len,L.day_name))
|
||||
mmeasure = 'A'*max(map(len,L.month_name))
|
||||
if self.options.month_with_year:
|
||||
mmeasure += 'A'*(len(str(year))+1)
|
||||
|
||||
def draw_day_cell(cr, rect, day, header, footer, theme, show_day_name, short_thres):
|
||||
if rect_ratio(rect) < short_thres:
|
||||
_draw_day_cell_short(cr, rect, day, header, footer, theme, show_day_name)
|
||||
else:
|
||||
_draw_day_cell_long(cr, rect, day, header, footer, theme, show_day_name)
|
||||
|
||||
|
||||
def draw_month_matrix(cr, rect, month, year, theme, daycell_thres):
|
||||
S,G = theme
|
||||
apply_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)
|
||||
dom = -day + 1;
|
||||
wmeasure = 'A'*max(map(len,calendar.day_name))
|
||||
mmeasure = 'A'*max(map(len,calendar.month_name))
|
||||
|
||||
grid = GLayout(rect_from_origin(rect), 7, 7)
|
||||
# 61.8% - 38.2% split (golden)
|
||||
R_mb, R_db = rect_vsplit(grid.item_span(1, 7, 0, 0), 0.618) # month name bar, day name bar
|
||||
R_dnc = HLayout(R_db, 7) # day name cells = 1/7-th of day name bar
|
||||
dom_grid = GLayout(grid.item_span(6, 7, 1, 0), weekrows, 7)
|
||||
|
||||
# draw box shadow
|
||||
if S.month.box_shadow:
|
||||
f = S.month.box_shadow_size
|
||||
shad = (f,-f) if G.landscape else (f,f)
|
||||
draw_shadow(cr, rect_from_origin(rect), shad)
|
||||
grid = GLayout(rect_from_origin(rect), 7, 7)
|
||||
# 61.8% - 38.2% split (golden)
|
||||
R_mb, R_db = rect_vsplit(grid.item_span(1, 7, 0, 0), 0.618) # month name bar, day name bar
|
||||
R_dnc = HLayout(R_db, 7) # day name cells = 1/7-th of day name bar
|
||||
dom_grid = GLayout(grid.item_span(6, 7, 1, 0), weekrows, 7)
|
||||
|
||||
# draw day names
|
||||
for col in range(7):
|
||||
R = R_dnc.item(col)
|
||||
draw_box(cr, rect = R, stroke_rgba = S.dom.frame,
|
||||
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 = calendar.day_name[col], rect = R_text, stretch = -1, stroke_rgba = S.dow.fg,
|
||||
align = (2,0), font = S.dow.font, measure = wmeasure)
|
||||
|
||||
# draw day cells
|
||||
for row in range(weekrows):
|
||||
# draw box shadow
|
||||
if S.month.box_shadow:
|
||||
f = S.month.box_shadow_size
|
||||
shad = (f,-f) if G.landscape else (f,f)
|
||||
draw_shadow(cr, rect_from_origin(rect), shad)
|
||||
|
||||
# draw day names
|
||||
for col in range(7):
|
||||
day_style = S.dom_weekend if col >= 5 else S.dom
|
||||
R = dom_grid.item(row, col)
|
||||
if dom > 0 and dom <= span:
|
||||
draw_day_cell(cr, rect = R, day = (dom, col),
|
||||
header = None, footer = None, theme = (day_style, G.dom), show_day_name = False,
|
||||
short_thres = daycell_thres)
|
||||
else:
|
||||
draw_box(cr, rect = R, stroke_rgba = day_style.frame, fill_rgba = day_style.bg,
|
||||
stroke_width = mm_to_dots(day_style.frame_thickness))
|
||||
dom += 1
|
||||
R = R_dnc.item(col)
|
||||
draw_box(cr, rect = R, stroke_rgba = S.dom.frame,
|
||||
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, scaling = -1, stroke_rgba = S.dow.fg,
|
||||
align = (2,0), font = S.dow.font, measure = wmeasure)
|
||||
|
||||
# draw month title (name)
|
||||
mcolor = S.month.color_map_bg[year%2][month]
|
||||
mcolor_fg = S.month.color_map_fg[year%2][month]
|
||||
draw_box(cr, rect = R_mb, stroke_rgba = S.month.frame, fill_rgba = mcolor,
|
||||
stroke_width = mm_to_dots(S.month.frame_thickness)) # title box
|
||||
draw_box(cr, rect = rect_from_origin(rect), stroke_rgba = S.month.frame, fill_rgba = (),
|
||||
stroke_width = mm_to_dots(S.month.frame_thickness)) # full box
|
||||
R_text = rect_rel_scale(R_mb, 1, 0.5)
|
||||
mshad = None
|
||||
if S.month.text_shadow:
|
||||
f = S.month.text_shadow_size
|
||||
mshad = (f,-f) if G.landscape else (f,f)
|
||||
title_str = calendar.month_name[month]
|
||||
if options.month_with_year: title_str += ' ' + str(year)
|
||||
draw_str(cr, text = title_str, rect = R_text, stretch = -1, stroke_rgba = mcolor_fg,
|
||||
align = (2,0), font = S.month.font, measure = mmeasure, shadow = mshad)
|
||||
cr.restore()
|
||||
# draw day cells
|
||||
for row in range(weekrows):
|
||||
for col in range(7):
|
||||
R = dom_grid.item(row, col)
|
||||
if dom > 0 and dom <= span:
|
||||
holiday_tuple = self.holiday_provider(year, month, dom, col)
|
||||
day_style = holiday_tuple[2]
|
||||
dcell = _base.DayCell(day = (dom, col), header = holiday_tuple[0], footer = holiday_tuple[1],
|
||||
theme = (day_style, G.dom, L), show_day_name = False)
|
||||
dcell.draw(cr, R, self.options.short_daycell_ratio)
|
||||
else:
|
||||
day_style = S.dom_weekend if col >= 5 else S.dom
|
||||
draw_box(cr, rect = R, stroke_rgba = day_style.frame, fill_rgba = day_style.bg,
|
||||
stroke_width = mm_to_dots(day_style.frame_thickness))
|
||||
dom += 1
|
||||
|
||||
# draw month title (name)
|
||||
mcolor = S.month.color_map_bg[year%2][month]
|
||||
mcolor_fg = S.month.color_map_fg[year%2][month]
|
||||
draw_box(cr, rect = R_mb, stroke_rgba = S.month.frame, fill_rgba = mcolor,
|
||||
stroke_width = mm_to_dots(S.month.frame_thickness)) # title box
|
||||
draw_box(cr, rect = rect_from_origin(rect), stroke_rgba = S.month.frame, fill_rgba = (),
|
||||
stroke_width = mm_to_dots(S.month.frame_thickness)) # full box
|
||||
R_text = rect_rel_scale(R_mb, 1, 0.5)
|
||||
mshad = None
|
||||
if S.month.text_shadow:
|
||||
f = S.month.text_shadow_size
|
||||
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, scaling = -1, stroke_rgba = mcolor_fg,
|
||||
align = (2,0), font = S.month.font, measure = mmeasure, shadow = mshad)
|
||||
cr.restore()
|
||||
|
||||
def draw_month_bar(cr, rect, month, year, theme, daycell_thres):
|
||||
S,G = theme
|
||||
apply_rect(cr, rect, G.month.sloppy_dx, G.month.sloppy_dy, G.month.sloppy_rot)
|
||||
|
||||
day, span = calendar.monthrange(year, month)
|
||||
wmeasure = 'A'*max(map(len,calendar.day_name))
|
||||
mmeasure = 'A'*max(map(len,calendar.month_name))
|
||||
|
||||
rows = 31 if G.month.symmetric else span
|
||||
grid = VLayout(rect_from_origin(rect), 32) # title bar always symmetric
|
||||
dom_grid = VLayout(grid.item_span(31,1), rows)
|
||||
|
||||
# draw box shadow
|
||||
if S.month.box_shadow:
|
||||
f = S.month.box_shadow_size
|
||||
shad = (f,-f) if G.landscape else (f,f)
|
||||
draw_shadow(cr, rect_from_origin(rect), shad)
|
||||
|
||||
# draw day cells
|
||||
for dom in range(1,rows+1):
|
||||
day_style = S.dom_weekend if day >= 5 and dom <= span else S.dom
|
||||
R = dom_grid.item(dom-1)
|
||||
if dom <= span:
|
||||
draw_day_cell(cr, rect = R, day = (dom, day), header = None, footer = None,
|
||||
theme = (day_style, G.dom), show_day_name = True, short_thres = daycell_thres)
|
||||
else:
|
||||
draw_box(cr, rect = R, stroke_rgba = day_style.frame, fill_rgba = day_style.bg,
|
||||
stroke_width = mm_to_dots(day_style.frame_thickness))
|
||||
day = (day + 1) % 7
|
||||
|
||||
# draw month title (name)
|
||||
mcolor = S.month.color_map_bg[year%2][month]
|
||||
mcolor_fg = S.month.color_map_fg[year%2][month]
|
||||
R_mb = grid.item(0)
|
||||
draw_box(cr, rect = R_mb, stroke_rgba = S.month.frame, fill_rgba = mcolor,
|
||||
stroke_width = mm_to_dots(S.month.frame_thickness)) # title box
|
||||
draw_box(cr, rect = rect_from_origin(rect), stroke_rgba = S.month.frame, fill_rgba = (),
|
||||
stroke_width = mm_to_dots(S.month.frame_thickness)) # full box
|
||||
R_text = rect_rel_scale(R_mb, 1, 0.5)
|
||||
mshad = None
|
||||
if S.month.text_shadow:
|
||||
f = S.month.text_shadow_size
|
||||
mshad = (f,-f) if G.landscape else (f,f)
|
||||
draw_str(cr, text = calendar.month_name[month], rect = R_text, stretch = -1, stroke_rgba = mcolor_fg,
|
||||
align = (2,0), font = S.month.font, measure = mmeasure, shadow = mshad)
|
||||
cr.restore()
|
||||
|
||||
def draw_month(cr, rect, month, year, theme, bar_thres = 0.7, daycell_thres = 2.5):
|
||||
if rect_ratio(rect) >= bar_thres:
|
||||
draw_month_matrix(cr, rect, month, year, theme, daycell_thres)
|
||||
else:
|
||||
draw_month_bar(cr, rect, month, year, theme, daycell_thres)
|
||||
|
||||
#1 1 1
|
||||
#2 2 1
|
||||
#3 3 1
|
||||
|
||||
#4 2 2
|
||||
#5 3 2
|
||||
#6 3 2
|
||||
#7 4 2
|
||||
#8 4 2
|
||||
|
||||
#9 3 3
|
||||
#10 4 3
|
||||
#11 4 3
|
||||
#12 4 3
|
||||
|
||||
#rows = 0
|
||||
#cols = 0
|
||||
|
||||
def draw_calendar(Outfile, Year, Month, MonthSpan, Theme, version_string):
|
||||
S,G = Theme
|
||||
rows, cols = options.rows, options.cols
|
||||
|
||||
if options.symmetric:
|
||||
G.month.symmetric = True
|
||||
if options.padding is not None:
|
||||
G.month.padding = options.padding
|
||||
if options.no_shadow == True:
|
||||
S.month.box_shadow = False
|
||||
if options.swap_colors:
|
||||
S.month.color_map_bg = (S.month.color_map_bg[1], S.month.color_map_bg[0])
|
||||
S.month.color_map_fg = (S.month.color_map_fg[1], S.month.color_map_fg[0])
|
||||
|
||||
try:
|
||||
page = PageWriter(Outfile, G.landscape, G.pagespec, G.border, not options.opaque)
|
||||
except InvalidFormat as e:
|
||||
print >> sys.stderr, "invalid output format", e.args[0]
|
||||
sys.exit(1)
|
||||
|
||||
if rows == 0 and cols == 0:
|
||||
# if MonthSpan < 4:
|
||||
# cols = 1; rows = MonthSpan
|
||||
# elif MonthSpan < 9:
|
||||
# cols = 2; rows = int(math.ceil(MonthSpan/2.0))
|
||||
# else:
|
||||
# TODO: improve this heuristic
|
||||
cols = int(floor(sqrt(MonthSpan)))
|
||||
rows = cols
|
||||
if rows*cols < MonthSpan: rows += 1
|
||||
if rows*cols < MonthSpan: rows += 1
|
||||
if rows*cols < MonthSpan: cols += 1; rows -= 1
|
||||
if G.landscape: rows, cols = cols, rows
|
||||
elif rows == 0:
|
||||
rows = int(ceil(MonthSpan*1.0/cols))
|
||||
elif cols == 0:
|
||||
cols = int(ceil(MonthSpan*1.0/rows))
|
||||
G.landscape = page.landscape # PNG is pseudo-landscape (portrait with width>height)
|
||||
|
||||
if not options.no_footer:
|
||||
V0 = VLayout(page.Text_rect, 40, (1,)*4)
|
||||
Rcal = V0.item_span(39,0)
|
||||
Rc = rect_rel_scale(V0.item(39),1,0.5,0,0)
|
||||
else:
|
||||
Rcal = page.Text_rect
|
||||
|
||||
grid = GLayout(Rcal, rows, cols, pad = (mm_to_dots(G.month.padding),)*4)
|
||||
mpp = grid.count() # months per page
|
||||
num_pages = int(ceil(MonthSpan*1.0/mpp))
|
||||
cur_month = Month
|
||||
cur_year = Year
|
||||
num_placed = 0
|
||||
page_layout = []
|
||||
for k in xrange(num_pages):
|
||||
page_layout.append([])
|
||||
for i in xrange(mpp):
|
||||
page_layout[k].append((cur_month,cur_year))
|
||||
num_placed += 1
|
||||
cur_month += 1
|
||||
if cur_month > 12: cur_month = 1; cur_year += 1
|
||||
if num_placed >= MonthSpan: break
|
||||
|
||||
num_pages_written = 0
|
||||
|
||||
z_order = options.z_order
|
||||
if z_order == "auto":
|
||||
if G.month.sloppy_dx != 0 or G.month.sloppy_dy != 0 or G.month.sloppy_rot != 0:
|
||||
z_order = "decreasing"
|
||||
else:
|
||||
z_order = "increasing"
|
||||
for p in page_layout:
|
||||
num_placed = 0
|
||||
yy = [p[0][1]]
|
||||
if z_order == "decreasing": p.reverse()
|
||||
for (m,y) in p:
|
||||
k = len(p) - num_placed - 1 if z_order == "decreasing" else num_placed
|
||||
draw_month(page.cr, grid.item_seq(k, options.grid_order == "column"),
|
||||
month=m, year=y, theme = Theme,
|
||||
bar_thres = options.month_bar_ratio, daycell_thres = options.short_daycell_ratio)
|
||||
num_placed += 1
|
||||
if (y > yy[-1]): yy.append(y)
|
||||
if not options.month_with_year and not 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,
|
||||
align = (0,0), font = (extract_font_name(S.month.font),0,0))
|
||||
if not options.no_footer:
|
||||
draw_str(page.cr, text = "rendered by Callirhoe ver. %s" % version_string,
|
||||
rect = Rc, stroke_rgba = (0,0,0,0.5), stretch = -1, align = (1,0),
|
||||
font = (extract_font_name(S.month.font),1,0))
|
||||
num_pages_written += 1
|
||||
page.end_page()
|
||||
if num_pages_written < num_pages:
|
||||
page.new_page()
|
||||
|
185
layouts/sparse.py
Normal file
185
layouts/sparse.py
Normal file
@ -0,0 +1,185 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# callirhoe - high quality calendar rendering
|
||||
# Copyright (C) 2012 George M. Tzoumas
|
||||
|
||||
# Sparse Layout Module
|
||||
# Copyright (C) 2013 Neels Hofmeyr
|
||||
|
||||
# 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 3 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, see http://www.gnu.org/licenses/
|
||||
|
||||
"""sparse layout"""
|
||||
|
||||
from lib.xcairo import *
|
||||
from lib.geom import *
|
||||
import calendar
|
||||
import optparse
|
||||
import sys
|
||||
from datetime import date, timedelta
|
||||
|
||||
import _base
|
||||
|
||||
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=1, help="force grid rows [%default]")
|
||||
parser.add_option("--cols", type="int", default=0,
|
||||
help="force grid columns [%default]; if ROWS and COLS are both non-zero, "
|
||||
"calendar will span multiple pages as needed; if one value is zero, it "
|
||||
"will be computed automatically in order to fill exactly 1 page")
|
||||
parser.add_option("--grid-order", choices=["row","column"],default="row",
|
||||
help="either `row' or `column' to set grid placing order row-wise or column-wise [%default]")
|
||||
parser.add_option("--z-order", choices=["auto", "increasing", "decreasing"], default="auto",
|
||||
help="either `increasing' or `decreasing' to set whether next month (in grid order) "
|
||||
"lies above or below the previously drawn month; this affects shadow casting, "
|
||||
"since rendering is always performed in increasing z-order; specifying `auto' "
|
||||
"selects increasing order if and only if sloppy boxes are enabled [%default]")
|
||||
parser.add_option("--month-with-year", action="store_true", default=False,
|
||||
help="displays year together with month name, e.g. January 1980; suppresses year from footer line")
|
||||
parser.add_option("--no-footer", action="store_true", default=False,
|
||||
help="disable footer line (with year and rendered-by message)")
|
||||
parser.add_option("--symmetric", action="store_true", default=False,
|
||||
help="force symmetric mode (equivalent to --geom-var=month.symmetric=1). "
|
||||
"In symmetric mode, day cells are equally sized and all month boxes contain "
|
||||
"the same number of (possibly empty) cells, independently of how many days or "
|
||||
"weeks per month. In asymmetric mode, empty rows are eliminated, by slightly "
|
||||
"resizing day cells, in order to have uniform month boxes.")
|
||||
parser.add_option("--padding", type="float", default=None,
|
||||
help="set month box padding (equivalent to --geom-var=month.padding=PADDING)")
|
||||
parser.add_option("--no-shadow", action="store_true", default=None,
|
||||
help="disable box shadows")
|
||||
parser.add_option("--opaque", action="store_true", default=False,
|
||||
help="make background opaque (white fill)")
|
||||
parser.add_option("--swap-colors", action="store_true", default=None,
|
||||
help="swap month colors for even/odd years")
|
||||
return parser
|
||||
|
||||
parser = get_parser(__name__)
|
||||
|
||||
def _draw_day_cell(cr, rect, day, header, footer, theme, show_day_name, text_height=None):
|
||||
ds,G,L = theme
|
||||
year, month, day_of_month, day_of_week = day
|
||||
draw_box(cr, rect, ds.bg, ds.bg, mm_to_dots(ds.frame_thickness))
|
||||
|
||||
if day_of_month > 1:
|
||||
x, y, w, h = rect
|
||||
draw_line(cr, (x, y, w, 0), ds.frame, mm_to_dots(ds.frame_thickness))
|
||||
|
||||
if (text_height is not None) and (text_height > 0):
|
||||
x, y, w, h = rect
|
||||
h_diff = h - text_height
|
||||
if h_diff > 0:
|
||||
y += h_diff / 2
|
||||
h = text_height
|
||||
rect = (x, y, w, h)
|
||||
|
||||
x, y, w, h = rect
|
||||
ww = h
|
||||
Rleft = (x + 0.1 * h, y + 0.2 * h, ww - 0.2 * h, .6 * h)
|
||||
Rmiddle = (x + h, y, ww, h)
|
||||
Rmiddle_top = (x + h + 0.1 * h, y + 0.2 * h, ww, .18 * h)
|
||||
bottom_h = .8 * h
|
||||
Rmiddle_bottom = (x + h + 0.1 * h, y + h - bottom_h, ww, bottom_h - 0.2 * h)
|
||||
#Rmiddle_top = rect_rel_scale(Rmiddle_top, .8, 0.6)
|
||||
#Rmiddle_bottom = rect_rel_scale(Rmiddle, .8, 0.6)
|
||||
Rright_header = (x + 2*h, y + 0.1 * h, w - 2 * ww - 0.2 * ww, 0.28 * h)
|
||||
Rright_footer = (x + 2*h, y + 0.6 * h, w - 2 * ww - 0.2 * ww, 0.28 * h)
|
||||
x, y, w, h = Rmiddle_bottom
|
||||
hh = h
|
||||
h = float(h) * 0.6
|
||||
y += float(hh) - h
|
||||
Rmiddle_bottom = (x, y, w, h)
|
||||
valign = 0 if show_day_name else 2
|
||||
# draw day of month (number)
|
||||
draw_str(cr, text = str(day_of_month), rect = Rleft, scaling = -1, stroke_rgba = ds.fg,
|
||||
align = (1,valign), font = ds.font, measure = "88")
|
||||
# draw name of day
|
||||
if show_day_name:
|
||||
draw_str(cr, text = L.day_name[day_of_week], rect = Rmiddle_bottom,
|
||||
scaling = -1, stroke_rgba = ds.fg, align = (0,valign),
|
||||
font = ds.font, measure = "Mo")
|
||||
# week number
|
||||
if day_of_week == 0 or (day_of_month == 1 and month == 1):
|
||||
week_nr = date(year, month, day_of_month).isocalendar()[1]
|
||||
draw_str(cr, text = "%s%d" % (L.week_of_year_prefix, week_nr), rect = Rmiddle_top,
|
||||
scaling = -1, stroke_rgba = ds.fg, align = (0,valign),
|
||||
font = ds.header_font, measure = "W88")
|
||||
|
||||
if header:
|
||||
draw_str(cr, text = header, rect = Rright_header, scaling = -1,
|
||||
stroke_rgba = ds.header, align = (1,1), font = ds.header_font,
|
||||
measure='MgMgMgMgMgMgMgMgMg')
|
||||
if footer:
|
||||
draw_str(cr, text = footer, rect = Rright_footer, scaling = -1,
|
||||
stroke_rgba = ds.footer, align = (1,1), font = ds.header_font,
|
||||
measure='MgMgMgMgMgMgMgMgMg')
|
||||
|
||||
class CalendarRenderer(_base.CalendarRenderer):
|
||||
"""sparse layout class"""
|
||||
def _draw_month(self, cr, rect, month, year):
|
||||
S,G,L = self.Theme
|
||||
make_sloppy_rect(cr, rect, G.month.sloppy_dx, G.month.sloppy_dy, G.month.sloppy_rot)
|
||||
|
||||
day, span = calendar.monthrange(year, month)
|
||||
wmeasure = 'A'*max(map(len,L.day_name))
|
||||
mmeasure = 'A'*max(map(len,L.month_name))
|
||||
|
||||
rows = 31 if G.month.symmetric else span
|
||||
grid = VLayout(rect_from_origin(rect), 32) # title bar always symmetric
|
||||
dom_grid = VLayout(grid.item_span(31,1), rows)
|
||||
|
||||
# determine text height
|
||||
tmp_grid = VLayout(grid.item_span(31,1), 31)
|
||||
text_height = tmp_grid.item(0)[3]
|
||||
|
||||
# draw box shadow
|
||||
if S.month.box_shadow:
|
||||
f = S.month.box_shadow_size
|
||||
shad = (f,-f) if G.landscape else (f,f)
|
||||
draw_shadow(cr, rect_from_origin(rect), shad)
|
||||
|
||||
# draw day cells
|
||||
for dom in range(1,span+1):
|
||||
R = dom_grid.item(dom-1)
|
||||
holiday_tuple = self.holiday_provider(year, month, dom, day)
|
||||
day_style = holiday_tuple[2]
|
||||
header = holiday_tuple[0]
|
||||
footer = holiday_tuple[1]
|
||||
if footer:
|
||||
if header:
|
||||
header = "%s, %s" % (header, footer)
|
||||
else:
|
||||
header = footer
|
||||
_draw_day_cell(cr, rect = R, day = (year, month, dom, day),
|
||||
header = header, footer = None,
|
||||
theme = (day_style, G.dom, L), show_day_name = True,
|
||||
text_height = text_height)
|
||||
|
||||
day = (day + 1) % 7
|
||||
|
||||
# draw month title (name)
|
||||
mcolor = S.month.color_map_bg[year%2][month]
|
||||
mcolor_fg = S.month.color_map_fg[year%2][month]
|
||||
R_mb = grid.item(0)
|
||||
R_text = rect_rel_scale(R_mb, 1, 0.5)
|
||||
mshad = None
|
||||
if S.month.text_shadow:
|
||||
f = S.month.text_shadow_size
|
||||
mshad = (f,-f) if G.landscape else (f,f)
|
||||
draw_str(cr, text = L.month_name[month], rect = R_text, scaling = -1, stroke_rgba = mcolor_fg,
|
||||
align = (2,0), font = S.month.font, measure = mmeasure, shadow = mshad)
|
||||
cr.restore()
|
91
lib/geom.py
91
lib/geom.py
@ -18,40 +18,68 @@
|
||||
|
||||
# *****************************************
|
||||
# #
|
||||
# general-purpose geometry routines #
|
||||
""" general-purpose geometry routines """
|
||||
# #
|
||||
# *****************************************
|
||||
|
||||
def rect_ratio(r):
|
||||
"""returns the ratio of rect I{r} which is defined as M{width/height}"""
|
||||
return r[2]*1.0/r[3]
|
||||
|
||||
def rect_rel_scale(r, fw, fh, align_x = 0, align_y = 0):
|
||||
"""relatively scale a rect
|
||||
|
||||
@type fw: float in [0,1]
|
||||
@param fw: width fraction (to be multiplied)
|
||||
@type fh: float in [0,1]
|
||||
@param fh: height fraction (to be multiplied)
|
||||
@type align_x: float in [-1,1]
|
||||
@param align_x: determines the relative position of the new rect with respect to
|
||||
the old one. A value of 0 aligns in the center, a value of -1 aligns on the
|
||||
left, a value of 1 aligns on the right hand side. Intermediate values do
|
||||
linear interpolation.
|
||||
@type align_y: float in [-1,1]
|
||||
@param align_y: Performs vertical (top-bottom) alignment similarly to L{align_x}.
|
||||
"""
|
||||
x, y, w, h = r
|
||||
return (x + (align_x + 1.0)*w*(1 - fw)/2.0,
|
||||
y + (align_y + 1.0)*h*(1 - fh)/2.0, w*fw, h*fh)
|
||||
|
||||
def rect_pad(r, pad):
|
||||
"""returns a padded rect by reducing border by the I{pad} tuple (top,left,bottom,right)"""
|
||||
x, y, w, h = r
|
||||
t_, l_, b_, r_ = pad
|
||||
return (x + l_, y + t_, w - r_ - l_, h - t_ - b_)
|
||||
|
||||
def rect_to_abs(r):
|
||||
"""get absolute coordinates (x0,y0,x1,y1) from rect definition (x,y,w,h)"""
|
||||
x, y, w, h = r
|
||||
return (x, y, x + w, y + h)
|
||||
|
||||
def abs_to_rect(a):
|
||||
"""get rect definition (x,y,w,h) from absolute coordinates (x0,y0,x1,y1)"""
|
||||
x1, y1, x2, y2 = a
|
||||
return (x1, y1, x2 - x1, y2 - y1)
|
||||
|
||||
def rect_from_origin(r):
|
||||
"""returns a similar rect with top-left corner at (0,0)"""
|
||||
return (0, 0, r[2], r[3])
|
||||
|
||||
def rect_hull(r1,r2):
|
||||
"""returns the smallest rect containing r1 and r2"""
|
||||
x1, y1, x2, y2 = rect_to_abs(r1)
|
||||
x3, y3, x4, y4 = rect_to_abs(r2)
|
||||
return abs_to_rect((min(x1,x3), min(y1,y3), max(x2,x4), max(y2,y4)))
|
||||
|
||||
def rect_hsplit(r, f = 0.5, fdist = 0.0):
|
||||
"""split a rect horizontally
|
||||
|
||||
@type f: float in [0,1]
|
||||
@param f: split fraction
|
||||
@param fdist: fraction of space to discard before splitting (free space)
|
||||
@return: tuple (r1,r2) with splits and free space evenly distributed
|
||||
before r1, between r1 and r2 and after r2
|
||||
"""
|
||||
x, y, w, h = r
|
||||
rw = w*(1.0 - fdist)
|
||||
r1 = (x + w*fdist/3.0, y, rw*f, h)
|
||||
@ -59,6 +87,7 @@ def rect_hsplit(r, f = 0.5, fdist = 0.0):
|
||||
return (r1, r2)
|
||||
|
||||
def rect_vsplit(r, f = 0.5, fdist = 0.0):
|
||||
"""split a rect vertically, similarly to L{rect_hsplit}"""
|
||||
x, y, w, h = r
|
||||
rh = h*(1.0 - fdist)
|
||||
r1 = (x, y + h*fdist/3.0, w, rh*f)
|
||||
@ -66,45 +95,73 @@ def rect_vsplit(r, f = 0.5, fdist = 0.0):
|
||||
return (r1, r2)
|
||||
|
||||
def color_mix(a, b, frac):
|
||||
"""mix two colors
|
||||
|
||||
@type frac: float in [0,1]
|
||||
@param frac: amount of first color
|
||||
"""
|
||||
return map(lambda (x,y): x*frac + y*(1 - frac), zip(a,b))
|
||||
|
||||
def color_scale(a, frac):
|
||||
"""scale color values
|
||||
|
||||
@type frac: float
|
||||
@param frac: scale amount (to be multiplied)
|
||||
"""
|
||||
return map(lambda x: min(1.0,x*frac), a)
|
||||
|
||||
def color_auto_fg(bg, light = (1,1,1), dark = (0,0,0)):
|
||||
"""return I{light} or I{dark} foreground color based on an ad-hoc evaluation of I{bg}"""
|
||||
return light if (bg[0] + 1.5*bg[1] + bg[2]) < 1.0 else dark
|
||||
|
||||
# ********* layout managers ***********
|
||||
|
||||
class VLayout(object):
|
||||
"""vertical layout manager
|
||||
|
||||
@ivar rect: bounding rect for layout -- this rect will be split and the slices assigned to every item
|
||||
@ivar nitems: maximum number of items in the layout
|
||||
@ivar pad: tuple(top,left,bottom,right) with item padding
|
||||
"""
|
||||
def __init__(self, rect, nitems = 1, pad = (0.0,0.0,0.0,0.0)): # TLBR
|
||||
self.rect = rect
|
||||
self.nitems = nitems
|
||||
self.pad = pad
|
||||
|
||||
def count(self):
|
||||
"""return maximum number of items in the layout"""
|
||||
return self.nitems
|
||||
|
||||
def resize(self, k):
|
||||
"""set maximum number of items"""
|
||||
self.nitems = k
|
||||
|
||||
def grow(self, delta = 1):
|
||||
"""increase number of items by I{delta}"""
|
||||
self.nitems += delta
|
||||
|
||||
def item(self, i = 0):
|
||||
"""get rect for item I{i}"""
|
||||
x, y, w, h = self.rect
|
||||
h *= 1.0/self.nitems
|
||||
y += i*h
|
||||
return rect_pad((x,y,w,h), self.pad)
|
||||
|
||||
def item_span(self, n, k = -1):
|
||||
"""get union of I{k} consecutive items, starting at position I{n}
|
||||
|
||||
@param n: first item
|
||||
@param k: number of items, -1 for all remaining items
|
||||
"""
|
||||
if k < 0: k = (self.count() - n) // 2
|
||||
return rect_hull(self.item(k), self.item(k + n - 1))
|
||||
|
||||
def items(self):
|
||||
"""returns a sequence of all items"""
|
||||
return map(self.item, range(self.count()))
|
||||
|
||||
class HLayout(VLayout): # transpose of VLayout
|
||||
class HLayout(VLayout):
|
||||
"""horizontal layout manager defined as a transpose of L{VLayout}"""
|
||||
def __init__(self, rect, nitems = 1, pad = (0.0,0.0,0.0,0.0)): # TLBR
|
||||
super(HLayout,self).__init__((rect[1],rect[0],rect[3],rect[2]),
|
||||
nitems, (pad[1], pad[0], pad[3], pad[2]))
|
||||
@ -113,32 +170,50 @@ class HLayout(VLayout): # transpose of 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
|
||||
@ivar hrep: internal L{HLayout} for column computations
|
||||
"""
|
||||
def __init__(self, rect, nrows = 1, ncols = 1, pad = (0.0,0.0,0.0,0.0)): # TLBR
|
||||
"""initialize layout
|
||||
|
||||
@param rect: layout rect (tuple)
|
||||
@param nrows: number of rows
|
||||
@param ncols: number of columns
|
||||
@param pad: cell padding
|
||||
"""
|
||||
self.vrep = VLayout(rect, nrows, (pad[0], 0.0, pad[2], 0.0))
|
||||
t = self.vrep.item(0)
|
||||
self.hrep = HLayout((rect[0], rect[1], t[2], t[3]), ncols, (0.0, pad[1], 0.0, pad[3]))
|
||||
|
||||
def row_count(self):
|
||||
"""get (max) number of rows in the grid"""
|
||||
return self.vrep.count()
|
||||
|
||||
def col_count(self):
|
||||
"""get (max) number of columns in the grid"""
|
||||
return self.hrep.count()
|
||||
|
||||
def count(self):
|
||||
"""get total number of cells in the grid (which is M{rows*cols})"""
|
||||
return self.row_count()*self.col_count()
|
||||
|
||||
def resize(self, rows, cols):
|
||||
"""resize grid by specifying new number of rows and columns"""
|
||||
self.vrep.resize(rows)
|
||||
t = self.vrep.item(0)
|
||||
self.hrep = HLayout(t[0:2], t[2:4], cols, (0.0, pad[1], 0.0, pad[3]))
|
||||
|
||||
def item(self, row, col):
|
||||
"""get rect of cell at position I{row,col}"""
|
||||
ty = self.vrep.item(row)
|
||||
tx = self.hrep.item(col)
|
||||
return (tx[0], ty[1], tx[2], tx[3])
|
||||
|
||||
def item_seq(self, k, column_wise = False):
|
||||
"""get rect of cell at position I{k} column-wise or row-wise"""
|
||||
if not column_wise:
|
||||
row, col = k // self.col_count(), k % self.col_count()
|
||||
else:
|
||||
@ -146,16 +221,26 @@ class GLayout:
|
||||
return self.item(row, col)
|
||||
|
||||
def items(self, column_wise = False):
|
||||
"""get sequence of rects of cells column-wise or row-wise"""
|
||||
return map(self.item_seq, range(self.count()))
|
||||
|
||||
def row_items(self, row):
|
||||
"""get sequence of cell rects of a row"""
|
||||
return map(lambda x: self.item(row, x), range(self.col_count()))
|
||||
|
||||
def col_items(self, col):
|
||||
"""get sequence of cell rects of a column"""
|
||||
return map(lambda x: self.item(x, col), range(self.row_count()))
|
||||
|
||||
|
||||
def item_span(self, nr, nc, row = -1, col = -1):
|
||||
"""get union of cell rects spanning a subgrid
|
||||
|
||||
@param nr: number of spanning rows
|
||||
@param nc: number of spanning columns
|
||||
@param row: starting row, -1 for vertically centered
|
||||
@param col: starting column, -1 for horizontally centered
|
||||
"""
|
||||
if row < 0: row = (self.row_count() - nr) // 2
|
||||
if col < 0: col = (self.col_count() - nc) // 2
|
||||
return rect_hull(self.item(row, col), self.item(row + nr - 1, col + nc - 1))
|
||||
|
429
lib/holiday.py
Normal file
429
lib/holiday.py
Normal file
@ -0,0 +1,429 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# callirhoe - high quality calendar rendering
|
||||
# Copyright (C) 2012-2013 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
|
||||
# the Free Software Foundation, either version 3 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, see http://www.gnu.org/licenses/
|
||||
|
||||
# *****************************************
|
||||
# #
|
||||
""" holiday support routines """
|
||||
# #
|
||||
# *****************************************
|
||||
|
||||
from datetime import date, timedelta
|
||||
|
||||
def _get_orthodox_easter(year):
|
||||
"""compute date of orthodox easter"""
|
||||
y1, y2, y3 = year % 4 , year % 7, year % 19
|
||||
a = 19*y3 + 15
|
||||
y4 = a % 30
|
||||
b = 2*y1 + 4*y2 + 6*(y4 + 1)
|
||||
y5 = b % 7
|
||||
r = 1 + 3 + y4 + y5
|
||||
return date(year, 3, 31) + timedelta(r)
|
||||
# res = date(year, 5, r - 30) if r > 30 else date(year, 4, r)
|
||||
# return res
|
||||
|
||||
def _get_catholic_easter(year):
|
||||
"""compute date of catholic easter"""
|
||||
a, b, c = year % 19, year // 100, year % 100
|
||||
d, e = divmod(b,4)
|
||||
f = (b + 8) // 25
|
||||
g = (b - f + 1) // 3
|
||||
h = (19*a + b - d - g + 15) % 30
|
||||
i, k = divmod(c,4)
|
||||
l = (32 + 2*e + 2*i - h - k) % 7
|
||||
m = (a + 11*h + 22*l) // 451
|
||||
emonth,edate = divmod(h + l - 7*m + 114,31)
|
||||
return date(year, emonth, edate+1)
|
||||
|
||||
def _strip_empty(sl):
|
||||
"""strip empty strings from list I{sl}"""
|
||||
return filter(lambda z: z, sl) if sl else []
|
||||
|
||||
def _flatten(sl):
|
||||
"""join list I{sl} into a comma-separated string"""
|
||||
if not sl: return None
|
||||
return ', '.join(sl)
|
||||
|
||||
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)
|
||||
|
||||
I{MULTI}: multi-day event (used to mark long day ranges,
|
||||
not necessarily holidays)
|
||||
|
||||
I{REMINDER}: do not mark the day as holiday
|
||||
|
||||
@note: Rendering style is determined considering L{flags} in this order:
|
||||
1. OFF
|
||||
2. MULTI
|
||||
|
||||
First flag that matches determines the style.
|
||||
"""
|
||||
OFF = 1
|
||||
MULTI = 2
|
||||
REMINDER = 4
|
||||
def __init__(self, header = [], footer = [], flags_str = None):
|
||||
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"""
|
||||
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 a comma-separated string for L{header_list}"""
|
||||
return _flatten(self.header_list)
|
||||
|
||||
def footer(self):
|
||||
"""return a comma-separated string for L{footer_list}"""
|
||||
return _flatten(self.footer_list)
|
||||
|
||||
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
|
||||
for s in fs:
|
||||
if s == 'off': val |= Holiday.OFF
|
||||
elif s == 'multi': val |= Holiday.MULTI
|
||||
# allow for prefix abbrev.
|
||||
elif 'reminder'.startswith(s): val |= Holiday.REMINDER
|
||||
return val
|
||||
|
||||
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:
|
||||
return (0,int(ddef[:2]),int(ddef[-2:]))
|
||||
if len(ddef) == 8:
|
||||
return (int(ddef[:4]),int(ddef[4:6]),int(ddef[-2:]))
|
||||
raise ValueError("invalid date definition '%s'" % ddef)
|
||||
|
||||
class HolidayProvider(object):
|
||||
"""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()
|
||||
self.orth_easter = dict() # key = daysdelta
|
||||
self.george = [] # key = n/a
|
||||
self.cath_easter = dict() # key = daysdelta
|
||||
self.cache = dict() # key = date()
|
||||
self.ycache = set() # key = year
|
||||
self.s_normal = s_normal
|
||||
self.s_weekend = s_weekend
|
||||
self.s_holiday = s_holiday
|
||||
self.s_weekend_holiday = s_weekend_holiday
|
||||
self.s_multi = s_multi
|
||||
self.s_weekend_multi = s_weekend_multi
|
||||
self.multiday_markers = multiday_markers
|
||||
|
||||
def _parse_day_record(self, fields):
|
||||
"""return tuple (etype,ddef,footer,header,flags)
|
||||
|
||||
@note: I{ddef} is one of the following:
|
||||
None
|
||||
int
|
||||
((y,m,d),)
|
||||
((y,m,d),(y,m,d))
|
||||
"""
|
||||
if len(fields) != 5:
|
||||
raise ValueError("Too many fields: " + str(fields))
|
||||
for i in range(len(fields)):
|
||||
if len(fields[i]) == 0: fields[i] = None
|
||||
if fields[0] == 'd':
|
||||
if fields[1]:
|
||||
if '*' in fields[1]:
|
||||
if fields[0] != 'd':
|
||||
raise ValueError("multi-day events not allowed with event type '%s'" % fields[0])
|
||||
dstr,spanstr = fields[1].split('*')
|
||||
if len(dstr) != 8:
|
||||
raise ValueError("multi-day events allowed only with full date, not '%s'" % dstr)
|
||||
span = int(spanstr)
|
||||
y,m,d = _decode_date_str(dstr)
|
||||
dt1 = date(y,m,d)
|
||||
dt2 = dt1 + timedelta(span-1)
|
||||
res = ((y,m,d),(dt2.year,dt2.month,dt2.day))
|
||||
elif '-' in fields[1]:
|
||||
if fields[0] != 'd':
|
||||
raise ValueError("multi-day events not allowed with event type '%s'" % fields[0])
|
||||
dstr,dstr2 = fields[1].split('-')
|
||||
if len(dstr) != 8:
|
||||
raise ValueError("multi-day events allowed only with full date, not '%s'" % dstr)
|
||||
y,m,d = _decode_date_str(dstr)
|
||||
y2,m2,d2 = _decode_date_str(dstr2)
|
||||
res = ((y,m,d),(y2,m2,d2))
|
||||
else:
|
||||
y,m,d = _decode_date_str(fields[1])
|
||||
if len(fields[1]) == 8:
|
||||
res = ((y,m,d),(y,m,d))
|
||||
else:
|
||||
res = ((y,m,d),)
|
||||
else:
|
||||
res = None
|
||||
else:
|
||||
res = int(fields[1])
|
||||
return (fields[0],res,fields[2],fields[3],fields[4])
|
||||
|
||||
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.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.multiday_markers:
|
||||
footer_tuple = (footer+'..', '..'+footer, '..'+footer+'..', None)
|
||||
else:
|
||||
footer_tuple = (footer, None, footer, None)
|
||||
else:
|
||||
footer_tuple = (None, None, None, None)
|
||||
return tuple(map(lambda k: Holiday([header_tuple[k]], [footer_tuple[k]], flags),
|
||||
range(4)))
|
||||
|
||||
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)
|
||||
hol = Holiday([header], [footer], flags)
|
||||
if etype == 'd':
|
||||
if len(ddef) == 1:
|
||||
y,m,d = ddef[0]
|
||||
if m > 0: # annual event
|
||||
if (d,m) not in self.annual: self.annual[(d,m)] = []
|
||||
self.annual[(d,m)].append(hol)
|
||||
else: # monthly event
|
||||
if d not in self.monthly: self.monthly[d] = []
|
||||
self.monthly[d].append(hol)
|
||||
else: # fixed date event
|
||||
dt1,dt2 = date(*ddef[0]),date(*ddef[1])
|
||||
span = (dt2-dt1).days + 1
|
||||
if span == 1:
|
||||
if dt1 not in self.fixed: self.fixed[dt1] = []
|
||||
self.fixed[dt1].append(hol)
|
||||
else:
|
||||
# properly annotate multi-day events
|
||||
hols = self._multi_holiday_tuple(header, footer, flags)
|
||||
dt = dt1
|
||||
while dt <= dt2:
|
||||
if dt not in self.fixed: self.fixed[dt] = []
|
||||
if dt == dt1: hol = hols[0]
|
||||
elif dt == dt2: hol = hols[1]
|
||||
elif dt.day == 1: hol = hols[2]
|
||||
else: hol = hols[3]
|
||||
self.fixed[dt].append(hol)
|
||||
dt += timedelta(1)
|
||||
|
||||
elif etype == 'oe':
|
||||
d = ddef
|
||||
if d not in self.orth_easter: self.orth_easter[d] = []
|
||||
self.orth_easter[d].append(hol)
|
||||
elif etype == 'ge':
|
||||
d = ddef
|
||||
self.george.append(hol)
|
||||
elif etype == 'ce':
|
||||
d = ddef
|
||||
if d not in self.cath_easter: self.cath_easter[d] = []
|
||||
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
|
||||
for d0,m0 in self.annual:
|
||||
dt = date(y,m0,d0)
|
||||
if not dt in self.cache: self.cache[dt] = Holiday()
|
||||
self.cache[dt].merge_with(self.annual[(d0,m0)])
|
||||
# monthly
|
||||
for d0 in self.monthly:
|
||||
for m0 in range(1,13):
|
||||
dt = date(y,m0,d0)
|
||||
if not dt in self.cache: self.cache[dt] = Holiday()
|
||||
self.cache[dt].merge_with(self.monthly[m0])
|
||||
# fixed
|
||||
for dt in filter(lambda z: z.year == y, self.fixed):
|
||||
if not dt in self.cache: self.cache[dt] = Holiday()
|
||||
self.cache[dt].merge_with(self.fixed[dt])
|
||||
# orthodox easter
|
||||
edt = _get_orthodox_easter(y)
|
||||
for delta in self.orth_easter:
|
||||
dt = edt + timedelta(delta)
|
||||
if not dt in self.cache: self.cache[dt] = Holiday()
|
||||
self.cache[dt].merge_with(self.orth_easter[delta])
|
||||
# Georgios day
|
||||
if self.george:
|
||||
dt = date(y,4,23)
|
||||
if edt >= dt: dt = edt + timedelta(1) # >= or > ??
|
||||
if not dt in self.cache: self.cache[dt] = Holiday()
|
||||
self.cache[dt].merge_with(self.george)
|
||||
# catholic easter
|
||||
edt = _get_catholic_easter(y)
|
||||
for delta in self.cath_easter:
|
||||
dt = edt + timedelta(delta)
|
||||
if not dt in self.cache: self.cache[dt] = Holiday()
|
||||
self.cache[dt].merge_with(self.cath_easter[delta])
|
||||
|
||||
self.ycache.add(y)
|
||||
|
||||
dt = date(y,m,d)
|
||||
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:
|
||||
return self.s_weekend_multi if dow >= 5 else self.s_multi
|
||||
return self.s_weekend if dow >= 5 else self.s_normal
|
||||
|
||||
def __call__(self, year, month, dom, dow):
|
||||
"""returns (header,footer,day_style)
|
||||
|
||||
@param month: month (0-12)
|
||||
@param dom: day of month (1-31)
|
||||
@param dow: day of week (0-6)
|
||||
"""
|
||||
hol = self.get_holiday(year,month,dom)
|
||||
if hol:
|
||||
return (hol.header(),hol.footer(),self.get_style(hol.flags,dow))
|
||||
else:
|
||||
return (None,None,self.get_style(0,dow))
|
||||
|
||||
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)
|
||||
if y == 0: y = date.today().year
|
||||
cur = date(y,1,1)
|
||||
d2 = date(y,12,31)
|
||||
while cur <= d2:
|
||||
y,m,d = cur.year, cur.month, cur.day
|
||||
hol = hp.get_holiday(y,m,d)
|
||||
if hol: print cur.strftime("%a %b %d %Y"),hol
|
||||
cur += timedelta(1)
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# callirhoe - high quality calendar rendering
|
||||
# Copyright (C) 2012 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
|
||||
@ -18,7 +18,7 @@
|
||||
|
||||
# *****************************************
|
||||
# #
|
||||
# plugin handling routines #
|
||||
""" plugin handling routines """
|
||||
# #
|
||||
# *****************************************
|
||||
|
||||
@ -26,20 +26,33 @@ import sys
|
||||
import os.path
|
||||
import glob
|
||||
|
||||
def available_files(parent, dir, fmatch = ""):
|
||||
def available_files(parent, dir, fmatch = None):
|
||||
"""find parent/dir/*.py files to be used for plugins
|
||||
|
||||
@note:
|
||||
1. __init__.py should exist
|
||||
2. files starting with underscore are ignored
|
||||
3. if fnmatch is defined (base name), it matches a single file
|
||||
"""
|
||||
good = False
|
||||
res = []
|
||||
pattern = parent + "/" + dir + "/*.py"
|
||||
for x in glob.glob(pattern):
|
||||
basex = os.path.basename(x)
|
||||
if basex == "__init__.py": good = True
|
||||
elif basex.startswith('_'):
|
||||
# ignore files aimed for internal use
|
||||
# safer than [a-z]-style matching...
|
||||
continue
|
||||
else:
|
||||
base = os.path.splitext(basex)[0]
|
||||
if base and ((not fmatch) or (fmatch == base)): res.append((base,parent))
|
||||
return res if good else []
|
||||
|
||||
def plugin_list(cat):
|
||||
return available_files(plugin_path[0], cat) + available_files(plugin_path[1], cat)
|
||||
"""return a sequence of available plugins, using L{available_files()} and L{get_plugin_paths()}"""
|
||||
plugin_paths = get_plugin_paths()
|
||||
return available_files(plugin_paths[0], cat) + available_files(plugin_paths[1], cat)
|
||||
|
||||
# cat = lang (category)
|
||||
# longcat = language
|
||||
@ -47,4 +60,6 @@ def plugin_list(cat):
|
||||
# listopt = --list-lang
|
||||
# preset = "EN"
|
||||
|
||||
plugin_path = [ os.path.expanduser("~/.callirhoe"), sys.path[0] if sys.path[0] else "." ]
|
||||
def get_plugin_paths():
|
||||
"""return the plugin search paths"""
|
||||
return [ os.path.expanduser("~/.callirhoe"), sys.path[0] if sys.path[0] else "." ]
|
||||
|
195
lib/xcairo.py
195
lib/xcairo.py
@ -16,12 +16,11 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see http://www.gnu.org/licenses/
|
||||
|
||||
# *****************************************
|
||||
# #
|
||||
# general-purpose drawing routines #
|
||||
# higher-level CAIRO routines #
|
||||
# #
|
||||
# *****************************************
|
||||
# ********************************************************************
|
||||
# #
|
||||
""" general-purpose drawing routines & higher-level CAIRO routines """
|
||||
# #
|
||||
# ********************************************************************
|
||||
|
||||
import cairo
|
||||
import math
|
||||
@ -30,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(':')
|
||||
@ -49,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:
|
||||
@ -68,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
|
||||
@ -91,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:
|
||||
@ -119,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:
|
||||
@ -132,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)
|
||||
@ -151,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,7 +256,34 @@ def draw_shadow(cr, rect, thickness = None, shadow_color = (0,0,0,0.3)):
|
||||
set_color(cr, shadow_color)
|
||||
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)
|
||||
cr.rel_line_to(w, h)
|
||||
cr.close_path()
|
||||
if stroke_rgba:
|
||||
set_color(cr, stroke_rgba)
|
||||
cr.set_line_width(stroke_width)
|
||||
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
|
||||
@ -189,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
|
||||
@ -202,13 +339,17 @@ def draw_str(cr, text, rect, stretch = -1, stroke_rgba = None, align = (2,0), bb
|
||||
if measure is None: measure = text
|
||||
te = cr.text_extents(measure)
|
||||
mw, mh = te[2], te[3]
|
||||
ratio, tratio = w*1.0/h, mw*1.0/mh;
|
||||
if mw < 5:
|
||||
mw = 5.
|
||||
if mh < 5:
|
||||
mh = 5.
|
||||
#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]
|
||||
|
24
style/bw.py
24
style/bw.py
@ -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,19 +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_style(dom_holiday):
|
||||
bg = (0,0,0)
|
||||
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
|
||||
|
75
style/bw_sparse.py
Normal file
75
style/bw_sparse.py
Normal file
@ -0,0 +1,75 @@
|
||||
# callirhoe - high quality calendar rendering
|
||||
# Copyright (C) 2012 George M. Tzoumas
|
||||
|
||||
# Sparse Style Definition
|
||||
# Copyright (C) 2013 Neels Hofmeyr
|
||||
|
||||
# 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 3 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, see http://www.gnu.org/licenses/
|
||||
|
||||
# --- style.bw ---
|
||||
|
||||
"""module defining the black & white sparse style
|
||||
|
||||
to be used with sparse layout
|
||||
"""
|
||||
|
||||
class dow:
|
||||
"""day of week style"""
|
||||
fg = (0,0,0)
|
||||
frame_thickness = 0.1
|
||||
frame = (.5, .5, .5)
|
||||
font = "Arial"
|
||||
|
||||
class dom:
|
||||
"""day of month style"""
|
||||
bg = (1,1,1)
|
||||
frame = (.9, .9, .9)
|
||||
frame_thickness = 0.1
|
||||
fg = (0.3,0.3,0.3)
|
||||
font = "Times New Roman"
|
||||
header = (0.3,0.3,0.3)
|
||||
footer = header
|
||||
header_font = footer_font = "Arial"
|
||||
|
||||
class dom_holiday(dom):
|
||||
"""day of month (holiday, indicated by the OFF flag in the holiday file)"""
|
||||
bg = (0.95,0.95,0.95)
|
||||
|
||||
class dom_weekend(dom_holiday):
|
||||
"""day of month style (weekend)"""
|
||||
font = ("Times New Roman", 0, 1)
|
||||
|
||||
class dom_weekend_holiday(dom_weekend):
|
||||
"""day of month (weekend & holiday)"""
|
||||
pass
|
||||
|
||||
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
|
||||
bg = (1,1,1)
|
||||
color_map = ((1,1,1),)*13
|
||||
color_map_bg = (((1,1,1),)*13,((.8,.8,.8),)*13)
|
||||
color_map_fg = (((0,0,0),)*13,((0,0,0),)*13)
|
||||
box_shadow = False
|
||||
text_shadow = False
|
@ -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,17 +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)
|
||||
|
||||
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_style(dom_holiday):
|
||||
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
|
||||
|
15
style/gfs.py
15
style/gfs.py
@ -14,12 +14,13 @@
|
||||
# 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 ---
|
||||
|
||||
# day of week
|
||||
"""module defining Greek Font Society fonts for default style"""
|
||||
|
||||
import default
|
||||
|
||||
# day of week
|
||||
class dow(default.dow):
|
||||
font = "GFS Neohellenic"
|
||||
|
||||
@ -36,7 +37,15 @@ class dom_holiday(default.dom_holiday):
|
||||
font = ("GFS Bodoni",)
|
||||
header_font = footer_font = "GFS Elpis"
|
||||
|
||||
class dom_weekend_holiday_style(default.dom_weekend_holiday_style):
|
||||
class dom_weekend_holiday(default.dom_weekend_holiday):
|
||||
font = "GFS Bodoni"
|
||||
header_font = footer_font = "GFS Elpis"
|
||||
|
||||
class dom_multi(default.dom_multi):
|
||||
font = ("GFS Bodoni",)
|
||||
header_font = footer_font = "GFS Elpis"
|
||||
|
||||
class dom_weekend_multi(default.dom_weekend_multi):
|
||||
font = "GFS Bodoni"
|
||||
header_font = footer_font = "GFS Elpis"
|
||||
|
||||
|
@ -16,10 +16,11 @@
|
||||
|
||||
# --- style.rainbow ---
|
||||
|
||||
# day of week
|
||||
"""module defining rainbow color & gfs style"""
|
||||
|
||||
import gfs
|
||||
|
||||
# day of week
|
||||
class dow(gfs.dow): pass
|
||||
|
||||
# day of month
|
||||
@ -29,7 +30,11 @@ class dom_weekend(gfs.dom_weekend): pass
|
||||
|
||||
class dom_holiday(gfs.dom_holiday): pass
|
||||
|
||||
class dom_weekend_holiday_style(gfs.dom_weekend_holiday_style): pass
|
||||
class dom_weekend_holiday(gfs.dom_weekend_holiday): pass
|
||||
|
||||
class dom_multi(gfs.dom_multi): pass
|
||||
|
||||
class dom_weekend_multi(gfs.dom_weekend_multi): pass
|
||||
|
||||
from lib.geom import color_mix, color_scale, color_auto_fg
|
||||
|
||||
|
@ -16,10 +16,11 @@
|
||||
|
||||
# --- style.rainbow ---
|
||||
|
||||
# day of week
|
||||
"""module defining rainbow color style"""
|
||||
|
||||
import default
|
||||
|
||||
# day of week
|
||||
class dow(default.dow): pass
|
||||
|
||||
# day of month
|
||||
@ -29,7 +30,11 @@ class dom_weekend(default.dom_weekend): pass
|
||||
|
||||
class dom_holiday(default.dom_holiday): pass
|
||||
|
||||
class dom_weekend_holiday_style(default.dom_weekend_holiday_style): pass
|
||||
class dom_weekend_holiday(default.dom_weekend_holiday): pass
|
||||
|
||||
class dom_multi(default.dom_multi): pass
|
||||
|
||||
class dom_weekend_multi(default.dom_weekend_multi): pass
|
||||
|
||||
from lib.geom import color_mix, color_scale, color_auto_fg
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user