Bug 2691 - LCCN split (for labels)
[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 sub split_lccn {
953     my ($lccn) = @_;    
954     $_ = $lccn;
955     # lccn examples: 'HE8700.7 .P6T44 1983', 'BS2545.E8 H39 1996';
956     my (@parts) = m/
957         ^([a-zA-Z]+)      # HE          # BS
958         (\d+(?:\.\d)*)    # 8700.7      # 2545
959         \s*
960         (\.*\D+\d*)       # .P6         # .E8
961         \s*
962         (.*)              # T44 1983    # H39 1996   # everything else (except any trailing spaces)
963         \s*
964         /x;
965     push @parts, split /\s+/, pop @parts;   # split the last piece into an arbitrary number of pieces at spaces
966     $debug and print STDERR "split_lccn array: ", join(" | ", @parts), "\n";
967     return @parts;
968 }
969
970 sub split_ddcn {
971     my ($ddcn) = @_;
972     $ddcn =~ s/\///g;   # in theory we should be able to simply remove all segmentation markers and arrive at the correct call number...
973     $_ = $ddcn;
974     # ddcn example R220.3 H2793Z H32 c.2
975     my @splits = m/^([A-Z]{0,3})                # R (OS, REF, etc. up do three letters)
976                     ([0-9]+\.[0-9]*)            # 220.3
977                     \s?                         # space (not requiring anything beyond the call number)
978                     ([a-zA-Z0-9]*\.?[a-zA-Z0-9])# cutter number... maybe, but if so it is in this position (Z indicates literary criticism)
979                     \s?                         # space if it exists
980                     ([a-zA-Z]*\.?[0-9]*)        # other indicators such as cutter for author of literary criticism in this example if it exists
981                     \s?                         # space if ie exists
982                     ([a-zA-Z]*\.?[0-9]*)        # other indicators such as volume number, copy number, edition date, etc. if it exists
983                     /x;
984     return @splits;
985 }
986
987 sub split_fcn {
988     my ($fcn) = @_;
989     my @fcn_split = ();
990     # Split fiction call numbers based on spaces
991     SPLIT_FCN:
992     while ($fcn) {
993         if ($fcn =~ m/([A-Za-z0-9]+\.?[0-9]?)(\W?).*?/x) {
994             push (@fcn_split, $1);
995             $fcn = $';
996         }
997         else {
998             last SPLIT_FCN;     # No match, break out of the loop
999         }
1000     }
1001     return @fcn_split;
1002 }
1003
1004 sub DrawSpineText {
1005
1006     my ( $x_pos, $y_pos, $label_height, $label_width, $fontname, $fontsize, $left_text_margin,
1007         $text_wrap_cols, $item, $conf_data, $printingtype ) = @_;
1008     
1009     # Replaced item's itemtype with the more user-friendly description...
1010     my $dbh = C4::Context->dbh;
1011     my $sth = $dbh->prepare("SELECT itemtype,description FROM itemtypes");
1012     $sth->execute();
1013     while ( my $data = $sth->fetchrow_hashref ) {
1014         $$item->{'itemtype'} = $data->{'description'} if ($$item->{'itemtype'} eq $data->{'itemtype'});
1015         $$item->{'itype'} = $data->{'description'} if ($$item->{'itype'} eq $data->{'itemtype'});
1016     }
1017
1018     my $str = '';
1019
1020     my $top_text_margin = ( $fontsize + 3 );    #FIXME: This should be a template parameter and passed in...
1021     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.).
1022
1023     my $layout_id = $$conf_data->{'id'};
1024
1025     my $vPos = ( $y_pos + ( $label_height - $top_text_margin ) );
1026
1027     my @str_fields = get_text_fields($layout_id, 'codes' );  
1028     my $record = GetMarcBiblio($$item->{biblionumber});
1029     # FIXME - returns all items, so you can't get data from an embedded holdings field.
1030     # TODO - add a GetMarcBiblio1item(bibnum,itemnum) or a GetMarcItem(itemnum).
1031
1032     my $old_fontname = $fontname; # We need to keep track of the original font passed in...
1033
1034     # Grab the cn_source and if that is NULL, the DefaultClassificationSource syspref
1035     my $cn_source = ($$item->{'cn_source'} ? $$item->{'cn_source'} : C4::Context->preference('DefaultClassificationSource'));
1036     for my $field (@str_fields) {
1037         $field->{'code'} or warn "get_text_fields($layout_id, 'codes') element missing 'code' field";
1038         if ($field->{'code'} eq 'itemtype') {
1039             $field->{'data'} = C4::Context->preference('item-level_itypes') ? $$item->{'itype'} : $$item->{'itemtype'};
1040         }
1041         elsif ($$conf_data->{'formatstring'}) {
1042             # if labels_conf.formatstring has a value, then it overrides the  hardcoded option.
1043             $field->{'data'} =  GetBarcodeData($field->{'code'},$$item,$record) ;
1044         }
1045         else {
1046             $field->{data} =   $$item->{$field->{'code'}}  ;
1047         }
1048         # This allows us to print the title in italic (oblique) type... (Times Roman has a different nomenclature.)
1049         # It seems there should be a better way to handle fonts in the label/patron card tool altogether -fbcit
1050         ($field->{code} eq 'title') ? (($old_fontname =~ /T/) ? ($fontname = 'TI') : ($fontname = ($old_fontname . 'O'))) : ($fontname = $old_fontname);
1051         my $font = prFont($fontname);
1052         # if the display option for this field is selected in the DB,
1053         # and the item record has some values for this field, display it.
1054         # Or if there is a csv list of fields to display, display them.
1055         if ( ($$conf_data->{'formatstring'}) || ( $$conf_data->{$field->{code}} && $$item->{$field->{code}} ) ) {
1056             # get the string
1057             my $str = $field->{data} ;
1058             # strip out naughty existing nl/cr's
1059             $str =~ s/\n//g;
1060             $str =~ s/\r//g;
1061             my @strings;
1062             my @callnumber_list = ('itemcallnumber', '050a', '050b', '082a', '952o'); # Fields which hold call number data  ( 060? 090? 092? 099? )
1063             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
1064                 if ($cn_source eq 'lcc') {
1065                     @strings = split_lccn($str);
1066                     @strings = split_fcn($str) if !@strings;    # If it was not a true lccn, try it as a fiction call number
1067                     push (@strings, $str) if !@strings;         # If it was not that, send it on unsplit
1068                 } elsif ($cn_source eq 'ddc') {
1069                     @strings = split_ddcn($str);
1070                     @strings = split_fcn($str) if !@strings;
1071                     push (@strings, $str) if !@strings;
1072                 } else {
1073                     # FIXME Need error trapping here; something to be informative to the user perhaps -crn
1074                     push @strings, $str;
1075                 }
1076             } else {
1077                 $str =~ s/\/$//g;       # Here we will strip out all trailing '/' in fields other than the call number...
1078                 $str =~ s/\(/\\\(/g;    # Escape '(' and ')' for the postscript stream...
1079                 $str =~ s/\)/\\\)/g;
1080                 # Wrap text lines exceeding $text_wrap_cols length...
1081                 $Text::Wrap::columns = $text_wrap_cols;
1082                 my @line = split(/\n/ ,wrap('', '', $str));
1083                 # If this is a title field, limit to two lines; all others limit to one...
1084                 if ($field->{code} eq 'title' && scalar(@line) >= 2) {
1085                     while (scalar(@line) > 2) {
1086                         pop @line;
1087                     }
1088                 } else {
1089                     while (scalar(@line) > 1) {
1090                         pop @line;
1091                     }
1092                 }
1093                 push(@strings, @line);
1094             }
1095             # loop for each string line
1096             foreach my $str (@strings) {
1097                 next if $str eq '';
1098                 my $hPos = 0;
1099                 my $stringwidth = prStrWidth($str, $fontname, $fontsize);
1100                 if ( $$conf_data->{'text_justify'} eq 'R' ) { 
1101                     $hPos = $x_pos + $label_width - ( $left_text_margin + $stringwidth );
1102                 } elsif($$conf_data->{'text_justify'} eq 'C') {
1103                      # some code to try and center each line on the label based on font size and string point width...
1104                      my $whitespace = ( $label_width - ( $stringwidth + (2 * $left_text_margin) ) );
1105                      $hPos = ( ( $whitespace  / 2 ) + $x_pos + $left_text_margin );
1106                 #warn "\$label_width=$label_width \$stringwidth=$stringwidth \$whitespace=$whitespace \$left_text_margin=$left_text_margin for $str\n";
1107                 } else {
1108                     $hPos = ( $x_pos + $left_text_margin );
1109                 }
1110                 PrintText( $hPos, $vPos, $font, $fontsize, $str );
1111                 $vPos = $vPos - $line_spacer;
1112             }
1113         }
1114     }   #foreach field
1115 }
1116
1117 sub PrintText {
1118     my ( $hPos, $vPos, $font, $fontsize, $text ) = @_;
1119     my $str = "BT /$font $fontsize Tf $hPos $vPos Td ($text) Tj ET";
1120     prAdd($str);
1121 }
1122
1123 sub DrawPatronCardText {
1124
1125     my ( $x_pos, $y_pos, $label_height, $label_width, $fontname, $fontsize, $left_text_margin,
1126         $text_wrap_cols, $text, $printingtype )
1127       = @_;
1128
1129     my $top_text_margin = 25;    #FIXME: This should be a template parameter and passed in...
1130
1131     my $vPos   = ( $y_pos + ( $label_height - $top_text_margin ) );
1132     my $font = prFont($fontname);
1133
1134     my $hPos = 0;
1135
1136     foreach my $line (keys %$text) {
1137         $debug and warn "Current text is \"$line\" and font size for \"$line\" is $text->{$line} points";
1138         # some code to try and center each line on the label based on font size and string point width...
1139         my $stringwidth = prStrWidth($line, $fontname, $text->{$line});
1140         my $whitespace = ( $label_width - ( $stringwidth + (2 * $left_text_margin) ) );
1141         $hPos = ( ( $whitespace  / 2 ) + $x_pos + $left_text_margin );
1142
1143         PrintText( $hPos, $vPos, $font, $text->{$line}, $line );
1144         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.).
1145         $vPos = $vPos - ($line_spacer + $text->{$line});   # Linefeed equiv: leading + font size
1146     }
1147 }
1148
1149 # Not used anywhere.
1150
1151 #sub SetFontSize {
1152 #
1153 #    my ($fontsize) = @_;
1154 #### fontsize
1155 #    my $str = "BT/F13 30 Tf288 720 Td( AAAAAAAAAA ) TjET";
1156 #    prAdd($str);
1157 #}
1158
1159 sub DrawBarcode {
1160
1161     # x and y are from the top-left :)
1162     my ( $x_pos, $y_pos, $height, $width, $barcode, $barcodetype ) = @_;
1163     my $num_of_bars = length($barcode);
1164     my $bar_width   = $width * .8;        # %80 of length of label width
1165     my $tot_bar_length = 0;
1166     my $bar_length = 0;
1167     my $guard_length = 10;
1168     my $xsize_ratio = 0;
1169
1170     if ( $barcodetype eq 'CODE39' ) {
1171         $bar_length = '17.5';
1172         $tot_bar_length =
1173           ( $bar_length * $num_of_bars ) + ( $guard_length * 2 );
1174         $xsize_ratio = ( $bar_width / $tot_bar_length );
1175         eval {
1176             PDF::Reuse::Barcode::Code39(
1177                 x => ( $x_pos + ( $width / 10 ) ),
1178                 y => ( $y_pos + ( $height / 10 ) ),
1179                 value         => "*$barcode*",
1180                 ySize         => ( .02 * $height ),
1181                 xSize         => $xsize_ratio,
1182                 hide_asterisk => 1,
1183             );
1184         };
1185         if ($@) {
1186             warn "$barcodetype, $barcode FAILED:$@";
1187         }
1188     }
1189
1190     elsif ( $barcodetype eq 'CODE39MOD' ) {
1191
1192         # get modulo43 checksum
1193         my $c39 = CheckDigits('code_39');
1194         $barcode = $c39->complete($barcode);
1195
1196         $bar_length = '19';
1197         $tot_bar_length =
1198           ( $bar_length * $num_of_bars ) + ( $guard_length * 2 );
1199         $xsize_ratio = ( $bar_width / $tot_bar_length );
1200         eval {
1201             PDF::Reuse::Barcode::Code39(
1202                 x => ( $x_pos + ( $width / 10 ) ),
1203                 y => ( $y_pos + ( $height / 10 ) ),
1204                 value         => "*$barcode*",
1205                 ySize         => ( .02 * $height ),
1206                 xSize         => $xsize_ratio,
1207                 hide_asterisk => 1,
1208             );
1209         };
1210
1211         if ($@) {
1212             warn "$barcodetype, $barcode FAILED:$@";
1213         }
1214     }
1215     elsif ( $barcodetype eq 'CODE39MOD10' ) {
1216  
1217         # get modulo43 checksum
1218         my $c39_10 = CheckDigits('visa');
1219         $barcode = $c39_10->complete($barcode);
1220
1221         $bar_length = '19';
1222         $tot_bar_length =
1223           ( $bar_length * $num_of_bars ) + ( $guard_length * 2 );
1224         $xsize_ratio = ( $bar_width / $tot_bar_length );
1225         eval {
1226             PDF::Reuse::Barcode::Code39(
1227                 x => ( $x_pos + ( $width / 10 ) ),
1228                 y => ( $y_pos + ( $height / 10 ) ),
1229                 value         => "*$barcode*",
1230                 ySize         => ( .02 * $height ),
1231                 xSize         => $xsize_ratio,
1232                 hide_asterisk => 1,
1233                                 text         => 0, 
1234             );
1235         };
1236
1237         if ($@) {
1238             warn "$barcodetype, $barcode FAILED:$@";
1239         }
1240     }
1241
1242  
1243     elsif ( $barcodetype eq 'COOP2OF5' ) {
1244         $bar_length = '9.43333333333333';
1245         $tot_bar_length =
1246           ( $bar_length * $num_of_bars ) + ( $guard_length * 2 );
1247         $xsize_ratio = ( $bar_width / $tot_bar_length ) * .9;
1248         eval {
1249             PDF::Reuse::Barcode::COOP2of5(
1250                 x => ( $x_pos + ( $width / 10 ) ),
1251                 y => ( $y_pos + ( $height / 10 ) ),
1252                 value => $barcode,
1253                 ySize => ( .02 * $height ),
1254                 xSize => $xsize_ratio,
1255             );
1256         };
1257         if ($@) {
1258             warn "$barcodetype, $barcode FAILED:$@";
1259         }
1260     }
1261
1262     elsif ( $barcodetype eq 'INDUSTRIAL2OF5' ) {
1263         $bar_length = '13.1333333333333';
1264         $tot_bar_length =
1265           ( $bar_length * $num_of_bars ) + ( $guard_length * 2 );
1266         $xsize_ratio = ( $bar_width / $tot_bar_length ) * .9;
1267         eval {
1268             PDF::Reuse::Barcode::Industrial2of5(
1269                 x => ( $x_pos + ( $width / 10 ) ),
1270                 y => ( $y_pos + ( $height / 10 ) ),
1271                 value => $barcode,
1272                 ySize => ( .02 * $height ),
1273                 xSize => $xsize_ratio,
1274             );
1275         };
1276         if ($@) {
1277             warn "$barcodetype, $barcode FAILED:$@";
1278         }
1279     }
1280
1281     my $moo2 = $tot_bar_length * $xsize_ratio;
1282
1283     warn "$x_pos, $y_pos, $barcode, $barcodetype" if $debug;
1284     warn "BAR_WDTH = $bar_width, TOT.BAR.LGHT=$tot_bar_length  R*TOT.BAR =$moo2" if $debug;
1285 }
1286
1287 =head2 build_circ_barcode;
1288
1289   build_circ_barcode( $x_pos, $y_pos, $barcode,
1290                 $barcodetype, \$item);
1291
1292 $item is the result of a previous call to GetLabelItems();
1293
1294 =cut
1295
1296 #'
1297 sub build_circ_barcode {
1298     my ( $x_pos_circ, $y_pos, $value, $barcodetype, $item ) = @_;
1299
1300     #warn Dumper \$item;
1301
1302     #warn "value = $value\n";
1303
1304     #$DB::single = 1;
1305
1306     if ( $barcodetype eq 'EAN13' ) {
1307
1308         #testing EAN13 barcodes hack
1309         $value = $value . '000000000';
1310         $value =~ s/-//;
1311         $value = substr( $value, 0, 12 );
1312
1313         #warn $value;
1314         eval {
1315             PDF::Reuse::Barcode::EAN13(
1316                 x     => ( $x_pos_circ + 27 ),
1317                 y     => ( $y_pos + 15 ),
1318                 value => $value,
1319
1320                 #            prolong => 2.96,
1321                 #            xSize   => 1.5,
1322
1323                 # ySize   => 1.2,
1324
1325 # added for xpdf compat. doesnt use type3 fonts., but increases filesize from 20k to 200k
1326 # i think its embedding extra fonts in the pdf file.
1327 #  mode => 'graphic',
1328             );
1329         };
1330         if ($@) {
1331             $item->{'barcodeerror'} = 1;
1332
1333             #warn "EAN13BARCODE FAILED:$@";
1334         }
1335
1336         #warn $barcodetype;
1337
1338     }
1339     elsif ( $barcodetype eq 'Code39' ) {
1340
1341         eval {
1342             PDF::Reuse::Barcode::Code39(
1343                 x     => ( $x_pos_circ + 9 ),
1344                 y     => ( $y_pos + 15 ),
1345                 value => $value,
1346
1347                 #           prolong => 2.96,
1348                 xSize => .85,
1349
1350                 ySize => 1.3,
1351             );
1352         };
1353         if ($@) {
1354             $item->{'barcodeerror'} = 1;
1355
1356             #warn "CODE39BARCODE $value FAILED:$@";
1357         }
1358
1359         #warn $barcodetype;
1360
1361     }
1362
1363     elsif ( $barcodetype eq 'Matrix2of5' ) {
1364
1365         #warn "MATRIX ELSE:";
1366
1367         #testing MATRIX25  barcodes hack
1368         #    $value = $value.'000000000';
1369         $value =~ s/-//;
1370
1371         #    $value = substr( $value, 0, 12 );
1372         #warn $value;
1373
1374         eval {
1375             PDF::Reuse::Barcode::Matrix2of5(
1376                 x     => ( $x_pos_circ + 27 ),
1377                 y     => ( $y_pos + 15 ),
1378                 value => $value,
1379
1380                 #        prolong => 2.96,
1381                 #       xSize   => 1.5,
1382
1383                 # ySize   => 1.2,
1384             );
1385         };
1386         if ($@) {
1387             $item->{'barcodeerror'} = 1;
1388
1389             #warn "BARCODE FAILED:$@";
1390         }
1391
1392         #warn $barcodetype;
1393
1394     }
1395
1396     elsif ( $barcodetype eq 'EAN8' ) {
1397
1398         #testing ean8 barcodes hack
1399         $value = $value . '000000000';
1400         $value =~ s/-//;
1401         $value = substr( $value, 0, 8 );
1402
1403         #warn $value;
1404
1405         #warn "EAN8 ELSEIF";
1406         eval {
1407             PDF::Reuse::Barcode::EAN8(
1408                 x       => ( $x_pos_circ + 42 ),
1409                 y       => ( $y_pos + 15 ),
1410                 value   => $value,
1411                 prolong => 2.96,
1412                 xSize   => 1.5,
1413
1414                 # ySize   => 1.2,
1415             );
1416         };
1417
1418         if ($@) {
1419             $item->{'barcodeerror'} = 1;
1420
1421             #warn "BARCODE FAILED:$@";
1422         }
1423
1424         #warn $barcodetype;
1425
1426     }
1427
1428     elsif ( $barcodetype eq 'UPC-E' ) {
1429         eval {
1430             PDF::Reuse::Barcode::UPCE(
1431                 x       => ( $x_pos_circ + 27 ),
1432                 y       => ( $y_pos + 15 ),
1433                 value   => $value,
1434                 prolong => 2.96,
1435                 xSize   => 1.5,
1436
1437                 # ySize   => 1.2,
1438             );
1439         };
1440
1441         if ($@) {
1442             $item->{'barcodeerror'} = 1;
1443
1444             #warn "BARCODE FAILED:$@";
1445         }
1446
1447         #warn $barcodetype;
1448
1449     }
1450     elsif ( $barcodetype eq 'NW7' ) {
1451         eval {
1452             PDF::Reuse::Barcode::NW7(
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 'ITF' ) {
1473         eval {
1474             PDF::Reuse::Barcode::ITF(
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 'Industrial2of5' ) {
1495         eval {
1496             PDF::Reuse::Barcode::Industrial2of5(
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         if ($@) {
1507             $item->{'barcodeerror'} = 1;
1508
1509             #warn "BARCODE FAILED:$@";
1510         }
1511
1512         #warn $barcodetype;
1513
1514     }
1515     elsif ( $barcodetype eq 'IATA2of5' ) {
1516         eval {
1517             PDF::Reuse::Barcode::IATA2of5(
1518                 x       => ( $x_pos_circ + 27 ),
1519                 y       => ( $y_pos + 15 ),
1520                 value   => $value,
1521                 prolong => 2.96,
1522                 xSize   => 1.5,
1523
1524                 # ySize   => 1.2,
1525             );
1526         };
1527         if ($@) {
1528             $item->{'barcodeerror'} = 1;
1529
1530             #warn "BARCODE FAILED:$@";
1531         }
1532
1533         #warn $barcodetype;
1534
1535     }
1536
1537     elsif ( $barcodetype eq 'COOP2of5' ) {
1538         eval {
1539             PDF::Reuse::Barcode::COOP2of5(
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     elsif ( $barcodetype eq 'UPC-A' ) {
1559
1560         eval {
1561             PDF::Reuse::Barcode::UPCA(
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
1581 }
1582
1583 =head2 draw_boundaries
1584
1585  sub draw_boundaries ($x_pos_spine, $x_pos_circ1, $x_pos_circ2,
1586                 $y_pos, $spine_width, $label_height, $circ_width)  
1587
1588 This sub draws boundary lines where the label outlines are, to aid in printer testing, and debugging.
1589
1590 =cut
1591
1592 #'
1593 sub draw_boundaries {
1594
1595     my (
1596         $x_pos_spine, $x_pos_circ1,  $x_pos_circ2, $y_pos,
1597         $spine_width, $label_height, $circ_width
1598     ) = @_;
1599
1600     my $y_pos_initial = ( ( 792 - 36 ) - 90 );
1601     $y_pos            = $y_pos_initial; # FIXME - why are we ignoring the y_pos parameter by redefining it?
1602     my $i             = 1;
1603
1604     for ( $i = 1 ; $i <= 8 ; $i++ ) {
1605
1606         &drawbox( $x_pos_spine, $y_pos, ($spine_width), ($label_height) );
1607
1608    #warn "OLD BOXES  x=$x_pos_spine, y=$y_pos, w=$spine_width, h=$label_height";
1609         &drawbox( $x_pos_circ1, $y_pos, ($circ_width), ($label_height) );
1610         &drawbox( $x_pos_circ2, $y_pos, ($circ_width), ($label_height) );
1611
1612         $y_pos = ( $y_pos - $label_height );
1613
1614     }
1615 }
1616
1617 =head2 drawbox
1618
1619         sub drawbox {   $lower_left_x, $lower_left_y, 
1620                         $upper_right_x, $upper_right_y )
1621
1622 this is a low level sub, that draws a pdf box, it is called by draw_boxes
1623
1624 FYI: the  $upper_right_x and $upper_right_y values are RELATIVE to  $lower_left_x and $lower_left_y
1625
1626 and $lower_left_x, $lower_left_y are ABSOLUTE, this caught me out!
1627
1628 =cut
1629
1630 #'
1631 sub drawbox {
1632     my ( $llx, $lly, $urx, $ury ) = @_;
1633
1634     #    warn "llx,y= $llx,$lly  ,   urx,y=$urx,$ury \n";
1635
1636     my $str = "q\n";    # save the graphic state
1637     $str .= "0.5 w\n";              # border color red
1638     $str .= "1.0 0.0 0.0  RG\n";    # border color red
1639          #   $str .= "0.5 0.75 1.0 rg\n";           # fill color blue
1640     $str .= "1.0 1.0 1.0  rg\n";    # fill color white
1641
1642     $str .= "$llx $lly $urx $ury re\n";    # a rectangle
1643     $str .= "B\n";                         # fill (and a little more)
1644     $str .= "Q\n";                         # save the graphic state
1645
1646     prAdd($str);
1647
1648 }
1649
1650 END { }    # module clean-up code here (global destructor)
1651
1652 1;
1653 __END__
1654
1655 =head1 AUTHOR
1656
1657 Mason James <mason@katipo.co.nz>
1658
1659 =cut
1660