PDF-Erzeugung: Support fürs Erzeugen von PDF/A-konformen PDFs
authorMoritz Bunkus <m.bunkus@linet-services.de>
Tue, 5 Nov 2019 12:46:38 +0000 (13:46 +0100)
committerMoritz Bunkus <m.bunkus@linet-services.de>
Fri, 28 Feb 2020 14:01:42 +0000 (15:01 +0100)
Es gibt einen Rose-DB-Helfer `S:D:H:PDF_A`, der die erforderliche
Struktur für `SL::Template::LaTeX` (via
`$form->{TEMPLATE_DRIVER_OPTIONS}->{pdf_a}`) erzeugt. Der Helfer ist
für die üblichen Belegklassen (`S:D:{Order,DeliveryOrder,Invoice}`)
eingebunden.

Weiterhin können über
`$form->{TEMPLATE_DRIVER_OPTIONS}->{pdf_attachments}` Dateien in PDFs
eingebettet werden.

Das Erzeugen funktioniert nur für LaTeX-Vorlagen, nicht für
OpenDocument-Vorlagen.

Für LaTeX werden nun weitere Pakete benötigt, deren Präsenz vom
Installations-Check geprüft werden: `pdfx`, `embedfile`.

Für `embedfile` muss leider eine von uns modifizierte Version benutzt
werden, da der letzte offizielle Release das PDF-Attribut
`/AFRelationship` nicht unterstützt. Dieses Attribut muss aber für
jede in ein PDF/A eingebettete Datei gesetzt sein. Daher liefern wir
die modifizierte Version im neuen Unterverzeichnis `texmf` mit, das
über die Umgebungsvariable `TEXINPUTS` höhere Präferenz bekommt, als
die Systemverzeichnisse.

Die relevante Modifikation ist der folgende Pull-Request:

https://github.com/ho-tex/oberdiek/pull/72

Siehe auch folgender Bug:

https://github.com/ho-tex/oberdiek/issues/37

SL/DB/Helper/PDF_A.pm [new file with mode: 0644]
SL/DB/Invoice.pm
SL/Template/LaTeX.pm
scripts/installation_check.pl
texmf/embedfile.sty [new file with mode: 0644]

diff --git a/SL/DB/Helper/PDF_A.pm b/SL/DB/Helper/PDF_A.pm
new file mode 100644 (file)
index 0000000..4f52844
--- /dev/null
@@ -0,0 +1,33 @@
+package SL::DB::Helper::PDF_A;
+
+use strict;
+
+use parent qw(Exporter);
+our @EXPORT = qw(create_pdf_a_print_options);
+
+sub create_pdf_a_print_options {
+  my ($self) = @_;
+
+  require SL::DB::Language;
+
+  my $language_code = $self->can('language_id') && $self->language_id ? SL::DB::Language->load_cached($self->language_id)->template_code : undef;
+  $language_code  ||= 'de';
+  my $pdf_language  = $language_code =~ m{deutsch|german|^de$}i   ? 'de-DE'
+                    : $language_code =~ m{englisch|english|^en$}i ? 'en-US'
+                    :                                               '';
+  my $author        = do {
+    no warnings 'once';
+    $::instance_conf->get_company
+  };
+
+  return {
+    version   => '3b',
+    meta_data => {
+      title    => $self->displayable_name,
+      author   => $author,
+      language => $pdf_language,
+    },
+  };
+}
+
+1;
index 6f8438f..673fae9 100644 (file)
@@ -13,6 +13,7 @@ use SL::DB::Helper::AttrHTML;
 use SL::DB::Helper::AttrSorted;
 use SL::DB::Helper::FlattenToForm;
 use SL::DB::Helper::LinkedRecords;
+use SL::DB::Helper::PDF_A;
 use SL::DB::Helper::PriceTaxCalculator;
 use SL::DB::Helper::PriceUpdater;
 use SL::DB::Helper::TransNumberGenerator;
index 6a5d8e4..d4b336b 100644 (file)
@@ -11,9 +11,11 @@ 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',
@@ -388,33 +390,79 @@ sub _parse_config_lines {
   }
 }
 
