File Coverage

blib/lib/PostScript/Calendar.pm
Criterion Covered Total %
statement 244 278 87.7
branch 78 112 69.6
condition 43 80 53.7
subroutine 26 28 92.8
pod 7 18 38.8
total 398 516 77.1


line stmt bran cond sub pod time code
1             #---------------------------------------------------------------------
2             package PostScript::Calendar;
3             #
4             # Copyright 2010 Christopher J. Madsen
5             #
6             # Author: Christopher J. Madsen
7             # Created: Sat Nov 25 14:32:55 2006
8             #
9             # This program is free software; you can redistribute it and/or modify
10             # it under the same terms as Perl itself.
11             #
12             # This program is distributed in the hope that it will be useful,
13             # but WITHOUT ANY WARRANTY; without even the implied warranty of
14             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See either the
15             # GNU General Public License or the Artistic License for more details.
16             #
17             # ABSTRACT: Generate a monthly calendar in PostScript
18             #---------------------------------------------------------------------
19              
20 3     3   46565 use 5.008;
  3         10  
  3         148  
21 3     3   20 use warnings;
  3         8  
  3         109  
22 3     3   73 use strict;
  3         7  
  3         120  
23 3     3   20 use Carp;
  3         5  
  3         614  
24 3         444 use Date::Calc 5.0 qw(Add_Delta_YM Day_of_Week Day_of_Week_to_Text
25 3     3   3867 Days_in_Month Localtime Mktime Month_to_Text);
  3         165017  
26 3     3   4791 use PostScript::File 2.20 qw(str); # need use_functions
  3         133986  
  3         19603  
