File Coverage

blib/lib/PostScript/LabelSheet.pm
Criterion Covered Total %
statement 101 112 90.1
branch 35 50 70.0
condition 7 16 43.7
subroutine 21 23 91.3
pod 10 10 100.0
total 174 211 82.4


line stmt bran cond sub pod time code
1             package PostScript::LabelSheet;
2              
3 4     4   166094 use warnings;
  4         9  
  4         143  
4 4     4   22 use strict;
  4         5  
  4         148  
5              
6 4     4   6591 use parent qw/ Class::Accessor /;
  4         3063  
  4         25  
7 4     4   33845 use Carp;
  4         12  
  4         450  
8 4     4   28 use File::Basename qw/ dirname /;
  4         9  
  4         267  
9 4     4   4660 use Template;
  4         181275  
  4         4165  
10              
11             our $VERSION = 0.02;
12              
13             __PACKAGE__->mk_accessors(qw/
14             columns rows
15             label_width label_height
16             width height
17             v_margin h_margin
18             v_spacing h_spacing
19             v_padding h_padding
20             skip fill_last_page grid
21             labels
22             postscript_skeleton
23             portrait
24             /
25             );
26              
27             sub set {
28 73     73 1 4681 my $self = shift;
29 73         258 $self->SUPER::set(@_);
30 73         617 return $self;
31             }
32              
33             sub new {
34 3     3 1 107 my $class = shift;
35 3         43 my $self = $class->SUPER::new(@_);
36              
37             # Default values
38 3 100       55 $self->width(595) unless defined $self->width(); # 210 mm (a4)
39 3 50       43 $self->height(842) unless defined $self->height(); # 297 mm (a4)
40 3 50       14 $self->v_margin(14.17) unless defined $self->v_margin(); # 5 mm
41 3 50       18 $self->h_margin(14.17) unless defined $self->h_margin(); # 5 mm
42 3 50       15 $self->v_spacing(0) unless defined $self->v_spacing();
43 3 50       24 $self->h_spacing(0) unless defined $self->h_spacing();
44 3 50       15 $self->v_padding(0) unless defined $self->v_padding();
45 3 50       15 $self->h_padding(0) unless defined $self->h_padding();
46 3 50       15 $self->skip(0) unless defined $self->skip();
47 3 50       12 $self->fill_last_page(1) unless defined $self->fill_last_page();
48 3 50       15 $self->grid(1) unless defined $self->grid();
49 3 50       16 $self->portrait(1) unless defined $self->portrait();
50 3         16 return $self;
51             }
52              
53             sub label_height {
54 4     4 1 510 my $self = shift;
55 4 100       24 if ( @_ ) {
    100          
56 1         5 $self->_rows_accessor(undef);
57             }
58             elsif ( !defined $self->_label_height_accessor() ) {
59 1         15 $self->_label_height_accessor(
60             ($self->height() - $self->v_margin() * 2 + $self->v_spacing()) / $self->rows() - $self->v_spacing()
61             );
62             }
63 4         33 $self->_label_height_accessor(@_);
64             }
65              
66             sub label_width {
67 4     4 1 468 my $self = shift;
68 4 100       21 if ( @_ ) {
    100          
69 1         4 $self->_columns_accessor(undef);
70             }
71             elsif ( !defined $self->_label_width_accessor() ) {
72 1         48 $self->_label_width_accessor(
73             ($self->width() - $self->h_margin() * 2 + $self->h_spacing()) / $self->columns() - $self->h_spacing()
74             );
75             }
76 4         31 $self->_label_width_accessor(@_);
77             }
78              
79             sub rows {
80 11     11 1 933 my $self = shift;
81 11 100       38 if ( @_ ) {
    100          
82 2         26 $self->_label_height_accessor(undef);
83             }
84             elsif ( !defined $self->_rows_accessor() ) {
85 1         16 $self->_rows_accessor(
86             int(($self->height() - $self->v_margin() * 2 + $self->v_spacing()) / ($self->label_height() + $self->v_spacing()))
87             );
88             }
89 11         100 $self->_rows_accessor(@_);
90             }
91              
92             sub columns {
93 11     11 1 505 my $self = shift;
94 11 100       43 if ( @_ ) {
    100          
95 2         8 $self->_label_width_accessor(undef);
96             }
97             elsif ( !defined $self->_columns_accessor() ) {
98 1         15 $self->_columns_accessor(
99             int(($self->width() - $self->h_margin() * 2 + $self->h_spacing()) / ($self->label_width() + $self->h_spacing()))
100             );
101             }
102 11         129 $self->_columns_accessor(@_);
103             }
104              
105             sub _parse_eps_file {
106 5     5   13 my ($self, $filename) = @_;
107              
108 5 50       373 open my $eps_fh, '<', $filename
109             or croak "Cannot read $filename: $!";
110 5         23 my %record = ( path => $filename );
111 5         11 my $buffer = '';
112 5         139 while ( <$eps_fh> ) {
113 318 100       18009 @record{map "eps_bb_$_", qw/ ll_x ll_y ur_x ur_y/} = /(\d+)/g if /%%BoundingBox/;
114 318         1256 $buffer .= $_;
115             }
116 5         96 close $eps_fh;
117 5         31 $record{code} = $buffer;
118 5 0 33     50 if ( !$record{eps_bb_ll_x}
      33        
      33        
119             && !$record{eps_bb_ll_y}
120             && !$record{eps_bb_ur_x}
121             && !$record{eps_bb_ur_y} ) {
122 0         0 croak "No bounding box info found in $filename"
123             }
124 5         36 return \%record;
125             }
126              
127             sub add {
128 5     5 1 8 my $self = shift;
129 5         29 my ($eps, $count) = @_;
130 5   100     25 $count ||= 1;
131 5   100     18 my $label_aref = $self->labels || [];
132 5         90 my $eps_record = $self->_parse_eps_file($eps);
133 5         13 $eps_record->{count} = $count;
134 5         12 push @$label_aref, $eps_record;
135              
136 5         28 $self->labels($label_aref);
137             }
138              
139             sub _make_h_and_v_setter {
140 12     12   22 my $setting_name = shift;
141 12         22 my $v_accessor = "v_$setting_name";
142 12         29 my $h_accessor = "h_$setting_name";
143 4     4   49 no strict 'refs';
  4         8  
  4         2566  
144             *$setting_name = sub {
145 4     4   794 my $self = shift;
146 4 50       11 if ( @_ ) {
147 4         13 $self->$v_accessor(@_);
148 4         13 $self->$h_accessor(@_);
149 4         14 return $self;
150             }
151             else {
152 0         0 return $self->$h_accessor();
153             }
154             }
155 12         122 }
156             _make_h_and_v_setter('margin');
157             _make_h_and_v_setter('spacing');
158             _make_h_and_v_setter('padding');
159              
160             sub _install_dir {
161 0     0   0 (my $module = __PACKAGE__ ) =~ s|::|/|g;
162 0         0 return dirname( $INC{"$module.pm"} );
163             }
164              
165             sub count_labels_per_page {
166 5     5 1 72 my $self = shift;
167 5         12 return $self->columns() * $self->rows();
168             }
169              
170             sub count_labels {
171 6     6 1 894 my $self = shift;
172 6         9 my $count = 0;
173 6         7 foreach ( @{$self->labels} ) {
  6         18  
174 7         73 $count += $_->{count};
175             }
176 6         35 return $count;
177             }
178              
179             sub _stretch_last_label {
180 2     2   3 my $self = shift;
181 2         6 my $rest = ( $self->count_labels() + $self->skip() ) % $self->count_labels_per_page();
182 2         25 $self->labels()->[-1]{count} += $self->count_labels_per_page() - $rest;
183             }
184              
185             sub _finalize {
186 4     4   298 my $self = shift;
187 4 100       14 if ( $self->fill_last_page() ) {
188 2         27 $self->_stretch_last_label();
189             }
190             }
191              
192             sub as_postscript {
193 0     0 1   my $self = shift;
194 0   0       my $template = $self->postscript_skeleton() || $self->_install_dir() . '/LabelSheet/LabelSheet.ps';
195 0           my $buffer = '';
196              
197 0           $self->_finalize();
198            
199 0           my $tt = new Template { ABSOLUTE => 1 };
200 0           $tt->process($template, { sheet => $self }, \$buffer);
201 0           return $buffer;
202             }
203              
204             =head1 NAME
205              
206             PostScript::LabelSheet - print multiple labels on a sheet, starting from PostScript label template.
207              
208             =head1 SYNOPSIS
209              
210             use PostScript::LabelSheet;
211              
212             my $sheet = new PostScript::LabelSheet;
213            
214             $sheet->columns(3); # 3 labels abreast
215             $sheet->rows(10); # on 10 rows
216             # Or specify the dimensions of the labels, in PostScript points
217             # $sheet->label_width(...); $sheet->label_height(...);
218              
219             $sheet->skip(5); # leave 5 labels blank
220              
221             $sheet->add('/path/to/label_1.eps', 5);
222             $sheet->add('/path/to/label_2.eps', 3);
223             $sheet->add('/path/to/label_3.eps');
224             $sheet->fill_last_page(1); # label_3 will fill the last sheet.
225              
226             print $sheet->as_postscript();
227              
228             =head1 DESCRIPTION
229              
230             =head2 Why this module?
231              
232             I sometimes have to print a sheet of labels, each label bearing the same
233             design, for example to label jars of marmelade. I tried to do this with
234             OpenOffice.org writer, in a table. But I had to manually copy and paste the
235             design of the first label into every cell, and of course, if I changed the
236             design, the changes had to be reported manually to the other cells. And of
237             course, changing the dimensions, or adding a column or a row, need some manual
238             intervention.
239              
240             This module is here to easily print a sheet (or sheets) of labels representing
241             a repeating pattern. Give it a design in Encapsulated PostScript (EPS), how
242             many labels you want, how big they should be or how many should fit in the
243             width and heigth of the page, and PostScript::LabelSheet generates the
244             PostScript code for you, which you can then directly print.
245              
246             There are options to print several kinds of labels on the same sheet, each with
247             its own design, to draw a grid around the labels for cutting them, and to
248             control how they are laid out on the page.
249              
250             Additionally, labels can be numbered. This can be useful to print numbered
251             tickets for a local event for instance.
252              
253             =head2 Drawing the design
254              
255             Use inkscape (http://www.inkscape.org) to draw the design that you want to
256             print on each label. Keep the original in the SVG format, and export it as
257             Encapsulated PostScript for use with PostScript::LabelSheet.
258              
259             The size of the design is unimportant, as this is vector graphics, the
260             generated PostScript program will resize without losing quality so that it fits
261             within a label. What is important, however, is that the design occupies all the
262             space on the page. The easiest is to draw without giving any thought to the
263             page, then adjust the page size to the drawing. In inkscape, you can use the
264             Document Properties dialog box to let the page fit the design exactly (menu
265             File, Document Properties, in the Page tab, click the "Fit page to selection"
266             button).
267              
268             To save the design in EPS format, in inkscape, go to menu File, Save a copy,
269             and choose the "C" format. Inkscape will show
270             a dialog with options for the conversion to EPS. Do check the box "Make
271             bounding box around full page", so that the generated EPS code contains
272             information about the size of the design. PostScript::LabelSheet needs it to
273             work out the scale at which the design should be included on the page.
274              
275             =head2 Constructor
276              
277             =over 4
278              
279             =item B
280              
281             Returns a new instance of PostScript::LabelSheet with all its attributes set to
282             the built-in default values.
283              
284             =back
285              
286             =head2 Accessors / Mutators
287              
288             The following methods are accessors when given no argument, and mutators when
289             given one. As accessor they return the corresponding attribute's value:
290              
291             print $sheet->width();
292              
293             As mutators, they set the attribute's value, and return the
294             PostScript::LabelSheet instance itself, making it possible to stack several
295             calls to mutators:
296              
297             my $sheet = PostScript::LabelSheet->new()
298             ->width(595)
299             ->height(842)
300             ->grid(0)
301             ;
302              
303             =head2 Labels management
304              
305             =over 4
306              
307             =item B
308              
309             =item B
310              
311             Give the number of columns and rows in which the labels should be laid out on
312             each page. The labels width and height will be calculated accordingly.
313              
314             =item B
315              
316             =item B
317              
318             Give the width and height of each label. The program will automatically
319             calculate how many labels will fit on each row and column.
320              
321             Either columns() or label_width(), and either row() or label_height(), must be
322             specified before invoking as_postscript().
323              
324             =item B (defaults to true)
325              
326             Set this option to a true value to have the last label repeat until the end of
327             the last page.
328              
329             =back
330              
331             =head2 Layout
332              
333             =over 4
334              
335             =item B
336              
337             =item B
338              
339             Dimensions of the page. Default to DIN A4 (S<< 210 E 297 mm >>).
340              
341             =item B
342              
343             =item B
344              
345             Vertical (top and bottom) and horizontal (left and right) margins.
346             Default to S<5 mm>.
347             Use margin() to set both v_margin() and h_margin() at the same time.
348             It is not possible to set the top margin independantly from the bottom
349             margin, nor the left margin independantly from the right one.
350              
351             =item B
352              
353             =item B
354              
355             Space left blank inside each label and around the design within.
356             Default to 0.
357             Use padding() to set both h_padding() and v_padding() at the same time.
358              
359             =item B
360              
361             =item B
362              
363             Space between columns (h_spacing()) or rows (v_spacing()) of labels.
364             Default to 0 (no space between rows and columns).
365             Use spacing() to set both h_spacing() and v_spacing() at the same time.
366              
367             =item B
368              
369             Number of labels to leave blank at the top of the first page. The default is to
370             start at the top left of the page.
371              
372             =item B
373              
374             If set to a true value, the designs are rotated 90 degrees clockwise inside
375             each label. Default to false.
376              
377             =item B
378              
379             If set to a true value, a grid is drawn around the labels. This is the default.
380              
381             =back
382              
383             =head2 Miscellaneous
384              
385             =over 4
386              
387             =item B
388              
389             =item B
390              
391             =back
392              
393             =head2 Methods
394              
395             =over 4
396              
397             =item B I, I
398              
399             Adds a design to the sheet of labels. I is the path to a file in the
400             Encapsulated PostScript format. See the L section above for
401             hints on how to make such a file. I is optional and defaults to 1.
402             However, it fill_last_page() is set to a true value, the last design will be
403             repeated until the end of the last page.
404              
405             Returns the instance of the PostScript::LabelSheet object, so calls can be
406             stacked:
407              
408             $sheet->add('file1.eps', 5)
409             ->add('file2.eps', 6)
410             ->add('file3.eps', 4)
411             ;
412              
413             =item B
414              
415             Returns the number labels that would fit on a page.
416              
417             =item B
418              
419             Returns the number of labels printed on all the pages. If fill_last_page() is
420             set to a true value, this might not reflect the number of labels actually
421             printed, as the last one will be printed multiple times. The number will be
422             accurate after the PostScript has been generated in a call to as_postscript(),
423             as the count property of the last design will be adjusted at that moment.
424              
425             =item B I, I
426              
427             A rewrite of Class::Accessor's set() mutator, that returns the instance of the
428             object, instead of the value.
429              
430             =item B I
431              
432             =item B I
433              
434             =item B I
435              
436             Sets both h_margin() and v_margin() to I at the same time, and
437             respectively for h_padding() and v_padding(), and for h_spacing() and
438             v_spacing().
439              
440             =item B
441              
442             Returns the PostScript code that prints the labels. It can be sent directly to
443             a printer spool, or converted to PDF, or whatever you can do with PostScript.
444              
445             =back
446              
447             =head1 AUTHOR
448              
449             Cédric Bouvier, C<< >>
450              
451             =head1 BUGS
452              
453             Please report any bugs or feature requests to C, or through
454             the web interface at L. I will be notified, and then you'll
455             automatically be notified of progress on your bug as I make changes.
456              
457             =head1 SUPPORT
458              
459             You can find documentation for this module with the perldoc command.
460              
461             perldoc PostScript::LabelSheet
462              
463              
464             You can also look for information at:
465              
466             =over 4
467              
468             =item * RT: CPAN's request tracker
469              
470             L
471              
472             =item * AnnoCPAN: Annotated CPAN documentation
473              
474             L
475              
476             =item * CPAN Ratings
477              
478             L
479              
480             =item * Search CPAN
481              
482             L
483              
484             =back
485              
486             =head1 COPYRIGHT & LICENSE
487              
488             Copyright 2009 Cédric Bouvier.
489              
490             This program is free software; you can redistribute it and/or modify it
491             under the terms of either: the GNU General Public License as published
492             by the Free Software Foundation; or the Artistic License.
493              
494             See http://dev.perl.org/licenses/ for more information.
495              
496              
497             =cut
498              
499             1; # End of PostScript::LabelSheet