X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;ds=inline;f=SL%2FTemplate%2FLaTeX.pm;h=293db2e18c5bbb58cb495563026aa3c18134867d;hb=d339e8dcec0d5e03f7f7a6cb1b80c679acad35a3;hp=7b89aa4ab370e78682f5207c7f3f5e15e0da0cf7;hpb=f6ea0fa8433622ebf657a3a6a92f80e75b058bff;p=kivitendo-erp.git diff --git a/SL/Template/LaTeX.pm b/SL/Template/LaTeX.pm index 7b89aa4ab..293db2e18 100644 --- a/SL/Template/LaTeX.pm +++ b/SL/Template/LaTeX.pm @@ -4,6 +4,115 @@ 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 Scalar::Util qw(blessed); +use Unicode::Normalize qw(); + +use SL::DB::Default; +use SL::System::Process; + +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", + '\s*
\s* )+ \Z }{}imgx; + + 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; @@ -13,25 +122,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; - } - - $variable =~ s/[\x00-\x1f]//g; + $content =~ s{[^\p{Print}\n]|\p{Cf}}{}g; + $variable =~ s{[^\p{Print}\n]|\p{Cf}}{}g; - return $variable; + return $formatter->($self, $content, variable => $variable); } sub parse_foreach { @@ -60,6 +161,7 @@ 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) @@ -100,6 +202,7 @@ sub parse_foreach { } $current_line += $lines; } + #stop removing code here. if ( ref $linetotal_array eq 'ARRAY' && $i < scalar(@{$linetotal_array})) { @@ -132,7 +235,7 @@ sub find_end { my $keyword_pos = $pos - 1 + $tag_start_len; - if ((substr($text, $keyword_pos, 2) eq 'if') || (substr($text, $keyword_pos, 3) eq 'foreach')) { + if ((substr($text, $keyword_pos, 2) eq 'if') || (substr($text, $keyword_pos, 7) eq 'foreach')) { $depth++; } elsif ((substr($text, $keyword_pos, 4) eq 'else') && (1 == $depth)) { @@ -186,15 +289,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); @@ -252,6 +355,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 { @@ -275,7 +381,7 @@ sub _parse_config_lines { while ($i < $num_lines) { my $line = $lines->[$i]; - if ($line !~ m/^${comment_start}\s*config\s*:(.*)${comment_end}$/i) { + if ($line !~ m/^${comment_start}\s*config\s*:(.*?)${comment_end}$/i) { $i++; next; } @@ -286,29 +392,76 @@ sub _parse_config_lines { } } +sub _embed_file_directive { + my ($self, $file) = @_; + + # { source => $xmlfile, + # name => 'factur-x.xml', + # description => $::locale->text('Factur-X/ZUGFeRD invoice'), } + + my $file_name = blessed($file->{source}) && $file->{source}->can('filename') ? $file->{source}->filename : "" . $file->{source}->filename; + my $embed_name = $file->{name} // $file_name; + $embed_name =~ s{.*/}{}; + + my $embed_name_ascii = $::locale->quote_special_chars('filenames', $embed_name); + $embed_name_ascii =~ s{[^a-z0-9!@#$%^&*(){}[\],.+'"=_-]+}{}gi; + + my @options; + + my $add_opt = sub { + my ($name, $value) = @_; + return if ($value // '') eq ''; + push @options, sprintf('%s={%s}', $name, $value); # TODO: escaping + }; + + $add_opt->('filespec', $embed_name_ascii); + $add_opt->('ucfilespec', $embed_name); + $add_opt->('desc', $file->{description}); + $add_opt->('afrelationship', $file->{relationship}); + $add_opt->('mimetype', $file->{mime_type}); + + return sprintf('\embedfile[%s]{%s}', join(',', @options), $file_name); +} + sub _force_mandatory_packages { - my $self = shift; - my $lines = shift; + my ($self, @lines) = @_; + my @new_lines; - my (%used_packages, $document_start_line); + my %used_packages; + my @required_packages = qw(textcomp ulem); + push @required_packages, 'embedfile' if $self->{pdf_a}; - foreach my $i (0 .. scalar @{ $lines } - 1) { - if ($lines->[$i] =~ m/\\usepackage[^\{]*{(.*?)}/) { + foreach my $line (@lines) { + if ($line =~ m/\\usepackage[^\{]*{(.*?)}/) { $used_packages{$1} = 1; - } elsif ($lines->[$i] =~ m/\\begin{document}/) { - $document_start_line = $i; - last; + } elsif ($line =~ m/\\begin\{document\}/) { + if ($self->{pdf_a} && $self->{pdf_a}->{xmp}) { + my $version = $self->{pdf_a}->{version} // '3a'; + my $xmp_file_name = $self->{userspath} . "/pdfa.xmp"; + my $out = IO::File->new($xmp_file_name, ">:encoding(utf-8)") || croak "Error creating ${xmp_file_name}: $!"; + $out->print(Encode::encode('utf-8', $self->{pdf_a}->{xmp})); + $out->close; + + push @new_lines, ( + "\\usepackage[a-${version},mathxmp]{pdfx}[2018/12/22]\n", + "\\usepackage[genericmode]{tagpdf}\n", + "\\tagpdfsetup{activate-all}\n", + "\\hypersetup{pdfstartview=}\n", + ); + } - } - } + push @new_lines, map { "\\usepackage{$_}\n" } grep { !$used_packages{$_} } @required_packages; + push @new_lines, $line; + push @new_lines, map { $self->_embed_file_directive($_) } @{ $self->{pdf_attachments} // [] }; - $document_start_line = scalar @{ $lines } - 1 if (!defined $document_start_line); + next; + } - if (!$used_packages{textcomp}) { - splice @{ $lines }, $document_start_line, 0, "\\usepackage{textcomp}\n"; - $document_start_line++; + push @new_lines, $line; } + + return @new_lines; } sub parse { @@ -317,38 +470,49 @@ 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 =