X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;f=SL%2FTemplate%2FLaTeX.pm;h=130a42484d1405f49f88bcae7d8c0c67d9ff191b;hb=d1d70f4ad540f4d57d1b577b337b6db38074e223;hp=c6d54d5934a601488ac2e9480734d1363592ee31;hpb=eca0862f8bcc69d22d94c41cc9076a008c77056d;p=kivitendo-erp.git diff --git a/SL/Template/LaTeX.pm b/SL/Template/LaTeX.pm index c6d54d593..130a42484 100644 --- a/SL/Template/LaTeX.pm +++ b/SL/Template/LaTeX.pm @@ -4,9 +4,111 @@ use parent qw(SL::Template::Simple); use strict; +use Carp; use Cwd; +use English qw(-no_match_vars); +use File::Basename; +use File::Temp; +use HTML::Entities (); +use List::MoreUtils qw(any); use Unicode::Normalize qw(); +use SL::DB::Default; + +my %text_markup_replace = ( + b => 'textbf', + i => 'textit', + u => 'underline', +); + +sub _format_text { + my ($self, $content, %params) = @_; + + $content = $::locale->quote_special_chars('Template/LaTeX', $content); + + # Allow some HTML markup to be converted into the output format's + # corresponding markup code, e.g. bold or italic. + foreach my $key (keys(%text_markup_replace)) { + my $new = $text_markup_replace{$key}; + $content =~ s/\$\<\$${key}\$\>\$(.*?)\$<\$\/${key}\$>\$/\\${new}\{$1\}/gi; + } + + $content =~ s/[\x00-\x1f]//g; + + return $content; +} + +my %html_replace = ( + '

' => "\n\n", + '' => "\\end{itemize} ", + '
    ' => "\\begin{enumerate} ", + '
' => "\\end{enumerate} ", + '
  • ' => "\\item ", + '
  • ' => " ", + '' => "\\textbf{", + '' => "}", + '' => "\\textbf{", + '' => "}", + '' => "\\textit{", + '' => "}", + '' => "\\textit{", + '' => "}", + '' => "\\underline{", + '' => "}", + '' => "\\sout{", + '' => "}", + '' => "\\textsubscript{", + '' => "}", + '' => "\\textsuperscript{", + '' => "}", + '
    ' => "\\newline ", + '
    ' => "\\newline ", +); + +sub _lb_to_space { + my ($to_replace) = @_; + + my $vspace = '\vspace*{0.5cm}'; + return $vspace x (length($to_replace) / length($html_replace{'
    '})); +} + +sub _format_html { + my ($self, $content, %params) = @_; + + $content =~ s{ \r+ }{}gx; + $content =~ s{ \n+ }{ }gx; + $content =~ s{ (?:\ |\s)+ }{ }gx; + $content =~ s{ (?:\ |\s)+$ }{}gx; + $content =~ s{ (?:
    )+$ }{}gx; + + my @parts = grep { $_ } map { + if (substr($_, 0, 1) eq '<') { + s{ +}{}g; + $html_replace{$_} || ''; + + } else { + $::locale->quote_special_chars('Template/LaTeX', HTML::Entities::decode_entities($_)); + } + } split(m{(<.*?>)}x, $content); + + $content = join '', @parts; + $content =~ s{ (?: [\n\s] | \\newline )+ $ }{}gx; # remove line breaks at the end of the text + $content =~ s{ ^ \s+ }{}gx; # remove white space at the start of the text + $content =~ s{ ^ ( \\newline \ )+ }{ _lb_to_space($1) }gxe; # convert line breaks at the start of the text to vertical space + $content =~ s{ ( \n\n+ ) ( \\newline \ )+ }{ $1 . _lb_to_space($2) }gxe; # convert line breaks at the start of a paragraph to vertical space + $content =~ s{ ( \\end\{ [^\}]+ \} \h* ) ( \\newline \ )+ }{ $1 . _lb_to_space($2) }gxe; # convert line breaks after LaTeX environments like lists to vertical space + $content =~ s{ ^ \h+ \\newline }{\\newline}gmx; + $content =~ s{ \n\n \h* \\newline \h* }{\n\n}gmx; + + return $content; +} + +my %formatters = ( + html => \&_format_html, + text => \&_format_text, +); + sub new { my $type = shift; @@ -16,25 +118,17 @@ sub new { } sub format_string { - my ($self, $variable) = @_; - my $form = $self->{"form"}; + my ($self, $content, $variable) = @_; - $variable = $main::locale->quote_special_chars('Template/LaTeX', $variable); + my $formatter = + $formatters{ $self->{variable_content_types}->{$variable} } + // $formatters{ $self->{default_content_type} } + // $formatters{ text }; - # Allow some HTML markup to be converted into the output format's - # corresponding markup code, e.g. bold or italic. - my %markup_replace = ('b' => 'textbf', - 'i' => 'textit', - 'u' => 'underline'); - - foreach my $key (keys(%markup_replace)) { - my $new = $markup_replace{$key}; - $variable =~ s/\$\<\$${key}\$\>\$(.*?)\$<\$\/${key}\$>\$/\\${new}\{$1\}/gi; - } + $content =~ s{[^\p{Print}\n]|\p{Cf}}{}g; + $variable =~ s{[^\p{Print}\n]|\p{Cf}}{}g; - $variable =~ s/[\x00-\x1f]//g; - - return $variable; + return $formatter->($self, $content, variable => $variable); } sub parse_foreach { @@ -63,6 +157,49 @@ sub parse_foreach { $form->{"__odd__"} = (($i + 1) % 2) == 1; $form->{"__counter__"} = $i + 1; + #everything from here to the next marker should be removed after the release of 2.7.0 + if ( ref $description_array eq 'ARRAY' + && scalar @{$description_array} == scalar @{$ary} + && $self->{"chars_per_line"} != 0) + { + my $lines = int(length($description_array->[$i]) / $self->{"chars_per_line"}); + my $lpp; + + $description_array->[$i] =~ s/(\\newline\s?)*$//; + $lines++ while ($description_array->[$i] =~ m/\\newline/g); + $lines++; + + if ($current_page == 1) { + $lpp = $self->{"lines_on_first_page"}; + } else { + $lpp = $self->{"lines_on_second_page"}; + } + + # Yes we need a manual page break -- or the user has forced one + if ( (($current_line + $lines) > $lpp) + || ($description_array->[$i] =~ //) + || ( ref $longdescription_array eq 'ARRAY' + && $longdescription_array->[$i] =~ //)) { + my $pb = $self->{"pagebreak_block"}; + + # replace the special variables <%sumcarriedforward%> + # and <%lastpage%> + + my $psum = $form->format_amount($self->{"myconfig"}, $sum, 2); + $pb =~ s/$self->{tag_start_qm}sumcarriedforward$self->{tag_end_qm}/$psum/g; + $pb =~ s/$self->{tag_start_qm}lastpage$self->{tag_end_qm}/$current_page/g; + + my $new_text = $self->parse_block($pb, (@indices, $i)); + return undef unless (defined($new_text)); + $new_contents .= $new_text; + + $current_page++; + $current_line = 0; + } + $current_line += $lines; + } + #stop removing code here. + if ( ref $linetotal_array eq 'ARRAY' && $i < scalar(@{$linetotal_array})) { $sum += $form->parse_amount($self->{"myconfig"}, $linetotal_array->[$i]); @@ -148,15 +285,15 @@ sub parse_block { $new_contents .= $self->substitute_vars(substr($contents, 0, $pos_foreach), @indices); substr($contents, 0, $pos_foreach) = ""; - if ($contents !~ m|^$self->{tag_start_qm}foreach (.+?)$self->{tag_end_qm}|) { + if ($contents !~ m|^($self->{tag_start_qm}foreach (.+?)$self->{tag_end_qm})|) { $self->{"error"} = "Malformed $self->{tag_start}foreach$self->{tag_end}."; $main::lxdebug->leave_sub(); return undef; } - my $var = $1; + my $var = $2; - substr($contents, 0, length($&)) = ""; + substr($contents, 0, length($1)) = ""; my $block; ($block, $contents) = $self->find_end($contents); @@ -214,6 +351,9 @@ sub _parse_config_option { if ($key eq 'tag-style') { $self->set_tag_style(split(m/\s+/, $value, 2)); } + if ($key eq 'use-template-toolkit') { + $self->set_use_template_toolkit($value); + } } sub _parse_config_lines { @@ -259,7 +399,7 @@ sub _force_mandatory_packages { $used_packages{$1} = 1; $last_usepackage_line = $i; - } elsif ($lines->[$i] =~ m/\\begin{document}/) { + } elsif ($lines->[$i] =~ m/\\begin\{document\}/) { $document_start_line = $i; last; @@ -270,7 +410,7 @@ sub _force_mandatory_packages { : defined($last_usepackage_line) ? $last_usepackage_line : scalar @{ $lines } - 1; - foreach my $package (qw(textcomp)) { + foreach my $package (qw(textcomp ulem)) { next if $used_packages{$package}; splice @{ $lines }, $insertion_point, 0, "\\usepackage{${package}}\n"; $insertion_point++; @@ -283,10 +423,10 @@ sub parse { my $form = $self->{"form"}; if (!open(IN, "$form->{templates}/$form->{IN}")) { - $self->{"error"} = "$!"; + $self->{"error"} = "$form->{templates}/$form->{IN}: $!"; return 0; } - binmode IN, ":utf8" if $::locale->is_utf8; + binmode IN, ":utf8"; my @lines = ; close(IN); @@ -295,20 +435,38 @@ sub parse { my $contents = join("", @lines); - my $new_contents = $self->parse_block($contents); - if (!defined($new_contents)) { - $main::lxdebug->leave_sub(); - return 0; + # detect pagebreak block and its parameters + if ($contents =~ /^(.*)($self->{tag_start_qm}pagebreak\s+(\d+)\s+(\d+)\s+(\d+)\s*$self->{tag_end_qm}(.*?)$self->{tag_start_qm}end(\s*pagebreak)?$self->{tag_end_qm})/s) { + $self->{"chars_per_line"} = $3; + $self->{"lines_on_first_page"} = $4; + $self->{"lines_on_second_page"} = $5; + $self->{"pagebreak_block"} = $6; + + substr($contents, length($1), length($2)) = ""; } - if ($::locale->is_utf8) { - binmode OUT, ":utf8"; - print OUT Unicode::Normalize::normalize('C', $new_contents); + $self->{"forced_pagebreaks"} = []; + + my $new_contents; + if ($self->{use_template_toolkit}) { + if ($self->{custom_tag_style}) { + $contents = "[% TAGS $self->{tag_start} $self->{tag_end} %]\n" . $contents; + } + my $globals = global_vars(); + + $::form->template->process(\$contents, { %$form, %$globals }, \$new_contents) || die $::form->template->error; } else { - print OUT $new_contents; + $new_contents = $self->parse_block($contents); + } + if (!defined($new_contents)) { + $main::lxdebug->leave_sub(); + return 0; } + binmode OUT, ":utf8"; + print OUT Unicode::Normalize::normalize('C', $new_contents); + if ($form->{"format"} =~ /postscript/i) { return $self->convert_to_postscript(); } elsif ($form->{"format"} =~ /pdf/i) { @@ -323,6 +481,7 @@ sub convert_to_postscript { my ($form, $userspath) = ($self->{"form"}, $self->{"userspath"}); # Convert the tex file to postscript + local $ENV{TEXINPUTS} = ".:" . $form->{cwd} . "/" . $form->{templates} . ":" . $ENV{TEXINPUTS}; if (!chdir("$userspath")) { $self->{"error"} = "chdir : $!"; @@ -339,8 +498,10 @@ sub convert_to_postscript { $ENV{openin_any} = "p"; for (my $run = 1; $run <= 2; $run++) { - system("${latex} --interaction=nonstopmode $form->{tmpfile} " . - "> $form->{tmpfile}.err"); + if (system("${latex} --interaction=nonstopmode $form->{tmpfile} " . + "> $form->{tmpfile}.err") == -1) { + die "system call to $latex failed: $!"; + } if ($?) { $ENV{HOME} = $old_home; $ENV{openin_any} = $old_openin_any; @@ -351,12 +512,14 @@ sub convert_to_postscript { $form->{tmpfile} =~ s/tex$/dvi/; - system("dvips $form->{tmpfile} -o -q > /dev/null"); + if (system("dvips $form->{tmpfile} -o -q > /dev/null") == -1) { + die "system call to dvips failed: $!"; + } $ENV{HOME} = $old_home; $ENV{openin_any} = $old_openin_any; if ($?) { - $self->{"error"} = "dvips : $!"; + $self->{"error"} = "dvips : $?"; $self->cleanup('dvips'); return 0; } @@ -372,6 +535,7 @@ sub convert_to_pdf { my ($form, $userspath) = ($self->{"form"}, $self->{"userspath"}); # Convert the tex file to PDF + local $ENV{TEXINPUTS} = ".:" . $form->{cwd} . "/" . $form->{templates} . ":" . $ENV{TEXINPUTS}; if (!chdir("$userspath")) { $self->{"error"} = "chdir : $!"; @@ -388,8 +552,11 @@ sub convert_to_pdf { $ENV{openin_any} = "p"; for (my $run = 1; $run <= 2; $run++) { - system("${latex} --interaction=nonstopmode $form->{tmpfile} " . - "> $form->{tmpfile}.err"); + if (system("${latex} --interaction=nonstopmode $form->{tmpfile} " . + "> $form->{tmpfile}.err") == -1) { + die "system call to $latex failed: $!"; + } + if ($?) { $ENV{HOME} = $old_home; $ENV{openin_any} = $old_openin_any; @@ -403,6 +570,8 @@ sub convert_to_pdf { $form->{tmpfile} =~ s/tex$/pdf/; $self->cleanup(); + + return 1; } sub _get_latex_path { @@ -423,4 +592,65 @@ sub uses_temp_file { return 1; } +sub parse_and_create_pdf { + my ($class, $template_file_name, %params) = @_; + + my $keep_temp = $::lx_office_conf{debug} && $::lx_office_conf{debug}->{keep_temp_files}; + my ($tex_fh, $tex_file_name) = File::Temp::tempfile( + 'kivitendo-printXXXXXX', + SUFFIX => '.tex', + DIR => $::lx_office_conf{paths}->{userspath}, + UNLINK => $keep_temp ? 0 : 1,, + ); + + my $old_wd = getcwd(); + + my $local_form = Form->new(''); + $local_form->{cwd} = $old_wd; + $local_form->{IN} = $template_file_name; + $local_form->{tmpdir} = $::lx_office_conf{paths}->{userspath}; + $local_form->{tmpfile} = $tex_file_name; + $local_form->{templates} = SL::DB::Default->get->templates; + + foreach (keys %params) { + croak "The parameter '$_' must not be used." if exists $local_form->{$_}; + $local_form->{$_} = $params{$_}; + } + + my $error; + eval { + my $template = SL::Template::LaTeX->new(file_name => $template_file_name, form => $local_form); + my $result = $template->parse($tex_fh) && $template->convert_to_pdf; + + die $template->{error} unless $result; + + 1; + } or do { $error = $EVAL_ERROR; }; + + chdir $old_wd; + close $tex_fh; + + if ($keep_temp) { + chmod(((stat $tex_file_name)[2] & 07777) | 0660, $tex_file_name); + } else { + my $tmpfile = $tex_file_name; + $tmpfile =~ s/\.\w+$//; + unlink(grep { !m/\.pdf$/ } <$tmpfile.*>); + } + + return (error => $error) if $error; + return (file_name => do { $tex_file_name =~ s/tex$/pdf/; $tex_file_name }); +} + +sub global_vars { + { + AUTH => $::auth, + INSTANCE_CONF => $::instance_conf, + LOCALE => $::locale, + LXCONFIG => $::lx_office_conf, + LXDEBUG => $::lxdebug, + MYCONFIG => \%::myconfig, + }; +} + 1;