+sub _embed_file_directive {
+  my ($self, $file) = @_;
+
+  # { source      => $xmlfile,
+  #   name        => 'ZUGFeRD-invoice.xml',
+  #   description => $::locale->text('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 @options;
+
+  my $add_opt = sub {
+    my ($name, $value) = @_;
+    return if ($value // '') eq '';
+    push @options, sprintf('%s={%s}', $name, $value); # TODO: escaping
+  };
+
+ $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, $last_usepackage_line);
+  my (%used_packages, $at_beginning_of_document);
+  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;
-      $last_usepackage_line = $i;
 
-    } elsif ($lines->[$i] =~ m/\\begin\{document\}/) {
-      $document_start_line = $i;
-      last;
+    } elsif (($line =~ m/\\documentclass/) && $self->{pdf_a}) {
+      my $version = $self->{pdf_a}->{version}   // '3a';
+      my $meta    = $self->{pdf_a}->{meta_data} // {};
+
+      push @new_lines, (
+        "\\RequirePackage{filecontents}\n",
+        "\\begin{filecontents*}{\\jobname.xmpdata}\n",
+        ($meta->{title}    ? sprintf("\\Title{%s}\n",    $meta->{title})    : ""),
+        ($meta->{author}   ? sprintf("\\Author{%s}\n",   $meta->{author})   : ""),
+        ($meta->{language} ? sprintf("\\Language{%s}\n", $meta->{language}) : ""),
+        "\\end{filecontents*}\n",
+        $line,
+        "\\usepackage[a-${version},mathxmp]{pdfx}[2018/12/22]\n",
+        "\\usepackage[genericmode]{tagpdf}\n",
+        "\\tagpdfsetup{activate-all}\n",
+        "\\hypersetup{pdfstartview=}\n",
+      );
 
+      next;
+
+    } elsif ($line =~ m/\\begin\{document\}/) {
+      $at_beginning_of_document = 1;
+      push @new_lines, map { "\\usepackage{$_}\n" } grep { !$used_packages{$_} } @required_packages;
     }
-  }
 
-  my $insertion_point = defined($document_start_line)  ? $document_start_line
-                      : defined($last_usepackage_line) ? $last_usepackage_line
-                      :                                  scalar @{ $lines } - 1;
+    push @new_lines, $line;
 
