1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 """base layout module -- others may inherit from this one"""
19
20 import optparse
21 from lib.xcairo import *
22 from lib.geom import *
23 from math import floor, ceil, sqrt
24
26 """get the parser object for the layout command-line arguments
27
28 @param layout_name: corresponding python module (.py file)
29 @rtype: optparse.OptionParser
30 """
31 lname = layout_name.split(".")[1]
32 parser = optparse.OptionParser(usage="%prog (...) --layout " + lname + " [options] (...)",add_help_option=False)
33 parser.add_option("--rows", type="int", default=0, help="force grid rows [%default]")
34 parser.add_option("--cols", type="int", default=0,
35 help="force grid columns [%default]; if ROWS and COLS are both non-zero, "
36 "calendar will span multiple pages as needed; if one value is zero, it "
37 "will be computed automatically in order to fill exactly 1 page")
38 parser.add_option("--grid-order", choices=["row","column"],default="row",
39 help="either `row' or `column' to set grid placing order row-wise or column-wise [%default]")
40 parser.add_option("--z-order", choices=["auto", "increasing", "decreasing"], default="auto",
41 help="either `increasing' or `decreasing' to set whether next month (in grid order) "
42 "lies above or below the previously drawn month; this affects shadow casting, "
43 "since rendering is always performed in increasing z-order; specifying `auto' "
44 "selects increasing order if and only if sloppy boxes are enabled [%default]")
45 parser.add_option("--month-with-year", action="store_true", default=False,
46 help="displays year together with month name, e.g. January 1980; suppresses year from footer line")
47 parser.add_option("--long-daycells", action="store_const", const=0.0, dest="short_daycell_ratio",
48 help="force use of only long daycells")
49 parser.add_option("--short-daycells", action="store_const", const=1.0e6, dest="short_daycell_ratio",
50 help="force use of only short daycells")
51 parser.add_option("--short-daycell-ratio", type="float", default=2.5,
52 help="ratio threshold for day cells below which short version is drawn [%default]")
53 parser.add_option("--no-footer", action="store_true", default=False,
54 help="disable footer line (with year and rendered-by message)")
55 parser.add_option("--symmetric", action="store_true", default=False,
56 help="force symmetric mode (equivalent to --geom-var=month.symmetric=1). "
57 "In symmetric mode, day cells are equally sized and all month boxes contain "
58 "the same number of (possibly empty) cells, independently of how many days or "
59 "weeks per month. In asymmetric mode, empty rows are eliminated, by slightly "
60 "resizing day cells, in order to have uniform month boxes.")
61 parser.add_option("--padding", type="float", default=None,
62 help="set month box padding (equivalent to --geom-var=month.padding=PADDING); "
63 "month bars look better with smaller padding, while matrix mode looks better with "
64 "larger padding")
65 parser.add_option("--no-shadow", action="store_true", default=None,
66 help="disable box shadows")
67 parser.add_option("--opaque", action="store_true", default=False,
68 help="make background opaque (white fill)")
69 parser.add_option("--swap-colors", action="store_true", default=None,
70 help="swap month colors for even/odd years")
71 return parser
72
73
75 """class Holding a day cell to be drawn
76
77 @type day: int
78 @ivar day: day of week
79 @ivar header: header string
80 @ivar footer: footer string
81 @ivar theme: (Style class,Geometry class,Language module) tuple
82 @type show_day_name: bool
83 @ivar show_day_name: whether day name is displayed
84 """
85 - def __init__(self, day, header, footer, theme, show_day_name):
86 self.day = day
87 self.header = header
88 self.footer = footer
89 self.theme = theme
90 self.show_day_name = show_day_name
91
93 """render the day cell in short mode"""
94 S,G,L = self.theme
95 x, y, w, h = rect
96 day_of_month, day_of_week = self.day
97 draw_box(cr, rect, S.frame, S.bg, mm_to_dots(S.frame_thickness))
98 R = rect_rel_scale(rect, G.size[0], G.size[1])
99 if self.show_day_name:
100 Rdom, Rdow = rect_hsplit(R, *G.mw_split)
101 else:
102 Rdom = R
103 valign = 0 if self.show_day_name else 2
104
105 draw_str(cr, text = str(day_of_month), rect = Rdom, scaling = -1, stroke_rgba = S.fg,
106 align = (2,valign), font = S.font, measure = "88")
107
108 if self.show_day_name:
109 draw_str(cr, text = L.day_name[day_of_week][0], rect = Rdow, scaling = -1, stroke_rgba = S.fg,
110 align = (2,valign), font = S.font, measure = "88")
111
112 if self.header:
113 R = rect_rel_scale(rect, G.header_size[0], G.header_size[1], 0, -1.0 + G.header_align)
114 draw_str(cr, text = self.header, rect = R, scaling = -1, stroke_rgba = S.header,
115 font = S.header_font)
116
117 if self.footer:
118 R = rect_rel_scale(rect, G.footer_size[0], G.footer_size[1], 0, 1.0 - G.footer_align)
119 draw_str(cr, text = self.footer, rect = R, scaling = -1, stroke_rgba = S.footer,
120 font = S.footer_font)
121
123 """render the day cell in long mode"""
124 S,G,L = self.theme
125 x, y, w, h = rect
126 day_of_month, day_of_week = self.day
127 draw_box(cr, rect, S.frame, S.bg, mm_to_dots(S.frame_thickness))
128 R1, Rhf = rect_hsplit(rect, *G.hf_hsplit)
129 if self.show_day_name:
130 R = rect_rel_scale(R1, G.size[2], G.size[3])
131 Rdom, Rdow = rect_hsplit(R, *G.mw_split)
132 else:
133 Rdom = rect_rel_scale(R1, G.size[0], G.size[1])
134 valign = 0 if self.show_day_name else 2
135
136 draw_str(cr, text = str(day_of_month), rect = Rdom, scaling = -1, stroke_rgba = S.fg,
137 align = (2,valign), font = S.font, measure = "88")
138
139 if self.show_day_name:
140 draw_str(cr, text = L.day_name[day_of_week], rect = Rdow, scaling = -1, stroke_rgba = S.fg,
141 align = (0,valign), font = S.font, measure = "M")
142 Rh, Rf = rect_vsplit(Rhf, *G.hf_vsplit)
143
144 if self.header:
145 draw_str(cr, text = self.header, rect = Rh, scaling = -1, stroke_rgba = S.header, align = (1,2),
146 font = S.header_font)
147
148 if self.footer:
149 draw_str(cr, text = self.footer, rect = Rf, scaling = -1, stroke_rgba = S.footer, align = (1,2),
150 font = S.footer_font)
151
152 - def draw(self, cr, rect, short_thres):
153 """automatically render a short or long day cell depending on threshold I{short_thres}
154
155 If C{rect} ratio is less than C{short_thres} then short mode is chosen, otherwise long mode.
156 """
157 if rect_ratio(rect) < short_thres:
158 self._draw_short(cr, rect)
159 else:
160 self._draw_long(cr, rect)
161
162
164 """base monthly calendar renderer - others inherit from this
165
166 @ivar Outfile: output file
167 @ivar Year: year of first month
168 @ivar Month: first month
169 @ivar MonthSpan: month span
170 @ivar Theme: (Style module,Geometry module,Language module) tuple
171 @ivar holiday_provider: L{HolidayProvider} object
172 @ivar version_string: callirhoe version string
173 @ivar options: parser options object
174 """
175 - def __init__(self, Outfile, Year, Month, MonthSpan, Theme, holiday_provider, version_string, options):
176 self.Outfile = Outfile
177 self.Year = Year
178 self.Month = Month
179 self.MonthSpan = MonthSpan
180 self.Theme = Theme
181 self.holiday_provider = holiday_provider
182 self.version_string = version_string
183 self.options = options
184
186 """this method renders a calendar month, it B{should be overridden} in any subclass
187
188 @param cr: cairo context
189 @param rect: rendering rect
190 @param month: month
191 @param year: year
192 """
193 raise NotImplementedError("base _draw_month() should be overridden")
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
213 """main calendar rendering routine"""
214 S,G,L = self.Theme
215 rows, cols = self.options.rows, self.options.cols
216
217 if self.options.symmetric:
218 G.month.symmetric = True
219 if self.options.padding is not None:
220 G.month.padding = self.options.padding
221 if self.options.no_shadow == True:
222 S.month.box_shadow = False
223 if self.Year % 2: self.options.swap_colors = not self.options.swap_colors
224 if self.options.swap_colors:
225 S.month.color_map_bg = (S.month.color_map_bg[1], S.month.color_map_bg[0])
226 S.month.color_map_fg = (S.month.color_map_fg[1], S.month.color_map_fg[0])
227
228 try:
229 page = PageWriter(self.Outfile, G.pagespec, not self.options.opaque, G.landscape, G.border)
230 except InvalidFormat as e:
231 print >> sys.stderr, "invalid output format", e.args[0]
232 sys.exit(1)
233
234 if rows == 0 and cols == 0:
235
236
237
238
239
240
241 cols = int(floor(sqrt(self.MonthSpan)))
242 rows = cols
243 if rows*cols < self.MonthSpan: rows += 1
244 if rows*cols < self.MonthSpan: rows += 1
245 if rows*cols < self.MonthSpan: cols += 1; rows -= 1
246 if G.landscape: rows, cols = cols, rows
247 elif rows == 0:
248 rows = int(ceil(self.MonthSpan*1.0/cols))
249 elif cols == 0:
250 cols = int(ceil(self.MonthSpan*1.0/rows))
251 G.landscape = page.landscape
252
253 if not self.options.no_footer:
254 V0 = VLayout(page.Text_rect, 40, (1,)*4)
255 Rcal = V0.item_span(39,0)
256 Rc = rect_rel_scale(V0.item(39),1,0.5,0,0)
257 else:
258 Rcal = page.Text_rect
259
260 grid = GLayout(Rcal, rows, cols, pad = (mm_to_dots(G.month.padding),)*4)
261 mpp = grid.count()
262 num_pages = int(ceil(self.MonthSpan*1.0/mpp))
263 cur_month = self.Month
264 cur_year = self.Year
265 num_placed = 0
266 page_layout = []
267 for k in xrange(num_pages):
268 page_layout.append([])
269 for i in xrange(mpp):
270 page_layout[k].append((cur_month,cur_year))
271 num_placed += 1
272 cur_month += 1
273 if cur_month > 12: cur_month = 1; cur_year += 1
274 if num_placed >= self.MonthSpan: break
275
276 num_pages_written = 0
277
278 z_order = self.options.z_order
279 if z_order == "auto":
280 if G.month.sloppy_dx != 0 or G.month.sloppy_dy != 0 or G.month.sloppy_rot != 0:
281 z_order = "decreasing"
282 else:
283 z_order = "increasing"
284 for p in page_layout:
285 num_placed = 0
286 yy = [p[0][1]]
287 if z_order == "decreasing": p.reverse()
288 for (m,y) in p:
289 k = len(p) - num_placed - 1 if z_order == "decreasing" else num_placed
290 self._draw_month(page.cr, grid.item_seq(k, self.options.grid_order == "column"),
291 month=m, year=y)
292 num_placed += 1
293 if (y > yy[-1]): yy.append(y)
294 if not self.options.month_with_year and not self.options.no_footer:
295 year_str = str(yy[0]) if yy[0] == yy[-1] else "%s – %s" % (yy[0],yy[-1])
296 draw_str(page.cr, text = year_str, rect = Rc, stroke_rgba = (0,0,0,0.5), scaling = -1,
297 align = (0,0), font = (extract_font_name(S.month.font),0,0))
298 if not self.options.no_footer:
299 draw_str(page.cr, text = "rendered by Callirhoe ver. %s" % self.version_string,
300 rect = Rc, stroke_rgba = (0,0,0,0.5), scaling = -1, align = (1,0),
301 font = (extract_font_name(S.month.font),1,0))
302 num_pages_written += 1
303 page.end_page()
304 if num_pages_written < num_pages:
305 page.new_page()
306