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