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