Add CLI support for working with attachments

This commit is contained in:
Jay Berkenbilt 2021-02-09 15:30:56 -05:00
parent 1f4771cd0d
commit 832d792e4e
21 changed files with 1809 additions and 7 deletions

View File

@ -1,3 +1,9 @@
2021-02-10 Jay Berkenbilt <ejb@ql.org>
* Add new command-line arguments for operating on attachments:
--list-attachments, --add-attachment, --remove-attachment,
--copy-attachments-from. See --help and manual for details.
2021-02-09 Jay Berkenbilt <ejb@ql.org>
* Add methods to QUtil for working with PDF timestamp strings:

View File

@ -1801,6 +1801,181 @@ outfile.pdf</option>
</itemizedlist>
</para>
</sect1>
<sect1 id="ref.attachments">
<title>Embedded Files/Attachments Options</title>
<para>
Starting with qpdf 10.2, you can work with file attachments in PDF
files from the command line. The following options are available:
<variablelist>
<varlistentry>
<term><option>--list-attachments</option></term>
<listitem>
<para>
Show the &ldquo;key&rdquo; and stream number for embedded
files. With <option>--verbose</option>, additional
information, including preferred file name, description,
dates, and more are also displayed. The key is usually but not
always equal to the file name, and is needed by some of the
other options.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--show-attachment=<replaceable>key</replaceable></option></term>
<listitem>
<para>
Write the contents of the specified attachment to standard
output as binary data. The key should match one of the keys
shown by <option>--list-attachments</option>. If specified
multiple times, only the last attachment will be shown.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--add-attachment <replaceable>file</replaceable> <replaceable>options</replaceable> --</option></term>
<listitem>
<para>
Add or replace an attachment with the contents of
<replaceable>file</replaceable>. This may be specified more
than once. The following additional options may appear before
the <literal>--</literal> that ends this option:
<variablelist>
<varlistentry>
<term><option>--key=<replaceable>key</replaceable></option></term>
<listitem>
<para>
The key to use to register the attachment in the embedded
files table. Defaults to the last path element of
<replaceable>file</replaceable>.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--filename=<replaceable>name</replaceable></option></term>
<listitem>
<para>
The file name to be used for the attachment. This is what is usually
displayed to the user and is the name most graphical PDF
viewers will use when saving a file. It defaults to the
last path element of <replaceable>file</replaceable>.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--creationdate=<replaceable>date</replaceable></option></term>
<listitem>
<para>
The attachment's creation date in PDF format; defaults to
the current time. The date format is explained below.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--moddate=<replaceable>date</replaceable></option></term>
<listitem>
<para>
The attachment's modification date in PDF format; defaults
to the current time. The date format is explained below.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--mimetype=<replaceable>type/subtype</replaceable></option></term>
<listitem>
<para>
The mime type for the attachment, e.g.
<literal>text/plain</literal> or
<literal>application/pdf</literal>. Note that the mimetype
appears in a field called <literal>/Subtype</literal> in
the PDF but actually includes the full type and subtype of
the mime type.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--description=<replaceable>&quot;text&quot;</replaceable></option></term>
<listitem>
<para>
Descriptive text for the attachment, displayed by some PDF
viewers.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--replace</option></term>
<listitem>
<para>
Indicates that any existing attachment with the same key
should be replaced by the new attachment. Otherwise,
<command>qpdf</command> gives an error if an attachment
with that key is already present.
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--remove-attachment=<replaceable>key</replaceable></option></term>
<listitem>
<para>
Remove the specified attachment. This doesn't only remove the
attachment from the embedded files table but also clears out
the file specification. That means that any potential internal
links to the attachment will be broken. This option may be
specified multiple times. Run with <option>--verbose</option>
to see status of the removal.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--copy-attachments-from <replaceable>file</replaceable> <replaceable>options</replaceable> --</option></term>
<listitem>
<para>
Copy attachments from another file. This may be specified more
than once. The following additional options may appear before
the <literal>--</literal> that ends this option:
<variablelist>
<varlistentry>
<term><option>--password=<replaceable>password</replaceable></option></term>
<listitem>
<para>
If required, the password needed to open
<replaceable>file</replaceable>
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--prefix=<replaceable>prefix</replaceable></option></term>
<listitem>
<para>
Only required if the file from which attachments are being
copied has attachments with keys that conflict with
attachments already in the file. In this case, the
specified prefix will be prepended to each key. This
affects only the key in the embedded files table, not the
file name. The PDF specification doesn't preclude multiple
attachments having the same file name.
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
</listitem>
</varlistentry>
</variablelist>
When a date is required, the date should conform to the PDF date
format specification, which is
<literal>D:</literal><replaceable>yyyymmddhhmmss&lt;z&gt;</replaceable>,
where <replaceable>&lt;z&gt;</replaceable> is either
<literal>Z</literal> for UTC or a timezone offset in the form
<replaceable>-hh'mm'</replaceable> or
<replaceable>+hh'mm'</replaceable>. Examples:
<literal>D:20210207161528-05'00'</literal>,
<literal>D:20210207211528Z</literal>.
</para>
</sect1>
<sect1 id="ref.advanced-parsing">
<title>Advanced Parsing Options</title>
<para>
@ -4911,6 +5086,13 @@ print "\n";
CLI Enhancements
</para>
<itemizedlist>
<listitem>
<para>
Add new command line options for listing, saving, adding,
removing, and and copying file attachments. See <xref
linkend="ref.attachments"/> for details.
</para>
</listitem>
<listitem>
<para>
The option

View File