-  foreach my $package (qw(textcomp ulem)) {
-    next if $used_packages{$package};
-    splice @{ $lines }, $insertion_point, 0, "\\usepackage{${package}}\n";
-    $insertion_point++;
+    if ($at_beginning_of_document) {
+      $at_beginning_of_document = 0;
+
+      push @new_lines, map { $self->_embed_file_directive($_) } @{ $self->{pdf_attachments} // [] };
+    }
   }
+
+  return @new_lines;
 }
 
 sub parse {
@@ -431,7 +479,7 @@ sub parse {
   close(IN);
 
   $self->_parse_config_lines(\@lines);
-  $self->_force_mandatory_packages(\@lines) if (ref $self eq 'SL::Template::LaTeX');
+  @lines = $self->_force_mandatory_packages(@lines) if (ref $self eq 'SL::Template::LaTeX');
 
   my $contents = join("", @lines);
 
@@ -476,12 +524,21 @@ sub parse {
   }
 }
 
+sub _texinputs_path {
+  my ($self, $templates_path) = @_;
+
+  my $exe_dir     = SL::System::Process::exe_dir();
+  $templates_path = $exe_dir . '/' . $templates_path unless $templates_path =~ m{^/};
+
+  return join(':', grep({ $_ } ('.', $exe_dir . '/texmf', $templates_path, $ENV{TEXINPUTS})), '');
+}
+
 sub convert_to_postscript {
   my ($self) = @_;
   my ($form, $userspath) = ($self->{"form"}, $self->{"userspath"});
 
   # Convert the tex file to postscript
-  local $ENV{TEXINPUTS} = ".:" . $form->{cwd} . "/" . $form->{templates} . ":" . $ENV{TEXINPUTS};
+  local $ENV{TEXINPUTS} = $self->_texinputs_path($form->{templates});
 
   if (!chdir("$userspath")) {
     $self->{"error"} = "chdir : $!";
@@ -535,7 +592,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};
+  local $ENV{TEXINPUTS} = $self->_texinputs_path($form->{templates});
 
   if (!chdir("$userspath")) {
     $self->{"error"} = "chdir : $!";
index fa60838..887013b 100755 (executable)
@@ -13,6 +13,7 @@ BEGIN {
 
 use strict;
 use Getopt::Long;
+use List::MoreUtils qw(uniq);
 use Pod::Usage;
 use Term::ANSIColor;
 use Text::Wrap;
@@ -156,7 +157,12 @@ sub check_template_dir {
 
   print_header("Checking LaTeX Dependencies for Master Templates '$dir'");
   kpsewhich($path, 'cls', $_) for SL::InstallationCheck::classes_from_latex($path, '\documentclass');
-  kpsewhich($path, 'sty', $_) for SL::InstallationCheck::classes_from_latex($path, '\usepackage');
+
+  my @sty = sort { $a cmp $b } uniq (
+    SL::InstallationCheck::classes_from_latex($path, '\usepackage'),
+    qw(textcomp ulem pdfx embedfile)
+  );
+  kpsewhich($path, 'sty', $_) for @sty;
 }
 
 our $mastertemplate_path = './templates/print/';
diff --git a/texmf/embedfile.sty b/texmf/embedfile.sty
new file mode 100644 (file)
index 0000000..167dea1
--- /dev/null
@@ -0,0 +1,799 @@
+%% !!NOTE NOTE NOTE!!
+%%
+%% This is a modified version of `embedfile.sty' generated from a
+%% modified `embedfile.dtx' incorporating the following pull request:
+%% https://github.com/ho-tex/oberdiek/pull/72
+%%
+%% This PR adds support for creating PDF/A-compliant attachments. See
+%% also the following issue:
+%% https://github.com/ho-tex/oberdiek/issues/37
+%%
+%% !!END OF NOTE NOTE NOTE!!
+%%
+%%
+%% This is file `embedfile.sty',
+%% generated with the docstrip utility.
+%%
+%% The original source files were:
+%%
+%% embedfile.dtx  (with options: `package')
+%%
+%% This is a generated file.
+%%
+%% Project: embedfile
+%% Version: 2018/11/01 v2.8
+%%
+%% Copyright (C) 2006-2011 by
+%%    Heiko Oberdiek <heiko.oberdiek at googlemail.com>
+%%
+%% This work may be distributed and/or modified under the
+%% conditions of the LaTeX Project Public License, either
+%% version 1.3c of this license or (at your option) any later
+%% version. This version of this license is in
+%%    http://www.latex-project.org/lppl/lppl-1-3c.txt
+%% and the latest version of this license is in
+%%    http://www.latex-project.org/lppl.txt
+%% and version 1.3 or later is part of all distributions of
+%% LaTeX version 2005/12/01 or later.
+%%
+%% This work has the LPPL maintenance status "maintained".
+%%
+%% This Current Maintainer of this work is Heiko Oberdiek.
+%%
+%% The Base Interpreter refers to any `TeX-Format',
+%% because some files are installed in TDS:tex/generic//.
+%%
+%% This work consists of the main source file embedfile.dtx
+%% and the derived files
+%%    embedfile.sty, embedfile.pdf, embedfile.ins, embedfile.drv,
+%%    dtx-attach.sty, embedfile-example-plain.tex,
+%%    embedfile-example-collection.tex, embedfile-test1.tex,
+%%    embedfile-test2.tex, embedfile-test3.tex,
+%%    embedfile-test4.tex.
+%%
+\begingroup\catcode61\catcode48\catcode32=10\relax%
+  \catcode13=5 % ^^M
+  \endlinechar=13 %
+  \catcode35=6 % #
+  \catcode39=12 % '
+  \catcode44=12 % ,
+  \catcode45=12 % -
+  \catcode46=12 % .
+  \catcode58=12 % :
+  \catcode64=11 % @
+  \catcode123=1 % {
+  \catcode125=2 % }
+  \expandafter\let\expandafter\x\csname ver@embedfile.sty\endcsname
+  \ifx\x\relax % plain-TeX, first loading
+  \else
+    \def\empty{}%
+    \ifx\x\empty % LaTeX, first loading,
+      % variable is initialized, but \ProvidesPackage not yet seen
+    \else
+      \expandafter\ifx\csname PackageInfo\endcsname\relax
+        \def\x#1#2{%
+          \immediate\write-1{Package #1 Info: #2.}%
+        }%
+      \else
+        \def\x#1#2{\PackageInfo{#1}{#2, stopped}}%
+      \fi
+      \x{embedfile}{The package is already loaded}%
+      \aftergroup\endinput
+    \fi
+  \fi
+\endgroup%
+\begingroup\catcode61\catcode48\catcode32=10\relax%
+  \catcode13=5 % ^^M
+  \endlinechar=13 %
+  \catcode35=6 % #
+  \catcode39=12 % '
+  \catcode40=12 % (
+  \catcode41=12 % )
+  \catcode44=12 % ,
+  \catcode45=12 % -
+  \catcode46=12 % .
+  \catcode47=12 % /
+  \catcode58=12 % :
+  \catcode64=11 % @
+  \catcode91=12 % [
+  \catcode93=12 % ]
+  \catcode123=1 % {
+  \catcode125=2 % }
+  \expandafter\ifx\csname ProvidesPackage\endcsname\relax
+    \def\x#1#2#3[#4]{\endgroup
+      \immediate\write-1{Package: #3 #4}%
+      \xdef#1{#4}%
+    }%
+  \else
+    \def\x#1#2[#3]{\endgroup
+      #2[{#3}]%
+      \ifx#1\@undefined
+        \xdef#1{#3}%
+      \fi
+      \ifx#1\relax
+        \xdef#1{#3}%
+      \fi
+    }%
+  \fi
+\expandafter\x\csname ver@embedfile.sty\endcsname
+\ProvidesPackage{embedfile}%
+  [2018/11/01 v2.8 Embed files into PDF (HO)]%
+\begingroup\catcode61\catcode48\catcode32=10\relax%
+  \catcode13=5 % ^^M
+  \endlinechar=13 %
+  \catcode123=1 % {
+  \catcode125=2 % }
+  \catcode64=11 % @
+  \def\x{\endgroup
+    \expandafter\edef\csname EmFi@AtEnd\endcsname{%
+      \endlinechar=\the\endlinechar\relax
+      \catcode13=\the\catcode13\relax
+      \catcode32=\the\catcode32\relax
+      \catcode35=\the\catcode35\relax
+      \catcode61=\the\catcode61\relax
+      \catcode64=\the\catcode64\relax
+      \catcode123=\the\catcode123\relax
+      \catcode125=\the\catcode125\relax
+    }%
+  }%
+\x\catcode61\catcode48\catcode32=10\relax%
+\catcode13=5 % ^^M
+\endlinechar=13 %
+\catcode35=6 % #
+\catcode64=11 % @
+\catcode123=1 % {
+\catcode125=2 % }
+\def\TMP@EnsureCode#1#2{%
+  \edef\EmFi@AtEnd{%
+    \EmFi@AtEnd
+    \catcode#1=\the\catcode#1\relax
+  }%
+  \catcode#1=#2\relax
+}
+\TMP@EnsureCode{39}{12}% '
+\TMP@EnsureCode{40}{12}% (
+\TMP@EnsureCode{41}{12}% )
+\TMP@EnsureCode{44}{12}% ,
+\TMP@EnsureCode{46}{12}% .
+\TMP@EnsureCode{47}{12}% /
+\TMP@EnsureCode{58}{12}% :
+\TMP@EnsureCode{60}{12}% <
+\TMP@EnsureCode{62}{12}% >
+\TMP@EnsureCode{91}{12}% [
+\TMP@EnsureCode{93}{12}% ]
+\TMP@EnsureCode{96}{12}% `
+\edef\EmFi@AtEnd{\EmFi@AtEnd\noexpand\endinput}
+\begingroup\expandafter\expandafter\expandafter\endgroup
+\expandafter\ifx\csname RequirePackage\endcsname\relax
+  \def\EmFi@RequirePackage#1[#2]{%
+    \input #1.sty\relax
+  }%
+\else
+  \let\EmFi@RequirePackage\RequirePackage
+\fi
+\EmFi@RequirePackage{infwarerr}[2007/09/09]%
+\def\EmFi@Error{%
+  \@PackageError{embedfile}%
+}
+\ifx\pdfextension\@undefined\else
+    \protected\def\pdflastobj {\numexpr\pdffeedback lastobj\relax}
+    \protected\def\pdfnames   {\pdfextension names }
+    \protected\def\pdfobj     {\pdfextension obj }
+    \let\pdfoutput            \outputmode
+\fi
+\EmFi@RequirePackage{ifpdf}[2007/09/09]
+\ifpdf
+\else
+  \EmFi@Error{%
+    Missing pdfTeX in PDF mode%
+  }{%
+    Currently other drivers are not supported. %
+    Package loading is aborted.%
+  }%
+  \expandafter\EmFi@AtEnd
+\fi%
+\EmFi@RequirePackage{pdftexcmds}[2007/11/11]
+\EmFi@RequirePackage{ltxcmds}[2010/03/01]
+\EmFi@RequirePackage{kvsetkeys}[2010/03/01]
+\EmFi@RequirePackage{kvdefinekeys}[2010/03/01]
+\begingroup\expandafter\expandafter\expandafter\endgroup
+\expandafter\ifx\csname pdf@filesize\endcsname\relax
+  \EmFi@Error{%
+    Unsupported pdfTeX version%
+  }{%
+    At least version 1.30 is necessary. Package loading is aborted.%
+  }%
+  \expandafter\EmFi@AtEnd
+\fi%
+\EmFi@RequirePackage{pdfescape}[2007/11/11]
+\def\EmFi@temp#1{%
+  \expandafter\EdefSanitize\csname EmFi@S@#1\endcsname{#1}%
+}
+\EmFi@temp{details}%
+\EmFi@temp{tile}%
+\EmFi@temp{hidden}%
+\EmFi@temp{text}
+\EmFi@temp{date}
+\EmFi@temp{number}
+\EmFi@temp{file}
+\EmFi@temp{desc}
+\EmFi@temp{afrelationship}
+\EmFi@temp{moddate}
+\EmFi@temp{creationdate}
+\EmFi@temp{size}
+\EmFi@temp{ascending}
+\EmFi@temp{descending}
+\EmFi@temp{true}
+\EmFi@temp{false}
+\ltx@newif\ifEmFi@collection
+\ltx@newif\ifEmFi@sort
+\ltx@newif\ifEmFi@visible
+\ltx@newif\ifEmFi@edit
+\ltx@newif\ifEmFi@item
+\ltx@newif\ifEmFi@finished
+\ltx@newif\ifEmFi@id
+\def\EmFi@GlobalKey#1#2{%
+  \global\expandafter\let\csname KV@#1@#2\expandafter\endcsname
+                         \csname KV@#1@#2\endcsname
+}
+\def\EmFi@GlobalDefaultKey#1#2{%
+  \EmFi@GlobalKey{#1}{#2}%
+  \global\expandafter\let
+      \csname KV@#1@#2@default\expandafter\endcsname
+      \csname KV@#1@#2@default\endcsname
+}
+\def\EmFi@DefineKey#1#2{%
+  \kv@define@key{EmFi}{#1}{%
+    \expandafter\def\csname EmFi@#1\endcsname{##1}%
+  }%
+  \expandafter\def\csname EmFi@#1\endcsname{#2}%
+}
+\EmFi@DefineKey{mimetype}{}
+\EmFi@DefineKey{filespec}{\EmFi@file}
+\EmFi@DefineKey{ucfilespec}{}
+\EmFi@DefineKey{filesystem}{}
+\EmFi@DefineKey{desc}{}
+\EmFi@DefineKey{afrelationship}{}
+\EmFi@DefineKey{stringmethod}{%
+  \ifx\pdfstringdef\@undefined
+    escape%
+  \else
+    \ifx\pdfstringdef\relax
+      escape%
+    \else
+      psd%
+    \fi
+  \fi
+}
+\kv@define@key{EmFi}{id}{%
+  \def\EmFi@id{#1}%
+  \EmFi@idtrue
+}
+\def\EmFi@defobj#1{%
+  \ifEmFi@id
+    \expandafter\xdef\csname EmFi@#1@\EmFi@id\endcsname{%
+      \the\pdflastobj\ltx@space 0 R%
+    }%
+  \fi
+}
+\def\embedfileifobjectexists#1#2{%
+  \expandafter\ifx\csname EmFi@#2@#1\endcsname\relax
+    \expandafter\ltx@secondoftwo
+  \else
+    \expandafter\ltx@firstoftwo
+  \fi
+}
+\def\embedfilegetobject#1#2{%
+  \embedfileifobjectexists{#1}{#2}{%
+    \csname EmFi@#2@#1\endcsname
+  }{%
+    0 0 R%
+  }%
+}
+\kv@define@key{EmFi}{view}[]{%
+  \EdefSanitize\EmFi@temp{#1}%
+  \def\EmFi@next{%
+    \global\EmFi@collectiontrue
+  }%
+  \ifx\EmFi@temp\ltx@empty
+    \let\EmFi@view\EmFi@S@details
+  \else\ifx\EmFi@temp\EmFi@S@details
+    \let\EmFi@view\EmFi@S@details
+  \else\ifx\EmFi@temp\EmFi@S@tile
+    \let\EmFi@view\EmFi@S@tile
+  \else\ifx\EmFi@temp\EmFi@S@hidden
+    \let\EmFi@view\EmFi@S@hidden
+  \else
+    \let\EmFi@next\relax
+    \EmFi@Error{%
+      Unknown value `\EmFi@temp' for key `view'.\MessageBreak
+      Supported values: `details', `tile', `hidden'.%
+    }\@ehc
+  \fi\fi\fi\fi
+  \EmFi@next
+}
+\EmFi@DefineKey{initialfile}{}
+\def\embedfilesetup{%
+  \ifEmFi@finished
+    \def\EmFi@next##1{}%
+    \EmFi@Error{%
+      \string\embedfilefield\ltx@space after \string\embedfilefinish
+    }{%
+      The list of embedded files is already written.%
+    }%
+  \else
+    \def\EmFi@next{%
+      \kvsetkeys{EmFi}%
+    }%
+  \fi
+  \EmFi@next
+}
+\def\EmFi@schema{}
+\gdef\EmFi@order{0}
+\let\EmFi@@order\relax
+\def\EmFi@fieldlist{}
+\def\EmFi@sortcase{0}%
+\def\embedfilefield#1#2{%
+  \ifEmFi@finished
+    \EmFi@Error{%
+      \string\embedfilefield\ltx@space after \string\embedfilefinish
+    }{%
+      The list of embedded files is already written.%
+    }%
+  \else
+    \global\EmFi@collectiontrue
+    \EdefSanitize\EmFi@key{#1}%
+    \expandafter\ifx\csname KV@EmFi@\EmFi@key.prefix\endcsname\relax
+      \begingroup
+        \count@=\EmFi@order
+        \advance\count@ 1 %
+        \xdef\EmFi@order{\the\count@}%
+        \let\EmFi@title\EmFi@key
+        \let\EmFi@type\EmFi@S@text
+        \EmFi@visibletrue
+        \EmFi@editfalse
+        \kvsetkeys{EmFiFi}{#2}%
+        \EmFi@convert\EmFi@title\EmFi@title
+        \xdef\EmFi@schema{%
+          \EmFi@schema
+          /\pdf@escapename{\EmFi@key}<<%
+            /Subtype/%
+            \ifx\EmFi@type\EmFi@S@date D%
+            \else\ifx\EmFi@type\EmFi@S@number N%
+            \else\ifx\EmFi@type\EmFi@S@file F%
+            \else\ifx\EmFi@type\EmFi@S@desc Desc%
+            \else\ifx\EmFi@type\EmFi@S@afrelationship AFRelationship%
+            \else\ifx\EmFi@type\EmFi@S@moddate ModDate%
+            \else\ifx\EmFi@type\EmFi@S@creationdate CreationDate%
+            \else\ifx\EmFi@type\EmFi@S@size Size%
+            \else S%
+            \fi\fi\fi\fi\fi\fi\fi
+            /N(\EmFi@title)%
+            \EmFi@@order{\EmFi@order}%
+            \ifEmFi@visible
+            \else
+              /V false%
+            \fi
+            \ifEmFi@edit
+              /E true%
+            \fi
+          >>%
+        }%
+        \let\do\relax
+        \xdef\EmFi@fieldlist{%
+          \EmFi@fieldlist
+          \do{\EmFi@key}%
+        }%
+        \ifx\EmFi@type\EmFi@S@text
+          \kv@define@key{EmFi}{\EmFi@key.value}{%
+            \EmFi@itemtrue
+            \def\EmFi@temp{##1}%
+            \EmFi@convert\EmFi@temp\EmFi@temp
+            \expandafter\def\csname EmFi@V@#1%
+            \expandafter\endcsname\expandafter{%
+              \expandafter(\EmFi@temp)%
+            }%
+          }%
+          \EmFi@GlobalKey{EmFi}{\EmFi@key.value}%
+        \else\ifx\EmFi@type\EmFi@S@date
+          \kv@define@key{EmFi}{\EmFi@key.value}{%
+            \EmFi@itemtrue
+            \def\EmFi@temp{##1}%
+            \EmFi@convert\EmFi@temp\EmFi@temp
+            \expandafter\def\csname EmFi@V@#1%
+            \expandafter\endcsname\expandafter{%
+              \expandafter(\EmFi@temp)%
+            }%
+          }%
+          \EmFi@GlobalKey{EmFi}{\EmFi@key.value}%
+        \else\ifx\EmFi@type\EmFi@S@number
+          \kv@define@key{EmFi}{\EmFi@key.value}{%
+            \EmFi@itemtrue
+            \expandafter\EdefSanitize\csname EmFi@V@#1\endcsname{ ##1}%
+          }%
+          \EmFi@GlobalKey{EmFi}{\EmFi@key.value}%
+        \fi\fi\fi
+        \kv@define@key{EmFi}{\EmFi@key.prefix}{%
+          \EmFi@itemtrue
+          \expandafter\def\csname EmFi@P@#1\endcsname{##1}%
+        }%
+        \EmFi@GlobalKey{EmFi}{\EmFi@key.prefix}%
+        \kv@define@key{EmFiSo}{\EmFi@key}[ascending]{%
+          \EdefSanitize\EmFi@temp{##1}%
+          \ifx\EmFi@temp\EmFi@S@ascending
+            \def\EmFi@temp{true}%
+          \else\ifx\EmFi@temp\EmFi@S@descending
+            \def\EmFi@temp{false}%
+          \else
+            \def\EmFi@temp{}%
+            \EmFi@Error{%
+              Unknown sort order `\EmFi@temp'.\MessageBreak
+              Supported values: `\EmFi@S@ascending', %
+              `\EmFi@S@descending
+            }\@ehc
+          \fi\fi
+          \ifx\EmFi@temp\ltx@empty
+          \else
+            \xdef\EmFi@sortkeys{%
+              \EmFi@sortkeys
+              /\pdf@escapename{#1}%
+            }%
+            \ifx\EmFi@sortorders\ltx@empty
+              \global\let\EmFi@sortorders\EmFi@temp
+              \gdef\EmFi@sortcase{1}%
+            \else
+              \xdef\EmFi@sortorders{%
+                \EmFi@sortorders
+                \ltx@space
+                \EmFi@temp
+              }%
+              \xdef\EmFi@sortcase{2}%
+            \fi
+          \fi
+        }%
+        \EmFi@GlobalDefaultKey{EmFiSo}\EmFi@key
+      \endgroup
+    \else
+      \EmFi@Error{%
+        Field `\EmFi@key' is already defined%
+      }\@ehc
+    \fi
+  \fi
+}
+\kv@define@key{EmFiFi}{type}{%
+  \EdefSanitize\EmFi@temp{#1}%
+  \ifx\EmFi@temp\EmFi@S@text
+    \let\EmFi@type\EmFi@temp
+  \else\ifx\EmFi@temp\EmFi@S@date
+    \let\EmFi@type\EmFi@temp
+  \else\ifx\EmFi@temp\EmFi@S@number
+    \let\EmFi@type\EmFi@temp
+  \else\ifx\EmFi@temp\EmFi@S@file
+    \let\EmFi@type\EmFi@temp
+  \else\ifx\EmFi@temp\EmFi@S@desc
+    \let\EmFi@type\EmFi@temp
+  \else\ifx\EmFi@temp\EmFi@S@afrelationship
+    \let\EmFi@type\EmFi@temp
+  \else\ifx\EmFi@temp\EmFi@S@moddate
+    \let\EmFi@type\EmFi@temp
+  \else\ifx\EmFi@temp\EmFi@S@creationdate
+    \let\EmFi@type\EmFi@temp
+  \else\ifx\EmFi@temp\EmFi@S@size
+    \let\EmFi@type\EmFi@temp
+  \else
+    \EmFi@Error{%
+      Unknown type `\EmFi@temp'.\MessageBreak
+      Supported types: `text', `date', `number', `file',\MessageBreak
+      `desc', `afrelationship', `moddate', `creationdate', `size'%
+    }%
+  \fi\fi\fi\fi\fi\fi\fi\fi\fi
+}
+\kv@define@key{EmFiFi}{title}{%
+  \def\EmFi@title{#1}%
+}
+\def\EmFi@setboolean#1#2{%
+  \EdefSanitize\EmFi@temp{#2}%
+  \ifx\EmFi@temp\EmFi@S@true
+    \csname EmFi@#1true\endcsname
+  \else
+    \ifx\EmFi@temp\EmFi@S@false
+      \csname EmFi@#1false\endcsname
+    \else
+      \EmFi@Error{%
+        Unknown value `\EmFi@temp' for key `#1'.\MessageBreak
+        Supported values: `true', `false'%
+      }\@ehc
+    \fi
+  \fi
+}
+\kv@define@key{EmFiFi}{visible}[true]{%
+  \EmFi@setboolean{visible}{#1}%
+}
+\kv@define@key{EmFiFi}{edit}[true]{%
+  \EmFi@setboolean{edit}{#1}%
+}
+\def\EmFi@sortkeys{}
+\def\EmFi@sortorders{}
+\def\embedfilesort{%
+  \kvsetkeys{EmFiSo}%
+}
+\def\embedfile{%
+  \ltx@ifnextchar[\EmFi@embedfile{\EmFi@embedfile[]}%
+}
+\def\EmFi@embedfile[#1]#2{%
+  \ifEmFi@finished
+    \EmFi@Error{%
+      \string\embedfile\ltx@space after \string\embedfilefinish
+    }{%
+      The list of embedded files is already written.%
+    }%
+  \else
+    \begingroup
+      \def\EmFi@file{#2}%
+      \kvsetkeys{EmFi}{#1}%
+      \expandafter\expandafter\expandafter
+      \ifx\expandafter\expandafter\expandafter
+          \\\pdf@filesize{\EmFi@file}\\%
+        \EmFi@Error{%
+          File `\EmFi@file' not found%
+        }{%
+          The unknown file is not embedded.%
+        }%
+      \else
+        \edef\EmFi@@filespec{%
+          \pdf@escapestring{\EmFi@filespec}%
+        }%
+        \ifx\EmFi@ucfilespec\ltx@empty
+          \let\EmFi@@ucfilespec\ltx@empty
+        \else
+          \EmFi@convert\EmFi@ucfilespec\EmFi@@ucfilespec
+        \fi
+        \ifx\EmFi@desc\ltx@empty
+          \let\EmFi@@desc\ltx@empty
+        \else
+          \EmFi@convert\EmFi@desc\EmFi@@desc
+        \fi
+        \ifx\EmFi@afrelationship\ltx@empty
+          \let\EmFi@@afrelationship\ltx@empty
+        \else
+          \EmFi@convert\EmFi@afrelationship\EmFi@@afrelationship
+        \fi
+        \ifEmFi@item
+          \let\do\EmFi@do
+          \immediate\pdfobj{%
+            <<%
+              \EmFi@fieldlist
+            >>%
+          }%
+          \edef\EmFi@ci{\the\pdflastobj}%
+        \fi
+        \immediate\pdfobj stream attr{%
+          /Type/EmbeddedFile%
+          \ifx\EmFi@mimetype\ltx@empty
+          \else
+            /Subtype/\pdf@escapename{\EmFi@mimetype}%
+          \fi
+          /Params<<%
+            /ModDate(\pdf@filemoddate{\EmFi@file})%
+            /Size \pdf@filesize{\EmFi@file}%
+            /CheckSum<\pdf@filemdfivesum{\EmFi@file}>%
+          >>%
+        }file{\EmFi@file}\relax
+        \EmFi@defobj{EmbeddedFile}%
+        \immediate\pdfobj{%
+          <<%
+            /Type/Filespec%
+            \ifx\EmFi@filesystem\ltx@empty
+            \else
+            /FS/\pdf@escapename{\EmFi@filesystem}%
+            \fi
+            /F(\EmFi@@filespec)%
+            \ifx\EmFi@@ucfilespec\ltx@empty
+            \else
+              /UF(\EmFi@@ucfilespec)%
+            \fi
+            \ifx\EmFi@@desc\ltx@empty
+            \else
+              /Desc(\EmFi@@desc)%
+            \fi
+            \ifx\EmFi@@afrelationship\ltx@empty
+            \else
+              /AFRelationship\EmFi@@afrelationship%
+            \fi
+            /EF<<%
+              /F \the\pdflastobj\ltx@space 0 R%
+            >>%
+            \ifEmFi@item
+              /CI \EmFi@ci\ltx@space 0 R%
+            \fi
+          >>%
+        }%
+        \EmFi@defobj{Filespec}%
+        \EmFi@add{%
+          \EmFi@@filespec
+        }{\the\pdflastobj\ltx@space 0 R}%
+      \fi
+    \endgroup
+  \fi
+}
+\def\EmFi@do#1{%
+  \expandafter\ifx\csname EmFi@P@#1\endcsname\relax
+    \expandafter\ifx\csname EmFi@V@#1\endcsname\relax
+    \else
+      /\pdf@escapename{#1}\csname EmFi@V@#1\endcsname
+    \fi
+  \else
+    /\pdf@escapename{#1}<<%
+      \expandafter\ifx\csname EmFi@V@#1\endcsname\relax
+      \else
+        /D\csname EmFi@V@#1\endcsname
+      \fi
+      /P(\csname EmFi@P@#1\endcsname)%
+    >>%
+  \fi
+}
+\def\EmFi@convert#1#2{%
+  \ifnum\pdf@strcmp{\EmFi@stringmethod}{psd}=0 %
+    \pdfstringdef\EmFi@temp{#1}%
+    \let#2\EmFi@temp
+  \else
+    \edef#2{\pdf@escapestring{#1}}%
+  \fi
+}
+\global\let\EmFi@list\ltx@empty
+\def\EmFi@add#1#2{%
+  \begingroup
+    \ifx\EmFi@list\ltx@empty
+      \xdef\EmFi@list{\noexpand\do{#1}{#2}}%
+    \else
+      \def\do##1##2{%
+        \ifnum\pdf@strcmp{##1}{#1}>0 %
+          \edef\x{%
+            \toks@{%
+              \the\toks@%
+              \noexpand\do{#1}{#2}%
+              \noexpand\do{##1}{##2}%
+            }%
+          }%
+          \x
+          \def\do####1####2{%
+            \toks@\expandafter{\the\toks@\do{####1}{####2}}%
+          }%
+          \def\stop{%
+            \xdef\EmFi@list{\the\toks@}%
+          }%
+        \else
+          \toks@\expandafter{\the\toks@\do{##1}{##2}}%
+        \fi
+      }%
+      \def\stop{%
+        \xdef\EmFi@list{\the\toks@\noexpand\do{#1}{#2}}%
+      }%
+      \toks@{}%
+      \EmFi@list\stop
+    \fi
+  \endgroup
+}
+\def\embedfilefinish{%
+  \ifEmFi@finished
+    \EmFi@Error{%
+      Too many invocations of \string\embedfilefinish
+    }{%
+      The list of embedded files is already written.%
+    }%
+  \else
+    \ifx\EmFi@list\ltx@empty
+    \else
+      \global\EmFi@finishedtrue
+      \begingroup
+        \def\do##1##2{%
+          (##1)##2%
+        }%
+        \immediate\pdfobj{%
+          <<%
+            /Names[\EmFi@list]%
+          >>%
+        }%
+        \pdfnames{%
+          /EmbeddedFiles \the\pdflastobj\ltx@space 0 R%
+        }%
+      \endgroup
+      \begingroup
+        \def\do##1##2{%
+          \ltx@space##2%
+        }%
+        \immediate\pdfobj{%
+          [\EmFi@list]%
+        }%
+        \pdfcatalog{%
+          /AF \the\pdflastobj\ltx@space 0 R%
+        }%
+      \endgroup
+      \ifx\EmFi@initialfile\ltx@empty
+      \else
+        \EmFi@collectiontrue
+      \fi
+      \ifEmFi@collection
+        \ifx\EmFi@initialfile\ltx@empty
+          \let\EmFi@@initialfile\ltx@empty
+        \else
+          \edef\EmFi@@initialfile{%
+            \pdf@escapestring{\EmFi@initialfile}%
+          }%
+        \fi
+        \begingroup
+          \let\f=N%
+          \def\do##1##2{%
+            \def\x{##1}%
+            \ifx\x\EmFi@@initialfile
+              \let\f=Y%
+              \let\do\ltx@gobbletwo
+            \fi
+          }%
+          \EmFi@list
+        \expandafter\endgroup
+        \ifx\f Y%
+        \else
+          \@PackageWarningNoLine{embedfile}{%
+            Missing initial file `\EmFi@initialfile'\MessageBreak
+            among the embedded files%
+          }%
+          \let\EmFi@initialfile\ltx@empty
+          \let\EmFi@@initialfile\ltx@empty
+        \fi
+        \ifcase\EmFi@sortcase
+          \def\EmFi@temp{}%
+        \or
+          \def\EmFi@temp{%
+            /S\EmFi@sortkeys
+            /A \EmFi@sortorders
+          }%
+        \else
+          \def\EmFi@temp{%
+            /S[\EmFi@sortkeys]%
+            /A[\EmFi@sortorders]%
+          }%
+        \fi
+        \def\EmFi@@order##1{%
+          \ifnum\EmFi@order>1 %
+            /O ##1%
+          \fi
+        }%
+        \immediate\pdfobj{%
+          <<%
+            \ifx\EmFi@schema\ltx@empty
+            \else
+              /Schema<<\EmFi@schema>>%
+            \fi
+            \ifx\EmFi@@initialfile\ltx@empty
+            \else
+              /D(\EmFi@@initialfile)%
+            \fi
+            \ifx\EmFi@view\EmFi@S@tile
+              /View/T%
+            \else\ifx\EmFi@view\EmFi@S@hidden
+              /View/H%
+            \fi\fi
+            \ifx\EmFi@temp\ltx@empty
+              \EmFi@temp
+            \else
+              /Sort<<\EmFi@temp>>%
+            \fi
+          >>%
+        }%
+        \pdfcatalog{%
+          /Collection \the\pdflastobj\ltx@space0 R%
+        }%
+      \fi
+    \fi
+  \fi
+}
+\begingroup\expandafter\expandafter\expandafter\endgroup
+\expandafter\ifx\csname AtEndDocument\endcsname\relax
+\else
+  \AtEndDocument{\embedfilefinish}%
+\fi
+\EmFi@AtEnd%
+\endinput
+%%
+%% End of file `embedfile.sty'.