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