File Coverage

blib/lib/Calendar/Plugin/Renderer/SVG.pm
Criterion Covered Total %
statement 29 130 22.3
branch 0 8 0.0
condition 0 5 0.0
subroutine 10 13 76.9
pod 0 3 0.0
total 39 159 24.5


line stmt bran cond sub pod time code
1             package Calendar::Plugin::Renderer::SVG;
2              
3             $Calendar::Plugin::Renderer::SVG::VERSION = '0.12';
4             $Calendar::Plugin::Renderer::SVG::AUTHORITY = 'cpan:MANWAR';
5              
6             =head1 NAME
7              
8             Calendar::Plugin::Renderer::SVG - Interface to render calendar in SVG format.
9              
10             =head1 VERSION
11              
12             Version 0.12
13              
14             =cut
15              
16 3     3   52 use 5.006;
  3         17  
17 3     3   10 use Data::Dumper;
  3         3  
  3         147  
18              
19 3     3   1136 use Calendar::Plugin::Renderer::Util qw(round_number get_max_week_rows);
  3         3  
  3         154  
20 3     3   1076 use Calendar::Plugin::Renderer::SVG::Box;
  3         5  
  3         68  
21 3     3   1096 use Calendar::Plugin::Renderer::SVG::Page;
  3         4  
  3         71  
22 3     3   1122 use Calendar::Plugin::Renderer::SVG::Text;
  3         5  
  3         72  
23 3     3   1090 use Calendar::Plugin::Renderer::SVG::Label;
  3         3  
  3         67  
24              
25 3     3   1467 use SVG;
  3         31291  
  3         15  
26 3     3   1784 use Moo;
  3         4  
  3         17  
27 3     3   623 use namespace::clean;
  3         7  
  3         16  
