1 package SL::Helper::CreatePDF;
7 use English qw(-no_match_vars);
11 use File::Copy qw(move);
12 use List::MoreUtils qw(uniq);
13 use List::Util qw(first);
14 use String::ShellQuote ();
22 use SL::Template::LaTeX;
24 use Exporter 'import';
25 our @EXPORT_OK = qw(create_pdf merge_pdfs find_template);
31 my ($class, %params) = @_;
33 return __PACKAGE__->create_parsed_file(
35 template_type => 'LaTeX',
40 sub create_parsed_file {
41 my ($class, %params) = @_;
43 my $userspath = $::lx_office_conf{paths}->{userspath};
44 my $vars = $params{variables} || {};
45 my $form = Form->new('');
46 $form->{$_} = $vars->{$_} for keys %{$vars};
47 $form->{format} = lc($params{format} || 'pdf');
48 $form->{cwd} = getcwd();
49 $form->{templates} = $::instance_conf->get_templates;
50 $form->{IN} = $params{template};
51 $form->{tmpdir} = $form->{cwd} . '/' . $userspath;
52 my $tmpdir = $form->{tmpdir};
53 my ($suffix) = $params{template} =~ m{\.(.+)};
55 my ($temp_fh, $tmpfile) = File::Temp::tempfile(
56 'kivitendo-printXXXXXX',
57 SUFFIX => ".${suffix}",
58 DIR => $form->{tmpdir},
59 UNLINK => $::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files},
62 $form->{tmpfile} = $tmpfile;
63 (undef, undef, $form->{template_meta}{tmpfile}) = File::Spec->splitpath($tmpfile);
65 my $parser = SL::Template::create(
66 type => ($params{template_type} || 'LaTeX'),
67 source => $form->{IN},
69 myconfig => \%::myconfig,
71 variable_content_types => $params{variable_content_types},
74 my $result = $parser->parse($temp_fh);
81 die $parser->get_error;
84 # SL::Template:** modify $form->{tmpfile} by removing its
85 # $form->{userspath} prefix. They also store the final file's actual
86 # file name in $form->{tmpfile} – but it is now relative to
87 # $form->{userspath}. Other modules return the full file name…
88 my ($volume, $directory, $file_name) = File::Spec->splitpath($form->{tmpfile});
89 my $full_file_name = File::Spec->catfile($tmpdir, $file_name);
90 if (($params{return} || 'content') eq 'file_name') {
91 my $new_name = File::Spec->catfile($tmpdir, 'keep-' . $form->{tmpfile});
92 rename $full_file_name, $new_name;
99 my $content = File::Slurp::read_file($full_file_name);
107 # Alternativen zu pdfinfo wären (aber wesentlich langamer):
109 # gs -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=/dev/null $filename | grep 'Processing pages'
110 # my (undef,undef,undef,undef,$pages) = split / +/,$shell_out;
112 # gs -dBATCH -dNOPAUSE -q -dNODISPLAY -c "($filename) (r) file runpdfbegin pdfpagecount = quit"
117 my ($class, $filename) = @_;
118 return 0 unless -f $filename;
119 my $shell_out = `pdfinfo $filename | grep 'Pages:'`;
120 my ($label, $pages) = split / +/, $shell_out;
125 my ($class, %params) = @_;
126 my $filecount = scalar(@{ $params{file_names} });
128 if ($params{inp_content}) {
129 return $params{inp_content} if $filecount == 0 && !$params{out_path};
130 } elsif ($params{out_path}) {
131 return 0 if $filecount == 0;
132 if ($filecount == 1) {
133 if (!rename($params{file_names}->[0], $params{out_path})) {
134 # special filesystem or cross filesystem etc
135 move($params{file_names}->[0], $params{out_path});
140 return '' if $filecount == 0;
141 return scalar(File::Slurp::read_file($params{file_names}->[0])) if $filecount == 1;
144 my ($temp_fh, $temp_name) = File::Temp::tempfile(
145 'kivitendo-printXXXXXX',
147 DIR => $::lx_office_conf{paths}->{userspath},
148 UNLINK => $::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files},
152 my $input_names = '';
155 if ($params{bothsided}) {
156 $emptypage = $::instance_conf->get_templates . '/emptyPage.pdf';
157 unless (-f $emptypage) {
159 delete $params{bothsided};
162 if ($params{inp_content}) {
163 my ($temp_fh, $inp_name) = File::Temp::tempfile(
164 'kivitendo-contentXXXXXX',
166 DIR => $::lx_office_conf{paths}->{userspath},
167 UNLINK => $::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files},
170 print $temp_fh $params{inp_content};
172 $input_names = $inp_name . ' ';
173 $hasodd = $params{bothsided} && __PACKAGE__->has_odd_pages($inp_name);
175 foreach (@{ $params{file_names} }) {
176 $input_names .= $emptypage . ' ' if $hasodd;
177 $input_names .= String::ShellQuote::shell_quote($_) . ' ';
178 $hasodd = $params{bothsided} && __PACKAGE__->has_odd_pages($_);
180 my $exe = $::lx_office_conf{applications}->{ghostscript} || 'gs';
182 `$exe -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite -sOutputFile=${temp_name} ${input_names} 2>&1`;
184 die "Executing gs failed: $ERRNO" if !defined $output;
185 die $output if $? != 0;
187 if ($params{out_path}) {
188 if (!rename($temp_name, $params{out_path})) {
190 # special filesystem or cross filesystem etc
191 move($temp_name, $params{out_path});
195 return scalar File::Slurp::read_file($temp_name);
199 my ($class, %params) = @_;
201 $params{name} or croak "Missing parameter 'name'";
203 my $path = $::instance_conf->get_templates;
204 my $extension = $params{extension} || "tex";
205 my ($printer, $language) = ('', '');
207 if ($params{printer} || $params{printer_id}) {
208 if ($params{printer} && !ref $params{printer}) {
209 $printer = '_' . $params{printer};
211 $printer = $params{printer} || SL::DB::Printer->new(id => $params{printer_id})->load;
212 $printer = $printer->template_code ? '_' . $printer->template_code : '';
216 if ($params{language} || $params{language_id}) {
217 if ($params{language} && !ref $params{language}) {
218 $language = '_' . $params{language};
220 $language = $params{language} || SL::DB::Language->new(id => $params{language_id})->load;
221 $language = $language->template_code ? '_' . $language->template_code : '';
225 my @template_files = (
226 $params{name} . "${language}${printer}",
227 $params{name} . "${language}",
232 if ($params{email}) {
233 unshift @template_files, (
234 $params{name} . "_email${language}${printer}",
235 $params{name} . "_email${language}",
239 @template_files = map { "${_}.${extension}" } uniq grep { $_ } @template_files;
241 my $template = first { -f ($path . "/$_") } @template_files;
243 return wantarray ? ($template, @template_files) : $template;
255 SL::Helper::CreatePDF - A helper for creating PDFs from template files
259 # Retrieve a sales order from the database and create a PDF for
261 my $order = SL::DB::Order->new(id => …)->load;
262 my $print_form = Form->new('');
263 $print_form->{type} = 'invoice';
264 $print_form->{formname} = 'invoice',
265 $print_form->{format} = 'pdf',
266 $print_form->{media} = 'file';
268 $order->flatten_to_form($print_form, format_amounts => 1);
269 $print_form->prepare_for_printing;
271 my $pdf = SL::Helper::CreatePDF->create_pdf(
272 template => 'sales_order',
273 variables => $print_form,
280 =item C<create_pdf %params>
282 Parses a LaTeX template file, creates a PDF for it and returns either
283 its content or its file name. The recognized parameters are the same
284 as the ones for L</create_parsed_file> with C<format> and
285 C<template_type> being pre-set.
287 =item C<create_parsed_file %params>
289 Parses a template file and returns either its content or its file
290 name. The recognized parameters are:
294 =item * C<template> – mandatory. The template file name relative to
295 the users' templates directory. Must be an existing file name,
296 e.g. one retrieved by L</find_template>.
298 =item * C<variables> – optional hash reference containing variables
299 available to the template.
301 =item * C<return> – optional scalar containing either C<content> (the
302 default) or C<file_name>. If it is set to C<file_name> then the file
303 name of the temporary file containing the PDF is returned, and the
304 caller is responsible for deleting it. Otherwise a scalar containing
305 the PDF itself is returned and all temporary files have already been
306 deleted by L</create_pdf>.
308 =item * C<format> – optional, defaults to C<pdf> and determines the
309 output format. Can be set to C<html> for HTML output if
310 C<template_type> is set to C<HTML> as well.
312 =item * C<template_type> – optional, defaults to C<LaTeX> and
313 determines the template's format. Can be set to C<HTML> for HTML
314 output if C<format> is set to C<html> as well.
318 =item C<find_template %params>
320 Searches the user's templates directory for a template file name to
321 use. The file names considered depend on the parameters; they can
322 contain a template base name and suffixes for email, language and
323 printers. As a fallback the name C<default.$extension> is also
326 The return value depends on the context. In scalar context the
327 template file name that matches the given parameters is returned. It's
328 a file name relative to the user's templates directory. If no template
329 file is found then C<undef> is returned.
331 In list context the first element is the same value as in scalar
332 context. Additionally a list of considered template file names is
335 The recognized parameters are:
339 =item * C<name> – mandatory. The template's file name basis
340 without any additional suffix or extension, e.g. C<sales_quotation>.
342 =item * C<extension> – optional file name extension to use without the
343 dot. Defaults to C<tex>.
345 =item * C<email> – optional flag indicating whether or not the
346 template is to be sent via email. If set to true then template file
347 names containing C<_email> are considered as well.
349 =item * C<language> and C<language_id> – optional parameters
350 indicating the language to be used. C<language> can be either a string
351 containing the language code to use or an instance of
352 C<SL::DB::Language>. C<language_id> can contain the ID of the
353 C<SL::DB:Language> instance to load and use. If given template file
354 names containing C<_language_template_code> are considered as well.
356 =item * C<printer> and C<printer_id> – optional parameters indicating
357 the printer to be used. C<printer> can be either a string containing
358 the printer code to use or an instance of
359 C<SL::DB::Printer>. C<printer_id> can contain the ID of the
360 C<SL::DB:Printer> instance to load and use. If given template file
361 names containing C<_printer_template_code> are considered as well.
365 =item C<merge_pdfs %params>
367 Merges two or more PDFs into a single PDF by using the external
368 application ghostscript.
370 Normally the function returns the contents of the resulting PDF.
371 if The parameter C<out_path> is set the resulting PDF is in this file
372 and the return value is 1 if it successful or 0 if not.
374 The recognized parameters are:
378 =item * C<file_names> – mandatory array reference containing the file
381 =item * C<inp_content> – optional, contents of first file to merge with C<file_names>.
383 =item * C<out_path> – optional, returns not the merged contents but wrote him into this file
387 Note that this function relies on the presence of the external
388 application ghostscript. The executable to use is configured via
389 kivitendo's configuration file setting C<application.ghostscript>.
399 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>