@ -26,6 +26,7 @@
#include <qpdf/QPDFExc.hh>
#include <qpdf/QPDFSystemError.hh>
#include <qpdf/QPDFCryptoProvider.hh>
#include <qpdf/QPDFEmbeddedFileDocumentHelper.hh>
#include <qpdf/QPDFWriter.hh>
#include <qpdf/QIntC.hh>
@ -95,6 +96,31 @@ struct UnderOverlay
std::vector<int> repeat_pagenos;
};
struct AddAttachment
{
AddAttachment() :
replace(false)
{
}
std::string path;
std::string key;
std::string filename;
std::string creationdate;
std::string moddate;
std::string mimetype;
std::string description;
bool replace;
};
struct CopyAttachmentFrom
{
std::string path;
std::string password;
std::string prefix;
};
enum remove_unref_e { re_auto, re_yes, re_no };
struct Options
@ -177,6 +203,7 @@ struct Options
show_page_images(false),
collate(false),
flatten_rotation(false),
list_attachments(false),
json(false),
check(false),
optimize_images(false),
@ -282,6 +309,11 @@ struct Options
bool show_page_images;
bool collate;
bool flatten_rotation;
bool list_attachments;
std::string attachment_to_show;
std::list<std::string> attachments_to_remove;
std::list<AddAttachment> attachments_to_add;
std::list<CopyAttachmentFrom> attachments_to_copy;
bool json;
std::set<std::string> json_keys;
std::set<std::string> json_objects;
@ -758,6 +790,11 @@ class ArgParser
void argRotate(char* parameter);
void argCollate();
void argFlattenRotation();
void argListAttachments();
void argShowAttachment(char* parameter);
void argRemoveAttachment(char* parameter);
void argAddAttachment();
void argCopyAttachments();
void argStreamData(char* parameter);
void argCompressStreams(char* parameter);
void argRecompressFlate();
@ -838,6 +875,19 @@ class ArgParser
void argReplaceInput();
void argIsEncrypted();
void argRequiresPassword();
void argAApositional(char* arg);
void argAAKey(char* parameter);
void argAAFilename(char* parameter);
void argAACreationDate(char* parameter);
void argAAModDate(char* parameter);
void argAAMimeType(char* parameter);
void argAADescription(char* parameter);
void argAAReplace();
void argEndAddAttachment();
void argCApositional(char* arg);
void argCAprefix(char* parameter);
void argCApassword(char* parameter);
void argEndCopyAttachments();
void usage(std::string const& message);
void checkCompletion();
@ -874,6 +924,8 @@ class ArgParser
std::map<std::string, OptionEntry> encrypt128_option_table;
std::map<std::string, OptionEntry> encrypt256_option_table;
std::map<std::string, OptionEntry> under_overlay_option_table;
std::map<std::string, OptionEntry> add_attachment_option_table;
std::map<std::string, OptionEntry> copy_attachments_option_table;
std::vector<PointerHolder<char> > new_argv;
std::vector<PointerHolder<char> > bash_argv;
PointerHolder<char*> argv_ph;
@ -982,6 +1034,13 @@ ArgParser::initOptionTable()
{"compress", "preserve", "uncompress", 0};
(*t)["collate"] = oe_bare(&ArgParser::argCollate);
(*t)["flatten-rotation"] = oe_bare(&ArgParser::argFlattenRotation);
(*t)["list-attachments"] = oe_bare(&ArgParser::argListAttachments);
(*t)["show-attachment"] = oe_requiredParameter(
&ArgParser::argShowAttachment, "attachment-key");
(*t)["remove-attachment"] = oe_requiredParameter(
&ArgParser::argRemoveAttachment, "attachment-key");
(*t)["add-attachment"] = oe_bare(&ArgParser::argAddAttachment);
(*t)["copy-attachments-from"] = oe_bare(&ArgParser::argCopyAttachments);
(*t)["stream-data"] = oe_requiredChoices(
&ArgParser::argStreamData, stream_data_choices);
(*t)["compress-streams"] = oe_requiredChoices(
@ -1129,6 +1188,31 @@ ArgParser::initOptionTable()
(*t)["password"] = oe_requiredParameter(
&ArgParser::argUOpassword, "password");
(*t)["--"] = oe_bare(&ArgParser::argEndUnderOverlay);
t = &this->add_attachment_option_table;
(*t)[""] = oe_positional(&ArgParser::argAApositional);
(*t)["key"] = oe_requiredParameter(
&ArgParser::argAAKey, "attachment-key");
(*t)["filename"] = oe_requiredParameter(
&ArgParser::argAAFilename, "filename");
(*t)["creationdate"] = oe_requiredParameter(
&ArgParser::argAACreationDate, "creation-date");
(*t)["moddate"] = oe_requiredParameter(
&ArgParser::argAAModDate, "modification-date");
(*t)["mimetype"] = oe_requiredParameter(
&ArgParser::argAAMimeType, "mime/type");
(*t)["description"] = oe_requiredParameter(
&ArgParser::argAADescription, "description");
(*t)["replace"] = oe_bare(&ArgParser::argAAReplace);
(*t)["--"] = oe_bare(&ArgParser::argEndAddAttachment);
t = &this->copy_attachments_option_table;
(*t)[""] = oe_positional(&ArgParser::argCApositional);
(*t)["prefix"] = oe_requiredParameter(
&ArgParser::argCAprefix, "prefix");
(*t)["password"] = oe_requiredParameter(
&ArgParser::argCApassword, "password");
(*t)["--"] = oe_bare(&ArgParser::argEndCopyAttachments);
}
void
@ -1361,7 +1445,6 @@ ArgParser::argHelp()
<< " --allow-insecure allow the owner password to be empty when the\n"
<< " user password is not empty\n"
<< "\n"
<< "\n"
<< " print-opt may be:\n"
<< "\n"
<< " full allow full printing\n"
@ -1487,6 +1570,55 @@ ArgParser::argHelp()
<< " any \"from\" pages have been exhausted\n"
<< "\n"
<< "\n"
<< "Embedded Files/Attachments Options\n"
<< "----------------------------------\n"
<< "\n"
<< "These options can be used to work with embedded files, also known as\n"
<< "attachments.\n"
<< "\n"
<< "--list-attachments show key and stream number for embedded files;\n"
<< " combine with --verbose for more detailed information\n"
<< "--show-attachment=key write the contents of the specified attachment to\n"
<< " standard output as binary data\n"
<< "--add-attachment file options --\n"
<< " add or replace an attachment\n"
<< "--remove-attachment=key remove the specified attachment; repeatable\n"
<< "--copy-attachments-from file options --\n"
<< " copy attachments from another file\n"
<< "\n"
<< "The \"key\" option is the unique name under which the attachment is registered\n"
<< "within the PDF file. You can get this using the --list-attachments option. This\n"
<< "is usually the same as the filename, but it doesn't have to be.\n"
<< "\n"
<< "Options for adding attachments:\n"
<< "\n"
<< " file path to the file to attach\n"
<< " --key=key the name of this in the embedded files table;\n"
<< " defaults to the last path element of file\n"
<< " --filename=name the file name of the attachment; this is what is\n"
<< " usually displayed to the user; defaults to the\n"
<< " last path element of file\n"
<< " --creationdate=date creation date in PDF format; defaults to the\n"
<< " current time\n"
<< " --moddate=date modification date in PDF format; defaults to the\n"
<< " current time\n"
<< " --mimetype=type/subtype mime type of attachment (e.g. application/pdf)\n"
<< " --description=\"text\" attachment description\n"
<< " --replace replace any existing attachment with the same key\n"
<< "\n"
<< "Options for copying attachments:\n"
<< "\n"
<< " file file whose attachments should be copied\n"
<< " --password=password password to open the other file, if needed\n"
<< " --prefix=prefix a prefix to insert in front of each key;\n"
<< " required if needed to ensure each attachment\n"
<< " has a unique key\n"
<< "\n"
<< "Date format: D:yyyymmddhhmmss<z> where <z> is either Z for UTC or a timezone\n"
<< "offset in the form -hh'mm' or +hh'mm'.\n"
<< "Examples: D:20210207161528-05'00', D:20210207211528Z\n"
<< "\n"
<< "\n"
<< "Advanced Parsing Options\n"
<< "------------------------\n"
<< "\n"
@ -1960,6 +2092,40 @@ ArgParser::argFlattenRotation()
o.flatten_rotation = true;
}
void
ArgParser::argListAttachments()
{
o.list_attachments = true;
o.require_outfile = false;
}
void
ArgParser::argShowAttachment(char* parameter)
{
o.attachment_to_show = parameter;
o.require_outfile = false;
}
void
ArgParser::argRemoveAttachment(char* parameter)
{
o.attachments_to_remove.push_back(parameter);
}
void
ArgParser::argAddAttachment()
{
this->option_table = &(this->add_attachment_option_table);
o.attachments_to_add.push_back(AddAttachment());
}
void
ArgParser::argCopyAttachments()
{
this->option_table = &(this->copy_attachments_option_table);
o.attachments_to_copy.push_back(CopyAttachmentFrom());
}
void
ArgParser::argStreamData(char* parameter)
{
@ -2617,6 +2783,134 @@ ArgParser::argRequiresPassword()
o.require_outfile = false;
}
void
ArgParser::argAApositional(char* arg)
{
o.attachments_to_add.back().path = arg;
}
void
ArgParser::argAAKey(char* parameter)
{
o.attachments_to_add.back().key = parameter;
}
void
ArgParser::argAAFilename(char* parameter)
{
o.attachments_to_add.back().filename = parameter;
}
void
ArgParser::argAACreationDate(char* parameter)
{
if (! QUtil::pdf_time_to_qpdf_time(parameter))
{
usage(std::string(parameter) + " is not a valid PDF timestamp");
}
o.attachments_to_add.back().creationdate = parameter;
}
void
ArgParser::argAAModDate(char* parameter)
{
if (! QUtil::pdf_time_to_qpdf_time(parameter))
{
usage(std::string(parameter) + " is not a valid PDF timestamp");
}
o.attachments_to_add.back().moddate = parameter;
}
void
ArgParser::argAAMimeType(char* parameter)
{
if (strchr(parameter, '/') == nullptr)
{
usage("mime type should be specified as type/subtype");
}
o.attachments_to_add.back().mimetype = parameter;
}
void
ArgParser::argAADescription(char* parameter)
{
o.attachments_to_add.back().description = parameter;
}
void
ArgParser::argAAReplace()
{
o.attachments_to_add.back().replace = true;
}
void
ArgParser::argEndAddAttachment()
{
static std::string now = QUtil::qpdf_time_to_pdf_time(
QUtil::get_current_qpdf_time());
this->option_table = &(this->main_option_table);
auto& cur = o.attachments_to_add.back();
if (cur.path.empty())
{
usage("add attachment: no path specified");
}
std::string last_element = cur.path;
size_t pathsep = cur.path.find_last_of("/\\");
if (pathsep != std::string::npos)
{
last_element = cur.path.substr(pathsep + 1);
if (last_element.empty())
{
usage("path for --add-attachment may not end"
" with a path separator");
}
}
if (cur.filename.empty())
{
cur.filename = last_element;
}
if (cur.key.empty())
{
cur.key = last_element;
}
if (cur.creationdate.empty())
{
cur.creationdate = now;
}
if (cur.moddate.empty())
{
cur.moddate = now;
}
}
void
ArgParser::argCApositional(char* arg)
{
o.attachments_to_copy.back().path = arg;
}
void
ArgParser::argCAprefix(char* parameter)
{
o.attachments_to_copy.back().prefix = parameter;
}
void
ArgParser::argCApassword(char* parameter)
{
o.attachments_to_copy.back().password = parameter;
}
void
ArgParser::argEndCopyAttachments()
{
this->option_table = &(this->main_option_table);
if (o.attachments_to_copy.back().path.empty())
{
usage("copy attachments: no path specified");
}
}
void
ArgParser::handleArgFileArguments()
{
@ -3768,6 +4062,66 @@ static void do_show_pages(QPDF& pdf, Options& o)
}
}
static void do_list_attachments(QPDF& pdf, Options& o)
{
QPDFEmbeddedFileDocumentHelper efdh(pdf);
if (efdh.hasEmbeddedFiles())
{
for (auto const& i: efdh.getEmbeddedFiles())
{
std::string const& key = i.first;
auto efoh = i.second;
std::cout << key << " -> "
<< efoh->getEmbeddedFileStream().getObjGen()
<< std::endl;
if (o.verbose)
{
auto desc = efoh->getDescription();
if (! desc.empty())
{
std::cout << " description: " << desc << std::endl;
}
std::cout << " preferred name: " << efoh->getFilename()
<< std::endl;
std::cout << " all names:" << std::endl;
for (auto const& i2: efoh->getFilenames())
{
std::cout << " " << i2.first << " -> " << i2.second
<< std::endl;
}
std::cout << " all data streams:" << std::endl;
for (auto i2: QPDFDictItems(efoh->getEmbeddedFileStreams()))
{
std::cout << " " << i2.first << " -> "
<< i2.second.getObjGen()
<< std::endl;
}
}
}
}
else
{
std::cout << o.infilename << " has no embedded files" << std::endl;
}
}
static void do_show_attachment(QPDF& pdf, Options& o, int& exit_code)
{
QPDFEmbeddedFileDocumentHelper efdh(pdf);
auto fs = efdh.getEmbeddedFile(o.attachment_to_show);
if (! fs)
{
std::cerr << whoami << ": attachment " << o.attachment_to_show
<< " not found" << std::endl;
exit_code = EXIT_ERROR;
return;
}
auto efs = fs->getEmbeddedFileStream();
QUtil::binary_stdout();
Pl_StdioFile out("stdout", stdout);
efs.pipeStreamData(&out, 0, qpdf_dl_all);
}
static std::set<QPDFObjGen>
get_wanted_json_objects(Options& o)
{
@ -4354,6 +4708,14 @@ static void do_inspection(QPDF& pdf, Options& o)
{
do_show_pages(pdf, o);
}
if (o.list_attachments)
{
do_list_attachments(pdf, o);
}
if (! o.attachment_to_show.empty())
{
do_show_attachment(pdf, o, exit_code);
}
if ((! pdf.getWarnings().empty()) && (exit_code != EXIT_ERROR))
{
std::cerr << whoami
@ -4858,7 +5220,106 @@ static void handle_under_overlay(QPDF& pdf, Options& o)
}
}
static void handle_transformations(QPDF& pdf, Options& o)
static void maybe_set_pagemode(QPDF& pdf, std::string const& pagemode)
{
auto root = pdf.getRoot();
if (root.getKey("/PageMode").isNull())
{
root.replaceKey("/PageMode", QPDFObjectHandle::newName(pagemode));
}
}
static void add_attachments(QPDF& pdf, Options& o, int& exit_code)
{
maybe_set_pagemode(pdf, "/UseAttachments");
QPDFEmbeddedFileDocumentHelper efdh(pdf);
for (auto const& to_add: o.attachments_to_add)
{
if ((! to_add.replace) && efdh.getEmbeddedFile(to_add.key))
{
std::cerr << whoami << ": " << pdf.getFilename()
<< " already has an attachment with key = "
<< to_add.key << "; use --replace to replace"
<< " or --key to specificy a different key"
<< std::endl;
exit_code = EXIT_ERROR;
continue;
}
auto fs = QPDFFileSpecObjectHelper::createFileSpec(
pdf, to_add.filename, to_add.path);
if (! to_add.description.empty())
{
fs.setDescription(to_add.description);
}
auto efs = QPDFEFStreamObjectHelper(fs.getEmbeddedFileStream());
efs.setCreationDate(to_add.creationdate)
.setModDate(to_add.moddate);
if (! to_add.mimetype.empty())
{
efs.setSubtype(to_add.mimetype);
}
efdh.replaceEmbeddedFile(to_add.key, fs);
if (o.verbose)
{
std::cout << whoami << ": attached " << to_add.path
<< " as " << to_add.filename
<< " with key " << to_add.key << std::endl;
}
}
}
static void copy_attachments(QPDF& pdf, Options& o, int& exit_code)
{
maybe_set_pagemode(pdf, "/UseAttachments");
QPDFEmbeddedFileDocumentHelper efdh(pdf);
for (auto const& to_copy: o.attachments_to_copy)
{
auto other = process_file(
to_copy.path.c_str(), to_copy.password.c_str(), o);
QPDFEmbeddedFileDocumentHelper other_efdh(*other);
auto other_attachments = other_efdh.getEmbeddedFiles();
for (auto const& iter: other_attachments)
{
if (o.verbose)
{
std::cout << whoami << ": copying attachments from "
<< to_copy.path << std::endl;
}
std::string new_key = to_copy.prefix + iter.first;
if (efdh.getEmbeddedFile(new_key))
{
exit_code = EXIT_ERROR;
std::cerr << whoami << to_copy.path << " and "
<< pdf.getFilename()
<< " both have attachments with key " << new_key
<< "; use --prefix with --copy-attachments-from"
<< " or manually copy individual attachments"
<< std::endl;
}
else
{
auto new_fs_oh = pdf.copyForeignObject(
iter.second->getObjectHandle());
efdh.replaceEmbeddedFile(
new_key, QPDFFileSpecObjectHelper(new_fs_oh));
if (o.verbose)
{
std::cout << " " << iter.first << " -> " << new_key
<< std::endl;
}
}
}
if ((other->anyWarnings()) && (exit_code == 0))
{
exit_code = EXIT_WARNING;
}
}
}
static void handle_transformations(QPDF& pdf, Options& o, int& exit_code)
{
QPDFPageDocumentHelper dh(pdf);
if (o.externalize_inline_images)
@ -4935,6 +5396,35 @@ static void handle_transformations(QPDF& pdf, Options& o)
{
pdf.getRoot().removeKey("/PageLabels");
}
if (! o.attachments_to_remove.empty())
{
QPDFEmbeddedFileDocumentHelper efdh(pdf);
for (auto const& key: o.attachments_to_remove)
{
if (efdh.removeEmbeddedFile(key))
{
if (o.verbose)
{
std::cout << whoami <<
": removed attachment " << key << std::endl;
}
}
else
{
std::cerr << whoami <<
": attachment " << key << " not found" << std::endl;
exit_code = EXIT_ERROR;
}
}
}
if (! o.attachments_to_add.empty())
{
add_attachments(pdf, o, exit_code);
}
if (! o.attachments_to_copy.empty())
{
copy_attachments(pdf, o, exit_code);
}
}
static bool should_remove_unreferenced_resources(QPDF& pdf, Options& o)
@ -5854,6 +6344,7 @@ int realmain(int argc, char* argv[])
Options o;
ArgParser ap(argc, argv, o);
int exit_code = 0;
try
{
ap.parseOptions();
@ -5906,7 +6397,7 @@ int realmain(int argc, char* argv[])
handle_rotations(pdf, o);
}
handle_under_overlay(pdf, o);
handle_transformations(pdf, o);
handle_transformations(pdf, o, exit_code);
if ((o.outfilename == 0) && (! o.replace_input))
{
@ -5929,7 +6420,10 @@ int realmain(int argc, char* argv[])
<< std::endl;
}
// Still return with warning code even if warnings were suppressed.
return EXIT_WARNING;
if (exit_code == 0)
{
exit_code = EXIT_WARNING;
}
}
}
catch (std::exception& e)
@ -5938,7 +6432,7 @@ int realmain(int argc, char* argv[])
return EXIT_ERROR;
}
return 0;
return exit_code;
}
#ifdef WINDOWS_WMAIN

