Final cleanup of labels for 3.0
[koha.git] / C4 / Labels.pm
1 package C4::Labels;
2
3 # Copyright 2006 Katipo Communications.
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
10 # version.
11 #
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along with
17 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
18 # Suite 330, Boston, MA  02111-1307 USA
19
20 use strict;
21 use vars qw($VERSION @ISA @EXPORT);
22
23 use PDF::Reuse;
24 use Text::Wrap;
25 use Algorithm::CheckDigits;
26 use C4::Members;
27 use C4::Branch;
28 use C4::Debug;
29 use C4::Biblio;
30 use Text::CSV_XS;
31 use Data::Dumper;
32
33 BEGIN {
34         $VERSION = 0.03;
35         require Exporter;
36         @ISA    = qw(Exporter);
37         @EXPORT = qw(
38                 &get_label_options &GetLabelItems
39                 &build_circ_barcode &draw_boundaries
40                 &drawbox &GetActiveLabelTemplate
41                 &GetAllLabelTemplates &DeleteTemplate
42                 &GetSingleLabelTemplate &SaveTemplate
43                 &CreateTemplate &SetActiveTemplate
44                 &SaveConf &DrawSpineText &GetTextWrapCols
45                 &GetUnitsValue &DrawBarcode &DrawPatronCardText
46                 &get_printingtypes &GetPatronCardItems
47                 &get_layouts
48                 &get_barcode_types
49                 &get_batches &delete_batch
50                 &add_batch &printText
51                 &GetItemFields
52                 &get_text_fields
53                 get_layout &save_layout &add_layout
54                 &set_active_layout
55                 &build_text_dropbox
56                 &delete_layout &get_active_layout
57                 &get_highest_batch
58                 &deduplicate_batch
59                 &GetAllPrinterProfiles &GetSinglePrinterProfile
60                 &SaveProfile &CreateProfile &DeleteProfile
61                 &GetAssociatedProfile &SetAssociatedProfile
62         );
63 }
64
65
66 =head1 NAME
67
68 C4::Labels - Functions for printing spine labels and barcodes in Koha
69
70 =head1 FUNCTIONS
71
72 =over 2
73
74 =item get_label_options;
75
76         $options = get_label_options()
77
78 Return a pointer on a hash list containing info from labels_conf table in Koha DB.
79
80 =cut
81
82 sub get_label_options {
83     my $query2 = " SELECT * FROM labels_conf where active = 1";         # FIXME: exact same as get_active_layout
84     my $sth    = C4::Context->dbh->prepare($query2);
85     $sth->execute();
86     return $sth->fetchrow_hashref;
87 }
88
89 sub get_layouts {
90     my $dbh = C4::Context->dbh;
91     my @data;
92     my $query = " Select * from labels_conf";
93     my $sth   = $dbh->prepare($query);
94     $sth->execute();
95     my @resultsloop;
96     while ( my $data = $sth->fetchrow_hashref ) {
97
98         $data->{'fieldlist'} = get_text_fields( $data->{'id'} );
99         push( @resultsloop, $data );
100     }
101     $sth->finish;
102     return @resultsloop;
103 }
104
105 sub get_layout {
106     my ($layout_id) = @_;
107     my $dbh = C4::Context->dbh;
108
109     # get the actual items to be printed.
110     my $query = " Select * from labels_conf where id = ?";
111     my $sth   = $dbh->prepare($query);
112     $sth->execute($layout_id);
113     my $data = $sth->fetchrow_hashref;
114     $sth->finish;
115     return $data;
116 }
117
118 sub get_active_layout {
119     my $query = " Select * from labels_conf where active = 1";          # FIXME: exact same as get_label_options
120     my $sth   = C4::Context->dbh->prepare($query);
121     $sth->execute();
122     return $sth->fetchrow_hashref;
123 }
124
125 sub delete_layout {
126     my ($layout_id) = @_;
127     my $dbh = C4::Context->dbh;
128
129     # get the actual items to be printed.
130     my $query = "delete from  labels_conf where id = ?";
131     my $sth   = $dbh->prepare($query);
132     $sth->execute($layout_id);
133     $sth->finish;
134 }
135
136 sub get_printingtypes {
137     my ($layout_id) = @_;
138     my @printtypes;
139 # FIXME hard coded print types
140     push( @printtypes, { code => 'BAR',    desc => "barcode only" } );
141     push( @printtypes, { code => 'BIB',    desc => "biblio only" } );
142     push( @printtypes, { code => 'BARBIB', desc => "barcode / biblio" } );
143     push( @printtypes, { code => 'BIBBAR', desc => "biblio / barcode" } );
144     push( @printtypes, { code => 'ALT',    desc => "alternating labels" } );
145     push( @printtypes, { code => 'CSV',    desc => "csv output" } );
146     push( @printtypes, { code => 'PATCRD', desc => "patron cards" } );
147
148     my $conf             = get_layout($layout_id);
149     my $active_printtype = $conf->{'printingtype'};
150
151     # lop thru layout, insert selected to hash
152
153     foreach my $printtype (@printtypes) {
154         if ( $printtype->{'code'} eq $active_printtype ) {
155             $printtype->{'active'} = 1;
156         }
157     }
158     return @printtypes;
159 }
160
161 # this sub (build_text_dropbox) is deprecated and should be deleted. 
162 # rch 2008.04.15
163 #
164 sub build_text_dropbox {
165     my ($order) = @_;
166     my $field_count = 7;    # <-----------       FIXME hard coded
167     my @lines;
168     !$order
169       ? push( @lines, { num => '', selected => '1' } )
170       : push( @lines, { num => '' } );
171     for ( my $i = 1 ; $i <= $field_count ; $i++ ) {
172         my $line = { num => "$i" };
173         $line->{'selected'} = 1 if $i eq $order;
174         push( @lines, $line );
175     }
176     return @lines;
177 }
178
179 sub get_text_fields {
180     my ( $layout_id, $sorttype ) = @_;
181     my @sorted_fields;
182     my $error;
183     my $sortorder = get_layout($layout_id);
184     if ( $sortorder->{formatstring} ) {
185         if ( !$sorttype ) {
186             return $sortorder->{formatstring};
187         }
188         else {
189             my $csv    = Text::CSV_XS->new( { allow_whitespace => 1 } );
190             my $line   = $sortorder->{formatstring};
191             my $status = $csv->parse($line);
192             @sorted_fields =
193               map { { 'code' => $_, desc => $_ } } $csv->fields();
194             $error = $csv->error_input();
195             warn $error if $error;    # TODO - do more with this.
196         }
197     }
198     else {
199
200      # These fields are hardcoded based on the template for label-edit-layout.pl
201         my @text_fields = (
202             {
203                 code  => 'itemtype',
204                 desc  => "Item Type",
205                 order => $sortorder->{'itemtype'}
206             },
207             {
208                 code  => 'issn',
209                 desc  => "ISSN",
210                 order => $sortorder->{'issn'}
211             },
212             {
213                 code  => 'isbn',
214                 desc  => "ISBN",
215                 order => $sortorder->{'isbn'}
216             },
217             {
218                 code  => 'barcode',
219                 desc  => "Barcode",
220                 order => $sortorder->{'barcode'}
221             },
222             {
223                 code  => 'author',
224                 desc  => "Author",
225                 order => $sortorder->{'author'}
226             },
227             {
228                 code  => 'title',
229                 desc  => "Title",
230                 order => $sortorder->{'title'}
231             },
232             {
233                 code  => 'itemcallnumber',
234                 desc  => "Call Number",
235                 order => $sortorder->{'itemcallnumber'}
236             },
237         );
238
239         my @new_fields = ();
240         foreach my $field (@text_fields) {
241             push( @new_fields, $field ) if $field->{'order'} > 0;
242         }
243
244         @sorted_fields = sort { $$a{order} <=> $$b{order} } @new_fields;
245     }
246
247     # if we have a 'formatstring', then we ignore these hardcoded fields.
248     my $active_fields;
249
250     if ( $sorttype eq 'codes' )
251     { # FIXME: This sub should really always return the array of hashrefs and let the caller take what he wants from that -fbcit
252         return @sorted_fields;
253     }
254     else {
255         foreach my $field (@sorted_fields) {
256             $active_fields .= "$field->{'desc'} ";
257         }
258         return $active_fields;
259     }
260
261 }
262
263 =head2 sub add_batch
264 =over 4
265  add_batch($batch_type,\@batch_list);
266  if $batch_list is supplied,
267    create a new batch with those items.
268  else, return the next available batch_id.
269 =return
270 =cut
271
272 sub add_batch ($;$) {
273         my $table = (@_ and 'patroncards' eq shift) ? 'patroncards' : 'labels';
274     my $batch_list = (@_) ? shift : undef;
275     my $dbh = C4::Context->dbh;
276     my $q ="SELECT MAX(DISTINCT batch_id) FROM $table";
277     my $sth = $dbh->prepare($q);
278     $sth->execute();
279     my ($batch_id) = $sth->fetchrow_array || 0;
280         $batch_id++;
281         if ($batch_list) {
282                 if ($table eq 'patroncards') {
283                         $sth = $dbh->prepare("INSERT INTO $table (`batch_id`,`borrowernumber`) VALUES (?,?)"); 
284                 } else {
285                         $sth = $dbh->prepare("INSERT INTO $table (`batch_id`,`itemnumber`    ) VALUES (?,?)"); 
286                 }
287                 for (@$batch_list) {
288                         $sth->execute($batch_id,$_);
289                 }
290         }
291         return $batch_id;
292 }
293
294 #FIXME: Needs to be ported to receive $batch_type
295 # ... this looks eerily like add_batch() ...
296 sub get_highest_batch {
297         my $table = (@_ and 'patroncards' eq shift) ? 'patroncards' : 'labels';
298     my $q =
299       "select distinct batch_id from $table order by batch_id desc limit 1";
300     my $sth = C4::Context->dbh->prepare($q);
301     $sth->execute();
302     my $data = $sth->fetchrow_hashref or return 1;
303         return ($data->{'batch_id'} || 1);
304 }
305
306
307 sub get_batches (;$) {
308         my $table = (@_ and 'patroncards' eq shift) ? 'patroncards' : 'labels';
309     my $q   = "SELECT batch_id, COUNT(*) AS num FROM $table GROUP BY batch_id";
310     my $sth = C4::Context->dbh->prepare($q);
311     $sth->execute();
312         my $batches = $sth->fetchall_arrayref({});
313         return @$batches;
314 }
315
316 sub delete_batch {
317     my ($batch_id, $batch_type) = @_;
318     warn "Deleteing batch of type $batch_type";
319     my $dbh        = C4::Context->dbh;
320     my $q          = "DELETE FROM $batch_type WHERE batch_id  = ?";
321     my $sth        = $dbh->prepare($q);
322     $sth->execute($batch_id);
323     $sth->finish;
324 }
325
326 sub get_barcode_types {
327     my ($layout_id) = @_;
328     my $layout      = get_layout($layout_id);
329     my $barcode     = $layout->{'barcodetype'};
330     my @array;
331
332     push( @array, { code => 'CODE39',      desc => 'Code 39' } );
333     push( @array, { code => 'CODE39MOD',   desc => 'Code39 + Modulo43' } );
334     push( @array, { code => 'CODE39MOD10', desc => 'Code39 + Modulo10' } ); 
335     push( @array, { code => 'ITF',         desc => 'Interleaved 2 of 5' } );
336
337     foreach my $line (@array) {
338         if ( $line->{'code'} eq $barcode ) {
339             $line->{'active'} = 1;
340         }
341
342     }
343     return @array;
344 }
345
346 sub GetUnitsValue {
347     my ($units) = @_;
348     my $unitvalue;
349
350     $unitvalue = '1'          if ( $units eq 'POINT' );
351     $unitvalue = '2.83464567' if ( $units eq 'MM' );
352     $unitvalue = '28.3464567' if ( $units eq 'CM' );
353     $unitvalue = 72           if ( $units eq 'INCH' );
354     return $unitvalue;
355 }
356
357 sub GetTextWrapCols {
358     my ( $font, $fontsize, $label_width, $left_text_margin ) = @_;
359     my $string = '0';
360     my $strwidth;
361     my $count = 0;
362 #    my $textlimit = $label_width - ($left_text_margin);
363     my $textlimit = $label_width - ( 3 * $left_text_margin);
364
365     while ( $strwidth < $textlimit ) {
366         $strwidth = prStrWidth( $string, $font, $fontsize );
367         $string = $string . '0';
368         #warn "strwidth:$strwidth, textlimit:$textlimit, count:$count string:$string";
369         $count++;
370     }
371     return $count;
372 }
373
374 sub GetActiveLabelTemplate {
375     my $dbh   = C4::Context->dbh;
376     my $query = " SELECT * FROM labels_templates where active = 1 limit 1";
377     my $sth   = $dbh->prepare($query);
378     $sth->execute();
379     my $active_tmpl = $sth->fetchrow_hashref;
380     $sth->finish;
381     return $active_tmpl;
382 }
383
384 sub GetSingleLabelTemplate {
385     my ($tmpl_id) = @_;
386     my $dbh       = C4::Context->dbh;
387     my $query     = " SELECT * FROM labels_templates where tmpl_id = ?";
388     my $sth       = $dbh->prepare($query);
389     $sth->execute($tmpl_id);
390     my $template = $sth->fetchrow_hashref;
391     $sth->finish;
392     return $template;
393 }
394
395 sub SetActiveTemplate {
396
397     my ($tmpl_id) = @_;
398   
399     my $dbh   = C4::Context->dbh;
400     my $query = " UPDATE labels_templates SET active = NULL";
401     my $sth   = $dbh->prepare($query);
402     $sth->execute();
403
404     $query = "UPDATE labels_templates SET active = 1 WHERE tmpl_id = ?";
405     $sth   = $dbh->prepare($query);
406     $sth->execute($tmpl_id);
407     $sth->finish;
408 }
409
410 sub set_active_layout {
411
412     my ($layout_id) = @_;
413     my $dbh         = C4::Context->dbh;
414     my $query       = " UPDATE labels_conf SET active = NULL";
415     my $sth         = $dbh->prepare($query);
416     $sth->execute();
417
418     $query = "UPDATE labels_conf SET active = 1 WHERE id = ?";
419     $sth   = $dbh->prepare($query);
420     $sth->execute($layout_id);
421     $sth->finish;
422 }
423
424 sub DeleteTemplate {
425     my ($tmpl_id) = @_;
426     my $dbh       = C4::Context->dbh;
427     my $query     = " DELETE  FROM labels_templates where tmpl_id = ?";
428     my $sth       = $dbh->prepare($query);
429     $sth->execute($tmpl_id);
430     $sth->finish;
431 }
432
433 sub SaveTemplate {
434     my (
435         $tmpl_id,     $tmpl_code,   $tmpl_desc,    $page_width,
436         $page_height, $label_width, $label_height, $topmargin,
437         $leftmargin,  $cols,        $rows,         $colgap,
438         $rowgap,      $font,        $fontsize,     $units
439     ) = @_;
440     $debug and warn "Passed \$font:$font";
441     my $dbh = C4::Context->dbh;
442     my $query =
443       " UPDATE labels_templates SET tmpl_code=?, tmpl_desc=?, page_width=?,
444                page_height=?, label_width=?, label_height=?, topmargin=?,
445                leftmargin=?, cols=?, rows=?, colgap=?, rowgap=?, font=?, fontsize=?,
446                            units=? 
447                   WHERE tmpl_id = ?";
448
449     my $sth = $dbh->prepare($query);
450     $sth->execute(
451         $tmpl_code,   $tmpl_desc,    $page_width, $page_height,
452         $label_width, $label_height, $topmargin,  $leftmargin,
453         $cols,        $rows,         $colgap,     $rowgap,
454         $font,        $fontsize,     $units,      $tmpl_id
455     );
456     my $dberror = $sth->errstr;
457     $sth->finish;
458     return $dberror;
459 }
460
461 sub CreateTemplate {
462     my $tmpl_id;
463     my (
464         $tmpl_code,   $tmpl_desc,    $page_width, $page_height,
465         $label_width, $label_height, $topmargin,  $leftmargin,
466         $cols,        $rows,         $colgap,     $rowgap,
467         $font,        $fontsize,     $units
468     ) = @_;
469
470     my $dbh = C4::Context->dbh;
471
472     my $query = "INSERT INTO labels_templates (tmpl_code, tmpl_desc, page_width,
473                          page_height, label_width, label_height, topmargin,
474                          leftmargin, cols, rows, colgap, rowgap, font, fontsize, units)
475                          VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
476
477     my $sth = $dbh->prepare($query);
478     $sth->execute(
479         $tmpl_code,   $tmpl_desc,    $page_width, $page_height,
480         $label_width, $label_height, $topmargin,  $leftmargin,
481         $cols,        $rows,         $colgap,     $rowgap,
482         $font,        $fontsize,    $units
483     );
484     my $dberror = $sth->errstr;
485     $sth->finish;
486     return $dberror;
487 }
488
489 sub GetAllLabelTemplates {
490     my $dbh = C4::Context->dbh;
491
492     # get the actual items to be printed.
493     my @data;
494     my $query = " Select * from labels_templates ";
495     my $sth   = $dbh->prepare($query);
496     $sth->execute();
497     my @resultsloop;
498     while ( my $data = $sth->fetchrow_hashref ) {
499         push( @resultsloop, $data );
500     }
501     $sth->finish;
502
503     #warn Dumper @resultsloop;
504     return @resultsloop;
505 }
506
507 #sub SaveConf {
508 sub add_layout {
509
510     my (
511         $barcodetype,  $title,          $subtitle,      $isbn,       $issn,
512         $itemtype,     $bcn,            $text_justify,        $callnum_split,
513         $itemcallnumber, $author,     $tmpl_id,
514         $printingtype, $guidebox,       $startlabel, $layoutname, $formatstring
515     ) = @_;
516
517     my $dbh    = C4::Context->dbh;
518     my $query2 = "update labels_conf set active = NULL";
519     my $sth2   = $dbh->prepare($query2);
520     $sth2->execute();
521     $query2 = "INSERT INTO labels_conf
522             ( barcodetype, title, subtitle, isbn,issn, itemtype, barcode,
523               text_justify, callnum_split, itemcallnumber, author, printingtype,
524                 guidebox, startlabel, layoutname, formatstring, active )
525                values ( ?, ?,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?,?, 1 )";
526     $sth2 = $dbh->prepare($query2);
527     $sth2->execute(
528         $barcodetype, $title, $subtitle, $isbn, $issn,
529
530         $itemtype, $bcn,            $text_justify,    $callnum_split,
531         $itemcallnumber, $author, $printingtype,
532         $guidebox, $startlabel,     $layoutname, $formatstring
533     );
534     $sth2->finish;
535
536     SetActiveTemplate($tmpl_id);
537     return;
538 }
539
540 sub save_layout {
541
542     my (
543         $barcodetype,  $title,          $subtitle,      $isbn,       $issn,
544         $itemtype,     $bcn,            $text_justify,        $callnum_split,
545         $itemcallnumber, $author,     $tmpl_id,
546         $printingtype, $guidebox,       $startlabel, $layoutname, $formatstring,
547         $layout_id
548     ) = @_;
549 ### $layoutname
550 ### $layout_id
551
552     my $dbh    = C4::Context->dbh;
553     my $query2 = "update labels_conf set 
554              barcodetype=?, title=?, subtitle=?, isbn=?,issn=?, 
555             itemtype=?, barcode=?,    text_justify=?, callnum_split=?,
556             itemcallnumber=?, author=?,  printingtype=?,  
557                guidebox=?, startlabel=?, layoutname=?, formatstring=? where id = ?";
558     my $sth2 = $dbh->prepare($query2);
559     $sth2->execute(
560         $barcodetype, $title,          $subtitle,       $isbn,       $issn,
561         $itemtype,    $bcn,            $text_justify,        $callnum_split,
562         $itemcallnumber, $author,     $printingtype,
563         $guidebox,    $startlabel,     $layoutname, $formatstring,  $layout_id
564     );
565     $sth2->finish;
566
567     return;
568 }
569
570 =item GetAllPrinterProfiles;
571
572     @profiles = GetAllPrinterProfiles()
573
574 Returns an array of references-to-hash, whos keys are .....
575
576 =cut
577
578 sub GetAllPrinterProfiles {
579
580     my $dbh = C4::Context->dbh;
581     my @data;
582     my $query = "SELECT * FROM printers_profile AS pp INNER JOIN labels_templates AS lt ON pp.tmpl_id = lt.tmpl_id; ";
583     my $sth = $dbh->prepare($query);
584     $sth->execute();
585     my @resultsloop;
586     while ( my $data = $sth->fetchrow_hashref ) {
587         push( @resultsloop, $data );
588     }
589     $sth->finish;
590
591     return @resultsloop;
592 }
593
594 =item GetSinglePrinterProfile;
595
596     $profile = GetSinglePrinterProfile()
597
598 Returns a hashref whos keys are...
599
600 =cut
601
602 sub GetSinglePrinterProfile {
603     my ($prof_id) = @_;
604     my $dbh       = C4::Context->dbh;
605     my $query     = " SELECT * FROM printers_profile WHERE prof_id = ?; ";
606     my $sth       = $dbh->prepare($query);
607     $sth->execute($prof_id);
608     my $template = $sth->fetchrow_hashref;
609     $sth->finish;
610     return $template;
611 }
612
613 =item SaveProfile;
614
615     SaveProfile('parameters')
616
617 When passed a set of parameters, this function updates the given profile with the new parameters.
618
619 =cut
620
621 sub SaveProfile {
622     my (
623         $prof_id,       $offset_horz,   $offset_vert,   $creep_horz,    $creep_vert,    $units
624     ) = @_;
625     my $dbh = C4::Context->dbh;
626     my $query =
627       " UPDATE printers_profile
628         SET offset_horz=?, offset_vert=?, creep_horz=?, creep_vert=?, unit=? 
629         WHERE prof_id = ? ";
630     my $sth = $dbh->prepare($query);
631     $sth->execute(
632         $offset_horz,   $offset_vert,   $creep_horz,    $creep_vert,    $units,         $prof_id
633     );
634     $sth->finish;
635 }
636
637 =item CreateProfile;
638
639     CreateProfile('parameters')
640
641 When passed a set of parameters, this function creates a new profile containing those parameters
642 and returns any errors.
643
644 =cut
645
646 sub CreateProfile {
647     my (
648         $prof_id,       $printername,   $paper_bin,     $tmpl_id,     $offset_horz,
649         $offset_vert,   $creep_horz,    $creep_vert,    $units
650     ) = @_;
651     my $dbh = C4::Context->dbh;
652     my $query = 
653         " INSERT INTO printers_profile (prof_id, printername, paper_bin, tmpl_id,
654                                         offset_horz, offset_vert, creep_horz, creep_vert, unit)
655           VALUES(?,?,?,?,?,?,?,?,?) ";
656     my $sth = $dbh->prepare($query);
657     $sth->execute(
658         $prof_id,       $printername,   $paper_bin,     $tmpl_id,     $offset_horz,
659         $offset_vert,   $creep_horz,    $creep_vert,    $units
660     );
661     my $error =  $sth->errstr;
662     $sth->finish;
663     return $error;
664 }
665
666 =item DeleteProfile;
667
668     DeleteProfile(prof_id)
669
670 When passed a profile id, this function deletes that profile from the database and returns any errors.
671
672 =cut
673
674 sub DeleteProfile {
675     my ($prof_id) = @_;
676     my $dbh       = C4::Context->dbh;
677     my $query     = " DELETE FROM printers_profile WHERE prof_id = ?";
678     my $sth       = $dbh->prepare($query);
679     $sth->execute($prof_id);
680     my $error = $sth->errstr;
681     $sth->finish;
682     return $error;
683 }
684
685 =item GetAssociatedProfile;
686
687     $assoc_prof = GetAssociatedProfile(tmpl_id)
688
689 When passed a template id, this function returns the parameters from the currently associated printer profile
690 in a hashref where key=fieldname and value=fieldvalue.
691
692 =cut
693
694 sub GetAssociatedProfile {
695     my ($tmpl_id) = @_;
696     my $dbh   = C4::Context->dbh;
697     # First we find out the prof_id for the associated profile...
698     my $query = "SELECT * FROM labels_profile WHERE tmpl_id = ?";
699     my $sth   = $dbh->prepare($query);
700     $sth->execute($tmpl_id);
701     my $assoc_prof = $sth->fetchrow_hashref;
702     $sth->finish;
703     # Then we retrieve that profile and return it to the caller...
704     $assoc_prof = GetSinglePrinterProfile($assoc_prof->{'prof_id'});
705     return $assoc_prof;
706 }
707
708 =item SetAssociatedProfile;
709
710     SetAssociatedProfile($prof_id, $tmpl_id)
711
712 When passed both a profile id and template id, this function establishes an association between the two. No more
713 than one profile may be associated with any given template at the same time.
714
715 =cut
716
717 sub SetAssociatedProfile {
718
719     my ($prof_id, $tmpl_id) = @_;
720   
721     my $dbh = C4::Context->dbh;
722     my $query = "INSERT INTO labels_profile (prof_id, tmpl_id) VALUES (?,?) ON DUPLICATE KEY UPDATE prof_id = ?";
723     my $sth = $dbh->prepare($query);
724     $sth->execute($prof_id, $tmpl_id, $prof_id);
725     $sth->finish;
726 }
727
728
729 =item GetLabelItems;
730
731         $options = GetLabelItems()
732
733 Returns an array of references-to-hash, whos keys are the fields from the biblio, biblioitems, items and labels tables in the Koha database.
734
735 =cut
736
737 sub GetLabelItems {
738     my ($batch_id) = @_;
739     my $dbh = C4::Context->dbh;
740
741     my @resultsloop = ();
742     my $count;
743     my @data;
744     my $sth;
745     
746     if ($batch_id) {
747         my $query3 = "
748             SELECT *
749             FROM labels
750             WHERE batch_id = ?
751             ORDER BY labelid";
752         $sth = $dbh->prepare($query3);
753         $sth->execute($batch_id);
754     }
755     else {
756         my $query3 = "
757             SELECT *
758             FROM labels";
759         $sth = $dbh->prepare($query3);
760         $sth->execute();
761     }
762     my $cnt = $sth->rows;
763     my $i1  = 1;
764     while ( my $data = $sth->fetchrow_hashref ) {
765
766         # lets get some summary info from each item
767         my $query1 =
768 #            FIXME This makes for a very bulky data structure; data from tables w/duplicate col names also gets overwritten.
769 #            Something like this, perhaps, but this also causes problems because we need more fields sometimes.
770 #            SELECT i.barcode, i.itemcallnumber, i.itype, bi.isbn, bi.issn, b.title, b.author
771            "SELECT bi.*, i.*, b.*
772             FROM items AS i, biblioitems AS bi ,biblio AS b
773             WHERE itemnumber=? AND i.biblioitemnumber=bi.biblioitemnumber AND bi.biblionumber=b.biblionumber";
774         my $sth1 = $dbh->prepare($query1);
775         $sth1->execute( $data->{'itemnumber'} );
776
777         my $data1 = $sth1->fetchrow_hashref();
778         $data1->{'labelno'}  = $i1;
779         $data1->{'labelid'}  = $data->{'labelid'};
780         $data1->{'batch_id'} = $batch_id;
781         $data1->{'summary'} = "$data1->{'barcode'}, $data1->{'title'}, $data1->{'isbn'}";
782
783         push( @resultsloop, $data1 );
784         $sth1->finish;
785
786         $i1++;
787     }
788     $sth->finish;
789     return @resultsloop;
790
791 }
792
793 sub GetItemFields {
794     my @fields = qw (
795       barcode           title
796       isbn              issn
797       author            itemtype
798       itemcallnumber
799     );
800     return @fields;
801 }
802
803 =head GetBarcodeData
804
805 =over 4
806 Parse labels_conf.formatstring value
807 (one value of the csv, which has already been split)
808 and return string from koha tables or MARC record.
809 =back
810 =cut
811 #'
812 sub GetBarcodeData {
813     my ( $f, $item, $record ) = @_;
814     my $kohatables = &_descKohaTables();
815 use Data::Dumper;
816 warn Dumper($kohatables);
817     my $datastring = '';
818     my $match_kohatable = join(
819         '|',
820         (
821             @{ $kohatables->{biblio} },
822             @{ $kohatables->{biblioitems} },
823             @{ $kohatables->{items} }
824         )
825     );
826     while ($f) {  warn $f;
827         $f =~ s/^\s?//;
828         if ( $f =~ /^'(.*)'.*/ ) {
829             # single quotes indicate a static text string.
830             $datastring .= $1;
831             $f = $';
832         }
833         elsif ( $f =~ /^($match_kohatable).*/ ) {
834             $datastring .= $item->{$f};
835             $f = $';
836         }
837         elsif ( $f =~ /^([0-9a-z]{3})(\w)(\W?).*?/ ) {
838             my $marc_field = $1;
839             $datastring .= $record->subfield($1,$2) . $3 if($record->subfield($1,$2)) ;
840             foreach my $subfield ($record->field($marc_field)) {
841                 if ( $subfield->subfield('9') eq $item->{'itemnumber'} ) {
842                     $datastring .= $subfield->subfield($2 ) . $3;
843                     last;
844                 }
845             }
846             $f = $';
847         }
848         else {
849             warn "failed to parse label formatstring: $f";
850             last;    # Failed to match
851         }
852     }
853     return $datastring;
854 }
855
856 =head descKohaTables
857 Return a hashref of an array of hashes,
858 with name,type keys.
859 =cut
860
861 sub _descKohaTables {
862         my $dbh = C4::Context->dbh();
863         my $kohatables;
864         for my $table ( 'biblio','biblioitems','items' ) {
865                 my $sth = $dbh->column_info(undef,undef,$table,'%');
866                 while (my $info = $sth->fetchrow_hashref()){
867                         push @{$kohatables->{$table}} , $info->{'COLUMN_NAME'} ;
868                 }
869                 $sth->finish;
870         }
871         return $kohatables;
872 }
873
874 sub GetPatronCardItems {
875
876     my ( $batch_id ) = @_;
877     my @resultsloop;
878     
879     my $dbh = C4::Context->dbh;
880 #    my $query = "SELECT * FROM patroncards WHERE batch_id = ? ORDER BY borrowernumber";
881     my $query = "SELECT * FROM patroncards WHERE batch_id = ? ORDER BY cardid";
882     my $sth = $dbh->prepare($query);
883     $sth->execute($batch_id);
884     my $cardno = 1;
885     while ( my $data = $sth->fetchrow_hashref ) {
886         my $patron_data = GetMember( $data->{'borrowernumber'} );
887         $patron_data->{'branchname'} = GetBranchName( $patron_data->{'branchcode'} );
888         $patron_data->{'cardno'} = $cardno;
889         $patron_data->{'cardid'} = $data->{'cardid'};
890         $patron_data->{'batch_id'} = $batch_id;
891         push( @resultsloop, $patron_data );
892         $cardno++;
893     }
894     $sth->finish;
895     return @resultsloop;
896
897 }
898
899 sub deduplicate_batch {
900         my ( $batch_id, $batch_type ) = @_;
901         my $query = "
902         SELECT DISTINCT
903                         batch_id," . (($batch_type eq 'labels') ? 'itemnumber' : 'borrowernumber') . ",
904                         count(". (($batch_type eq 'labels') ? 'labelid' : 'cardid') . ") as count 
905         FROM $batch_type 
906         WHERE batch_id = ?
907         GROUP BY " . (($batch_type eq 'labels') ? 'itemnumber' : 'borrowernumber') . ",batch_id
908         HAVING count > 1
909         ORDER BY batch_id,
910         count DESC  ";
911         my $sth = C4::Context->dbh->prepare($query);
912         $sth->execute($batch_id);
913         warn $sth->errstr if $sth->errstr;
914         $sth->rows or return undef, $sth->errstr;
915
916         my $del_query = "
917         DELETE 
918         FROM     $batch_type
919         WHERE    batch_id = ?
920         AND      " . (($batch_type eq 'labels') ? 'itemnumber' : 'borrowernumber') . " = ?
921         ORDER BY timestamp ASC
922         ";
923         my $killed = 0;
924         while (my $data = $sth->fetchrow_hashref()) {
925                 my $itemnumber = $data->{(($batch_type eq 'labels') ? 'itemnumber' : 'borrowernumber')} or next;
926                 my $limit      = $data->{count} - 1  or next;
927                 my $sth2 = C4::Context->dbh->prepare("$del_query  LIMIT $limit");
928                 # die sprintf "$del_query LIMIT %s\n (%s, %s)", $limit, $batch_id, $itemnumber;
929                 # $sth2->execute($batch_id, C4::Context->dbh->quote($data->{itemnumber}), $data->{count} - 1)
930                 $sth2->execute($batch_id, $itemnumber) and
931                         $killed += ($data->{count} - 1);
932                 warn $sth2->errstr if $sth2->errstr;
933         }
934         return $killed, undef;
935 }
936
937 sub split_lccn {
938     my ($lccn) = @_;    
939     my ($ll, $wnl, $dec, $cutter, $pubdate) = (0, 0, 0, 0, 0);
940     $_ = $lccn;
941     # lccn example 'HE8700.7 .P6T44 1983';
942     my    @splits   = m/
943         (^[a-zA-Z]+)            # HE
944         ([0-9]+\.*[0-9]*)             # 8700.7
945         \s*
946         (\.*[a-zA-Z0-9]*)       # P6T44
947         \s*
948         ([0-9]*)                # 1983
949         /x;  
950
951     # strip something occuring spaces too
952     $splits[0] =~ s/\s+$//;
953     $splits[1] =~ s/\s+$//;
954     $splits[2] =~ s/\s+$//;
955
956     return @splits;
957 }
958
959 sub split_ddcn {
960     my ($ddcn) = @_;
961     $ddcn =~ s/\///g;   # in theory we should be able to simply remove all segmentation markers and arrive at the correct call number...
962     $_ = $ddcn;
963     # ddcn example R220.3 H2793Z H32 c.2
964     my @splits = m/^([A-Z]{0,3})                # R (OS, REF, etc. up do three letters)
965                     ([0-9]+\.[0-9]*)            # 220.3
966                     \s?                         # space (not requiring anything beyond the call number)
967                     ([a-zA-Z0-9]*\.?[a-zA-Z0-9])# cutter number... maybe, but if so it is in this position (Z indicates literary criticism)
968                     \s?                         # space if it exists
969                     ([a-zA-Z]*\.?[0-9]*)        # other indicators such as cutter for author of literary criticism in this example if it exists
970                     \s?                         # space if ie exists
971                     ([a-zA-Z]*\.?[0-9]*)        # other indicators such as volume number, copy number, edition date, etc. if it exists
972                     /x;
973     return @splits;
974 }
975
976 sub split_fcn {
977     my ($fcn) = @_;
978     my @fcn_split = ();
979     # Split fiction call numbers based on spaces
980     SPLIT_FCN:
981     while ($fcn) {
982         if ($fcn =~ m/([A-Za-z0-9]+)(\W?).*?/x) {
983             push (@fcn_split, $1);
984             $fcn = $';
985         }
986         else {
987             last SPLIT_FCN;     # No match, break out of the loop
988         }
989     }
990     return @fcn_split;
991 }
992
993 sub DrawSpineText {
994
995     my ( $x_pos, $y_pos, $label_height, $label_width, $fontname, $fontsize, $left_text_margin,
996         $text_wrap_cols, $item, $conf_data, $printingtype ) = @_;
997     
998     # Replaced item's itemtype with the more user-friendly description...
999     my $dbh = C4::Context->dbh;
1000     my $sth = $dbh->prepare("SELECT itemtype,description FROM itemtypes");
1001     $sth->execute();
1002     while ( my $data = $sth->fetchrow_hashref ) {
1003         $$item->{'itemtype'} = $data->{'description'} if ($$item->{'itemtype'} eq $data->{'itemtype'});
1004         $$item->{'itype'} = $data->{'description'} if ($$item->{'itype'} eq $data->{'itemtype'});
1005     }
1006
1007     my $str = '';
1008
1009     my $top_text_margin = ( $fontsize + 3 );    #FIXME: This should be a template parameter and passed in...
1010     my $line_spacer = ( $fontsize * 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.).
1011
1012     my $layout_id = $$conf_data->{'id'};
1013
1014     my $vPos = ( $y_pos + ( $label_height - $top_text_margin ) );
1015
1016     my @str_fields = get_text_fields($layout_id, 'codes' );  
1017     my $record = GetMarcBiblio($$item->{biblionumber});
1018     # FIXME - returns all items, so you can't get data from an embedded holdings field.
1019     # TODO - add a GetMarcBiblio1item(bibnum,itemnum) or a GetMarcItem(itemnum).
1020
1021     my $old_fontname = $fontname; # We need to keep track of the original font passed in...
1022
1023     # Grab the cn_source and if that is NULL, the DefaultClassificationSource syspref
1024     my $cn_source = ($$item->{'cn_source'} ? $$item->{'cn_source'} : C4::Context->preference('DefaultClassificationSource'));
1025     for my $field (@str_fields) {
1026         $field->{'code'} or warn "get_text_fields($layout_id, 'codes') element missing 'code' field";
1027         if ($field->{'code'} eq 'itemtype') {
1028             $field->{'data'} = C4::Context->preference('item-level_itypes') ? $$item->{'itype'} : $$item->{'itemtype'};
1029         }
1030         elsif ($$conf_data->{'formatstring'}) {
1031             # if labels_conf.formatstring has a value, then it overrides the  hardcoded option.
1032             $field->{'data'} =  GetBarcodeData($field->{'code'},$$item,$record) ;
1033         }
1034         else {
1035             $field->{data} =   $$item->{$field->{'code'}}  ;
1036         }
1037         # This allows us to print the title in italic (oblique) type... (Times Roman has a different nomenclature.)
1038         # It seems there should be a better way to handle fonts in the label/patron card tool altogether -fbcit
1039         ($field->{code} eq 'title') ? (($old_fontname =~ /T/) ? ($fontname = 'TI') : ($fontname = ($old_fontname . 'O'))) : ($fontname = $old_fontname);
1040         my $font = prFont($fontname);
1041         # if the display option for this field is selected in the DB,
1042         # and the item record has some values for this field, display it.
1043         # Or if there is a csv list of fields to display, display them.
1044         if ( ($$conf_data->{'formatstring'}) || ( $$conf_data->{$field->{code}} && $$item->{$field->{code}} ) ) {
1045             # get the string
1046             my $str = $field->{data} ;
1047             # strip out naughty existing nl/cr's
1048             $str =~ s/\n//g;
1049             $str =~ s/\r//g;
1050             my @strings;
1051             my @callnumber_list = ('itemcallnumber', '050a', '050b', '082a', '952o'); # Fields which hold call number data  ( 060? 090? 092? 099? )
1052             if ((grep {$field->{code} =~ m/$_/} @callnumber_list) and ($printingtype eq 'BIB') and ($$conf_data->{'callnum_split'})) { # If the field contains the call number, we do some sp
1053                 if ($cn_source eq 'lcc') {
1054                     @strings = split_lccn($str);
1055                     @strings = split_fcn($str) if !@strings;    # If it was not a true lccn, try it as a fiction call number
1056                     push (@strings, $str) if !@strings;         # If it was not that, send it on unsplit
1057                 } elsif ($cn_source eq 'ddc') {
1058                     @strings = split_ddcn($str);
1059                     @strings = split_fcn($str) if !@strings;
1060                     push (@strings, $str) if !@strings;
1061                 } else {
1062                     # FIXME Need error trapping here; something to be informative to the user perhaps -crn
1063                     push @strings, $str;
1064                 }
1065             } else {
1066                 $str =~ s/\/$//g;       # Here we will strip out all trailing '/' in fields other than the call number...
1067                 $str =~ s/\(/\\\(/g;    # Escape '(' and ')' for the postscript stream...
1068                 $str =~ s/\)/\\\)/g;
1069                 # Wrap text lines exceeding $text_wrap_cols length...
1070                 $Text::Wrap::columns = $text_wrap_cols;
1071                 my @line = split(/\n/ ,wrap('', '', $str));
1072                 # If this is a title field, limit to two lines; all others limit to one...
1073                 if ($field->{code} eq 'title' && scalar(@line) >= 2) {
1074                     while (scalar(@line) > 2) {
1075                         pop @line;
1076                     }
1077                 } else {
1078                     while (scalar(@line) > 1) {
1079                         pop @line;
1080                     }
1081                 }
1082                 push(@strings, @line);
1083             }
1084             # loop for each string line
1085             foreach my $str (@strings) {
1086                 my $hPos = 0;
1087                 my $stringwidth = prStrWidth($str, $fontname, $fontsize);
1088                 if ( $$conf_data->{'text_justify'} eq 'R' ) { 
1089                     $hPos = $x_pos + $label_width - ( $left_text_margin + $stringwidth );
1090                 } elsif($$conf_data->{'text_justify'} eq 'C') {
1091                      # some code to try and center each line on the label based on font size and string point width...
1092                      my $whitespace = ( $label_width - ( $stringwidth + (2 * $left_text_margin) ) );
1093                      $hPos = ( ( $whitespace  / 2 ) + $x_pos + $left_text_margin );
1094                 #warn "\$label_width=$label_width \$stringwidth=$stringwidth \$whitespace=$whitespace \$left_text_margin=$left_text_margin for $str\n";
1095                 } else {
1096                     $hPos = ( $x_pos + $left_text_margin );
1097                 }
1098                 PrintText( $hPos, $vPos, $font, $fontsize, $str );
1099                 $vPos = $vPos - $line_spacer;
1100             }
1101         }
1102     }   #foreach field
1103 }
1104
1105 sub PrintText {
1106     my ( $hPos, $vPos, $font, $fontsize, $text ) = @_;
1107     my $str = "BT /$font $fontsize Tf $hPos $vPos Td ($text) Tj ET";
1108     prAdd($str);
1109 }
1110
1111 sub DrawPatronCardText {
1112
1113     my ( $x_pos, $y_pos, $label_height, $label_width, $fontname, $fontsize, $left_text_margin,
1114         $text_wrap_cols, $text, $printingtype )
1115       = @_;
1116
1117     my $top_text_margin = 25;    #FIXME: This should be a template parameter and passed in...
1118
1119     my $vPos   = ( $y_pos + ( $label_height - $top_text_margin ) );
1120     my $font = prFont($fontname);
1121
1122     my $hPos = 0;
1123
1124     foreach my $line (keys %$text) {
1125         $debug and warn "Current text is \"$line\" and font size for \"$line\" is $text->{$line} points";
1126         # some code to try and center each line on the label based on font size and string point width...
1127         my $stringwidth = prStrWidth($line, $fontname, $text->{$line});
1128         my $whitespace = ( $label_width - ( $stringwidth + (2 * $left_text_margin) ) );
1129         $hPos = ( ( $whitespace  / 2 ) + $x_pos + $left_text_margin );
1130
1131         PrintText( $hPos, $vPos, $font, $text->{$line}, $line );
1132         my $line_spacer = ( $text->{$line} * 1 );    # number of pixels between text rows (This is actually leading: baseline to baseline minus font size. Recommended starting point is 20% (0.20) of font size.).
1133         $vPos = $vPos - ($line_spacer + $text->{$line});   # Linefeed equiv: leading + font size
1134     }
1135 }
1136
1137 # Not used anywhere.
1138
1139 #sub SetFontSize {
1140 #
1141 #    my ($fontsize) = @_;
1142 #### fontsize
1143 #    my $str = "BT/F13 30 Tf288 720 Td( AAAAAAAAAA ) TjET";
1144 #    prAdd($str);
1145 #}
1146
1147 sub DrawBarcode {
1148
1149     # x and y are from the top-left :)
1150     my ( $x_pos, $y_pos, $height, $width, $barcode, $barcodetype ) = @_;
1151     my $num_of_bars = length($barcode);
1152     my $bar_width   = $width * .8;        # %80 of length of label width
1153     my $tot_bar_length = 0;
1154     my $bar_length = 0;
1155     my $guard_length = 10;
1156     my $xsize_ratio = 0;
1157
1158     if ( $barcodetype eq 'CODE39' ) {
1159         $bar_length = '17.5';
1160         $tot_bar_length =
1161           ( $bar_length * $num_of_bars ) + ( $guard_length * 2 );
1162         $xsize_ratio = ( $bar_width / $tot_bar_length );
1163         eval {
1164             PDF::Reuse::Barcode::Code39(
1165                 x => ( $x_pos + ( $width / 10 ) ),
1166                 y => ( $y_pos + ( $height / 10 ) ),
1167                 value         => "*$barcode*",
1168                 ySize         => ( .02 * $height ),
1169                 xSize         => $xsize_ratio,
1170                 hide_asterisk => 1,
1171             );
1172         };
1173         if ($@) {
1174             warn "$barcodetype, $barcode FAILED:$@";
1175         }
1176     }
1177
1178     elsif ( $barcodetype eq 'CODE39MOD' ) {
1179
1180         # get modulo43 checksum
1181         my $c39 = CheckDigits('code_39');
1182         $barcode = $c39->complete($barcode);
1183
1184         $bar_length = '19';
1185         $tot_bar_length =
1186           ( $bar_length * $num_of_bars ) + ( $guard_length * 2 );
1187         $xsize_ratio = ( $bar_width / $tot_bar_length );
1188         eval {
1189             PDF::Reuse::Barcode::Code39(
1190                 x => ( $x_pos + ( $width / 10 ) ),
1191                 y => ( $y_pos + ( $height / 10 ) ),
1192                 value         => "*$barcode*",
1193                 ySize         => ( .02 * $height ),
1194                 xSize         => $xsize_ratio,
1195                 hide_asterisk => 1,
1196             );
1197         };
1198
1199         if ($@) {
1200             warn "$barcodetype, $barcode FAILED:$@";
1201         }
1202     }
1203     elsif ( $barcodetype eq 'CODE39MOD10' ) {
1204  
1205         # get modulo43 checksum
1206         my $c39_10 = CheckDigits('visa');
1207         $barcode = $c39_10->complete($barcode);
1208
1209         $bar_length = '19';
1210         $tot_bar_length =
1211           ( $bar_length * $num_of_bars ) + ( $guard_length * 2 );
1212         $xsize_ratio = ( $bar_width / $tot_bar_length );
1213         eval {
1214             PDF::Reuse::Barcode::Code39(
1215                 x => ( $x_pos + ( $width / 10 ) ),
1216                 y => ( $y_pos + ( $height / 10 ) ),
1217                 value         => "*$barcode*",
1218                 ySize         => ( .02 * $height ),
1219                 xSize         => $xsize_ratio,
1220                 hide_asterisk => 1,
1221                                 text         => 0, 
1222             );
1223         };
1224
1225         if ($@) {
1226             warn "$barcodetype, $barcode FAILED:$@";
1227         }
1228     }
1229
1230  
1231     elsif ( $barcodetype eq 'COOP2OF5' ) {
1232         $bar_length = '9.43333333333333';
1233         $tot_bar_length =
1234           ( $bar_length * $num_of_bars ) + ( $guard_length * 2 );
1235         $xsize_ratio = ( $bar_width / $tot_bar_length ) * .9;
1236         eval {
1237             PDF::Reuse::Barcode::COOP2of5(
1238                 x => ( $x_pos + ( $width / 10 ) ),
1239                 y => ( $y_pos + ( $height / 10 ) ),
1240                 value => $barcode,
1241                 ySize => ( .02 * $height ),
1242                 xSize => $xsize_ratio,
1243             );
1244         };
1245         if ($@) {
1246             warn "$barcodetype, $barcode FAILED:$@";
1247         }
1248     }
1249
1250     elsif ( $barcodetype eq 'INDUSTRIAL2OF5' ) {
1251         $bar_length = '13.1333333333333';
1252         $tot_bar_length =
1253           ( $bar_length * $num_of_bars ) + ( $guard_length * 2 );
1254         $xsize_ratio = ( $bar_width / $tot_bar_length ) * .9;
1255         eval {
1256             PDF::Reuse::Barcode::Industrial2of5(
1257                 x => ( $x_pos + ( $width / 10 ) ),
1258                 y => ( $y_pos + ( $height / 10 ) ),
1259                 value => $barcode,
1260                 ySize => ( .02 * $height ),
1261                 xSize => $xsize_ratio,
1262             );
1263         };
1264         if ($@) {
1265             warn "$barcodetype, $barcode FAILED:$@";
1266         }
1267     }
1268
1269     my $moo2 = $tot_bar_length * $xsize_ratio;
1270
1271     warn "$x_pos, $y_pos, $barcode, $barcodetype" if $debug;
1272     warn "BAR_WDTH = $bar_width, TOT.BAR.LGHT=$tot_bar_length  R*TOT.BAR =$moo2" if $debug;
1273 }
1274
1275 =item build_circ_barcode;
1276
1277   build_circ_barcode( $x_pos, $y_pos, $barcode,
1278                 $barcodetype, \$item);
1279
1280 $item is the result of a previous call to GetLabelItems();
1281
1282 =cut
1283
1284 #'
1285 sub build_circ_barcode {
1286     my ( $x_pos_circ, $y_pos, $value, $barcodetype, $item ) = @_;
1287
1288     #warn Dumper \$item;
1289
1290     #warn "value = $value\n";
1291
1292     #$DB::single = 1;
1293
1294     if ( $barcodetype eq 'EAN13' ) {
1295
1296         #testing EAN13 barcodes hack
1297         $value = $value . '000000000';
1298         $value =~ s/-//;
1299         $value = substr( $value, 0, 12 );
1300
1301         #warn $value;
1302         eval {
1303             PDF::Reuse::Barcode::EAN13(
1304                 x     => ( $x_pos_circ + 27 ),
1305                 y     => ( $y_pos + 15 ),
1306                 value => $value,
1307
1308                 #            prolong => 2.96,
1309                 #            xSize   => 1.5,
1310
1311                 # ySize   => 1.2,
1312
1313 # added for xpdf compat. doesnt use type3 fonts., but increases filesize from 20k to 200k
1314 # i think its embedding extra fonts in the pdf file.
1315 #  mode => 'graphic',
1316             );
1317         };
1318         if ($@) {
1319             $item->{'barcodeerror'} = 1;
1320
1321             #warn "EAN13BARCODE FAILED:$@";
1322         }
1323
1324         #warn $barcodetype;
1325
1326     }
1327     elsif ( $barcodetype eq 'Code39' ) {
1328
1329         eval {
1330             PDF::Reuse::Barcode::Code39(
1331                 x     => ( $x_pos_circ + 9 ),
1332                 y     => ( $y_pos + 15 ),
1333                 value => $value,
1334
1335                 #           prolong => 2.96,
1336                 xSize => .85,
1337
1338                 ySize => 1.3,
1339             );
1340         };
1341         if ($@) {
1342             $item->{'barcodeerror'} = 1;
1343
1344             #warn "CODE39BARCODE $value FAILED:$@";
1345         }
1346
1347         #warn $barcodetype;
1348
1349     }
1350
1351     elsif ( $barcodetype eq 'Matrix2of5' ) {
1352
1353         #warn "MATRIX ELSE:";
1354
1355         #testing MATRIX25  barcodes hack
1356         #    $value = $value.'000000000';
1357         $value =~ s/-//;
1358
1359         #    $value = substr( $value, 0, 12 );
1360         #warn $value;
1361
1362         eval {
1363             PDF::Reuse::Barcode::Matrix2of5(
1364                 x     => ( $x_pos_circ + 27 ),
1365                 y     => ( $y_pos + 15 ),
1366                 value => $value,
1367
1368                 #        prolong => 2.96,
1369                 #       xSize   => 1.5,
1370
1371                 # ySize   => 1.2,
1372             );
1373         };
1374         if ($@) {
1375             $item->{'barcodeerror'} = 1;
1376
1377             #warn "BARCODE FAILED:$@";
1378         }
1379
1380         #warn $barcodetype;
1381
1382     }
1383
1384     elsif ( $barcodetype eq 'EAN8' ) {
1385
1386         #testing ean8 barcodes hack
1387         $value = $value . '000000000';
1388         $value =~ s/-//;
1389         $value = substr( $value, 0, 8 );
1390
1391         #warn $value;
1392
1393         #warn "EAN8 ELSEIF";
1394         eval {
1395             PDF::Reuse::Barcode::EAN8(
1396                 x       => ( $x_pos_circ + 42 ),
1397                 y       => ( $y_pos + 15 ),
1398                 value   => $value,
1399                 prolong => 2.96,
1400                 xSize   => 1.5,
1401
1402                 # ySize   => 1.2,
1403             );
1404         };
1405
1406         if ($@) {
1407             $item->{'barcodeerror'} = 1;
1408
1409             #warn "BARCODE FAILED:$@";
1410         }
1411
1412         #warn $barcodetype;
1413
1414     }
1415
1416     elsif ( $barcodetype eq 'UPC-E' ) {
1417         eval {
1418             PDF::Reuse::Barcode::UPCE(
1419                 x       => ( $x_pos_circ + 27 ),
1420                 y       => ( $y_pos + 15 ),
1421                 value   => $value,
1422                 prolong => 2.96,
1423                 xSize   => 1.5,
1424
1425                 # ySize   => 1.2,
1426             );
1427         };
1428
1429         if ($@) {
1430             $item->{'barcodeerror'} = 1;
1431
1432             #warn "BARCODE FAILED:$@";
1433         }
1434
1435         #warn $barcodetype;
1436
1437     }
1438     elsif ( $barcodetype eq 'NW7' ) {
1439         eval {
1440             PDF::Reuse::Barcode::NW7(
1441                 x       => ( $x_pos_circ + 27 ),
1442                 y       => ( $y_pos + 15 ),
1443                 value   => $value,
1444                 prolong => 2.96,
1445                 xSize   => 1.5,
1446
1447                 # ySize   => 1.2,
1448             );
1449         };
1450
1451         if ($@) {
1452             $item->{'barcodeerror'} = 1;
1453
1454             #warn "BARCODE FAILED:$@";
1455         }
1456
1457         #warn $barcodetype;
1458
1459     }
1460     elsif ( $barcodetype eq 'ITF' ) {
1461         eval {
1462             PDF::Reuse::Barcode::ITF(
1463                 x       => ( $x_pos_circ + 27 ),
1464                 y       => ( $y_pos + 15 ),
1465                 value   => $value,
1466                 prolong => 2.96,
1467                 xSize   => 1.5,
1468
1469                 # ySize   => 1.2,
1470             );
1471         };
1472
1473         if ($@) {
1474             $item->{'barcodeerror'} = 1;
1475
1476             #warn "BARCODE FAILED:$@";
1477         }
1478
1479         #warn $barcodetype;
1480
1481     }
1482     elsif ( $barcodetype eq 'Industrial2of5' ) {
1483         eval {
1484             PDF::Reuse::Barcode::Industrial2of5(
1485                 x       => ( $x_pos_circ + 27 ),
1486                 y       => ( $y_pos + 15 ),
1487                 value   => $value,
1488                 prolong => 2.96,
1489                 xSize   => 1.5,
1490
1491                 # ySize   => 1.2,
1492             );
1493         };
1494         if ($@) {
1495             $item->{'barcodeerror'} = 1;
1496
1497             #warn "BARCODE FAILED:$@";
1498         }
1499
1500         #warn $barcodetype;
1501
1502     }
1503     elsif ( $barcodetype eq 'IATA2of5' ) {
1504         eval {
1505             PDF::Reuse::Barcode::IATA2of5(
1506                 x       => ( $x_pos_circ + 27 ),
1507                 y       => ( $y_pos + 15 ),
1508                 value   => $value,
1509                 prolong => 2.96,
1510                 xSize   => 1.5,
1511
1512                 # ySize   => 1.2,
1513             );
1514         };
1515         if ($@) {
1516             $item->{'barcodeerror'} = 1;
1517
1518             #warn "BARCODE FAILED:$@";
1519         }
1520
1521         #warn $barcodetype;
1522
1523     }
1524
1525     elsif ( $barcodetype eq 'COOP2of5' ) {
1526         eval {
1527             PDF::Reuse::Barcode::COOP2of5(
1528                 x       => ( $x_pos_circ + 27 ),
1529                 y       => ( $y_pos + 15 ),
1530                 value   => $value,
1531                 prolong => 2.96,
1532                 xSize   => 1.5,
1533
1534                 # ySize   => 1.2,
1535             );
1536         };
1537         if ($@) {
1538             $item->{'barcodeerror'} = 1;
1539
1540             #warn "BARCODE FAILED:$@";
1541         }
1542
1543         #warn $barcodetype;
1544
1545     }
1546     elsif ( $barcodetype eq 'UPC-A' ) {
1547
1548         eval {
1549             PDF::Reuse::Barcode::UPCA(
1550                 x       => ( $x_pos_circ + 27 ),
1551                 y       => ( $y_pos + 15 ),
1552                 value   => $value,
1553                 prolong => 2.96,
1554                 xSize   => 1.5,
1555
1556                 # ySize   => 1.2,
1557             );
1558         };
1559         if ($@) {
1560             $item->{'barcodeerror'} = 1;
1561
1562             #warn "BARCODE FAILED:$@";
1563         }
1564
1565         #warn $barcodetype;
1566
1567     }
1568
1569 }
1570
1571 =item draw_boundaries
1572
1573  sub draw_boundaries ($x_pos_spine, $x_pos_circ1, $x_pos_circ2,
1574                 $y_pos, $spine_width, $label_height, $circ_width)  
1575
1576 This sub draws boundary lines where the label outlines are, to aid in printer testing, and debugging.
1577
1578 =cut
1579
1580 #'
1581 sub draw_boundaries {
1582
1583     my (
1584         $x_pos_spine, $x_pos_circ1,  $x_pos_circ2, $y_pos,
1585         $spine_width, $label_height, $circ_width
1586     ) = @_;
1587
1588     my $y_pos_initial = ( ( 792 - 36 ) - 90 );
1589     $y_pos            = $y_pos_initial; # FIXME - why are we ignoring the y_pos parameter by redefining it?
1590     my $i             = 1;
1591
1592     for ( $i = 1 ; $i <= 8 ; $i++ ) {
1593
1594         &drawbox( $x_pos_spine, $y_pos, ($spine_width), ($label_height) );
1595
1596    #warn "OLD BOXES  x=$x_pos_spine, y=$y_pos, w=$spine_width, h=$label_height";
1597         &drawbox( $x_pos_circ1, $y_pos, ($circ_width), ($label_height) );
1598         &drawbox( $x_pos_circ2, $y_pos, ($circ_width), ($label_height) );
1599
1600         $y_pos = ( $y_pos - $label_height );
1601
1602     }
1603 }
1604
1605 =item drawbox
1606
1607         sub drawbox {   $lower_left_x, $lower_left_y, 
1608                         $upper_right_x, $upper_right_y )
1609
1610 this is a low level sub, that draws a pdf box, it is called by draw_boxes
1611
1612 FYI: the  $upper_right_x and $upper_right_y values are RELATIVE to  $lower_left_x and $lower_left_y
1613
1614 and $lower_left_x, $lower_left_y are ABSOLUTE, this caught me out!
1615
1616 =cut
1617
1618 #'
1619 sub drawbox {
1620     my ( $llx, $lly, $urx, $ury ) = @_;
1621
1622     #    warn "llx,y= $llx,$lly  ,   urx,y=$urx,$ury \n";
1623
1624     my $str = "q\n";    # save the graphic state
1625     $str .= "0.5 w\n";              # border color red
1626     $str .= "1.0 0.0 0.0  RG\n";    # border color red
1627          #   $str .= "0.5 0.75 1.0 rg\n";           # fill color blue
1628     $str .= "1.0 1.0 1.0  rg\n";    # fill color white
1629
1630     $str .= "$llx $lly $urx $ury re\n";    # a rectangle
1631     $str .= "B\n";                         # fill (and a little more)
1632     $str .= "Q\n";                         # save the graphic state
1633
1634     prAdd($str);
1635
1636 }
1637
1638 END { }    # module clean-up code here (global destructor)
1639
1640 1;
1641 __END__
1642
1643 =back
1644
1645 =head1 AUTHOR
1646
1647 Mason James <mason@katipo.co.nz>
1648
1649 =cut
1650