Bug 15798: Remove C4::Koha::displayServers
[koha.git] / C4 / Labels / Label.pm
1 package C4::Labels::Label;
2
3 use strict;
4 use warnings;
5
6 use Text::Wrap;
7 use Algorithm::CheckDigits;
8 use Text::CSV_XS;
9 use Data::Dumper;
10 use Library::CallNumber::LC;
11 use Text::Bidi qw( log2vis );
12
13 use C4::Context;
14 use C4::Debug;
15 use C4::Biblio;
16
17 BEGIN {
18     use version; our $VERSION = qv('3.07.00.049');
19 }
20
21 my $possible_decimal = qr/\d{3,}(?:\.\d+)?/; # at least three digits for a DDCN
22
23 sub _check_params {
24     my $given_params = {};
25     my $exit_code = 0;
26     my @valid_label_params = (
27         'batch_id',
28         'item_number',
29         'llx',
30         'lly',
31         'height',
32         'width',
33         'top_text_margin',
34         'left_text_margin',
35         'barcode_type',
36         'printing_type',
37         'guidebox',
38         'oblique_title',
39         'font',
40         'font_size',
41         'callnum_split',
42         'justify',
43         'format_string',
44         'text_wrap_cols',
45         'barcode',
46     );
47     if (scalar(@_) >1) {
48         $given_params = {@_};
49         foreach my $key (keys %{$given_params}) {
50             if (!(grep m/$key/, @valid_label_params)) {
51                 warn sprintf('Unrecognized parameter type of "%s".', $key);
52                 $exit_code = 1;
53             }
54         }
55     }
56     else {
57         if (!(grep m/$_/, @valid_label_params)) {
58             warn sprintf('Unrecognized parameter type of "%s".', $_);
59             $exit_code = 1;
60         }
61     }
62     return $exit_code;
63 }
64
65 sub _guide_box {
66     my ( $llx, $lly, $width, $height ) = @_;
67     return unless ( defined $llx and defined $lly and
68                     defined $width and defined $height );
69     my $obj_stream = "q\n";                            # save the graphic state
70     $obj_stream .= "0.5 w\n";                          # border line width
71     $obj_stream .= "1.0 0.0 0.0  RG\n";                # border color red
72     $obj_stream .= "1.0 1.0 1.0  rg\n";                # fill color white
73     $obj_stream .= "$llx $lly $width $height re\n";    # a rectangle
74     $obj_stream .= "B\n";                              # fill (and a little more)
75     $obj_stream .= "Q\n";                              # restore the graphic state
76     return $obj_stream;
77 }
78
79 sub _get_label_item {
80     my $item_number = shift;
81     my $barcode_only = shift || 0;
82     my $dbh = C4::Context->dbh;
83 #        FIXME This makes for a very bulky data structure; data from tables w/duplicate col names also gets overwritten.
84 #        Something like this, perhaps, but this also causes problems because we need more fields sometimes.
85 #        SELECT i.barcode, i.itemcallnumber, i.itype, bi.isbn, bi.issn, b.title, b.author
86     my $sth = $dbh->prepare("SELECT bi.*, i.*, b.*,br.* FROM items AS i, biblioitems AS bi ,biblio AS b, branches AS br WHERE itemnumber=? AND i.biblioitemnumber=bi.biblioitemnumber AND bi.biblionumber=b.biblionumber AND i.homebranch=br.branchcode;");
87     $sth->execute($item_number);
88     if ($sth->err) {
89         warn sprintf('Database returned the following error: %s', $sth->errstr);
90     }
91     my $data = $sth->fetchrow_hashref;
92     # Replaced item's itemtype with the more user-friendly description...
93     my $sth1 = $dbh->prepare("SELECT itemtype,description FROM itemtypes WHERE itemtype = ?");
94     $sth1->execute($data->{'itemtype'});
95     if ($sth1->err) {
96         warn sprintf('Database returned the following error: %s', $sth1->errstr);
97     }
98     my $data1 = $sth1->fetchrow_hashref;
99     $data->{'itemtype'} = $data1->{'description'};
100     $data->{'itype'} = $data1->{'description'};
101     # add *_description fields
102     if ($data->{'homebranch'} || $data->{'holdingbranch'}){
103         require C4::Branch;
104         $data->{'homebranch_description'} = C4::Branch::GetBranchName($data->{'homebranch'}) if $data->{'homebranch'};
105         $data->{'holdingbranch_description'} = C4::Branch::GetBranchName($data->{'holdingbranch'}) if $data->{'holdingbranch'};
106     }
107     $data->{'ccode_description'} = C4::Biblio::GetAuthorisedValueDesc('','', $data->{'ccode'} ,'','','CCODE', 1) if $data->{'ccode'};
108     $data->{'location_description'} = C4::Biblio::GetAuthorisedValueDesc('','', $data->{'location'} ,'','','LOC', 1) if $data->{'location'};
109     $data->{'permanent_location_description'} = C4::Biblio::GetAuthorisedValueDesc('','', $data->{'permanent_location'} ,'','','LOC', 1) if $data->{'permanent_location'};
110
111     $barcode_only ? return $data->{'barcode'} : return $data;
112 }
113
114 sub _get_text_fields {
115     my $format_string = shift;
116     my $csv = Text::CSV_XS->new({allow_whitespace => 1});
117     my $status = $csv->parse($format_string);
118     my @sorted_fields = map {{ 'code' => $_, desc => $_ }} 
119                         map { $_ && $_ eq 'callnumber' ? 'itemcallnumber' : $_ } # see bug 5653
120                         $csv->fields();
121     my $error = $csv->error_input();
122     warn sprintf('Text field sort failed with this error: %s', $error) if $error;
123     return \@sorted_fields;
124 }
125
126
127 sub _split_lccn {
128     my ($lccn) = @_;
129     $_ = $lccn;
130     # lccn examples: 'HE8700.7 .P6T44 1983', 'BS2545.E8 H39 1996';
131     my @parts = Library::CallNumber::LC->new($lccn)->components();
132     unless (scalar @parts && defined $parts[0])  {
133         $debug and warn sprintf('regexp failed to match string: %s', $_);
134         @parts = $_;     # if no match, just use the whole string.
135     }
136     my $LastPiece = pop @parts;
137     push @parts, split /\s+/, $LastPiece if $LastPiece;   # split the last piece into an arbitrary number of pieces at spaces
138     $debug and warn "split_lccn array: ", join(" | ", @parts), "\n";
139     return @parts;
140 }
141
142 sub _split_ddcn {
143     my ($ddcn) = @_;
144     $_ = $ddcn;
145     s/\///g;   # in theory we should be able to simply remove all segmentation markers and arrive at the correct call number...
146     my (@parts) = m/
147         ^([-a-zA-Z]*\s?(?:$possible_decimal)?) # R220.3  CD-ROM 787.87 # will require extra splitting
148         \s+
149         (.+)                               # H2793Z H32 c.2 EAS # everything else (except bracketing spaces)
150         \s*
151         /x;
152     unless (scalar @parts)  {
153         warn sprintf('regexp failed to match string: %s', $_);
154         push @parts, $_;     # if no match, just push the whole string.
155     }
156
157     if ($parts[0] =~ /^([-a-zA-Z]+)\s?($possible_decimal)$/) {
158           shift @parts;         # pull off the mathching first element, like example 1
159         unshift @parts, $1, $2; # replace it with the two pieces
160     }
161
162     push @parts, split /\s+/, pop @parts;   # split the last piece into an arbitrary number of pieces at spaces
163     $debug and print STDERR "split_ddcn array: ", join(" | ", @parts), "\n";
164     return @parts;
165 }
166
167 ## NOTE: Custom call number types go here. It may be necessary to create additional splitting algorithms if some custom call numbers
168 ##      cannot be made to work here. Presently this splits standard non-ddcn, non-lccn fiction and biography call numbers.
169
170 sub _split_ccn {
171     my ($fcn) = @_;
172     my @parts = ();
173     # Split call numbers based on spaces
174     push @parts, split /\s+/, $fcn;   # split the call number into an arbitrary number of pieces at spaces
175     if ($parts[-1] !~ /^.*\d-\d.*$/ && $parts[-1] =~ /^(.*\d+)(\D.*)$/) {
176         pop @parts;            # pull off the matching last element
177         push @parts, $1, $2;    # replace it with the two pieces
178     }
179     unless (scalar @parts) {
180         warn sprintf('regexp failed to match string: %s', $_);
181         push (@parts, $_);
182     }
183     $debug and print STDERR "split_ccn array: ", join(" | ", @parts), "\n";
184     return @parts;
185 }
186
187 sub _get_barcode_data {
188     my ( $f, $item, $record ) = @_;
189     my $kohatables = _desc_koha_tables();
190     my $datastring = '';
191     my $match_kohatable = join(
192         '|',
193         (
194             @{ $kohatables->{'biblio'} },
195             @{ $kohatables->{'biblioitems'} },
196             @{ $kohatables->{'items'} },
197             @{ $kohatables->{'branches'} }
198         )
199     );
200     FIELD_LIST:
201     while ($f) {
202         my $err = '';
203         $f =~ s/^\s?//;
204         if ( $f =~ /^'(.*)'.*/ ) {
205             # single quotes indicate a static text string.
206             $datastring .= $1;
207             $f = $';
208             next FIELD_LIST;
209         }
210         elsif ( $f =~ /^($match_kohatable).*/ ) {
211             if ($item->{$f}) {
212                 $datastring .= $item->{$f};
213             } else {
214                 $debug and warn sprintf("The '%s' field contains no data.", $f);
215             }
216             $f = $';
217             next FIELD_LIST;
218         }
219         elsif ( $f =~ /^([0-9a-z]{3})(\w)(\W?).*?/ ) {
220             my ($field,$subf,$ws) = ($1,$2,$3);
221             my $subf_data;
222             my ($itemtag, $itemsubfieldcode) = &GetMarcFromKohaField("items.itemnumber",'');
223             my @marcfield = $record->field($field);
224             if(@marcfield) {
225                 if($field eq $itemtag) {  # item-level data, we need to get the right item.
226                     ITEM_FIELDS:
227                     foreach my $itemfield (@marcfield) {
228                         if ( $itemfield->subfield($itemsubfieldcode) eq $item->{'itemnumber'} ) {
229                             if ($itemfield->subfield($subf)) {
230                                 $datastring .= $itemfield->subfield($subf) . $ws;
231                             }
232                             else {
233                                 warn sprintf("The '%s' field contains no data.", $f);
234                             }
235                             last ITEM_FIELDS;
236                         }
237                     }
238                 } else {  # bib-level data, we'll take the first matching tag/subfield.
239                     if ($marcfield[0]->subfield($subf)) {
240                         $datastring .= $marcfield[0]->subfield($subf) . $ws;
241                     }
242                     else {
243                         warn sprintf("The '%s' field contains no data.", $f);
244                     }
245                 }
246             }
247             $f = $';
248             next FIELD_LIST;
249         }
250         else {
251             warn sprintf('Failed to parse label format string: %s', $f);
252             last FIELD_LIST;    # Failed to match
253         }
254     }
255     return $datastring;
256 }
257
258 sub _desc_koha_tables {
259         my $dbh = C4::Context->dbh();
260         my $kohatables;
261         for my $table ( 'biblio','biblioitems','items','branches' ) {
262                 my $sth = $dbh->column_info(undef,undef,$table,'%');
263                 while (my $info = $sth->fetchrow_hashref()){
264                         push @{$kohatables->{$table}} , $info->{'COLUMN_NAME'} ;
265                 }
266                 $sth->finish;
267         }
268         return $kohatables;
269 }
270
271 ### This series of functions calculates the position of text and barcode on individual labels
272 ### Please *do not* add printing types which are non-atomic. Instead, build code which calls the necessary atomic printing types to form the non-atomic types. See the ALT type
273 ### in labels/label-create-pdf.pl as an example.
274 ### NOTE: Each function must be passed seven parameters and return seven even if some are 0 or undef
275
276 sub _BIB {
277     my $self = shift;
278     my $line_spacer = ($self->{'font_size'} * 1);       # number of pixels between text rows (This is actually leading: baseline to baseline minus font size. Recommended starting point is 20% of font size.).
279     my $text_lly = ($self->{'lly'} + ($self->{'height'} - $self->{'top_text_margin'}));
280     return $self->{'llx'}, $text_lly, $line_spacer, 0, 0, 0, 0;
281 }
282
283 sub _BAR {
284     my $self = shift;
285     my $barcode_llx = $self->{'llx'} + $self->{'left_text_margin'};     # this places the bottom left of the barcode the left text margin distance to right of the left edge of the label ($llx)
286     my $barcode_lly = $self->{'lly'} + $self->{'top_text_margin'};      # this places the bottom left of the barcode the top text margin distance above the bottom of the label ($lly)
287     my $barcode_width = 0.8 * $self->{'width'};                         # this scales the barcode width to 80% of the label width
288     my $barcode_y_scale_factor = 0.01 * $self->{'height'};              # this scales the barcode height to 10% of the label height
289     return 0, 0, 0, $barcode_llx, $barcode_lly, $barcode_width, $barcode_y_scale_factor;
290 }
291
292 sub _BIBBAR {
293     my $self = shift;
294     my $barcode_llx = $self->{'llx'} + $self->{'left_text_margin'};     # this places the bottom left of the barcode the left text margin distance to right of the left edge of the label ($self->{'llx'})
295     my $barcode_lly = $self->{'lly'} + $self->{'top_text_margin'};      # this places the bottom left of the barcode the top text margin distance above the bottom of the label ($lly)
296     my $barcode_width = 0.8 * $self->{'width'};                         # this scales the barcode width to 80% of the label width
297     my $barcode_y_scale_factor = 0.01 * $self->{'height'};              # this scales the barcode height to 10% of the label height
298     my $line_spacer = ($self->{'font_size'} * 1);       # number of pixels between text rows (This is actually leading: baseline to baseline minus font size. Recommended starting point is 20% of font size.).
299     my $text_lly = ($self->{'lly'} + ($self->{'height'} - $self->{'top_text_margin'}));
300     $debug and warn  "Label: llx $self->{'llx'}, lly $self->{'lly'}, Text: lly $text_lly, $line_spacer, Barcode: llx $barcode_llx, lly $barcode_lly, $barcode_width, $barcode_y_scale_factor\n";
301     return $self->{'llx'}, $text_lly, $line_spacer, $barcode_llx, $barcode_lly, $barcode_width, $barcode_y_scale_factor;
302 }
303
304 sub _BARBIB {
305     my $self = shift;
306     my $barcode_llx = $self->{'llx'} + $self->{'left_text_margin'};                             # this places the bottom left of the barcode the left text margin distance to right of the left edge of the label ($self->{'llx'})
307     my $barcode_lly = ($self->{'lly'} + $self->{'height'}) - $self->{'top_text_margin'};        # this places the bottom left of the barcode the top text margin distance below the top of the label ($self->{'lly'})
308     my $barcode_width = 0.8 * $self->{'width'};                                                 # this scales the barcode width to 80% of the label width
309     my $barcode_y_scale_factor = 0.01 * $self->{'height'};                                      # this scales the barcode height to 10% of the label height
310     my $line_spacer = ($self->{'font_size'} * 1);                               # number of pixels between text rows (This is actually leading: baseline to baseline minus font size. Recommended starting point is 20% of font size.).
311     my $text_lly = (($self->{'lly'} + $self->{'height'}) - $self->{'top_text_margin'} - (($self->{'lly'} + $self->{'height'}) - $barcode_lly));
312     return $self->{'llx'}, $text_lly, $line_spacer, $barcode_llx, $barcode_lly, $barcode_width, $barcode_y_scale_factor;
313 }
314
315 sub new {
316     my ($invocant, %params) = @_;
317     my $type = ref($invocant) || $invocant;
318     my $self = {
319         batch_id                => $params{'batch_id'},
320         item_number             => $params{'item_number'},
321         llx                     => $params{'llx'},
322         lly                     => $params{'lly'},
323         height                  => $params{'height'},
324         width                   => $params{'width'},
325         top_text_margin         => $params{'top_text_margin'},
326         left_text_margin        => $params{'left_text_margin'},
327         barcode_type            => $params{'barcode_type'},
328         printing_type           => $params{'printing_type'},
329         guidebox                => $params{'guidebox'},
330         oblique_title           => $params{'oblique_title'},
331         font                    => $params{'font'},
332         font_size               => $params{'font_size'},
333         callnum_split           => $params{'callnum_split'},
334         justify                 => $params{'justify'},
335         format_string           => $params{'format_string'},
336         text_wrap_cols          => $params{'text_wrap_cols'},
337         barcode                 => 0,
338     };
339     if ($self->{'guidebox'}) {
340         $self->{'guidebox'} = _guide_box($self->{'llx'}, $self->{'lly'}, $self->{'width'}, $self->{'height'});
341     }
342     bless ($self, $type);
343     return $self;
344 }
345
346 sub get_label_type {
347     my $self = shift;
348     return $self->{'printing_type'};
349 }
350
351 sub get_attr {
352     my $self = shift;
353     if (_check_params(@_) eq 1) {
354         return -1;
355     }
356     my ($attr) = @_;
357     if (exists($self->{$attr})) {
358         return $self->{$attr};
359     }
360     else {
361         return -1;
362     }
363     return;
364 }
365
366 sub create_label {
367     my $self = shift;
368     my $label_text = '';
369     my ($text_llx, $text_lly, $line_spacer, $barcode_llx, $barcode_lly, $barcode_width, $barcode_y_scale_factor);
370     {
371         no strict 'refs';
372         ($text_llx, $text_lly, $line_spacer, $barcode_llx, $barcode_lly, $barcode_width, $barcode_y_scale_factor) = &{"_$self->{'printing_type'}"}($self); # an obfuscated call to the correct printing type sub
373     }
374     if ($self->{'printing_type'} =~ /BIB/) {
375         $label_text = draw_label_text(  $self,
376                                         llx             => $text_llx,
377                                         lly             => $text_lly,
378                                         line_spacer     => $line_spacer,
379                                     );
380     }
381     if ($self->{'printing_type'} =~ /BAR/) {
382         barcode(    $self,
383                     llx                 => $barcode_llx,
384                     lly                 => $barcode_lly,
385                     width               => $barcode_width,
386                     y_scale_factor      => $barcode_y_scale_factor,
387         );
388     }
389     return $label_text if $label_text;
390     return;
391 }
392
393 sub draw_label_text {
394     my ($self, %params) = @_;
395     my @label_text = ();
396     my $text_llx = 0;
397     my $text_lly = $params{'lly'};
398     my $font = $self->{'font'};
399     my $item = _get_label_item($self->{'item_number'});
400     my $label_fields = _get_text_fields($self->{'format_string'});
401     my $record = GetMarcBiblio($item->{'biblionumber'});
402     # FIXME - returns all items, so you can't get data from an embedded holdings field.
403     # TODO - add a GetMarcBiblio1item(bibnum,itemnum) or a GetMarcItem(itemnum).
404     my $cn_source = ($item->{'cn_source'} ? $item->{'cn_source'} : C4::Context->preference('DefaultClassificationSource'));
405     LABEL_FIELDS:       # process data for requested fields on current label
406     for my $field (@$label_fields) {
407         if ($field->{'code'} eq 'itemtype') {
408             $field->{'data'} = C4::Context->preference('item-level_itypes') ? $item->{'itype'} : $item->{'itemtype'};
409         }
410         else {
411             $field->{'data'} = _get_barcode_data($field->{'code'},$item,$record);
412         }
413         # Find apropriate font it oblique title selected, except main font is oblique
414         if ( ( $field->{'code'} eq 'title' ) and ( $self->{'oblique_title'} == 1 ) ) {
415             if ( $font =~ /^TB$/ ) {
416                 $font .= 'I';
417             }
418             elsif ( $font =~ /^TR$/ ) {
419                 $font = 'TI';
420             }
421             elsif ( $font !~ /^T/ and $font !~ /O$/ ) {
422                 $font .= 'O';
423             }
424         }
425         my $field_data = $field->{'data'};
426         if ($field_data) {
427             $field_data =~ s/\n//g;
428             $field_data =~ s/\r//g;
429         }
430         my @label_lines;
431         # Fields which hold call number data  FIXME: ( 060? 090? 092? 099? )
432         my @callnumber_list = qw(itemcallnumber 050a 050b 082a 952o 995k);
433         if ((grep {$field->{'code'} =~ m/$_/} @callnumber_list) and ($self->{'printing_type'} eq 'BIB') and ($self->{'callnum_split'})) { # If the field contains the call number, we do some sp
434             if ($cn_source eq 'lcc' || $cn_source eq 'nlm') { # NLM and LCC should be split the same way
435                 @label_lines = _split_lccn($field_data);
436                 @label_lines = _split_ccn($field_data) if !@label_lines;    # If it was not a true lccn, try it as a custom call number
437                 push (@label_lines, $field_data) if !@label_lines;         # If it was not that, send it on unsplit
438             } elsif ($cn_source eq 'ddc') {
439                 @label_lines = _split_ddcn($field_data);
440                 @label_lines = _split_ccn($field_data) if !@label_lines;
441                 push (@label_lines, $field_data) if !@label_lines;
442             } else {
443                 warn sprintf('Call number splitting failed for: %s. Please add this call number to bug #2500 at bugs.koha-community.org', $field_data);
444                 push @label_lines, $field_data;
445             }
446         }
447         else {
448             if ($field_data) {
449                 $field_data =~ s/\/$//g;       # Here we will strip out all trailing '/' in fields other than the call number...
450                 # Escaping the parens was causing odd output, see bug 13124
451                 # $field_data =~ s/\(/\\\(/g;    # Escape '(' and ')' for the pdf object stream...
452                 # $field_data =~ s/\)/\\\)/g;
453             }
454             eval{$Text::Wrap::columns = $self->{'text_wrap_cols'};};
455             my @line = split(/\n/ ,wrap('', '', $field_data));
456             # If this is a title field, limit to two lines; all others limit to one... FIXME: this is rather arbitrary
457             if ($field->{'code'} eq 'title' && scalar(@line) >= 2) {
458                 while (scalar(@line) > 2) {
459                     pop @line;
460                 }
461             } else {
462                 while (scalar(@line) > 1) {
463                     pop @line;
464                 }
465             }
466             push(@label_lines, @line);
467         }
468         LABEL_LINES:    # generate lines of label text for current field
469         foreach my $line (@label_lines) {
470             next LABEL_LINES if $line eq '';
471             $line = log2vis( $line );
472             my $string_width = C4::Creators::PDF->StrWidth($line, $font, $self->{'font_size'});
473             if ($self->{'justify'} eq 'R') {
474                 $text_llx = $params{'llx'} + $self->{'width'} - ($self->{'left_text_margin'} + $string_width);
475             }
476             elsif($self->{'justify'} eq 'C') {
477                  # some code to try and center each line on the label based on font size and string point width...
478                  my $whitespace = ($self->{'width'} - ($string_width + (2 * $self->{'left_text_margin'})));
479                  $text_llx = (($whitespace  / 2) + $params{'llx'} + $self->{'left_text_margin'});
480             }
481             else {
482                 $text_llx = ($params{'llx'} + $self->{'left_text_margin'});
483             }
484             push @label_text,   {
485                                 text_llx        => $text_llx,
486                                 text_lly        => $text_lly,
487                                 font            => $font,
488                                 font_size       => $self->{'font_size'},
489                                 line            => $line,
490                                 };
491             $text_lly = $text_lly - $params{'line_spacer'};
492         }
493         $font = $self->{'font'};        # reset font for next field
494     }   #foreach field
495     return \@label_text;
496 }
497
498 sub draw_guide_box {
499     return $_[0]->{'guidebox'};
500 }
501
502 sub barcode {
503     my $self = shift;
504     my %params = @_;
505     $params{'barcode_data'} = _get_label_item($self->{'item_number'}, 1) if !$params{'barcode_data'};
506     $params{'barcode_type'} = $self->{'barcode_type'} if !$params{'barcode_type'};
507     my $x_scale_factor = 1;
508     my $num_of_bars = length($params{'barcode_data'});
509     my $tot_bar_length = 0;
510     my $bar_length = 0;
511     my $guard_length = 10;
512     my $hide_text = 'yes';
513     if ($params{'barcode_type'} =~ m/CODE39/) {
514         $bar_length = '17.5';
515         $tot_bar_length = ($bar_length * $num_of_bars) + ($guard_length * 2);
516         $x_scale_factor = ($params{'width'} / $tot_bar_length);
517         if ($params{'barcode_type'} eq 'CODE39MOD') {
518             my $c39 = CheckDigits('code_39');   # get modulo43 checksum
519             $params{'barcode_data'} = $c39->complete($params{'barcode_data'});
520         }
521         elsif ($params{'barcode_type'} eq 'CODE39MOD10') {
522             my $c39_10 = CheckDigits('siret');   # get modulo43 checksum
523             $params{'barcode_data'} = $c39_10->complete($params{'barcode_data'});
524             $hide_text = '';
525         }
526         eval {
527             PDF::Reuse::Barcode::Code39(
528                 x                   => $params{'llx'},
529                 y                   => $params{'lly'},
530                 value               => "*$params{barcode_data}*",
531                 xSize               => $x_scale_factor,
532                 ySize               => $params{'y_scale_factor'},
533                 hide_asterisk       => 1,
534                 text                => $hide_text,
535                 mode                => 'graphic',
536             );
537         };
538         if ($@) {
539             warn sprintf('Barcode generation failed for item %s with this error: %s', $self->{'item_number'}, $@);
540         }
541     }
542     elsif ($params{'barcode_type'} eq 'COOP2OF5') {
543         $bar_length = '9.43333333333333';
544         $tot_bar_length = ($bar_length * $num_of_bars) + ($guard_length * 2);
545         $x_scale_factor = ($params{'width'} / $tot_bar_length) * 0.9;
546         eval {
547             PDF::Reuse::Barcode::COOP2of5(
548                 x                   => $params{'llx'},
549                 y                   => $params{'lly'},
550                 value               => $params{barcode_data},
551                 xSize               => $x_scale_factor,
552                 ySize               => $params{'y_scale_factor'},
553                 mode                    => 'graphic',
554             );
555         };
556         if ($@) {
557             warn sprintf('Barcode generation failed for item %s with this error: %s', $self->{'item_number'}, $@);
558         }
559     }
560     elsif ( $params{'barcode_type'} eq 'INDUSTRIAL2OF5' ) {
561         $bar_length = '13.1333333333333';
562         $tot_bar_length = ($bar_length * $num_of_bars) + ($guard_length * 2);
563         $x_scale_factor = ($params{'width'} / $tot_bar_length) * 0.9;
564         eval {
565             PDF::Reuse::Barcode::Industrial2of5(
566                 x                   => $params{'llx'},
567                 y                   => $params{'lly'},
568                 value               => $params{barcode_data},
569                 xSize               => $x_scale_factor,
570                 ySize               => $params{'y_scale_factor'},
571                 mode                    => 'graphic',
572             );
573         };
574         if ($@) {
575             warn sprintf('Barcode generation failed for item %s with this error: %s', $self->{'item_number'}, $@);
576         }
577     }
578     elsif ($params{'barcode_type'} eq 'EAN13') {
579         $bar_length = 4; # FIXME
580     $num_of_bars = 13;
581         $tot_bar_length = ($bar_length * $num_of_bars) + ($guard_length * 2);
582         $x_scale_factor = ($params{'width'} / $tot_bar_length) * 0.9;
583         eval {
584             PDF::Reuse::Barcode::EAN13(
585                 x                   => $params{'llx'},
586                 y                   => $params{'lly'},
587                 value               => sprintf('%013d',$params{barcode_data}),
588 #                xSize               => $x_scale_factor,
589 #                ySize               => $params{'y_scale_factor'},
590                 mode                    => 'graphic',
591             );
592         };
593         if ($@) {
594             warn sprintf('Barcode generation failed for item %s with this error: %s', $self->{'item_number'}, $@);
595         }
596     }
597     else {
598     warn "unknown barcode_type: $params{barcode_type}";
599     }
600 }
601
602 sub csv_data {
603     my $self = shift;
604     my $label_fields = _get_text_fields($self->{'format_string'});
605     my $item = _get_label_item($self->{'item_number'});
606     my $bib_record = GetMarcBiblio($item->{biblionumber});
607     my @csv_data = (map { _get_barcode_data($_->{'code'},$item,$bib_record) } @$label_fields);
608     return \@csv_data;
609 }
610
611 1;
612 __END__
613
614 =head1 NAME
615
616 C4::Labels::Label - A class for creating and manipulating label objects in Koha
617
618 =head1 ABSTRACT
619
620 This module provides methods for creating, and otherwise manipulating single label objects used by Koha to create and export labels.
621
622 =head1 METHODS
623
624 =head2 new()
625
626     Invoking the I<new> method constructs a new label object containing the supplied values. Depending on the final output format of the label data
627     the minimal required parameters change. (See the implimentation of this object type in labels/label-create-pdf.pl and labels/label-create-csv.pl
628     and labels/label-create-xml.pl for examples.) The following parameters are optionally accepted as key => value pairs:
629
630         C<batch_id>             Batch id with which this label is associated
631         C<item_number>          Item number of item to be the data source for this label
632         C<height>               Height of this label (All measures passed to this method B<must> be supplied in postscript points)
633         C<width>                Width of this label
634         C<top_text_margin>      Top margin of this label
635         C<left_text_margin>     Left margin of this label
636         C<barcode_type>         Defines the barcode type to be used on labels. NOTE: At present only the following barcode types are supported in the label creator code:
637
638 =over 9
639
640 =item .
641             CODE39          = Code 3 of 9
642
643 =item .
644             CODE39MOD       = Code 3 of 9 with modulo 43 checksum
645
646 =item .
647             CODE39MOD10     = Code 3 of 9 with modulo 10 checksum
648
649 =item .
650             COOP2OF5        = A variant of 2 of 5 barcode based on NEC's "Process 8000" code
651
652 =item .
653             INDUSTRIAL2OF5  = The standard 2 of 5 barcode (a binary level bar code developed by Identicon Corp. and Computer Identics Corp. in 1970)
654
655 =item .
656             EAN13           = The standard EAN-13 barcode
657
658 =back
659
660         C<printing_type>        Defines the general layout to be used on labels. NOTE: At present there are only five printing types supported in the label creator code:
661
662 =over 9
663
664 =item .
665 BIB     = Only the bibliographic data is printed
666
667 =item .
668 BARBIB  = Barcode proceeds bibliographic data
669
670 =item .
671 BIBBAR  = Bibliographic data proceeds barcode
672
673 =item .
674 ALT     = Barcode and bibliographic data are printed on alternating labels
675
676 =item .
677 BAR     = Only the barcode is printed
678
679 =back
680
681         C<guidebox>             Setting this to '1' will result in a guide box being drawn around the labels marking the edge of each label
682         C<font>                 Defines the type of font to be used on labels. NOTE: The following fonts are available by default on most systems:
683
684 =over 9
685
686 =item .
687 TR      = Times-Roman
688
689 =item .
690 TB      = Times Bold
691
692 =item .
693 TI      = Times Italic
694
695 =item .
696 TBI     = Times Bold Italic
697
698 =item .
699 C       = Courier
700
701 =item .
702 CB      = Courier Bold
703
704 =item .
705 CO      = Courier Oblique (Italic)
706
707 =item .
708 CBO     = Courier Bold Oblique
709
710 =item .
711 H       = Helvetica
712
713 =item .
714 HB      = Helvetica Bold
715
716 =item .
717 HBO     = Helvetical Bold Oblique
718
719 =back
720
721         C<font_size>            Defines the size of the font in postscript points to be used on labels
722         C<callnum_split>        Setting this to '1' will enable call number splitting on labels
723         C<text_justify>         Defines the text justification to be used on labels. NOTE: The following justification styles are currently supported by label creator code:
724
725 =over 9
726
727 =item .
728 L       = Left
729
730 =item .
731 C       = Center
732
733 =item .
734 R       = Right
735
736 =back
737
738         C<format_string>        Defines what fields will be printed and in what order they will be printed on labels. These include any of the data fields that may be mapped
739                                 to your MARC frameworks. Specify MARC subfields as a 4-character tag-subfield string: ie. 254a Enclose a whitespace-separated list of fields
740                                 to concatenate on one line in double quotes. ie. "099a 099b" or "itemcallnumber barcode" Static text strings may be entered in single-quotes:
741                                 ie. 'Some static text here.'
742         C<text_wrap_cols>       Defines the column after which the text will wrap to the next line.
743
744 =head2 get_label_type()
745
746    Invoking the I<get_label_type> method will return the printing type of the label object.
747
748    example:
749         C<my $label_type = $label->get_label_type();>
750
751 =head2 get_attr($attribute)
752
753     Invoking the I<get_attr> method will return the value of the requested attribute or -1 on errors.
754
755     example:
756         C<my $value = $label->get_attr($attribute);>
757
758 =head2 create_label()
759
760     Invoking the I<create_label> method generates the text for that label and returns it as an arrayref of an array contianing the formatted text as well as creating the barcode
761     and writing it directly to the pdf stream. The handling of the barcode is not quite good OO form due to the linear format of PDF::Reuse::Barcode. Be aware that the instantiating
762     code is responsible to properly format the text for insertion into the pdf stream as well as the actual insertion.
763
764     example:
765         my $label_text = $label->create_label();
766
767 =head2 draw_label_text()
768
769     Invoking the I<draw_label_text> method generates the label text for the label object and returns it as an arrayref of an array containing the formatted text. The same caveats
770     apply to this method as to C<create_label()>. This method accepts the following parameters as key => value pairs: (NOTE: The unit is the postscript point - 72 per inch)
771
772         C<llx>                  The lower-left x coordinate for the text block (The point of origin for all PDF's is the lower left of the page per ISO 32000-1)
773         C<lly>                  The lower-left y coordinate for the text block
774         C<top_text_margin>      The top margin for the text block.
775         C<line_spacer>          The number of pixels between text rows (This is actually leading: baseline to baseline minus font size. Recommended starting point is 20% of font size)
776         C<font>                 The font to use for this label. See documentation on the new() method for supported fonts.
777         C<font_size>            The font size in points to use for this label.
778         C<justify>              The style of justification to use for this label. See documentation on the new() method for supported justification styles.
779
780     example:
781        C<my $label_text = $label->draw_label_text(
782                                                 llx                 => $text_llx,
783                                                 lly                 => $text_lly,
784                                                 top_text_margin     => $label_top_text_margin,
785                                                 line_spacer         => $text_leading,
786                                                 font                => $text_font,
787                                                 font_size           => $text_font_size,
788                                                 justify             => $text_justification,
789                         );>
790
791 =head2 barcode()
792
793     Invoking the I<barcode> method generates a barcode for the label object and inserts it into the current pdf stream. This method accepts the following parameters as key => value
794     pairs (C<barcode_data> is optional and omitting it will cause the barcode from the current item to be used. C<barcode_type> is also optional. Omission results in the barcode
795     type of the current template being used.):
796
797         C<llx>                  The lower-left x coordinate for the barcode block (The point of origin for all PDF's is the lower left of the page per ISO 32000-1)
798         C<lly>                  The lower-left y coordinate for the barcode block
799         C<width>                The width of the barcode block
800         C<y_scale_factor>       The scale factor to be applied to the y axis of the barcode block
801         C<barcode_data>         The data to be encoded in the barcode
802         C<barcode_type>         The barcode type (See the C<new()> method for supported barcode types)
803
804     example:
805        C<$label->barcode(
806                     llx                 => $barcode_llx,
807                     lly                 => $barcode_lly,
808                     width               => $barcode_width,
809                     y_scale_factor      => $barcode_y_scale_factor,
810                     barcode_data        => $barcode,
811                     barcode_type        => $barcodetype,
812         );>
813
814 =head2 csv_data()
815
816     Invoking the I<csv_data> method returns an arrayref of an array containing the label data suitable for passing to Text::CSV_XS->combine() to produce csv output.
817
818     example:
819         C<my $csv_data = $label->csv_data();>
820
821 =head1 AUTHOR
822
823 Mason James <mason@katipo.co.nz>
824
825 Chris Nighswonger <cnighswonger AT foundations DOT edu>
826
827 =head1 COPYRIGHT
828
829 Copyright 2006 Katipo Communications.
830
831 Copyright 2009 Foundations Bible College.
832
833 =head1 LICENSE
834
835 This file is part of Koha.
836
837 Koha is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software
838 Foundation; either version 2 of the License, or (at your option) any later version.
839
840 You should have received a copy of the GNU General Public License along with Koha; if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
841 Fifth Floor, Boston, MA 02110-1301 USA.
842
843 =head1 DISCLAIMER OF WARRANTY
844
845 Koha is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
846 A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
847
848 =cut