View File

@ -523,7 +523,7 @@ $td->runtest("page operations on form xobject",
show_ntests();
# ----------
$td->notify("--- File Attachments ---");
$n_tests += 4;
$n_tests += 33;
open(F, ">auto-txt") or die;
print F "from file";
@ -532,16 +532,183 @@ $td->runtest("attachments",
{$td->COMMAND => "test_driver 76 minimal.pdf auto-txt"},
{$td->FILE => "test76.out", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("show attachment",
{$td->COMMAND => "qpdf --show-attachment=att1 a.pdf"},
{$td->STRING => "from file", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("check output",
{$td->FILE => "a.pdf"},
{$td->FILE => "test76.pdf"});
$td->runtest("attachments",
$td->runtest("list attachments",
{$td->COMMAND => "qpdf --list-attachments a.pdf"},
{$td->FILE => "test76-list.out", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("list attachments verbose",
{$td->COMMAND => "qpdf --list-attachments --verbose a.pdf"},
{$td->FILE => "test76-list-verbose.out", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("remove attachment (test_driver)",
{$td->COMMAND => "test_driver 77 test76.pdf"},
{$td->STRING => "test 77 done\n", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("check output",
{$td->FILE => "a.pdf"},
{$td->FILE => "test77.pdf"});
$td->runtest("remove attachment (cli)",
{$td->COMMAND => "qpdf --remove-attachment=att2 test76.pdf" .
" --static-id --qdf --verbose b.pdf"},
{$td->FILE => "remove-attachment.out", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("check output",
{$td->FILE => "b.pdf"},
{$td->FILE => "test77.pdf"});
$td->runtest("show missing attachment",
{$td->COMMAND => "qpdf --show-attachment=att2 b.pdf"},
{$td->STRING => "qpdf: attachment att2 not found\n",
$td->EXIT_STATUS => 2},
$td->NORMALIZE_NEWLINES);
$td->runtest("remove missing attachment",
{$td->COMMAND => "qpdf --remove-attachment=att2 b.pdf c.pdf"},
{$td->STRING => "qpdf: attachment att2 not found\n",
$td->EXIT_STATUS => 2},
$td->NORMALIZE_NEWLINES);
$td->runtest("add attachment: bad creation date",
{$td->COMMAND => "qpdf minimal.pdf a.pdf" .
" --add-attachment auto-txt --creationdate=potato --"},
{$td->REGEXP => ".*potato is not a valid PDF timestamp.*",
$td->EXIT_STATUS => 2},
$td->NORMALIZE_NEWLINES);
$td->runtest("add attachment: bad mod date",
{$td->COMMAND => "qpdf minimal.pdf a.pdf" .
" --add-attachment auto-txt --moddate=potato --"},
{$td->REGEXP => ".*potato is not a valid PDF timestamp.*",
$td->EXIT_STATUS => 2},
$td->NORMALIZE_NEWLINES);
$td->runtest("add attachment: bad mod date",
{$td->COMMAND => "qpdf minimal.pdf a.pdf" .
" --add-attachment auto-txt --mimetype=potato --"},
{$td->REGEXP =>
".*mime type should be specified as type/subtype.*",
$td->EXIT_STATUS => 2},
$td->NORMALIZE_NEWLINES);
$td->runtest("add attachment: trailing slash",
{$td->COMMAND => "qpdf minimal.pdf a.pdf" .
" --add-attachment auto-txt/ --"},
{$td->REGEXP => ".*may not end with a path separator.*",
$td->EXIT_STATUS => 2},
$td->NORMALIZE_NEWLINES);
$td->runtest("add attachment: trailing slash",
{$td->COMMAND => "qpdf minimal.pdf a.pdf" .
" --add-attachment --"},
{$td->REGEXP => ".*add attachment: no path specified.*",
$td->EXIT_STATUS => 2},
$td->NORMALIZE_NEWLINES);
foreach my $i (qw(1 2 3))
{
open(F, ">auto-$i") or die;
print F "attachment $i";
close(F);
}
my @dates = ("--creationdate=D:20210210091359-05'00'",
"--moddate=D:20210210141359Z");
$td->runtest("add attachments",
{$td->COMMAND =>
[qw(qpdf minimal.pdf a.pdf --no-original-object-ids),
qw(--verbose --static-id --qdf),
qw(--add-attachment ./auto-1), @dates,
qw(--mimetype=text/plain --),
qw(--add-attachment ./auto-2 --key=auto-Two), @dates, '--',
qw(--add-attachment ./auto-3 --filename=auto-Three.txt),
@dates, '--description=two words', '--']},
{$td->FILE => "add-attachments-1.out", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("list attachments",
{$td->COMMAND => "qpdf --list-attachments a.pdf --verbose"},
{$td->FILE => "list-attachments-1.out", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("check output",
{$td->FILE => "a.pdf"},
{$td->FILE => "add-attachments-1.pdf"},
$td->NORMALIZE_NEWLINES);
$td->runtest("add attachments: duplicate",
{$td->COMMAND =>
"qpdf a.pdf b.pdf --verbose --add-attachment ./auto-1 --"},
{$td->FILE => "add-attachments-duplicate.out",
$td->EXIT_STATUS => 2},
$td->NORMALIZE_NEWLINES);
$td->runtest("add attachments: replace",
{$td->COMMAND =>
[qw(qpdf a.pdf b.pdf --no-original-object-ids),
qw(--verbose --static-id --qdf),
qw(--add-attachment ./auto-2 --key=auto-1 --replace),
@dates, '--']},
{$td->FILE => "add-attachments-2.out", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("list attachments",
{$td->COMMAND => "qpdf --list-attachments b.pdf --verbose"},
{$td->FILE => "list-attachments-3.out", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("check output",
{$td->FILE => "b.pdf"},
{$td->FILE => "add-attachments-2.pdf"},
$td->NORMALIZE_NEWLINES);
$td->runtest("copy attachments",
{$td->COMMAND =>
"qpdf --verbose --no-original-object-ids" .
" --static-id --qdf minimal.pdf b.pdf" .
" --copy-attachments-from a.pdf --"},
{$td->FILE => "copy-attachments-1.out", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("list attachments",
{$td->COMMAND => "qpdf --list-attachments b.pdf --verbose"},
{$td->FILE => "list-attachments-1.out", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("check output",
{$td->FILE => "b.pdf"},
{$td->FILE => "add-attachments-1.pdf"},
$td->NORMALIZE_NEWLINES);
$td->runtest("copy attachments: duplicate",
{$td->COMMAND =>
"qpdf --verbose --no-original-object-ids" .
" --static-id --qdf a.pdf c.pdf" .
" --copy-attachments-from b.pdf --"},
{$td->FILE => "copy-attachments-duplicate.out",
$td->EXIT_STATUS => 2},
$td->NORMALIZE_NEWLINES);
$td->runtest("copy attachments: prefix",
{$td->COMMAND =>
"qpdf --verbose --no-original-object-ids" .
" --static-id --qdf a.pdf c.pdf" .
" --copy-attachments-from b.pdf --prefix=1- --"},
{$td->FILE => "copy-attachments-2.out", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("list attachments",
{$td->COMMAND => "qpdf --list-attachments c.pdf --verbose"},
{$td->FILE => "list-attachments-2.out", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("check output",
{$td->FILE => "c.pdf"},
{$td->FILE => "copy-attachments-2.pdf"},
$td->NORMALIZE_NEWLINES);
$td->runtest("add attachments: current date",
{$td->COMMAND =>
[qw(qpdf minimal.pdf a.pdf --encrypt u o 256 --),
qw(--verbose --add-attachment ./auto-1 --)]},
{$td->FILE => "add-attachments-3.out", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("list attachments",
{$td->COMMAND =>
"qpdf --password=u --list-attachments a.pdf --verbose"},
{$td->FILE => "list-attachments-4.out", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
# The object to show here is the one in list-attachments-4.out
$td->runtest("check dates",
{$td->COMMAND => "qpdf --show-object=6 a.pdf --password=u"},
{$td->REGEXP => ".*CreationDate \\(D:\\d+.*ModDate \\(D:\\d+.*",
$td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
show_ntests();
# ----------

View File

@ -0,0 +1,4 @@
qpdf: attached ./auto-1 as auto-1 with key auto-1
qpdf: attached ./auto-2 as auto-2 with key auto-Two
qpdf: attached ./auto-3 as auto-Three.txt with key auto-3
qpdf: wrote file a.pdf

View File

@ -0,0 +1,223 @@
%PDF-1.3
%¿÷¢þ
%QDF-1.0
1 0 obj
<<
/Names <<
/EmbeddedFiles 2 0 R
>>
/PageMode /UseAttachments
/Pages 3 0 R
/Type /Catalog
>>
endobj
2 0 obj
<<
/Names [
(auto-1)
4 0 R
(auto-3)
5 0 R
(auto-Two)
6 0 R
]
>>
endobj
3 0 obj
<<
/Count 1
/Kids [
7 0 R
]
/Type /Pages
>>
endobj
4 0 obj
<<
/EF <<
/F 8 0 R
/UF 8 0 R
>>
/F (auto-1)
/Type /Filespec
/UF (auto-1)
>>
endobj
5 0 obj
<<
/Desc (two words)
/EF <<
/F 10 0 R
/UF 10 0 R
>>
/F (auto-Three.txt)
/Type /Filespec
/UF (auto-Three.txt)
>>
endobj
6 0 obj
<<
/EF <<
/F 12 0 R
/UF 12 0 R
>>
/F (auto-2)
/Type /Filespec
/UF (auto-2)
>>
endobj
%% Page 1
7 0 obj
<<
/Contents 14 0 R
/MediaBox [
0
0
612
792
]
/Parent 3 0 R
/Resources <<
/Font <<
/F1 16 0 R
>>
/ProcSet 17 0 R
>>
/Type /Page
>>
endobj
8 0 obj
<<
/Params <<
/CheckSum <a857d18d3fc23ad412122ef040733331>
/CreationDate (D:20210210091359-05'00')
/ModDate (D:20210210141359Z)
/Size 12
/Subtype /text#2fplain
>>
/Type /EmbeddedFile
/Length 9 0 R
>>
stream
attachment 1
endstream
endobj
%QDF: ignore_newline
9 0 obj
12
endobj
10 0 obj
<<
/Params <<
/CheckSum <d6c7ac7cf295ae133fea186cfd068dab>
/CreationDate (D:20210210091359-05'00')
/ModDate (D:20210210141359Z)
/Size 12
>>
/Type /EmbeddedFile
/Length 11 0 R
>>
stream
attachment 3
endstream
endobj
%QDF: ignore_newline
11 0 obj
12
endobj
12 0 obj
<<
/Params <<
/CheckSum <9f991a5669c47a94f9350f53e3953e57>
/CreationDate (D:20210210091359-05'00')
/ModDate (D:20210210141359Z)
/Size 12
>>
/Type /EmbeddedFile
/Length 13 0 R
>>
stream
attachment 2
endstream
endobj
%QDF: ignore_newline
13 0 obj
12
endobj
%% Contents for page 1
14 0 obj
<<
/Length 15 0 R
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato) Tj
ET
endstream
endobj
15 0 obj
44
endobj
16 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Name /F1
/Subtype /Type1
/Type /Font
>>
endobj
17 0 obj
[
/PDF
/Text
]
endobj
xref
0 18
0000000000 65535 f
0000000025 00000 n
0000000149 00000 n
0000000257 00000 n
0000000329 00000 n
0000000439 00000 n
0000000587 00000 n
0000000709 00000 n
0000000904 00000 n
0000001199 00000 n
0000001218 00000 n
0000001488 00000 n
0000001508 00000 n
0000001778 00000 n
0000001821 00000 n
0000001922 00000 n
0000001942 00000 n
0000002061 00000 n
trailer <<
/Root 1 0 R
/Size 18
/ID [<31415926535897932384626433832795><31415926535897932384626433832795>]
>>
startxref
2097
%%EOF

View File

@ -0,0 +1,2 @@
qpdf: attached ./auto-2 as auto-2 with key auto-1
qpdf: wrote file b.pdf

View File

@ -0,0 +1,222 @@
%PDF-1.3
%¿÷¢þ
%QDF-1.0
1 0 obj
<<
/Names <<
/EmbeddedFiles 2 0 R
>>
/PageMode /UseAttachments
/Pages 3 0 R
/Type /Catalog
>>
endobj
2 0 obj
<<
/Names [
(auto-1)
4 0 R
(auto-3)
5 0 R
(auto-Two)
6 0 R
]
>>
endobj
3 0 obj
<<
/Count 1
/Kids [
7 0 R
]
/Type /Pages
>>
endobj
4 0 obj
<<
/EF <<
/F 8 0 R
/UF 8 0 R
>>
/F (auto-2)
/Type /Filespec
/UF (auto-2)
>>
endobj
5 0 obj
<<
/Desc (two words)
/EF <<
/F 10 0 R
/UF 10 0 R
>>
/F (auto-Three.txt)
/Type /Filespec
/UF (auto-Three.txt)
>>
endobj
6 0 obj
<<
/EF <<
/F 12 0 R
/UF 12 0 R
>>
/F (auto-2)
/Type /Filespec
/UF (auto-2)
>>
endobj
%% Page 1
7 0 obj
<<
/Contents 14 0 R
/MediaBox [
0
0
612
792
]
/Parent 3 0 R
/Resources <<
/Font <<
/F1 16 0 R
>>
/ProcSet 17 0 R
>>
/Type /Page
>>
endobj
8 0 obj
<<
/Params <<
/CheckSum <9f991a5669c47a94f9350f53e3953e57>
/CreationDate (D:20210210091359-05'00')
/ModDate (D:20210210141359Z)
/Size 12
>>
/Type /EmbeddedFile
/Length 9 0 R
>>
stream
attachment 2
endstream
endobj
%QDF: ignore_newline
9 0 obj
12
endobj
10 0 obj
<<
/Params <<
/CheckSum <d6c7ac7cf295ae133fea186cfd068dab>
/CreationDate (D:20210210091359-05'00')
/ModDate (D:20210210141359Z)
/Size 12
>>
/Type /EmbeddedFile
/Length 11 0 R
>>
stream
attachment 3
endstream
endobj
%QDF: ignore_newline
11 0 obj
12
endobj
12 0 obj
<<
/Params <<
/CheckSum <9f991a5669c47a94f9350f53e3953e57>
/CreationDate (D:20210210091359-05'00')
/ModDate (D:20210210141359Z)
/Size 12
>>
/Type /EmbeddedFile
/Length 13 0 R
>>
stream
attachment 2
endstream
endobj
%QDF: ignore_newline
13 0 obj
12
endobj
%% Contents for page 1
14 0 obj
<<
/Length 15 0 R
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato) Tj
ET
endstream
endobj
15 0 obj
44
endobj
16 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Name /F1
/Subtype /Type1
/Type /Font
>>
endobj
17 0 obj
[
/PDF
/Text
]
endobj
xref
0 18
0000000000 65535 f
0000000025 00000 n
0000000149 00000 n
0000000257 00000 n
0000000329 00000 n
0000000439 00000 n
0000000587 00000 n
0000000709 00000 n
0000000904 00000 n
0000001172 00000 n
0000001191 00000 n
0000001461 00000 n
0000001481 00000 n
0000001751 00000 n
0000001794 00000 n
0000001895 00000 n
0000001915 00000 n
0000002034 00000 n
trailer <<
/Root 1 0 R
/Size 18
/ID [<31415926535897932384626433832795><31415926535897932384626433832795>]
>>
startxref
2070
%%EOF

View File

@ -0,0 +1,2 @@
qpdf: attached ./auto-1 as auto-1 with key auto-1
qpdf: wrote file a.pdf

View File

@ -0,0 +1,2 @@
qpdf: a.pdf already has an attachment with key = auto-1; use --replace to replace or --key to specificy a different key
qpdf: wrote file b.pdf

View File

@ -0,0 +1,7 @@
qpdf: copying attachments from a.pdf
auto-1 -> auto-1
qpdf: copying attachments from a.pdf
auto-3 -> auto-3
qpdf: copying attachments from a.pdf
auto-Two -> auto-Two
qpdf: wrote file b.pdf

View File

@ -0,0 +1,7 @@
qpdf: copying attachments from b.pdf
auto-1 -> 1-auto-1
qpdf: copying attachments from b.pdf
auto-3 -> 1-auto-3
qpdf: copying attachments from b.pdf
auto-Two -> 1-auto-Two
qpdf: wrote file c.pdf

View File

@ -0,0 +1,339 @@
%PDF-1.3
%¿÷¢þ
%QDF-1.0
1 0 obj
<<
/Names <<
/EmbeddedFiles 2 0 R
>>
/PageMode /UseAttachments
/Pages 3 0 R
/Type /Catalog
>>
endobj
2 0 obj
<<
/Names [
(1-auto-1)
4 0 R
(1-auto-3)
5 0 R
(1-auto-Two)
6 0 R
(auto-1)
7 0 R
(auto-3)
8 0 R
(auto-Two)
9 0 R
]
>>
endobj
3 0 obj
<<
/Count 1
/Kids [
10 0 R
]
/Type /Pages
>>
endobj
4 0 obj
<<
/EF <<
/F 11 0 R
/UF 11 0 R
>>
/F (auto-1)
/Type /Filespec
/UF (auto-1)
>>
endobj
5 0 obj
<<
/Desc (two words)
/EF <<
/F 13 0 R
/UF 13 0 R
>>
/F (auto-Three.txt)
/Type /Filespec
/UF (auto-Three.txt)
>>
endobj
6 0 obj
<<
/EF <<
/F 15 0 R
/UF 15 0 R
>>
/F (auto-2)
/Type /Filespec
/UF (auto-2)
>>
endobj
7 0 obj
<<
/EF <<
/F 17 0 R
/UF 17 0 R
>>
/F (auto-1)
/Type /Filespec
/UF (auto-1)
>>
endobj
8 0 obj
<<
/Desc (two words)
/EF <<
/F 19 0 R
/UF 19 0 R
>>
/F (auto-Three.txt)
/Type /Filespec
/UF (auto-Three.txt)
>>
endobj
9 0 obj
<<
/EF <<
/F 21 0 R
/UF 21 0 R
>>
/F (auto-2)
/Type /Filespec
/UF (auto-2)
>>
endobj
%% Page 1
10 0 obj
<<
/Contents 23 0 R
/MediaBox [
0
0
612
792
]
/Parent 3 0 R
/Resources <<
/Font <<
/F1 25 0 R
>>
/ProcSet 26 0 R
>>
/Type /Page
>>
endobj
11 0 obj
<<
/Params <<
/CheckSum <a857d18d3fc23ad412122ef040733331>
/CreationDate (D:20210210091359-05'00')
/ModDate (D:20210210141359Z)
/Size 12
/Subtype /text#2fplain
>>
/Type /EmbeddedFile
/Length 12 0 R
>>
stream
attachment 1
endstream
endobj
%QDF: ignore_newline
12 0 obj
12
endobj
13 0 obj
<<
/Params <<
/CheckSum <d6c7ac7cf295ae133fea186cfd068dab>
/CreationDate (D:20210210091359-05'00')
/ModDate (D:20210210141359Z)
/Size 12
>>
/Type /EmbeddedFile
/Length 14 0 R
>>
stream
attachment 3
endstream
endobj
%QDF: ignore_newline
14 0 obj
12
endobj
15 0 obj
<<
/Params <<
/CheckSum <9f991a5669c47a94f9350f53e3953e57>
/CreationDate (D:20210210091359-05'00')
/ModDate (D:20210210141359Z)
/Size 12
>>
/Type /EmbeddedFile
/Length 16 0 R
>>
stream
attachment 2
endstream
endobj
%QDF: ignore_newline
16 0 obj
12
endobj
17 0 obj
<<
/Params <<
/CheckSum <a857d18d3fc23ad412122ef040733331>
/CreationDate (D:20210210091359-05'00')
/ModDate (D:20210210141359Z)
/Size 12
/Subtype /text#2fplain
>>
/Type /EmbeddedFile
/Length 18 0 R
>>
stream
attachment 1
endstream
endobj
%QDF: ignore_newline
18 0 obj
12
endobj
19 0 obj
<<
/Params <<
/CheckSum <d6c7ac7cf295ae133fea186cfd068dab>
/CreationDate (D:20210210091359-05'00')
/ModDate (D:20210210141359Z)
/Size 12
>>
/Type /EmbeddedFile
/Length 20 0 R
>>
stream
attachment 3
endstream
endobj
%QDF: ignore_newline
20 0 obj
12
endobj
21 0 obj
<<
/Params <<
/CheckSum <9f991a5669c47a94f9350f53e3953e57>
/CreationDate (D:20210210091359-05'00')
/ModDate (D:20210210141359Z)
/Size 12
>>
/Type /EmbeddedFile
/Length 22 0 R
>>
stream
attachment 2
endstream
endobj
%QDF: ignore_newline
22 0 obj
12
endobj
%% Contents for page 1
23 0 obj
<<
/Length 24 0 R
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato) Tj
ET
endstream
endobj
24 0 obj
44
endobj
25 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Name /F1
/Subtype /Type1
/Type /Font
>>
endobj
26 0 obj
[
/PDF
/Text
]
endobj
xref
0 27
0000000000 65535 f
0000000025 00000 n
0000000149 00000 n
0000000334 00000 n
0000000407 00000 n
0000000519 00000 n
0000000667 00000 n
0000000779 00000 n
0000000891 00000 n
0000001039 00000 n
0000001161 00000 n
0000001357 00000 n
0000001654 00000 n
0000001674 00000 n
0000001944 00000 n
0000001964 00000 n
0000002234 00000 n
0000002254 00000 n
0000002551 00000 n
0000002571 00000 n
0000002841 00000 n
0000002861 00000 n
0000003131 00000 n
0000003174 00000 n
0000003275 00000 n
0000003295 00000 n
0000003414 00000 n
trailer <<
/Root 1 0 R
/Size 27
/ID [<31415926535897932384626433832795><31415926535897932384626433832795>]
>>
startxref
3450
%%EOF

View File

@ -0,0 +1,7 @@
qpdf: copying attachments from b.pdf
qpdfb.pdf and a.pdf both have attachments with key auto-1; use --prefix with --copy-attachments-from or manually copy individual attachments
qpdf: copying attachments from b.pdf
qpdfb.pdf and a.pdf both have attachments with key auto-3; use --prefix with --copy-attachments-from or manually copy individual attachments
qpdf: copying attachments from b.pdf
qpdfb.pdf and a.pdf both have attachments with key auto-Two; use --prefix with --copy-attachments-from or manually copy individual attachments
qpdf: wrote file c.pdf

View File

@ -0,0 +1,25 @@
auto-1 -> 8,0
preferred name: auto-1
all names:
/F -> auto-1
/UF -> auto-1
all data streams:
/F -> 8,0
/UF -> 8,0
auto-3 -> 10,0
description: two words
preferred name: auto-Three.txt
all names:
/F -> auto-Three.txt
/UF -> auto-Three.txt
all data streams:
/F -> 10,0
/UF -> 10,0
auto-Two -> 12,0
preferred name: auto-2
all names:
/F -> auto-2
/UF -> auto-2
all data streams:
/F -> 12,0
/UF -> 12,0

View File

@ -0,0 +1,50 @@
1-auto-1 -> 11,0
preferred name: auto-1
all names:
/F -> auto-1
/UF -> auto-1
all data streams:
/F -> 11,0
/UF -> 11,0
1-auto-3 -> 13,0
description: two words
preferred name: auto-Three.txt
all names:
/F -> auto-Three.txt
/UF -> auto-Three.txt
all data streams:
/F -> 13,0
/UF -> 13,0
1-auto-Two -> 15,0
preferred name: auto-2
all names:
/F -> auto-2
/UF -> auto-2
all data streams:
/F -> 15,0
/UF -> 15,0
auto-1 -> 17,0
preferred name: auto-1
all names:
/F -> auto-1
/UF -> auto-1
all data streams:
/F -> 17,0
/UF -> 17,0
auto-3 -> 19,0
description: two words
preferred name: auto-Three.txt
all names:
/F -> auto-Three.txt
/UF -> auto-Three.txt
all data streams:
/F -> 19,0
/UF -> 19,0
auto-Two -> 21,0
preferred name: auto-2
all names:
/F -> auto-2
/UF -> auto-2
all data streams:
/F -> 21,0
/UF -> 21,0

View File

@ -0,0 +1,25 @@
auto-1 -> 8,0
preferred name: auto-2
all names:
/F -> auto-2
/UF -> auto-2
all data streams:
/F -> 8,0
/UF -> 8,0
auto-3 -> 10,0
description: two words
preferred name: auto-Three.txt
all names:
/F -> auto-Three.txt
/UF -> auto-Three.txt
all data streams:
/F -> 10,0
/UF -> 10,0
auto-Two -> 12,0
preferred name: auto-2
all names:
/F -> auto-2
/UF -> auto-2
all data streams:
/F -> 12,0
/UF -> 12,0

View File

@ -0,0 +1,8 @@
auto-1 -> 6,0
preferred name: auto-1
all names:
/F -> auto-1
/UF -> auto-1
all data streams:
/F -> 6,0
/UF -> 6,0

View File

@ -0,0 +1,2 @@
qpdf: removed attachment att2
qpdf: wrote file b.pdf

View File

@ -0,0 +1,25 @@
att1 -> 8,0
description: some text
preferred name: att1.txt
all names:
/F -> att1.txt
/UF -> att1.txt
all data streams:
/F -> 8,0
/UF -> 8,0
att2 -> 10,0
preferred name: att2.txt
all names:
/F -> att2.txt
/UF -> att2.txt
all data streams:
/F -> 10,0
/UF -> 10,0
att3 -> 12,0
preferred name: π.txt
all names:
/F -> att3.txt
/UF -> π.txt
all data streams:
/F -> 12,0
/UF -> 12,0

View File

@ -0,0 +1,3 @@
att1 -> 8,0
att2 -> 10,0
att3 -> 12,0