Package layouts :: Module _base
[hide private]
[frames] | no frames]

Source Code for Module layouts._base

  1  # -*- coding: utf-8 -*- 
  2  #    callirhoe - high quality calendar rendering 
  3  #    Copyright (C) 2012-2014 George M. Tzoumas 
  4   
  5  #    This program is free software: you can redistribute it and/or modify 
  6  #    it under the terms of the GNU General Public License as published by 
  7  #    the Free Software Foundation, either version 3 of the License, or 
  8  #    (at your option) any later version. 
  9  # 
 10  #    This program is distributed in the hope that it will be useful, 
 11  #    but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 13  #    GNU General Public License for more details. 
 14  # 
 15  #    You should have received a copy of the GNU General Public License 
 16  #    along with this program.  If not, see http://www.gnu.org/licenses/ 
 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   
25 -def get_parser(layout_name):
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
74 -class DayCell(object):
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
92 - def _draw_short(self, cr, rect):
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 # draw day of month (number) 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 # draw name of day 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 # draw header 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) # , measure = "MgMgMgMgMgMg" 116 # draw footer 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
122 - def _draw_long(self, cr, rect):
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 # draw day of month (number) 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 # draw name of day 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 # draw header 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 # draw footer 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
163 -class CalendarRenderer(object):
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
185 - def _draw_month(self, cr, rect, month, year):
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 #1 1 1 196 #2 2 1 197 #3 3 1 198 199 #4 2 2 200 #5 3 2 201 #6 3 2 202 #7 4 2 203 #8 4 2 204 205 #9 3 3 206 #10 4 3 207 #11 4 3 208 #12 4 3 209 210 #rows = 0 211 #cols = 0
212 - def render(self):
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 # if MonthSpan < 4: 236 # cols = 1; rows = MonthSpan 237 # elif MonthSpan < 9: 238 # cols = 2; rows = int(math.ceil(MonthSpan/2.0)) 239 # else: 240 # TODO: improve this heuristic 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 # PNG is pseudo-landscape (portrait with width>height) 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() # months per page 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