3 # Copyright 2006 Katipo Communications.
5 # This file is part of Koha.
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
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.
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
21 use vars qw($VERSION @ISA @EXPORT);
25 use Algorithm::CheckDigits;
32 # use Smart::Comments;
39 &get_label_options &GetLabelItems
40 &build_circ_barcode &draw_boundaries
41 &drawbox &GetActiveLabelTemplate
42 &GetAllLabelTemplates &DeleteTemplate
43 &GetSingleLabelTemplate &SaveTemplate
44 &CreateTemplate &SetActiveTemplate
45 &SaveConf &DrawSpineText &GetTextWrapCols
46 &GetUnitsValue &DrawBarcode &DrawPatronCardText
47 &get_printingtypes &GetPatronCardItems
50 &get_batches &delete_batch
54 get_layout &save_layout &add_layout
57 &delete_layout &get_active_layout
60 &GetAllPrinterProfiles &GetSinglePrinterProfile
61 &SaveProfile &CreateProfile &DeleteProfile
62 &GetAssociatedProfile &SetAssociatedProfile
69 C4::Labels - Functions for printing spine labels and barcodes in Koha
75 =item get_label_options;
77 $options = get_label_options()
79 Return a pointer on a hash list containing info from labels_conf table in Koha DB.
84 sub get_label_options {
85 my $dbh = C4::Context->dbh;
86 my $query2 = " SELECT * FROM labels_conf where active = 1";
87 my $sth = $dbh->prepare($query2);
89 my $conf_data = $sth->fetchrow_hashref;
96 ## FIXME: this if/else could be compacted...
97 my $dbh = C4::Context->dbh;
99 my $query = " Select * from labels_conf";
100 my $sth = $dbh->prepare($query);
103 while ( my $data = $sth->fetchrow_hashref ) {
105 $data->{'fieldlist'} = get_text_fields( $data->{'id'} );
106 push( @resultsloop, $data );
116 my ($layout_id) = @_;
117 my $dbh = C4::Context->dbh;
119 # get the actual items to be printed.
120 my $query = " Select * from labels_conf where id = ?";
121 my $sth = $dbh->prepare($query);
122 $sth->execute($layout_id);
123 my $data = $sth->fetchrow_hashref;
128 sub get_active_layout {
129 my ($layout_id) = @_;
130 my $dbh = C4::Context->dbh;
132 # get the actual items to be printed.
133 my $query = " Select * from labels_conf where active = 1";
134 my $sth = $dbh->prepare($query);
136 my $data = $sth->fetchrow_hashref;
142 my ($layout_id) = @_;
143 my $dbh = C4::Context->dbh;
145 # get the actual items to be printed.
146 my $query = "delete from labels_conf where id = ?";
147 my $sth = $dbh->prepare($query);
148 $sth->execute($layout_id);
152 sub get_printingtypes {
153 my ($layout_id) = @_;
155 # FIXME: hard coded print types
156 push( @printtypes, { code => 'BAR', desc => "barcode only" } );
157 push( @printtypes, { code => 'BIB', desc => "biblio only" } );
158 push( @printtypes, { code => 'BARBIB', desc => "barcode / biblio" } );
159 push( @printtypes, { code => 'BIBBAR', desc => "biblio / barcode" } );
160 push( @printtypes, { code => 'ALT', desc => "alternating labels" } );
161 push( @printtypes, { code => 'CSV', desc => "csv output" } );
162 push( @printtypes, { code => 'PATCRD', desc => "patron cards" } );
164 my $conf = get_layout($layout_id);
165 my $active_printtype = $conf->{'printingtype'};
167 # lop thru layout, insert selected to hash
169 foreach my $printtype (@printtypes) {
170 if ( $printtype->{'code'} eq $active_printtype ) {
171 $printtype->{'active'} = 1;
177 # this sub (build_text_dropbox) is deprecated and should be deleted.
180 sub build_text_dropbox {
183 # my @fields = get_text_fields();
184 # my $field_count = scalar @fields;
185 my $field_count = 10; # <----------- FIXME hard coded
189 ? push( @lines, { num => '', selected => '1' } )
190 : push( @lines, { num => '' } );
191 for ( my $i = 1 ; $i <= $field_count ; $i++ ) {
192 my $line = { num => "$i" };
193 $line->{'selected'} = 1 if $i eq $order;
194 push( @lines, $line );
197 # add a blank row too
202 sub get_text_fields {
203 my ($layout_id, $sorttype) = @_;
206 my $sortorder = get_layout($layout_id);
207 if( $sortorder->{formatstring}) {
209 return $sortorder->{formatstring} ;
211 my $csv = Text::CSV_XS->new( { allow_whitespace => 1 } ) ;
212 my $line= $sortorder->{formatstring} ;
213 my $status = $csv->parse( $line );
214 @sorted_fields = map {{ 'code' => $_ , desc => $_ } } $csv->fields() ;
215 $error = $csv->error_input();
216 warn $error if $error ; # TODO - do more with this.
219 # These fields are hardcoded based on the template for label-edit-layout.pl
224 order => $sortorder->{'itemtype'}
229 order => $sortorder->{'dewey'}
234 order => $sortorder->{'issn'}
239 order => $sortorder->{'isbn'}
243 desc => "Classification",
244 order => $sortorder->{'class'}
249 order => $sortorder->{'subclass'}
254 order => $sortorder->{'barcode'}
259 order => $sortorder->{'author'}
264 order => $sortorder->{'title'}
267 code => 'itemcallnumber',
268 desc => "Call Number",
269 order => $sortorder->{'itemcallnumber'}
274 order => $sortorder->{'subtitle'}
280 foreach my $field (@text_fields) {
281 push( @new_fields, $field ) if $field->{'order'} > 0;
284 @sorted_fields = sort { $$a{order} <=> $$b{order} } @new_fields;
286 # if we have a 'formatstring', then we ignore these hardcoded fields.
289 if ($sorttype eq 'codes') { # FIXME: This sub should really always return the array of hashrefs and let the caller take what he wants from that -fbcit
290 return @sorted_fields;
292 foreach my $field (@sorted_fields) {
293 $active_fields .= "$field->{'desc'} ";
295 return $active_fields;
302 add_batch($batch_type,\@batch_list);
303 if $batch_list is supplied,
304 create a new batch with those items.
305 else, return the next available batch_id.
309 my ( $batch_type,$batch_list ) = @_;
311 my $dbh = C4::Context->dbh;
312 my $q ="SELECT MAX(DISTINCT batch_id) FROM $batch_type";
313 my $sth = $dbh->prepare($q);
315 my ($batch_id) = $sth->fetchrow_array;
322 # TODO: let this block use $batch_type
323 if(ref($batch_list) && ($batch_type eq 'labels') ) {
324 my $sth = $dbh->prepare("INSERT INTO labels (`batch_id`,`itemnumber`) VALUES (?,?)");
325 for my $item (@$batch_list) {
326 $sth->execute($batch_id,$item);
332 #FIXME: Needs to be ported to receive $batch_type
333 # ... this looks eerily like add_batch() ...
334 sub get_highest_batch {
336 my $dbh = C4::Context->dbh;
338 "select distinct batch_id from labels order by batch_id desc limit 1";
339 my $sth = $dbh->prepare($q);
341 my $data = $sth->fetchrow_hashref;
344 if ( !$data->{'batch_id'} ) {
348 $new_batch = $data->{'batch_id'};
356 # my $q = "SELECT batch_id, COUNT(*) AS num FROM " . shift . " GROUP BY batch_id";
357 # FIXEDME: There is only ONE table with batch_id, so why try to select a different one?
358 # get_batches() was frequently being called w/ no args, crashing DBD
359 my $q = "SELECT batch_id, COUNT(*) AS num FROM labels GROUP BY batch_id";
360 my $sth = C4::Context->dbh->prepare($q);
362 my $batches = $sth->fetchall_arrayref({});
367 my ($batch_id, $batch_type) = @_;
368 warn "Deleteing batch of type $batch_type";
369 my $dbh = C4::Context->dbh;
370 my $q = "DELETE FROM $batch_type WHERE batch_id = ?";
371 my $sth = $dbh->prepare($q);
372 $sth->execute($batch_id);
376 sub get_barcode_types {
377 my ($layout_id) = @_;
378 my $layout = get_layout($layout_id);
379 my $barcode = $layout->{'barcodetype'};
382 push( @array, { code => 'CODE39', desc => 'Code 39' } );
383 push( @array, { code => 'CODE39MOD', desc => 'Code39 + Modulo43' } );
384 push( @array, { code => 'CODE39MOD10', desc => 'Code39 + Modulo10' } );
385 push( @array, { code => 'ITF', desc => 'Interleaved 2 of 5' } );
387 foreach my $line (@array) {
388 if ( $line->{'code'} eq $barcode ) {
389 $line->{'active'} = 1;
400 $unitvalue = '1' if ( $units eq 'POINT' );
401 $unitvalue = '2.83464567' if ( $units eq 'MM' );
402 $unitvalue = '28.3464567' if ( $units eq 'CM' );
403 $unitvalue = 72 if ( $units eq 'INCH' );
407 sub GetTextWrapCols {
408 my ( $font, $fontsize, $label_width, $left_text_margin ) = @_;
412 # my $textlimit = $label_width - ($left_text_margin);
413 my $textlimit = $label_width - ( 3 * $left_text_margin);
415 while ( $strwidth < $textlimit ) {
416 $strwidth = prStrWidth( $string, $font, $fontsize );
417 $string = $string . '0';
418 #warn "strwidth:$strwidth, textlimit:$textlimit, count:$count string:$string";
424 sub GetActiveLabelTemplate {
425 my $dbh = C4::Context->dbh;
426 my $query = " SELECT * FROM labels_templates where active = 1 limit 1";
427 my $sth = $dbh->prepare($query);
429 my $active_tmpl = $sth->fetchrow_hashref;
434 sub GetSingleLabelTemplate {
436 my $dbh = C4::Context->dbh;
437 my $query = " SELECT * FROM labels_templates where tmpl_id = ?";
438 my $sth = $dbh->prepare($query);
439 $sth->execute($tmpl_id);
440 my $template = $sth->fetchrow_hashref;
445 sub SetActiveTemplate {
449 my $dbh = C4::Context->dbh;
450 my $query = " UPDATE labels_templates SET active = NULL";
451 my $sth = $dbh->prepare($query);
454 $query = "UPDATE labels_templates SET active = 1 WHERE tmpl_id = ?";
455 $sth = $dbh->prepare($query);
456 $sth->execute($tmpl_id);
460 sub set_active_layout {
462 my ($layout_id) = @_;
463 my $dbh = C4::Context->dbh;
464 my $query = " UPDATE labels_conf SET active = NULL";
465 my $sth = $dbh->prepare($query);
468 $query = "UPDATE labels_conf SET active = 1 WHERE id = ?";
469 $sth = $dbh->prepare($query);
470 $sth->execute($layout_id);
476 my $dbh = C4::Context->dbh;
477 my $query = " DELETE FROM labels_templates where tmpl_id = ?";
478 my $sth = $dbh->prepare($query);
479 $sth->execute($tmpl_id);
485 $tmpl_id, $tmpl_code, $tmpl_desc, $page_width,
486 $page_height, $label_width, $label_height, $topmargin,
487 $leftmargin, $cols, $rows, $colgap,
488 $rowgap, $font, $fontsize, $units
490 warn "Passed \$font:$font";
491 my $dbh = C4::Context->dbh;
493 " UPDATE labels_templates SET tmpl_code=?, tmpl_desc=?, page_width=?,
494 page_height=?, label_width=?, label_height=?, topmargin=?,
495 leftmargin=?, cols=?, rows=?, colgap=?, rowgap=?, font=?, fontsize=?,
499 my $sth = $dbh->prepare($query);
501 $tmpl_code, $tmpl_desc, $page_width, $page_height,
502 $label_width, $label_height, $topmargin, $leftmargin,
503 $cols, $rows, $colgap, $rowgap,
504 $font, $fontsize, $units, $tmpl_id
506 my $dberror = $sth->errstr;
514 $tmpl_code, $tmpl_desc, $page_width, $page_height,
515 $label_width, $label_height, $topmargin, $leftmargin,
516 $cols, $rows, $colgap, $rowgap,
517 $font, $fontsize, $units
520 my $dbh = C4::Context->dbh;
522 my $query = "INSERT INTO labels_templates (tmpl_code, tmpl_desc, page_width,
523 page_height, label_width, label_height, topmargin,
524 leftmargin, cols, rows, colgap, rowgap, font, fontsize, units)
525 VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
527 my $sth = $dbh->prepare($query);
529 $tmpl_code, $tmpl_desc, $page_width, $page_height,
530 $label_width, $label_height, $topmargin, $leftmargin,
531 $cols, $rows, $colgap, $rowgap,
532 $font, $fontsize, $units
534 my $dberror = $sth->errstr;
539 sub GetAllLabelTemplates {
540 my $dbh = C4::Context->dbh;
542 # get the actual items to be printed.
544 my $query = " Select * from labels_templates ";
545 my $sth = $dbh->prepare($query);
548 while ( my $data = $sth->fetchrow_hashref ) {
549 push( @resultsloop, $data );
553 #warn Dumper @resultsloop;
561 $barcodetype, $title, $subtitle, $isbn, $issn,
562 $itemtype, $bcn, $dcn, $classif,
563 $subclass, $itemcallnumber, $author, $tmpl_id,
564 $printingtype, $guidebox, $startlabel, $layoutname, $formatstring
567 my $dbh = C4::Context->dbh;
568 my $query2 = "update labels_conf set active = NULL";
569 my $sth2 = $dbh->prepare($query2);
571 $query2 = "INSERT INTO labels_conf
572 ( barcodetype, title, subtitle, isbn,issn, itemtype, barcode,
573 dewey, classification, subclass, itemcallnumber, author, printingtype,
574 guidebox, startlabel, layoutname, formatstring, active )
575 values ( ?, ?,?, ?, ?, ?, ?, ?, ?,?, ?, ?, ?, ?, ?,?,?, 1 )";
576 $sth2 = $dbh->prepare($query2);
578 $barcodetype, $title, $subtitle, $isbn, $issn,
580 $itemtype, $bcn, $dcn, $classif,
581 $subclass, $itemcallnumber, $author, $printingtype,
582 $guidebox, $startlabel, $layoutname, $formatstring
586 SetActiveTemplate($tmpl_id);
593 $barcodetype, $title, $subtitle, $isbn, $issn,
594 $itemtype, $bcn, $dcn, $classif,
595 $subclass, $itemcallnumber, $author, $tmpl_id,
596 $printingtype, $guidebox, $startlabel, $layoutname, $formatstring,
602 my $dbh = C4::Context->dbh;
603 my $query2 = "update labels_conf set
604 barcodetype=?, title=?, subtitle=?, isbn=?,issn=?,
605 itemtype=?, barcode=?, dewey=?, classification=?,
606 subclass=?, itemcallnumber=?, author=?, printingtype=?,
607 guidebox=?, startlabel=?, layoutname=?, formatstring=? where id = ?";
608 my $sth2 = $dbh->prepare($query2);
610 $barcodetype, $title, $subtitle, $isbn, $issn,
611 $itemtype, $bcn, $dcn, $classif,
612 $subclass, $itemcallnumber, $author, $printingtype,
613 $guidebox, $startlabel, $layoutname, $formatstring, $layout_id
620 =item GetAllPrinterProfiles;
622 @profiles = GetAllPrinterProfiles()
624 Returns an array of references-to-hash, whos keys are .....
628 sub GetAllPrinterProfiles {
630 my $dbh = C4::Context->dbh;
632 my $query = "SELECT * FROM printers_profile AS pp INNER JOIN labels_templates AS lt ON pp.tmpl_id = lt.tmpl_id; ";
633 my $sth = $dbh->prepare($query);
636 while ( my $data = $sth->fetchrow_hashref ) {
637 push( @resultsloop, $data );
644 =item GetSinglePrinterProfile;
646 $profile = GetSinglePrinterProfile()
648 Returns a hashref whos keys are...
652 sub GetSinglePrinterProfile {
654 my $dbh = C4::Context->dbh;
655 my $query = " SELECT * FROM printers_profile WHERE prof_id = ?; ";
656 my $sth = $dbh->prepare($query);
657 $sth->execute($prof_id);
658 my $template = $sth->fetchrow_hashref;
665 SaveProfile('parameters')
667 When passed a set of parameters, this function updates the given profile with the new parameters.
673 $prof_id, $offset_horz, $offset_vert, $creep_horz, $creep_vert, $units
675 my $dbh = C4::Context->dbh;
677 " UPDATE printers_profile
678 SET offset_horz=?, offset_vert=?, creep_horz=?, creep_vert=?, unit=?
680 my $sth = $dbh->prepare($query);
682 $offset_horz, $offset_vert, $creep_horz, $creep_vert, $units, $prof_id
689 CreateProfile('parameters')
691 When passed a set of parameters, this function creates a new profile containing those parameters
692 and returns any errors.
698 $prof_id, $printername, $paper_bin, $tmpl_id, $offset_horz,
699 $offset_vert, $creep_horz, $creep_vert, $units
701 my $dbh = C4::Context->dbh;
703 " INSERT INTO printers_profile (prof_id, printername, paper_bin, tmpl_id,
704 offset_horz, offset_vert, creep_horz, creep_vert, unit)
705 VALUES(?,?,?,?,?,?,?,?,?) ";
706 my $sth = $dbh->prepare($query);
708 $prof_id, $printername, $paper_bin, $tmpl_id, $offset_horz,
709 $offset_vert, $creep_horz, $creep_vert, $units
711 my $error = $sth->errstr;
718 DeleteProfile(prof_id)
720 When passed a profile id, this function deletes that profile from the database and returns any errors.
726 my $dbh = C4::Context->dbh;
727 my $query = " DELETE FROM printers_profile WHERE prof_id = ?";
728 my $sth = $dbh->prepare($query);
729 $sth->execute($prof_id);
730 my $error = $sth->errstr;
735 =item GetAssociatedProfile;
737 $assoc_prof = GetAssociatedProfile(tmpl_id)
739 When passed a template id, this function returns the parameters from the currently associated printer profile
740 in a hashref where key=fieldname and value=fieldvalue.
744 sub GetAssociatedProfile {
746 my $dbh = C4::Context->dbh;
747 # First we find out the prof_id for the associated profile...
748 my $query = "SELECT * FROM labels_profile WHERE tmpl_id = ?";
749 my $sth = $dbh->prepare($query);
750 $sth->execute($tmpl_id);
751 my $assoc_prof = $sth->fetchrow_hashref;
753 # Then we retrieve that profile and return it to the caller...
754 $assoc_prof = GetSinglePrinterProfile($assoc_prof->{'prof_id'});
758 =item SetAssociatedProfile;
760 SetAssociatedProfile($prof_id, $tmpl_id)
762 When passed both a profile id and template id, this function establishes an association between the two. No more
763 than one profile may be associated with any given template at the same time.
767 sub SetAssociatedProfile {
769 my ($prof_id, $tmpl_id) = @_;
771 my $dbh = C4::Context->dbh;
772 my $query = "INSERT INTO labels_profile (prof_id, tmpl_id) VALUES (?,?) ON DUPLICATE KEY UPDATE prof_id = ?";
773 my $sth = $dbh->prepare($query);
774 $sth->execute($prof_id, $tmpl_id, $prof_id);
780 $options = GetLabelItems()
782 Returns an array of references-to-hash, whos keys are the field from the biblio, biblioitems, items and labels tables in the Koha database.
789 my $dbh = C4::Context->dbh;
791 my @resultsloop = ();
797 my $query3 = "Select * from labels where batch_id = ? order by labelid ";
798 $sth = $dbh->prepare($query3);
799 $sth->execute($batch_id);
804 my $query3 = "Select * from labels";
805 $sth = $dbh->prepare($query3);
808 my $cnt = $sth->rows;
810 while ( my $data = $sth->fetchrow_hashref ) {
812 # lets get some summary info from each item
814 select i.*, bi.*, b.* from items i,biblioitems bi,biblio b
815 where itemnumber=? and i.biblioitemnumber=bi.biblioitemnumber and
816 bi.biblionumber=b.biblionumber";
818 my $sth1 = $dbh->prepare($query1);
819 $sth1->execute( $data->{'itemnumber'} );
821 my $data1 = $sth1->fetchrow_hashref();
822 $data1->{'labelno'} = $i1;
823 $data1->{'labelid'} = $data->{'labelid'};
824 $data1->{'batch_id'} = $batch_id;
825 $data1->{'summary'} =
826 "$data1->{'barcode'}, $data1->{'title'}, $data1->{'isbn'}";
828 push( @resultsloop, $data1 );
840 barcode title subtitle
841 dewey isbn issn author class
842 itemtype subclass itemcallnumber
851 Parse labels_conf.formatstring value
852 (one value of the csv, which has already been split)
853 and return string from koha tables or MARC record.
858 my ($f,$item,$record) = @_;
859 my $kohatables= &_descKohaTables();
862 my $match_kohatable = join('|', (@{$kohatables->{biblio}},@{$kohatables->{biblioitems}},@{$kohatables->{items}}) );
864 if( $f =~ /^'(.*)'.*/ ) {
865 # single quotes indicate a static text string.
868 } elsif ( $f =~ /^($match_kohatable).*/ ) {
869 # grep /$f/, (@$kohatables->{biblio},@$kohatables->{biblioitems},@$kohatables->{items}) ) {
870 $datastring .= $item->{$f};
872 } elsif ( $f =~ /^([0-9a-z]{3})(\w)(\W*).*/ ) {
873 $datastring .= $record->subfield($1,$2) . $3 if($record->subfield($1,$2)) ;
876 last if ( $f eq $last_f ); # failed to match
882 Return a hashref of an array of hashes,
886 sub _descKohaTables {
887 my $dbh = C4::Context->dbh();
889 for my $table ( 'biblio','biblioitems','items' ) {
890 my $sth = $dbh->column_info(undef,undef,$table,'%');
891 while (my $info = $sth->fetchrow_hashref()){
892 push @{$kohatables->{$table}} , $info->{'COLUMN_NAME'} ;
899 sub GetPatronCardItems {
901 my ( $batch_id ) = @_;
904 my $dbh = C4::Context->dbh;
905 # my $query = "SELECT * FROM patroncards WHERE batch_id = ? ORDER BY borrowernumber";
906 my $query = "SELECT * FROM patroncards WHERE batch_id = ? ORDER BY cardid";
907 my $sth = $dbh->prepare($query);
908 $sth->execute($batch_id);
910 while ( my $data = $sth->fetchrow_hashref ) {
911 my $patron_data = GetMember( $data->{'borrowernumber'} );
912 $patron_data->{'branchname'} = GetBranchName( $patron_data->{'branchcode'} );
913 $patron_data->{'cardno'} = $cardno;
914 $patron_data->{'cardid'} = $data->{'cardid'};
915 $patron_data->{'batch_id'} = $batch_id;
916 push( @resultsloop, $patron_data );
924 sub deduplicate_batch {
925 my ( $batch_id, $batch_type ) = @_;
928 batch_id," . (($batch_type eq 'labels') ? 'itemnumber' : 'borrowernumber') . ",
929 count(". (($batch_type eq 'labels') ? 'labelid' : 'cardid') . ") as count
932 GROUP BY " . (($batch_type eq 'labels') ? 'itemnumber' : 'borrowernumber') . ",batch_id
936 my $sth = C4::Context->dbh->prepare($query);
937 $sth->execute($batch_id);
938 warn $sth->errstr if $sth->errstr;
939 $sth->rows or return undef, $sth->errstr;
945 AND " . (($batch_type eq 'labels') ? 'itemnumber' : 'borrowernumber') . " = ?
946 ORDER BY timestamp ASC
949 while (my $data = $sth->fetchrow_hashref()) {
950 my $itemnumber = $data->{(($batch_type eq 'labels') ? 'itemnumber' : 'borrowernumber')} or next;
951 my $limit = $data->{count} - 1 or next;
952 my $sth2 = C4::Context->dbh->prepare("$del_query LIMIT $limit");
953 # die sprintf "$del_query LIMIT %s\n (%s, %s)", $limit, $batch_id, $itemnumber;
954 # $sth2->execute($batch_id, C4::Context->dbh->quote($data->{itemnumber}), $data->{count} - 1)
955 $sth2->execute($batch_id, $itemnumber) and
956 $killed += ($data->{count} - 1);
957 warn $sth2->errstr if $sth2->errstr;
959 return $killed, undef;
964 my ( $x_pos, $y_pos, $label_height, $label_width, $fontname, $fontsize, $left_text_margin,
965 $text_wrap_cols, $item, $conf_data, $printingtype, $nowrap ) = @_;
967 # Replaced item's itemtype with the more user-friendly description...
968 my $dbh = C4::Context->dbh;
970 my $sth = $dbh->prepare("SELECT itemtype,description FROM itemtypes");
972 while ( my $data = $sth->fetchrow_hashref ) {
973 $$item->{'itemtype'} = $data->{'description'} if ($$item->{'itemtype'} eq $data->{'itemtype'});
978 my $top_text_margin = ( $fontsize + 3 ); #FIXME: This should be a template parameter and passed in...
979 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.).
981 my $layout_id = $$conf_data->{'id'};
983 my $vPos = ( $y_pos + ( $label_height - $top_text_margin ) );
985 my @str_fields = get_text_fields($layout_id, 'codes' );
986 my $record = GetMarcBiblio($$item->{biblionumber});
987 # FIXME - returns all items, so you can't get data from an embedded holdings field.
988 # TODO - add a GetMarcBiblio1item(bibnum,itemnum) or a GetMarcItem(itemnum).
990 my $old_fontname = $fontname; # We need to keep track of the original font passed in...
992 for my $field (@str_fields) {
993 $field->{'code'} or warn "get_text_fields($layout_id, 'codes') element missing 'code' field";
994 if ($$conf_data->{'formatstring'}) {
995 $field->{'data'} = GetBarcodeData($field->{'code'},$$item,$record) ;
997 $field->{data} = $$item->{$field->{'code'}} ;
1000 # This allows us to print the title in italic (oblique) type... (Times Roman has a different nomenclature.)
1001 # It seems there should be a better way to handle fonts in the label/patron card tool altogether -fbcit
1002 ($field->{code} eq 'title') ? (($old_fontname =~ /T/) ? ($fontname = 'TI') : ($fontname = ($old_fontname . 'O'))) : ($fontname = $old_fontname);
1003 my $font = prFont($fontname);
1004 # if the display option for this field is selected in the DB,
1005 # and the item record has some values for this field, display it.
1006 if ( ($$conf_data->{'formatstring'}) || ( $$conf_data->{$field->{code}} && $$item->{$field->{code}} ) ) {
1008 my $str = $field->{data} ;
1009 # strip out naughty existing nl/cr's
1013 if ($field->{code} eq 'itemcallnumber') { # If the field contains the call number, we do some special processing on it here...
1014 if (($nowrap == 0) || (!$nowrap)) { # wrap lines based on segmentation markers: '/' (other types of segmentation markers can be added as needed here or this could be added as a syspref.)
1015 while ( $str =~ /\// ) {
1016 $str =~ /^(.*)\/(.*)$/;
1017 unshift @strings, $2;
1020 unshift @strings, $str;
1022 push @strings, $str; # if $nowrap == 1 do not wrap or remove segmentation markers...
1025 $str =~ s/\/$//g; # Here we will strip out all trailing '/' in fields other than the call number...
1026 if ( length($str) > $text_wrap_cols ) { # wrap lines greater than $text_wrap_cols width...
1027 my $wrap = substr($str, ($text_wrap_cols - length($str)), $text_wrap_cols, "");
1028 push @strings, $str;
1029 push @strings, $wrap;
1031 push @strings, $str;
1034 # loop for each string line
1035 foreach my $str (@strings) {
1037 if ( $printingtype eq 'BIB' ) { #FIXME: This is a hack and needs to be implimented as a text justification option in the template...
1038 # some code to try and center each line on the label based on font size and string point width...
1039 my $stringwidth = prStrWidth($str, $fontname, $fontsize);
1040 my $whitespace = ( $label_width - ( $stringwidth + (2 * $left_text_margin) ) );
1041 $hPos = ( ( $whitespace / 2 ) + $x_pos + $left_text_margin );
1042 #warn "\$label_width=$label_width \$stringwidth=$stringwidth \$whitespace=$whitespace \$left_text_margin=$left_text_margin for $str\n";
1044 $hPos = ( $x_pos + $left_text_margin );
1046 PrintText( $hPos, $vPos, $font, $fontsize, $str );
1047 $vPos = $vPos - $line_spacer;
1054 my ( $hPos, $vPos, $font, $fontsize, $text ) = @_;
1055 my $str = "BT /$font $fontsize Tf $hPos $vPos Td ($text) Tj ET";
1059 sub DrawPatronCardText {
1061 my ( $x_pos, $y_pos, $label_height, $label_width, $fontname, $fontsize, $left_text_margin,
1062 $text_wrap_cols, $text, $printingtype )
1065 my $top_text_margin = 25; #FIXME: This should be a template parameter and passed in...
1067 my $vPos = ( $y_pos + ( $label_height - $top_text_margin ) );
1068 my $font = prFont($fontname);
1072 foreach my $line (keys %$text) {
1073 warn "Current text is \"$line\" and font size for \"$line\" is $text->{$line} points";
1074 # some code to try and center each line on the label based on font size and string point width...
1075 my $stringwidth = prStrWidth($line, $fontname, $text->{$line});
1076 my $whitespace = ( $label_width - ( $stringwidth + (2 * $left_text_margin) ) );
1077 $hPos = ( ( $whitespace / 2 ) + $x_pos + $left_text_margin );
1079 PrintText( $hPos, $vPos, $font, $text->{$line}, $line );
1080 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.).
1081 $vPos = $vPos - ($line_spacer + $text->{$line}); # Linefeed equiv: leading + font size
1085 # Not used anywhere.
1089 # my ($fontsize) = @_;
1091 # my $str = "BT/F13 30 Tf288 720 Td( AAAAAAAAAA ) TjET";
1097 # x and y are from the top-left :)
1098 my ( $x_pos, $y_pos, $height, $width, $barcode, $barcodetype ) = @_;
1099 my $num_of_bars = length($barcode);
1100 my $bar_width = $width * .8; # %80 of length of label width
1103 my $guard_length = 10;
1106 if ( $barcodetype eq 'CODE39' ) {
1107 $bar_length = '17.5';
1109 ( $bar_length * $num_of_bars ) + ( $guard_length * 2 );
1110 $xsize_ratio = ( $bar_width / $tot_bar_length );
1112 PDF::Reuse::Barcode::Code39(
1113 x => ( $x_pos + ( $width / 10 ) ),
1114 y => ( $y_pos + ( $height / 10 ) ),
1115 value => "*$barcode*",
1116 ySize => ( .02 * $height ),
1117 xSize => $xsize_ratio,
1122 warn "$barcodetype, $barcode FAILED:$@";
1126 elsif ( $barcodetype eq 'CODE39MOD' ) {
1128 # get modulo43 checksum
1129 my $c39 = CheckDigits('code_39');
1130 $barcode = $c39->complete($barcode);
1134 ( $bar_length * $num_of_bars ) + ( $guard_length * 2 );
1135 $xsize_ratio = ( $bar_width / $tot_bar_length );
1137 PDF::Reuse::Barcode::Code39(
1138 x => ( $x_pos + ( $width / 10 ) ),
1139 y => ( $y_pos + ( $height / 10 ) ),
1140 value => "*$barcode*",
1141 ySize => ( .02 * $height ),
1142 xSize => $xsize_ratio,
1148 warn "$barcodetype, $barcode FAILED:$@";
1151 elsif ( $barcodetype eq 'CODE39MOD10' ) {
1153 # get modulo43 checksum
1154 my $c39_10 = CheckDigits('visa');
1155 $barcode = $c39_10->complete($barcode);
1159 ( $bar_length * $num_of_bars ) + ( $guard_length * 2 );
1160 $xsize_ratio = ( $bar_width / $tot_bar_length );
1162 PDF::Reuse::Barcode::Code39(
1163 x => ( $x_pos + ( $width / 10 ) ),
1164 y => ( $y_pos + ( $height / 10 ) ),
1165 value => "*$barcode*",
1166 ySize => ( .02 * $height ),
1167 xSize => $xsize_ratio,
1174 warn "$barcodetype, $barcode FAILED:$@";
1179 elsif ( $barcodetype eq 'COOP2OF5' ) {
1180 $bar_length = '9.43333333333333';
1182 ( $bar_length * $num_of_bars ) + ( $guard_length * 2 );
1183 $xsize_ratio = ( $bar_width / $tot_bar_length ) * .9;
1185 PDF::Reuse::Barcode::COOP2of5(
1186 x => ( $x_pos + ( $width / 10 ) ),
1187 y => ( $y_pos + ( $height / 10 ) ),
1189 ySize => ( .02 * $height ),
1190 xSize => $xsize_ratio,
1194 warn "$barcodetype, $barcode FAILED:$@";
1198 elsif ( $barcodetype eq 'INDUSTRIAL2OF5' ) {
1199 $bar_length = '13.1333333333333';
1201 ( $bar_length * $num_of_bars ) + ( $guard_length * 2 );
1202 $xsize_ratio = ( $bar_width / $tot_bar_length ) * .9;
1204 PDF::Reuse::Barcode::Industrial2of5(
1205 x => ( $x_pos + ( $width / 10 ) ),
1206 y => ( $y_pos + ( $height / 10 ) ),
1208 ySize => ( .02 * $height ),
1209 xSize => $xsize_ratio,
1213 warn "$barcodetype, $barcode FAILED:$@";
1217 my $moo2 = $tot_bar_length * $xsize_ratio;
1219 warn "$x_pos, $y_pos, $barcode, $barcodetype" if $debug;
1220 warn "BAR_WDTH = $bar_width, TOT.BAR.LGHT=$tot_bar_length R*TOT.BAR =$moo2" if $debug;
1223 =item build_circ_barcode;
1225 build_circ_barcode( $x_pos, $y_pos, $barcode,
1226 $barcodetype, \$item);
1228 $item is the result of a previous call to GetLabelItems();
1233 sub build_circ_barcode {
1234 my ( $x_pos_circ, $y_pos, $value, $barcodetype, $item ) = @_;
1236 #warn Dumper \$item;
1238 #warn "value = $value\n";
1242 if ( $barcodetype eq 'EAN13' ) {
1244 #testing EAN13 barcodes hack
1245 $value = $value . '000000000';
1247 $value = substr( $value, 0, 12 );
1251 PDF::Reuse::Barcode::EAN13(
1252 x => ( $x_pos_circ + 27 ),
1253 y => ( $y_pos + 15 ),
1261 # added for xpdf compat. doesnt use type3 fonts., but increases filesize from 20k to 200k
1262 # i think its embedding extra fonts in the pdf file.
1263 # mode => 'graphic',
1267 $item->{'barcodeerror'} = 1;
1269 #warn "EAN13BARCODE FAILED:$@";
1275 elsif ( $barcodetype eq 'Code39' ) {
1278 PDF::Reuse::Barcode::Code39(
1279 x => ( $x_pos_circ + 9 ),
1280 y => ( $y_pos + 15 ),
1290 $item->{'barcodeerror'} = 1;
1292 #warn "CODE39BARCODE $value FAILED:$@";
1299 elsif ( $barcodetype eq 'Matrix2of5' ) {
1301 #warn "MATRIX ELSE:";
1303 #testing MATRIX25 barcodes hack
1304 # $value = $value.'000000000';
1307 # $value = substr( $value, 0, 12 );
1311 PDF::Reuse::Barcode::Matrix2of5(
1312 x => ( $x_pos_circ + 27 ),
1313 y => ( $y_pos + 15 ),
1323 $item->{'barcodeerror'} = 1;
1325 #warn "BARCODE FAILED:$@";
1332 elsif ( $barcodetype eq 'EAN8' ) {
1334 #testing ean8 barcodes hack
1335 $value = $value . '000000000';
1337 $value = substr( $value, 0, 8 );
1341 #warn "EAN8 ELSEIF";
1343 PDF::Reuse::Barcode::EAN8(
1344 x => ( $x_pos_circ + 42 ),
1345 y => ( $y_pos + 15 ),
1355 $item->{'barcodeerror'} = 1;
1357 #warn "BARCODE FAILED:$@";
1364 elsif ( $barcodetype eq 'UPC-E' ) {
1366 PDF::Reuse::Barcode::UPCE(
1367 x => ( $x_pos_circ + 27 ),
1368 y => ( $y_pos + 15 ),
1378 $item->{'barcodeerror'} = 1;
1380 #warn "BARCODE FAILED:$@";
1386 elsif ( $barcodetype eq 'NW7' ) {
1388 PDF::Reuse::Barcode::NW7(
1389 x => ( $x_pos_circ + 27 ),
1390 y => ( $y_pos + 15 ),
1400 $item->{'barcodeerror'} = 1;
1402 #warn "BARCODE FAILED:$@";
1408 elsif ( $barcodetype eq 'ITF' ) {
1410 PDF::Reuse::Barcode::ITF(
1411 x => ( $x_pos_circ + 27 ),
1412 y => ( $y_pos + 15 ),
1422 $item->{'barcodeerror'} = 1;
1424 #warn "BARCODE FAILED:$@";
1430 elsif ( $barcodetype eq 'Industrial2of5' ) {
1432 PDF::Reuse::Barcode::Industrial2of5(
1433 x => ( $x_pos_circ + 27 ),
1434 y => ( $y_pos + 15 ),
1443 $item->{'barcodeerror'} = 1;
1445 #warn "BARCODE FAILED:$@";
1451 elsif ( $barcodetype eq 'IATA2of5' ) {
1453 PDF::Reuse::Barcode::IATA2of5(
1454 x => ( $x_pos_circ + 27 ),
1455 y => ( $y_pos + 15 ),
1464 $item->{'barcodeerror'} = 1;
1466 #warn "BARCODE FAILED:$@";
1473 elsif ( $barcodetype eq 'COOP2of5' ) {
1475 PDF::Reuse::Barcode::COOP2of5(
1476 x => ( $x_pos_circ + 27 ),
1477 y => ( $y_pos + 15 ),
1486 $item->{'barcodeerror'} = 1;
1488 #warn "BARCODE FAILED:$@";
1494 elsif ( $barcodetype eq 'UPC-A' ) {
1497 PDF::Reuse::Barcode::UPCA(
1498 x => ( $x_pos_circ + 27 ),
1499 y => ( $y_pos + 15 ),
1508 $item->{'barcodeerror'} = 1;
1510 #warn "BARCODE FAILED:$@";
1519 =item draw_boundaries
1521 sub draw_boundaries ($x_pos_spine, $x_pos_circ1, $x_pos_circ2,
1522 $y_pos, $spine_width, $label_height, $circ_width)
1524 This sub draws boundary lines where the label outlines are, to aid in printer testing, and debugging.
1529 sub draw_boundaries {
1532 $x_pos_spine, $x_pos_circ1, $x_pos_circ2, $y_pos,
1533 $spine_width, $label_height, $circ_width
1536 my $y_pos_initial = ( ( 792 - 36 ) - 90 );
1537 $y_pos = $y_pos_initial; # FIXME - why are we ignoring the y_pos parameter by redefining it?
1540 for ( $i = 1 ; $i <= 8 ; $i++ ) {
1542 &drawbox( $x_pos_spine, $y_pos, ($spine_width), ($label_height) );
1544 #warn "OLD BOXES x=$x_pos_spine, y=$y_pos, w=$spine_width, h=$label_height";
1545 &drawbox( $x_pos_circ1, $y_pos, ($circ_width), ($label_height) );
1546 &drawbox( $x_pos_circ2, $y_pos, ($circ_width), ($label_height) );
1548 $y_pos = ( $y_pos - $label_height );
1555 sub drawbox { $lower_left_x, $lower_left_y,
1556 $upper_right_x, $upper_right_y )
1558 this is a low level sub, that draws a pdf box, it is called by draw_boxes
1560 FYI: the $upper_right_x and $upper_right_y values are RELATIVE to $lower_left_x and $lower_left_y
1562 and $lower_left_x, $lower_left_y are ABSOLUTE, this caught me out!
1568 my ( $llx, $lly, $urx, $ury ) = @_;
1570 # warn "llx,y= $llx,$lly , urx,y=$urx,$ury \n";
1572 my $str = "q\n"; # save the graphic state
1573 $str .= "0.5 w\n"; # border color red
1574 $str .= "1.0 0.0 0.0 RG\n"; # border color red
1575 # $str .= "0.5 0.75 1.0 rg\n"; # fill color blue
1576 $str .= "1.0 1.0 1.0 rg\n"; # fill color white
1578 $str .= "$llx $lly $urx $ury re\n"; # a rectangle
1579 $str .= "B\n"; # fill (and a little more)
1580 $str .= "Q\n"; # save the graphic state
1586 END { } # module clean-up code here (global destructor)
1595 Mason James <mason@katipo.co.nz>