27              
28              
29             #=====================================================================
30             # Package Global Variables:
31              
32             our $VERSION = '1.01';
33             # This file is part of PostScript-Calendar 1.01 (February 12, 2012)
34              
35             our @phaseName = qw(NewMoon FirstQuarter FullMoon LastQuarter);
36              
37             #---------------------------------------------------------------------
38             # Tied hashes for interpolating function calls into strings:
39              
40             { package PostScript::Calendar::Interpolation;
41              
42 6     6   24 sub TIEHASH { bless $_[1], $_[0] }
43 1687     1687   3783 sub FETCH { $_[0]->($_[1]) }
44             } # end PostScript::Calendar::Interpolation
45              
46             our (%C, %E, %S, $psFile);
47             tie %E, 'PostScript::Calendar::Interpolation', sub { $_[0] }; # eval
48             # quoted string:
49             tie %S, 'PostScript::Calendar::Interpolation', sub { $psFile->pstr(shift) };
50              
51             #---------------------------------------------------------------------
52             # Return the first defined value:
53              
54             sub firstdef
55             {
56 414     414 0 1149 foreach (@_) {
57 987 100       3709 return $_ if defined $_;
58             }
59              
60 0         0 $_[-1];
61             } # end firstdef
62              
63             #---------------------------------------------------------------------
64             sub _fmt_color
65             {
66 61     61   85 my $color = shift;
67              
68 61 50 66     481 if (not ref $color and $color =~ /^#((?:[0-9a-f]{3})+)$/i) {
69 0         0 my $hexcolor = $1;
70              
71 0         0 my $digits = int(length($hexcolor) / 3); # Number of digits per color
72 0         0 my $max = hex('F' x $digits); # Max intensity per color
73              
74 0         0 $color = [ map {
75 0         0 my $n = sprintf('%.3f',
76             hex(substr($hexcolor, $_ * $digits, $digits)) / $max);
77 0         0 $n =~ s/\.?0+$//;
78 0         0 $n
79             } 0 .. 2 ];
80             } # end if color as hex triplet
81              
82 61         179 str($color);
83             } # end _fmt_color
84              
85             #---------------------------------------------------------------------
86             # Round to an integer, but preserve undef:
87              
88             sub round
89             {
90 144 100   144 0 1539 defined $_[0] ? sprintf('%d', $_[0]) : $_[0];
91             } # end round
92              
93             #---------------------------------------------------------------------
94             # Add delta months:
95             #
96             # ($year, $month) = Add_Delta_M($year, $month, $delta_months);
97              
98             sub Add_Delta_M
99             {
100 25     25 0 179 (Add_Delta_YM($_[0], $_[1], 1, 0, $_[2]))[0,1];
101             }
102              
103             #=====================================================================
104             # Constants:
105             #---------------------------------------------------------------------
106              
107             # This is one time subroutine prototypes are useful:
108             ## no critic (ProhibitSubroutinePrototypes)
109              
110             sub evTxt () { 0 }
111             sub evPS () { 1 }
112             sub evBackground () { 2 }
113             sub evTopMargin () { 3 }
114             sub evDict () { 4 }
115              
116             ## use critic
117              
118             #=====================================================================
119             # Package PostScript::Calendar:
120              
121             sub new
122             {
123 18     18 1 92741 my ($class, $year, $month, %p) = @_;
124              
125 18   50     135 my $self = bless {
      50        
      50        
      50        
      50        
      50        
      50        
      50        
      50        
      50        
      50        
126             events => [],
127             psFile => $p{ps_file},
128             condense => $p{condense},
129             border => firstdef($p{border}, 1),
130             dayHeight => round($p{day_height}),
131             grid => firstdef($p{grid}, 1),
132             gridWidth => firstdef($p{grid_width}, 0.72), # 3 pixels at 300dpi
133             mini => $p{mini_calendars},
134             phases => $p{phases},
135             title => firstdef($p{title},
136             sprintf '%s %d', Month_to_Text($month), $year),
137             days => ($p{days} || [ 0 .. 6 ]), # Sun .. Sat
138             year => $year,
139             month => $month,
140             sideMar => round(firstdef($p{side_margins}, $p{margin}, 24)),
141             topMar => round(firstdef($p{top_margin}, $p{margin}, 36)),
142             botMar => round(firstdef($p{bottom_margin}, $p{margin}, 24)),
143             titleFont => $p{title_font} || 'Helvetica-iso',
144             titleSize => $p{title_size} || 14,
145             titleSkip => round(firstdef($p{title_skip}, 5)),
146             labelFont => $p{label_font} || $p{title_font} || 'Helvetica-iso',
147             labelSize => $p{label_size} || $p{title_size} || 14,
148             labelSkip => round(firstdef($p{label_skip}, $p{title_skip}, 5)),
149             dateFont => $p{date_font} || 'Helvetica-Oblique-iso',
150             dateSize => $p{date_size} || $p{title_size} || 14,
151             eventFont => $p{event_font} || 'Helvetica-iso',
152             eventSize => $p{event_size} || 8,
153             eventSkip => firstdef($p{event_skip}, 2),
154             miniFont => $p{mini_font} || 'Helvetica-iso',
155             miniSize => $p{mini_size} || 6,
156             miniSkip => firstdef($p{mini_skip}, 3),
157             borderWidth => firstdef($p{border_width}, 0.72), # 3 pixels at 300dpi
158             dateRightMar => firstdef($p{date_right_margin}, 4),
159             dateTopMar => firstdef($p{date_top_margin}, 2),
160             eventTopMar => firstdef($p{event_top_margin}, $p{event_margin}, 2),
161             eventLeftMar => firstdef($p{event_left_margin}, $p{event_margin}, 3),
162             eventRightMar => firstdef($p{event_right_margin}, $p{event_margin}, 2),
163             miniSideMar => firstdef($p{mini_side_margins}, $p{mini_margin}, 4),
164             miniTopMar => firstdef($p{mini_top_margin}, $p{mini_margin}, 4),
165             moonDark => _fmt_color(firstdef($p{moon_dark}, 0)),
166             moonLight => _fmt_color(firstdef($p{moon_light}, 1)),
167             moonMargin => firstdef($p{moon_margin}, 6),
168             shadeColor => _fmt_color(firstdef($p{shade_color}, 0.85)),
169             }, $class;
170              
171 18         933 my $days = $self->{days};
172 18         36 my $firstDay = $days->[0];
173 18         42 $self->{dayOffsets} = [ map { $_ - $firstDay } @$days ];
  126         257  
174              
175             $self->{dayNames} =
176             ($p{day_names} or
177 18   50     98 [ map { Day_of_Week_to_Text($_ % 7 || 7) } @$days ]);
178              
179             # If no title, suppress it completely:
180 18 50       268 if (not length $self->{title}) {
181 0         0 $self->{titleSize} = 0;
182 0         0 $self->{titleSkip} = 0;
183             } # end if title is suppressed
184              
185             # Create a PostScript::File object if necessary:
186 18 100       59 unless ($self->{psFile}) {
187 7   50     85 $self->{psFile} = PostScript::File->new(
188             paper => ($p{paper} || 'Letter'),
189             top => $self->{topMar},
190             left => $self->{sideMar},
191             right => $self->{sideMar},
192             title => PostScript::File->quote_text($self->{title}),
193             reencode => 'cp1252',
194             strip => 'all_comments',
195             landscape => $p{landscape},
196             );
197              
198 7         39466 $self->{psFile}->add_comment(
199             sprintf 'Creator: %s %s', ref($self), $self->VERSION
200             );
201             } # end unless supplied ps_file
202              
203             # Compile the list of required fonts:
204 18         89 my %font;
205 18         110 while (my ($k, $v) = each %$self) {
206 792 100       2968 next unless $k =~ /Font$/;
207 90 50       630 $font{ $v =~ /^(.+)-iso$/ ? $1 : $v } = 1;
208             }
209 18         128 $self->{psFile}->need_resource(font => keys %font);
210              
211             # Shade specified days of the week:
212 18 100       936 $self->shade_days_of_week(@{ $p{shade_days_of_week} })
  3         16  
213             if $p{shade_days_of_week};
214              
215 18         90 $self;
216             } # end new
217              
218             #---------------------------------------------------------------------
219             sub calc_moon_phases
220             {
221 1     1 0 4 my ($self, $year, $month) = @_;
222              
223             # RECOMMEND PREREQ: Astro::MoonPhase 0.60
224 1         11 require Astro::MoonPhase;
225 1         26 Astro::MoonPhase->VERSION(0.60); # Need phaselist
226              
227 1         10 my ($phase, @dates) = Astro::MoonPhase::phaselist(
228             Mktime($year, $month, 1, 0,0,0),
229             Mktime(Add_Delta_M($year, $month, 1), 1, 0,0,0)
230             );
231              
232             # Convert Unix times to day-of-month:
233 1         1122 ($phase, map { (Localtime $_)[2] } @dates);
  4         102  
234             } # end calc_moon_phases
235              
236             #---------------------------------------------------------------------
237             sub compute_grid
238             {
239 42     42 0 88 my ($self, $year, $month, $condense) = @_;
240              
241 42         148 my ($days, $offsets) = @$self{qw(days dayOffsets)};
242              
243 42         212 my $numDays = Days_in_Month($year, $month);
244              
245 42         591 my @grid;
246              
247 42         176 my $leftDate = 1 + $days->[0] - Day_of_Week($year, $month, 1);
248              
249 42 100       1499 $leftDate += 7 if $leftDate + $offsets->[-1] < 1;
250              
251 42         121 while ($leftDate <= $numDays) {
252 219         354 push @grid, [ map { my $d = $leftDate + $_;
  1533         1685  
253 1533 100 100     6031 ($d > 0 and $d <= $numDays) ? $d : undef } @$offsets ];
254 219         622 $leftDate += 7;
255             }
256              
257 42 50 33     125 if ($condense and @grid == 6) {
258 0 0       0 if ($grid[0][-2]) { # merge up the bottom row
259 0         0 $grid[-2][0] = [ split => $grid[-2][0], $grid[-1][0] ];
260 0         0 pop @grid;
261             } else { # merge down the top row
262 0         0 $grid[1][-1] = [ split => $grid[0][-1], $grid[1][-1] ];
263 0         0 shift @grid;
264             }
265             } # end if grid needs to be condensed
266              
267 42         113 return \@grid;
268             } # end compute_grid
269              
270             #---------------------------------------------------------------------
271             sub get_metrics
272             {
273 30     30 0 55 my ($self, $font, $size) = @_;
274              
275 30   66     426 $self->{fontCache}{$font}{$size}
276             ||= $self->{psFile}->get_metrics($font, $size);
277             } # end get_metrics
278              
279             #---------------------------------------------------------------------
280             sub add_event
281             {
282 3     3 1 1310 my ($self, $date, $message) = @_;
283              
284 3         6 push @{$self->{events}[$date][evTxt]}, split(/[ \t]*\n/, $message);
  3         34  
285             } # end add_event
286              
287             #---------------------------------------------------------------------
288             sub _set_colors
289             {
290 9     9   11 my $hash = shift;
291              
292 9         29 while (@_) {
293 27         94 my $key = shift;
294 27         40 my $color = shift;
295              
296 27 100       85 $hash->{$key} = _fmt_color($color) if defined $color;
297             }
298             } # end _set_colors
299              
300             #---------------------------------------------------------------------
301             sub shade
302             {
303 9     9 1 1509 my $self = shift @_;
304              
305 9 100       29 my $options = ref($_[0]) ? shift @_ : {};
306              
307 9         15 my %dict;
308 9         46 _set_colors(\%dict,
309             DayBackground => $options->{shade_color},
310             MoonDark => $options->{moon_dark},
311             MoonLight => $options->{moon_light},
312             );
313              
314 9         43 my $events = $self->{events};
315              
316 9         19 for my $date (@_) {
317 30         64 $events->[$date][evBackground] = "ShadeDay";
318              
319 30 100       92 @{ $events->[$date][evDict] }{keys %dict} = values %dict if %dict;
  4         51  
320             }
321             } # end shade
322              
323             #---------------------------------------------------------------------
324             sub shade_days_of_week
325             {
326 3     3 1 7 my $self = shift @_;
327              
328 3         10 my ($year, $month) = @$self{qw(year month)};
329              
330 3         5 my (@shade, @dates);
331              
332             # Copy options over to shade:
333 3 50       10 push @dates, shift @_ if ref $_[0];
334              
335             # @shade indicates which days of week to shade
336 3         6 foreach (@_) { $shade[$_ % 7] = 1 }
  6         15  
337              
338 3         18 my $dow = Day_of_Week($year, $month, 1) % 7;
339              
340 3         151 for my $date (1 .. Days_in_Month($year, $month)) {
341 91 100       204 push @dates, $date if $shade[$dow];
342 91         109 $dow = ($dow + 1) % 7;
343             }
344              
345 3 50       20 $self->shade(@dates) if @dates;
346             } # end shade_days_of_week
347              
348             #---------------------------------------------------------------------
349             sub print_calendar
350             {
351 42     42 0 550 my ($self, $grid, %p) = @_;
352              
353             # Must set $psFile for interpolation
354 42         102 local $psFile = my $ps = $self->{psFile};
355              
356 42 50       426 $ps->add_to_page( <<"END_TITLE" ) if length($p{title});
357             $p{titleFont}$p{midpoint} $p{titleY} $S{$p{title}} showCenter
358             END_TITLE
359              
360 42         5545 $ps->add_to_page("$p{labelFont}\n");
361              
362 42         1771 my ($dayHeight, $dayWidth, $dateStartX)
363             = @p{qw(dayHeight dayWidth dateStartX)};
364              
365 42         61 $dateStartX -= $dayWidth;
366              
367 42         98 my $x = $p{leftEdge} + $p{midday};
368 42         58 foreach (@{ $p{dayNames} }) {
  42         101  
369 294         1836 $ps->add_to_page("$x $p{labelY} $S{$_} showCenter\n");
370 294         23067 $x += $dayWidth;
371             }
372              
373 42 100       205 $ps->add_to_page($p{dateFont}) if $p{dateFont};
374              
375 42   100     1006 my $showdate = $p{dateShow} || 'showRight';
376 42         92 my $y = $p{dayTop} - $p{dateSize} - $p{dateTopMar};
377              
378 42         90 foreach my $row (@$grid) {
379 219         284 $x = $dateStartX;
380              
381 219         361 foreach my $day (@$row) {
382 1533         86710 $x += $dayWidth;
383 1533 100       3009 next unless $day;
384              
385 1297 100       2198 if (ref $day) {
386 24 50       109 next unless $day->[0] eq 'split';
387 0         0 $ps->add_to_page("$x $y $S{$day->[1]} $showdate\n" .
388             "$x $E{$y - $dayHeight/2} $S{$day->[2]} $showdate\n");
389             } else {
390 1273         7402 $ps->add_to_page("$x $y $S{$day} $showdate\n");
391             }
392             } # end foreach $day
393              
394 219         14963 $y -= $dayHeight;
395             } # end foreach $row
396              
397             } # end print_calendar
398              
399             #---------------------------------------------------------------------
400             sub print_mini_calendar
401             {
402 24     24 0 56 my ($self, $year, $month, $x, $y, $width, $height) = @_;
403              
404 24         60 my $yTop = $y + $height - $self->{miniTopMar};
405 24         73 my $grid = $self->compute_grid($year, $month);
406 24         45 my $cols = @{ $grid->[0] };
  24         54  
407              
408 24         59 my $fontsize = $self->{miniSize};
409 24         57 my $linespacing = $fontsize + $self->{miniSkip};
410 24         38 my $sideMar = $self->{miniSideMar};
411              
412 24         98 my $font = $self->get_metrics($self->{miniFont}, $fontsize);
413 24         7745 my $numWidth = $font->width('22');
414              
415 24 50       845 my $colSpacing = (($cols > 1)
416             ? ($width - 2 * $sideMar - $cols * $numWidth) / ($cols - 1)
417             : 0);
418              
419 24         62 my $dayWidth = int(($numWidth + $colSpacing) * 8) / 8.0; # Round to 1/8
420 24         46 my $midday = int($numWidth * 4) / 8.0; # Divide by 2 and round to 1/8
421              
422 168         527 $self->print_calendar($grid,
423             titleFont => "MiniFont setfont\n",
424             labelFont => '',
425             midpoint => $x + $width/2,
426             midday => $midday,
427             titleY => $yTop - $fontsize,
428             title => Month_to_Text($month),
429             dayHeight => $linespacing,
430             dayWidth => $dayWidth,
431             dateStartX => $x + $sideMar + $midday,
432             dateShow => 'showCenter',
433             leftEdge => $x + $sideMar,
434 24         111 dayNames => [ map { substr($_,0,1) } @{$self->{dayNames}} ],
  24         319  
435             labelY => $yTop - $fontsize - $linespacing,
436             dayTop => $yTop - 2 * $linespacing,
437             dateSize => $fontsize,
438             dateTopMar => 0,
439             );
440              
441             } # end print_mini_calendar
442              
443             #---------------------------------------------------------------------
444             sub print_events
445             {
446 34     34 0 76 my ($self, $eventArray, $date, $x, $y, $width, $height, $special) = @_;
447              
448 34         48 my $events = $eventArray->[$date];
449 34         52 my $ps = $self->{psFile};
450              
451             # Handle background:
452 34 100       85 unshift @{$events->[evPS]}, $events->[evBackground]
  28         84  
453             if $events->[evBackground];
454              
455 34         50 my $dict = $events->[evDict];
456              
457 34 50 33     91 if ($special and $events->[evPS]) {
458 0 0       0 $dict = { $dict ? %$dict : () };
459 0         0 $dict->{DayHeight} = $height;
460             }
461              
462 34 100       71 if ($dict) {
463 4         18 $ps->set_min_langlevel(2); # using dictionary literals
464 7         52 $ps->add_to_page(join("\n",
465             '<<',
466 4         37 ( map { "/$_ $dict->{$_}" } sort keys %$dict ),
467             ">> begin\n"
468             ));
469             } # end if dictionary
470              
471             # Handle PostScript events:
472 34 100       663 if ($events->[evPS]) {
473 32         153 $ps->add_to_page(join "\n",
474             "gsave\n$x $y translate",
475 32         73 @{ $events->[evPS] },
476             "grestore\n"
477             );
478             } # end if we have PostScript events
479              
480             # Handle text events:
481 34 100       2946 if ($events->[evTxt]) {
482 3         11 my ($eventSize, $eventTopMar, $eventLeftMar, $eventRightMar) =
483             @$self{qw(eventSize eventTopMar eventLeftMar eventRightMar)};
484 3   50     17 my $useY = $height - $eventTopMar - ($events->[evTopMargin] || 0);
485              
486 3         12 my $text = $self->wrap_events($useY, $width, $height, $events->[evTxt],
487             $date);
488 3         65 $ps->add_to_page(<<"END_EVENTS");
489             $E{$x + $eventLeftMar} $E{$y + $useY - $eventSize} [$text] Events
490             END_EVENTS
491             } # end if we have text events
492              
493 34 100       391 $ps->add_to_page("end\n") if $dict;
494             } # end print_events
495              
496             #---------------------------------------------------------------------
497             sub wrap_events
498             {
499 3     3 0 9 my ($self, $y, $width, $height, $events, $date) = @_;
500              
501 3         5 my $ps = $self->{psFile};
502 3         9 my $eventSize = $self->{eventSize};
503 3         11 my $metrics = $self->get_metrics($self->{eventFont}, $eventSize);
504 3         6010 my $eventSpacing = $eventSize + $self->{eventSkip};
505              
506 3         6 my $dateSize = $self->{dateSize};
507 3         10 my $dateBottom = $height - $dateSize - $self->{dateTopMar};
508              
509 3         9 my $fullWidth = ($width -= $self->{eventLeftMar} + $self->{eventRightMar});
510              
511 3 50       10 if ($y > $dateBottom) {
512 3         11 my $dateMetrics = $self->get_metrics($self->{dateFont}, $dateSize);
513              
514 3         1561 $width -= ($dateMetrics->width($date) +
515             $self->{dateRightMar});
516             }
517              
518 3         98 my $next;
519              
520 3         12 for (my $i = 0; $i <= $#$events; ++$i, $y -= $eventSpacing) {
521 4 50       14 $width = $fullWidth if $y < $dateBottom;
522              
523 4 50       10 if ($y < $eventSize) {
524 0         0 carp sprintf("WARNING: Event text for %s-%02d-%02d doesn't fit",
525             $self->{year}, $self->{month}, $date);
526 0         0 splice @$events, $i, scalar @$events;
527 0         0 last;
528             } # end if we ran out of space
529              
530 4         11 for ($events->[$i]) {
531 4         11 s/\s+$//; # Remove trailing space, if any
532              
533 4         7 $next = '';
534 4   33     15 while (($metrics->width($_) > $width) and
      66        
535             (s/-([^- \t]+-*)$/-/ or
536             s/([ \t]+[^- \t]*-*)$// or
537             s/(.)$//)) {
538 1         55 $next = $1 . $next;
539             } # end while string too wide
540              
541 4 100       128 if (length $next) {
542 1         4 $next =~ s/^\s+//;
543 1         7 splice @$events, $i+1,0, $next;
544             } # end if string was too wide
545             } # end for this event string
546             } # end for each event
547              
548 3         7 join("\n", map { $ps->pstr($_) } @$events);
  4         33  
549             } # end wrap_events
550              
551             #---------------------------------------------------------------------
552             sub generate
553             {
554 18     18 1 20720 my $self = $_[0];
555              
556 18         134 my ($ps, $days, $events, $year, $month, $topMar, $botMar, $sideMar, $mini,
557             $titleSize, $dayLabelSize, $labelSkip)
558             = @$self{qw(psFile days events year month topMar botMar sideMar mini
559             titleSize labelSize labelSkip)};
560              
561 18         113 my ($width, $height, $landscape) =
562             ($ps->get_width, $ps->get_height, $ps->get_landscape);
563              
564 18 50       293 ($width, $height) = ($height, $width) if $landscape;
565              
566 18         99 my $dayWidth = round(($width - 2 * $sideMar) / @$days);
567 18         54 my $midday = $dayWidth / 2;
568 18         39 my $gridWidth = $dayWidth * @$days;
569 18         75 my $leftEdge = $sideMar;
570 18         32 my $gridRight = $leftEdge + $gridWidth;
571              
572 18         31 my $midpoint = $width / 2;
573              
574 18         40 my $titleY = $height - $titleSize - $topMar;
575              
576 18         45 my $labelY = $titleY - $dayLabelSize - $self->{titleSkip};
577              
578 18         33 my $dayTop = $labelY - $labelSkip;
579              
580 18         80 my $grid = $self->compute_grid($year, $month, $self->{condense});
581              
582 18 100       52 if ($mini) {
583 12         16 my (@prev, @next);
584 12 50       44 push @$grid, [ (undef) x @$days ] if @$grid == 4;
585              
586 12 100 66     195 if ($grid->[-1][-1] or
    50 66        
      33        
      66        
      33        
      66        
      33        
      33        
587             ($mini eq 'before' and not $grid->[0][1]) or
588             ($mini eq 'after' and $grid->[-1][-2])) {
589 8         18 @prev = (0,0); @next = (0,1); # Both calendars at beginning
  8         19  
590             } elsif ($grid->[0][0] or
591             ($mini eq 'after' and not $grid->[-1][-2]) or
592             ($mini eq 'before' and $grid->[0][1])) {
593 4         11 @prev = (-1,-2); @next = (-1,-1); # Both calendars at end
  4         10  
594             } else {
595 0         0 @prev = (0,0); @next = (-1,-1); # Split between beginning & end
  0         0  
596             }
597              
598 12         50 $grid->[$prev[0]][$prev[1]] = [calendar => Add_Delta_M($year, $month, -1)];
599 12         638 $grid->[$next[0]][$next[1]] = [calendar => Add_Delta_M($year, $month, 1)];
600             } # end if mini calendars
601              
602 18         455 my $dayHeight = round(($dayTop - $botMar) / @$grid);
603 18 100 66     229 if ($dayHeight > ($self->{dayHeight} || $dayHeight)) {
604 12         26 $dayHeight = $self->{dayHeight};
605             }
606              
607 18         45 my $gridBottom = $dayTop - $dayHeight * @$grid;
608 18         33 my $gridHeight = $dayTop - $gridBottom + $dayLabelSize + $labelSkip;
609 18         28 my $gridTop = $gridBottom + $gridHeight;
610              
611 18         404 $ps->add_to_page(<<"END_PAGE_INIT");
612             0 setlinecap
613             0 setlinejoin
614              
615             /DayHeight $dayHeight def
616             /DayWidth $dayWidth def
617             /DayBackground $self->{shadeColor} def
618             /TitleSize $titleSize def
619             /TitleFont /$self->{titleFont} findfont TitleSize scalefont def
620             /LabelSize $dayLabelSize def
621             /LabelFont /$self->{labelFont} findfont LabelSize scalefont def
622             /DateSize $self->{dateSize} def
623             /DateFont /$self->{dateFont} findfont DateSize scalefont def
624             /EventSize $self->{eventSize} def
625             /EventFont /$self->{eventFont} findfont EventSize scalefont def
626             /EventSpacing $E{$self->{eventSize} + $self->{eventSkip}} def
627             /MiniSize $self->{miniSize} def
628             /MiniFont /$self->{miniFont} findfont MiniSize scalefont def
629             END_PAGE_INIT
630              
631 18         13153 $ps->use_functions(qw(hLine vLine setColor showCenter showLeft showLines
632             showRight));
633              
634 18 100       16624 unless ($ps->has_procset('PostScript_Calendar'))
635 7         297 { $ps->add_procset('PostScript_Calendar', <<'END_FUNCTIONS') }
636             /pixel {72 mul 300 div} bind def % 300 dpi only
637              
638             %---------------------------------------------------------------------
639             % Display text events: X Y [STRING ...] Events
640              
641             /Events
642             {
643             EventFont setfont
644             EventSpacing /showLeft showLines
645             } bind def
646              
647             %---------------------------------------------------------------------
648             % Fill a day rect with the current ink:
649              
650             /FillDay
651             {
652             newpath
653             0 0 moveto
654             DayWidth 0 lineto
655             DayWidth DayHeight lineto
656             0 DayHeight lineto
657             closepath
658             fill
659             } bind def
660              
661             %---------------------------------------------------------------------
662             % Shade a day: ShadeDay
663              
664             /ShadeDay
665             {
666             gsave
667             DayBackground setColor
668             FillDay
669             grestore
670             } bind def
671             END_FUNCTIONS
672              
673 18 100       10685 if ($self->{phases}) {
674 1         5 my ($phase, @dates) = $self->calc_moon_phases($year, $month);
675 1         34 my $margin = $self->{moonMargin} + $self->{dateSize};
676 1         5 while (@dates) {
677 4 50 50     29 if ($margin > ($events->[$dates[0]][evTopMargin] || 0)) {
678 4         13 $events->[$dates[0]][evTopMargin] = $margin;
679             }
680 4         6 push @{$events->[shift @dates][evPS]}, "/$phaseName[$phase] ShowPhase";
  4         19  
681 4         14 $phase = ($phase + 1) % 4;
682             } # end while @dates
683              
684 1         12 $ps->add_to_page(<<"END_MOON_SETTINGS");
685             /MoonDark $self->{moonDark} def
686             /MoonLight $self->{moonLight} def
687             /MoonMargin $self->{moonMargin} def
688             END_MOON_SETTINGS
689              
690 1 50       140 unless ($ps->has_procset('PostScript_Calendar_Moon'))
691 1         75 { $ps->add_procset('PostScript_Calendar_Moon', <<'END_MOON_FUNCTIONS') }
692             %---------------------------------------------------------------------
693             % Show the phase of the moon: PHASE ShowPhase
694              
695             /ShowPhase
696             {
697             gsave
698             3 pixel setlinewidth
699             newpath
700             MoonMargin DateSize 2 div add
701             DayHeight MoonMargin sub
702             DateSize 2 div sub
703             DateSize 2 div
704             0 360 arc
705             closepath
706             cvx exec
707             grestore
708             } bind def
709              
710             /NewMoon { MoonDark setColor fill } bind def
711             /FullMoon {
712             gsave MoonLight setColor fill grestore
713             MoonDark setColor stroke
714             } bind def
715              
716             /FirstQuarter
717             {
718             FullMoon
719             newpath
720             MoonMargin DateSize 2 div add
721             DayHeight MoonMargin sub DateSize 2 div sub
722             DateSize 2 div
723             90 270 arc
724             closepath fill
725             } bind def
726              
727             /LastQuarter
728             {
729             FullMoon
730             newpath
731             MoonMargin DateSize 2 div add
732             DayHeight MoonMargin sub DateSize 2 div sub
733             DateSize 2 div
734             270 90 arc
735             closepath fill
736             } bind def
737             END_MOON_FUNCTIONS
738             } # end if showing phases of the moon
739              
740 18         2729 my $splitHeight = $dayHeight/2;
741              
742 18         59 my $y = $dayTop;
743 18         45 foreach my $row (@$grid) {
744 93         208 $y -= $dayHeight;
745 93         132 my $x = $leftEdge - $dayWidth;
746              
747 93         152 foreach my $day (@$row) {
748 651         748 $x += $dayWidth;
749 651 100       1112 next unless $day;
750              
751 567 100       846 if (ref $day) {
752 24 50       108 if ($day->[0] eq 'split') {
    50          
753 0         0 my $lineY = $y + $splitHeight;
754 0 0       0 $self->print_events($events, $day->[1], $x, $lineY,
755             $dayWidth, $splitHeight, 1)
756             if $events->[$day->[1]];
757 0 0       0 $self->print_events($events, $day->[2], $x, $y,
758             $dayWidth, $splitHeight, 1)
759             if $events->[$day->[2]];
760              
761 0         0 $ps->add_to_page(<<"END_SPLIT_LINE");
762             $dayWidth $x $lineY hLine
763             END_SPLIT_LINE
764             } elsif ($day->[0] eq 'calendar') {
765 24         123 $self->print_mini_calendar(@$day[1,2], $x, $y, $dayWidth, $dayHeight);
766             }
767             } else {
768 543 100       1316 $self->print_events($events, $day, $x, $y, $dayWidth, $dayHeight)
769             if $events->[$day];
770             }
771             } # end foreach $day
772             } # end foreach $row
773              
774 18         171 $self->print_calendar($grid,
775             titleFont => "TitleFont setfont\n",
776             labelFont => "LabelFont setfont\n",
777             dateFont => "DateFont setfont\n",
778             midpoint => $midpoint,
779             midday => $midday,
780             titleY => $titleY,
781             title => $self->{title},
782             dayHeight => $dayHeight,
783             dayWidth => $dayWidth,
784             dateStartX => $leftEdge + $dayWidth - $self->{dateRightMar},
785             leftEdge => $leftEdge,
786             dayNames => $self->{dayNames},
787             labelY => $labelY,
788             dayTop => $dayTop,
789             dateSize => $self->{dateSize},
790             dateTopMar => $self->{dateTopMar},
791             );
792              
793 18 50       86 if ($self->{grid}) {
794 18         216 $ps->add_to_page(<<"END_GRID");
795             $self->{gridWidth} setlinewidth
796             $E{$gridBottom + $dayHeight} $dayHeight $dayTop\ {
797             $gridWidth $leftEdge 3 -1 roll hLine
798             } for
799              
800             $E{$leftEdge + $dayWidth} $dayWidth $E{$gridRight - $midday}\ {
801             $gridHeight exch $gridBottom vLine
802             } for
803             END_GRID
804             } # end if grid
805              
806 18 50       5308 if ($self->{border}) {
807 18         193 $ps->add_to_page(<<"END_BORDER");
808             $self->{borderWidth} setlinewidth
809             newpath
810             $leftEdge $gridTop moveto
811             $gridWidth 0 rlineto
812             0 -$gridHeight rlineto
813             -$gridWidth 0 rlineto
814             closepath stroke
815             END_BORDER
816             } else {
817 0         0 $ps->add_to_page("$gridWidth $leftEdge $gridTop hLine\n");
818             }
819              
820 18         6887 $self->{generated} = 1;
821             } # end generate
822              
823             #---------------------------------------------------------------------
824             sub output
825             {
826 0     0 1 0 my $self = shift @_;
827              
828 0 0       0 $self->generate unless $self->{generated};
829              
830 0         0 $self->{psFile}->output(@_);
831             } # end output
832              
833             #---------------------------------------------------------------------
834 7     7 1 274 sub ps_file { $_[0]->{psFile} }
835              
836             #---------------------------------------------------------------------
837             sub get__PostScript_File
838             {
839 0     0 0   my $self = shift @_;
840              
841 0 0         $self->generate unless $self->{generated};
842              
843 0           $self->{psFile};
844             } # end output
845              
846             #=====================================================================
847             # Package Return Value:
848              
849             1;
850              
851             __END__