Bug 16699: Remove requirement from borrowernumberQueryParam
[koha.git] / C4 / Patroncards / Patroncard.pm
blob2203d1a1e5b819bc3cc49f8ee337b7822fc8e5ee
1 package C4::Patroncards::Patroncard;
3 # Copyright 2009 Foundations Bible College.
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
20 use strict;
21 use warnings;
23 use autouse 'Data::Dumper' => qw(Dumper);
24 use Text::Wrap qw(wrap);
25 #use Font::TTFMetrics;
27 use C4::Creators::Lib qw(get_font_types);
28 use C4::Creators::PDF qw(StrWidth);
29 use C4::Patroncards::Lib qw(unpack_UTF8 text_alignment leading box get_borrower_attributes);
32 sub new {
33 my ($invocant, %params) = @_;
34 my $type = ref($invocant) || $invocant;
35 my $self = {
36 batch_id => $params{'batch_id'},
37 #card_number => $params{'card_number'},
38 borrower_number => $params{'borrower_number'},
39 llx => $params{'llx'},
40 lly => $params{'lly'},
41 height => $params{'height'},
42 width => $params{'width'},
43 layout => $params{'layout'},
44 text_wrap_cols => $params{'text_wrap_cols'},
45 barcode_height_scale => $params{'layout'}->{'barcode'}[0]->{'height_scale'} || 0.01,
46 barcode_width_scale => $params{'layout'}->{'barcode'}[0]->{'width_scale'} || 0.8,
48 bless ($self, $type);
49 return $self;
52 sub draw_barcode {
53 my ($self, $pdf) = @_;
54 # Default values for barcode scaling are set in constructor to work with pre-existing installations
55 my $barcode_height_scale = $self->{'barcode_height_scale'};
56 my $barcode_width_scale = $self->{'barcode_width_scale'};
58 _draw_barcode( $self,
59 llx => $self->{'llx'} + $self->{'layout'}->{'barcode'}->[0]->{'llx'},
60 lly => $self->{'lly'} + $self->{'layout'}->{'barcode'}->[0]->{'lly'},
61 width => $self->{'width'} * $barcode_width_scale,
62 y_scale_factor => $self->{'height'} * $barcode_height_scale,
63 barcode_type => $self->{'layout'}->{'barcode'}->[0]->{'type'},
64 barcode_data => $self->{'layout'}->{'barcode'}->[0]->{'data'},
65 text => $self->{'layout'}->{'barcode'}->[0]->{'text_print'},
69 sub draw_guide_box {
70 my ($self, $pdf) = @_;
71 warn sprintf('No pdf object passed in.') and return -1 if !$pdf;
72 my $obj_stream = "q\n"; # save the graphic state
73 $obj_stream .= "0.5 w\n"; # border line width
74 $obj_stream .= "1.0 0.0 0.0 RG\n"; # border color red
75 $obj_stream .= "1.0 1.0 1.0 rg\n"; # fill color white
76 $obj_stream .= "$self->{'llx'} $self->{'lly'} $self->{'width'} $self->{'height'} re\n"; # a rectangle
77 $obj_stream .= "B\n"; # fill (and a little more)
78 $obj_stream .= "Q\n"; # restore the graphic state
79 $pdf->Add($obj_stream);
82 sub draw_text {
83 my ($self, $pdf, %params) = @_;
84 warn sprintf('No pdf object passed in.') and return -1 if !$pdf;
85 my @card_text = ();
86 return unless (ref($self->{'layout'}->{'text'}) eq 'ARRAY'); # just in case there is not text
87 my $text = [@{$self->{'layout'}->{'text'}}]; # make a copy of the arrayref *not* simply a pointer
88 while (scalar @$text) {
89 my $line = shift @$text;
90 my $parse_line = $line;
91 my @orig_line = split(/ /,$line);
92 if ($parse_line =~ m/<[A-Za-z0-9]+>/) { # test to see if the line has db fields embedded...
93 my @fields = ();
94 while ($parse_line =~ m/<([A-Za-z0-9]+)>(.*$)/) {
95 push (@fields, $1);
96 $parse_line = $2;
98 my $borrower_attributes = get_borrower_attributes($self->{'borrower_number'},@fields);
99 grep{ # substitute data for db fields
100 if ($_ =~ m/<([A-Za-z0-9]+)>/) {
101 my $field = $1;
102 $_ =~ s/$_/$borrower_attributes->{$field}/;
104 } @orig_line;
105 $line = join(' ',@orig_line);
107 my $text_attribs = shift @$text;
108 my $origin_llx = $self->{'llx'} + $text_attribs->{'llx'};
109 my $origin_lly = $self->{'lly'} + $text_attribs->{'lly'};
110 my $Tx = 0; # final text llx
111 my $Ty = $origin_lly; # final text lly
112 my $Tw = 0; # final text word spacing. See http://www.adobe.com/devnet/pdf/pdf_reference.html ISO 32000-1
113 #FIXME: Move line wrapping code to its own sub if possible
114 my $trim = '';
115 my @lines = ();
116 #FIXME: Using embedded True Type fonts is a far superior way of handing things as well as being much more unicode friendly.
117 # However this will take significant work using better than PDF::Reuse to do it. For the time being, I'm leaving
118 # the basic code here commented out to preserve the basic method of accomplishing this. -chris_n
120 # my $m = Font::TTFMetrics->new("/usr/share/fonts/truetype/msttcorefonts/Times_New_Roman_Bold.ttf");
121 # my $units_per_em = $m->get_units_per_em();
122 # my $font_units_width = $m->string_width($line);
123 # my $string_width = ($font_units_width * $text_attribs->{'font_size'}) / $units_per_em;
124 my $string_width = C4::Creators::PDF->StrWidth($line, $text_attribs->{'font'}, $text_attribs->{'font_size'});
125 if (($string_width + $text_attribs->{'llx'}) > $self->{'width'}) {
126 WRAP_LINES:
127 while (1) {
128 # $line =~ m/^.*(\s\b.*\b\s*|\s&|\<\b.*\b\>)$/; # original regexp... can be removed after dev stage is over
129 $line =~ m/^.*(\s.*\s*|\s&|\<.*\>)$/;
130 warn sprintf('Line wrap failed. DEBUG INFO: Data: \'%s\'\n Method: C4::Patroncards->draw_text Additional Information: Line wrap regexp failed. (Please file in this information in a bug report at http://bugs.koha-community.org', $line) and last WRAP_LINES if !$1;
131 $trim = $1 . $trim;
132 $line =~ s/$1//;
133 $string_width = C4::Creators::PDF->StrWidth($line, $text_attribs->{'font'}, $text_attribs->{'font_size'});
134 # $font_units_width = $m->string_width($line);
135 # $string_width = ($font_units_width * $text_attribs->{'font_size'}) / $units_per_em;
136 if (($string_width + $text_attribs->{'llx'}) < $self->{'width'}) {
137 ($Tx, $Tw) = text_alignment($origin_llx, $self->{'width'}, $text_attribs->{'llx'}, $string_width, $line, $text_attribs->{'text_alignment'});
138 push @lines, {line=> $line, Tx => $Tx, Ty => $Ty, Tw => $Tw};
139 $line = undef;
140 last WRAP_LINES if $trim eq '';
141 $Ty -= leading($text_attribs->{'font_size'});
142 $line = $trim;
143 $trim = '';
144 $string_width = C4::Creators::PDF->StrWidth($line, $text_attribs->{'font'}, $text_attribs->{'font_size'});
145 #$font_units_width = $m->string_width($line);
146 #$string_width = ($font_units_width * $text_attribs->{'font_size'}) / $units_per_em;
147 if (($string_width + $text_attribs->{'llx'}) < $self->{'width'}) {
148 ($Tx, $Tw) = text_alignment($origin_llx, $self->{'width'}, $text_attribs->{'llx'}, $string_width, $line, $text_attribs->{'text_alignment'});
149 $line =~ s/^\s+//g; # strip naughty leading spaces
150 push @lines, {line=> $line, Tx => $Tx, Ty => $Ty, Tw => $Tw};
151 last WRAP_LINES;
156 else {
157 ($Tx, $Tw) = text_alignment($origin_llx, $self->{'width'}, $text_attribs->{'llx'}, $string_width, $line, $text_attribs->{'text_alignment'});
158 $line =~ s/^\s+//g; # strip naughty leading spaces
159 push @lines, {line=> $line, Tx => $Tx, Ty => $Ty, Tw => $Tw};
161 # Draw boxes around text box areas
162 # FIXME: This needs to compensate for the point height of decenders. In its current form it is helpful but not really usable. The boxes are also not transparent atm.
163 # If these things were fixed, it may be desirable to give the user control over whether or not to display these boxes for layout design.
164 if (0) {
165 my $box_height = 0;
166 my $box_lly = $origin_lly;
167 if (scalar(@lines) > 1) {
168 $box_height += scalar(@lines) * ($text_attribs->{'font_size'} * 1.2);
169 $box_lly -= ($text_attribs->{'font_size'} * 0.2);
171 else {
172 $box_height += $text_attribs->{'font_size'};
174 box ($origin_llx, $box_lly, $self->{'width'} - $text_attribs->{'llx'}, $box_height, $pdf);
176 $pdf->Font($text_attribs->{'font'});
177 $pdf->FontSize($text_attribs->{'font_size'});
178 foreach my $line (@lines) {
179 $pdf->Text($line->{'Tx'}, $line->{'Ty'}, $line->{'line'});
184 sub draw_image {
185 my ($self, $pdf) = @_;
186 warn sprintf('No pdf object passed in.') and return -1 if !$pdf;
187 my $images = $self->{'layout'}->{'images'};
188 PROCESS_IMAGES:
189 foreach my $image (keys %$images) {
190 next PROCESS_IMAGES if $images->{$image}->{'data_source'}->[0]->{'image_source'} eq 'none';
191 my $Tx = $self->{'llx'} + $images->{$image}->{'Tx'};
192 my $Ty = $self->{'lly'} + $images->{$image}->{'Ty'};
193 warn sprintf('No image passed in.') and next if !$images->{$image}->{'data'};
194 my $intName = $pdf->AltJpeg($images->{$image}->{'data'},$images->{$image}->{'Sx'}, $images->{$image}->{'Sy'}, 1, $images->{$image}->{'alt'}->{'data'},$images->{$image}->{'alt'}->{'Sx'}, $images->{$image}->{'alt'}->{'Sy'}, 1);
195 my $obj_stream = "q\n";
196 $obj_stream .= "$images->{$image}->{'Sx'} $images->{$image}->{'Ox'} $images->{$image}->{'Oy'} $images->{$image}->{'Sy'} $Tx $Ty cm\n"; # see http://www.adobe.com/devnet/pdf/pdf_reference.html sec 8.3.3 of ISO 32000-1
197 $obj_stream .= "$images->{$image}->{'scale'} 0 0 $images->{$image}->{'scale'} 0 0 cm\n"; #scale to 20%
198 $obj_stream .= "/$intName Do\n";
199 $obj_stream .= "Q\n";
200 $pdf->Add($obj_stream);
204 sub _draw_barcode { # this is cut-and-paste from Label.pm because there is no common place for it atm...
205 my $self = shift;
206 my %params = @_;
207 my $x_scale_factor = 1;
208 my $num_of_chars = length($params{'barcode_data'});
209 my $tot_bar_length = 0;
210 my $bar_length = 0;
211 my $guard_length = 10;
212 if ($params{'barcode_type'} =~ m/CODE39/) {
213 $bar_length = '17.5';
214 $tot_bar_length = ($bar_length * $num_of_chars) + ($guard_length * 2); # not sure what all is going on here and on the next line; this is old (very) code
215 $x_scale_factor = ($params{'width'} / $tot_bar_length);
216 if ($params{'barcode_type'} eq 'CODE39MOD') {
217 my $c39 = CheckDigits('code_39'); # get modulo 43 checksum
218 $params{'barcode_data'} = $c39->complete($params{'barcode_data'});
220 elsif ($params{'barcode_type'} eq 'CODE39MOD10') {
221 my $c39_10 = CheckDigits('siret'); # get modulo 10 checksum
222 $params{'barcode_data'} = $c39_10->complete($params{'barcode_data'});
224 eval {
225 PDF::Reuse::Barcode::Code39(
226 x => $params{'llx'},
227 y => $params{'lly'},
228 value => "*$params{barcode_data}*",
229 xSize => $x_scale_factor,
230 ySize => $params{'y_scale_factor'},
231 hide_asterisk => 1,
232 text => $params{'text'},
233 mode => 'graphic',
236 if ($@) {
237 warn sprintf('Barcode generation failed for item %s with this error: %s', $self->{'item_number'}, $@);
240 elsif ($params{'barcode_type'} eq 'COOP2OF5') {
241 $bar_length = '9.43333333333333';
242 $tot_bar_length = ($bar_length * $num_of_chars) + ($guard_length * 2);
243 $x_scale_factor = ($params{'width'} / $tot_bar_length) * 0.9;
244 eval {
245 PDF::Reuse::Barcode::COOP2of5(
246 x => $params{'llx'},
247 y => $params{'lly'},
248 value => "*$params{barcode_data}*",
249 xSize => $x_scale_factor,
250 ySize => $params{'y_scale_factor'},
251 mode => 'graphic',
254 if ($@) {
255 warn sprintf('Barcode generation failed for item %s with this error: %s', $self->{'item_number'}, $@);
258 elsif ( $params{'barcode_type'} eq 'INDUSTRIAL2OF5' ) {
259 $bar_length = '13.1333333333333';
260 $tot_bar_length = ($bar_length * $num_of_chars) + ($guard_length * 2);
261 $x_scale_factor = ($params{'width'} / $tot_bar_length) * 0.9;
262 eval {
263 PDF::Reuse::Barcode::Industrial2of5(
264 x => $params{'llx'},
265 y => $params{'lly'},
266 value => "*$params{barcode_data}*",
267 xSize => $x_scale_factor,
268 ySize => $params{'y_scale_factor'},
269 mode => 'graphic',
272 if ($@) {
273 warn sprintf('Barcode generation failed for item %s with this error: %s', $self->{'item_number'}, $@);
279 __END__
281 =head1 AUTHOR
283 Chris Nighswonger <cnighswonger AT foundations DOT edu>
285 =cut