28              
29             has 'days' => (is => 'ro', required => 1);
30             has 'month_name' => (is => 'ro', required => 1);
31             has 'year' => (is => 'ro', required => 1);
32             has 'start_index' => (is => 'ro', required => 1);
33              
34             has 'days_box' => (is => 'rw');
35             has 'month_label' => (is => 'rw');
36             has 'year_label' => (is => 'rw');
37             has 'wdays' => (is => 'rw');
38             has 'page' => (is => 'rw');
39             has 'boundary_box' => (is => 'rw');
40             has 'adjust_height' => (is => 'rw', default => sub { 0 });
41             has '_row' => (is => 'rw');
42              
43             my $HEIGHT = 0.5;
44             my $MARGIN_RATIO = 0.04;
45             my $DAY_COLS = 8;
46             my $ROUNDING_FACTOR = 0.5;
47             my $TEXT_OFFSET_Y = 0.1;
48             my $TEXT_OFFSET_X = 0.15;
49             my $TEXT_WIDTH_RATIO = 0.1;
50             my $TEXT_HEIGHT_RATIO = 0.145;
51             my $HEADING_WIDTH_SCALE = 0.8;
52             my $HEADING_HEIGHT_SCALE = 0.45;
53             my $HEADING_DOW_WIDTH_SCALE = 2;
54             my $HEADING_DOW_HEIGHT_SCALE = 0.4;
55             my $HEADING_WOY_WIDTH_SCALE = 4;
56             my $HEADING_WOY_HEIGHT_SCALE = 0.9;
57             my $MAX_WEEK_ROW = 5;
58              
59             =head1 DESCRIPTION
60              
61             B
62              
63             =cut
64              
65             sub BUILD {
66 0     0 0   my ($self, $params) = @_;
67              
68 0   0       my $adjust_height = $params->{adjust_height} || 0;
69 0           my $start_index = $params->{start_index};
70 0           my $year = $params->{year};
71 0           my $month_name = $params->{month_name};
72 0           my $days = $params->{days};
73              
74 0           my ($width, $width_unit) = (round_number(210 * 1.0), 'mm');
75 0           my ($height, $height_unit) = (round_number(297 * 1.0), 'mm');
76              
77 0           my $page = Calendar::Plugin::Renderer::SVG::Page->new(
78             {
79             width => $width,
80             width_unit => $width_unit,
81             height => $height,
82             height_unit => $height_unit,
83             x_margin => round_number($width * $MARGIN_RATIO),
84             y_margin => round_number($height * $MARGIN_RATIO),
85             });
86              
87 0           my $rows = get_max_week_rows($start_index, $days, $MAX_WEEK_ROW) + 1;
88 0           my $t = round_number(($rows + $ROUNDING_FACTOR) * (0.5 + $HEIGHT));
89 0           my $boundary_box = Calendar::Plugin::Renderer::SVG::Box->new(
90             {
91             'x' => $page->x_margin,
92             'y' => round_number(($page->height * (1 - $HEIGHT)) + $page->y_margin),
93             'height' => round_number((($page->height * $HEIGHT) - ($page->y_margin * 2)) - $t),
94             'width' => round_number($page->width - ($page->x_margin * 2))
95             });
96              
97 0           my $row_height = round_number($boundary_box->height / ($rows + $ROUNDING_FACTOR) * (0.5 + $HEIGHT));
98 0           my $row_margin_height = round_number($row_height / ($rows * 2));
99              
100 0           my $cols = $DAY_COLS;
101 0           my $col_width = round_number($boundary_box->width / ($cols + $ROUNDING_FACTOR));
102 0           my $col_margin_width = round_number($col_width / ($cols * 2));
103              
104 0           my $month_label = Calendar::Plugin::Renderer::SVG::Label->new(
105             {
106             'x' => round_number($boundary_box->x + ($col_margin_width * 2) + 11),
107             'y' => round_number($boundary_box->y - $page->y_margin/2),
108             'style' => 'font-size: ' . ($row_height),
109             });
110              
111 0           my $year_label = Calendar::Plugin::Renderer::SVG::Label->new(
112             {
113             'x' => round_number($boundary_box->x + $boundary_box->width),
114             'y' => round_number($boundary_box->y - $page->y_margin/2),
115             'style' => 'text-align: end; text-anchor: end; font-size: ' . $row_height,
116             });
117              
118 0           my $count = 1;
119 0           my $wdays = [];
120 0           for my $day (qw/Sun Mon Tue Wed Thu Fri Sat/) {
121 0           my $x = round_number($boundary_box->x + $col_margin_width * (2 * $count + 1) + $col_width * ($count - 1) + $col_width / 2);
122 0           my $y = round_number($boundary_box->y + $row_margin_height);
123              
124 0           my $wday_text = Calendar::Plugin::Renderer::SVG::Text->new(
125             {
126             'value' => $day,
127             'x' => round_number($x + $col_width / $HEADING_DOW_WIDTH_SCALE),
128             'y' => round_number($y + $row_height * $HEADING_DOW_HEIGHT_SCALE),
129             'length' => round_number($col_width * $HEADING_WIDTH_SCALE),
130             'adjust' => 'spacing',
131             'font_size' => round_number(($row_height * $HEADING_HEIGHT_SCALE))
132             });
133              
134 0           push @$wdays, Calendar::Plugin::Renderer::SVG::Box->new(
135             {
136             'x' => $x,
137             'y' => $y,
138             'height' => round_number($row_height * $HEIGHT),
139             'width' => $col_width,
140             'text' => $wday_text
141             });
142              
143 0           $count++;
144             }
145              
146 0           my $days_box = [];
147 0           foreach my $i (2 .. $rows) {
148 0           my $row_y = round_number($boundary_box->y + $row_margin_height * (2 * $i - 1) + $row_height * ($i - 1));
149 0           foreach my $j (2 .. $cols) {
150 0           my $x = round_number(($boundary_box->x + $col_margin_width * (2 * $j - 1) + $col_width * ($j - 1)) - $col_width / 2);
151 0           my $y = round_number($row_y - $row_height / 2);
152              
153 0           my $day_text = Calendar::Plugin::Renderer::SVG::Text->new(
154             {
155             'x' => round_number($x + $col_margin_width * $TEXT_OFFSET_X),
156             'y' => round_number($y + $row_height * $TEXT_OFFSET_X),
157             'length' => round_number($col_width * $TEXT_WIDTH_RATIO),
158             'font_size' => round_number((($row_height * $TEXT_HEIGHT_RATIO) + 5)),
159             });
160              
161 0           $days_box->[$i - 1][$j - 2] = Calendar::Plugin::Renderer::SVG::Box->new(
162             {
163             'x' => $x,
164             'y' => $y,
165             'height' => $row_height,
166             'width' => $col_width,
167             'text' => $day_text
168             });
169             }
170             }
171              
172 0           $year_label->text($year);
173 0           $month_label->text($month_name);
174              
175 0           $self->{days_box} = $days_box;
176 0           $self->{month_label} = $month_label;
177 0           $self->{year_label} = $year_label;
178 0           $self->{wdays} = $wdays;
179 0           $self->{page} = $page;
180 0           $self->{boundary_box} = $boundary_box;
181 0           $self->{adjust_height} = $adjust_height;
182             };
183              
184             sub process {
185 0     0 0   my ($self) = @_;
186              
187 0           my $start_index = $self->start_index;
188 0           my $max_month_days = $self->days;
189 0           my $days_box = $self->days_box;
190              
191 0           my $row = 1;
192 0           my $i = 0;
193 0           while ($i < $start_index) {
194 0           $days_box->[$row][$i]->text->value(' ');
195 0           $i++;
196             }
197              
198 0           my $d = 1;
199 0           while ($i <= 6) {
200 0           $days_box->[$row][$i]->text->value($d);
201 0           $i++;
202 0           $d++;
203             }
204              
205 0           $row++;
206 0           my $k = 0;
207 0           while ($d <= $max_month_days) {
208 0           $days_box->[$row][$k]->text->value($d);
209 0 0         if ($k == 6) {
210 0           $row++;
211 0           $k = 0;
212             }
213             else {
214 0           $k++;
215             }
216 0           $d++;
217             }
218              
219 0           $self->_row($row);
220             }
221              
222             sub as_string {
223 0     0 0   my ($self) = @_;
224              
225 0           my $p_height = sprintf("%d%s", $self->page->height, $self->page->height_unit);
226 0           my $p_width = sprintf("%d%s", $self->page->width, $self->page->width_unit);
227 0           my $view_box = sprintf("0 0 %d %d", $self->page->width, $self->page->height);
228              
229 0           my $svg = SVG->new(
230             height => $p_height,
231             width => $p_width,
232             viewBox => $view_box,
233             -svg_version => '1.1',
234             -pubid => '-//W3C//DTD SVG 1.1//EN',
235             -sysid => 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd');
236 0           my $calendar = $svg->group(id => 'calendar', label => "Calendar");
237              
238 0           my $month_label = $self->month_label;
239 0           $calendar->text('id' => "month",
240             'fill' => 'blue',
241             'x' => $month_label->x,
242             'y' => $month_label->y,
243             'style' => $month_label->style)->cdata($month_label->text);
244              
245 0           my $year_label = $self->year_label;
246 0           $calendar->text('id' => "year",
247             'fill' => 'blue',
248             'x' => $year_label->x,
249             'y' => $year_label->y,
250             'style' => $year_label->style)->cdata($year_label->text);
251              
252 0           my $boundary_box = $self->boundary_box;
253 0           $calendar->rect('id' => 'bounding_box',
254             'height' => $boundary_box->height - $self->adjust_height,
255             'width' => $boundary_box->width - 14,
256             'x' => $boundary_box->x + 7 + 7,
257             'y' => $boundary_box->y,
258             'style' => 'fill:none; stroke: blue; stroke-width: 0.5;');
259              
260 0           my $wdays = $self->wdays;
261 0           foreach (0..6) {
262 0           my $day = $calendar->tag('g',
263             'id' => "row0_col$_",
264             'text-anchor' => 'middle',
265             'fill' => 'none',
266             'stroke' => 'blue',
267             'stroke-width' => '0.5');
268 0 0         next unless defined $wdays->[$_];
269              
270 0           $day->rect('id' => "box_row0_col$_",
271             'x' => $wdays->[$_]->x,
272             'y' => $wdays->[$_]->y,
273             'height' => $wdays->[$_]->height,
274             'width' => $wdays->[$_]->width);
275 0           $day->text('id' => "text_row0_col$_",
276             'x' => $wdays->[$_]->text->x,
277             'y' => $wdays->[$_]->text->y,
278             'length' => $wdays->[$_]->text->length,
279             'adjust' => $wdays->[$_]->text->adjust,
280             'font-size' => $wdays->[$_]->text->font_size,
281             'text-anchor' => 'middle',
282             'stroke' => 'red')
283             ->cdata($wdays->[$_]->text->value);
284             }
285              
286 0           my $row = $self->_row;
287 0           my $days_box = $self->days_box;
288 0           foreach my $r (1..$row) {
289 0           foreach my $c (0..6) {
290 0           my $g_id = sprintf("row%d_col%d" , $r, $c);
291 0           my $r_id = sprintf("box_row%d_col%d" , $r, $c);
292 0           my $t_id = sprintf("text_row%d_col%d", $r, $c);
293 0 0         next unless defined $days_box->[$r]->[$c];
294              
295 0           my $d = $calendar->tag('g',
296             'id' => "$g_id",
297             'fill' => 'none',
298             'stroke' => 'blue',
299             'stroke-width' => '0.5');
300 0           $d->rect('id' => "$r_id",
301             'x' => $days_box->[$r]->[$c]->x,
302             'y' => $days_box->[$r]->[$c]->y,
303             'height' => $days_box->[$r]->[$c]->height,
304             'width' => $days_box->[$r]->[$c]->width,
305             'fill' => 'none',
306             'stroke' => 'blue',
307             'stroke-width' => '0.5');
308              
309 0           my $text = ' ';
310 0 0 0       if (defined $days_box->[$r]->[$c]->text
311             && defined $days_box->[$r]->[$c]->text->value) {
312 0           $text = $days_box->[$r]->[$c]->text->value;
313             }
314              
315 0           $d->text('id' => "$t_id",
316             'x' => $days_box->[$r]->[$c]->text->x + 1,
317             'y' => $days_box->[$r]->[$c]->text->y + 5,
318             'length' => $days_box->[$r]->[$c]->text->length,
319             'adjust' => 'spacing',
320             'font-size' => $days_box->[$r]->[$c]->text->font_size,
321             'stroke' => 'green',
322             'text-anchor' => 'right',
323             'fill' => 'silver',
324             'fill-opacity' => '50%')
325             ->cdata($text);
326             }
327             }
328              
329 0           return $svg->to_xml;
330             }
331              
332             =head1 AUTHOR
333              
334             Mohammad S Anwar, C<< >>
335              
336             =head1 REPOSITORY
337              
338             L
339              
340             =head1 BUGS
341              
342             Please report any bugs / feature requests to C,
343             or through the web interface at L.
344             I will be notified, and then you'll automatically be notified of progress on your
345             bug as I make changes.
346              
347             =head1 SUPPORT
348              
349             You can find documentation for this module with the perldoc command.
350              
351             perldoc Calendar::Plugin::Renderer::SVG
352              
353             You can also look for information at:
354              
355             =over 4
356              
357             =item * RT: CPAN's request tracker
358              
359             L
360              
361             =item * AnnoCPAN: Annotated CPAN documentation
362              
363             L
364              
365             =item * CPAN Ratings
366              
367             L
368              
369             =item * Search CPAN
370              
371             L
372              
373             =back
374              
375             =head1 LICENSE AND COPYRIGHT
376              
377             Copyright (C) 2015 - 2016 Mohammad S Anwar.
378              
379             This program is free software; you can redistribute it and / or modify it under
380             the terms of the the Artistic License (2.0). You may obtain a copy of the full
381             license at:
382              
383             L
384              
385             Any use, modification, and distribution of the Standard or Modified Versions is
386             governed by this Artistic License.By using, modifying or distributing the Package,
387             you accept this license. Do not use, modify, or distribute the Package, if you do
388             not accept this license.
389              
390             If your Modified Version has been derived from a Modified Version made by someone
391             other than you,you are nevertheless required to ensure that your Modified Version
392             complies with the requirements of this license.
393              
394             This license does not grant you the right to use any trademark, service mark,
395             tradename, or logo of the Copyright Holder.
396              
397             This license includes the non-exclusive, worldwide, free-of-charge patent license
398             to make, have made, use, offer to sell, sell, import and otherwise transfer the
399             Package with respect to any patent claims licensable by the Copyright Holder that
400             are necessarily infringed by the Package. If you institute patent litigation
401             (including a cross-claim or counterclaim) against any party alleging that the
402             Package constitutes direct or contributory patent infringement,then this Artistic
403             License to you shall terminate on the date that such litigation is filed.
404              
405             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND
406             CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
407             WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
408             NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS
409             REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT,
410             INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE
411             OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
412              
413             =cut
414              
415             1; # End of Calendar::Plugin::Renderer::SVG