2021-02-18 01:14:04 +00:00
|
|
|
#include <qpdf/QPDF.hh>
|
|
|
|
#include <qpdf/QPDFEmbeddedFileDocumentHelper.hh>
|
|
|
|
#include <qpdf/QPDFFileSpecObjectHelper.hh>
|
2022-04-02 21:14:10 +00:00
|
|
|
#include <qpdf/QPDFWriter.hh>
|
|
|
|
#include <qpdf/QUtil.hh>
|
2021-02-18 01:14:04 +00:00
|
|
|
|
|
|
|
#include <cstring>
|
2022-04-02 21:14:10 +00:00
|
|
|
#include <iostream>
|
2021-02-18 01:14:04 +00:00
|
|
|
|
|
|
|
//
|
2023-05-27 17:19:52 +00:00
|
|
|
// This example attaches a file to an input file, adds a page to the beginning of the file that
|
|
|
|
// includes a file attachment annotation, and writes the result to an output file. It also
|
|
|
|
// illustrates a number of new API calls that were added in qpdf 10.2 as well as the use of the qpdf
|
|
|
|
// literal syntax introduced in qpdf 10.6.
|
2021-02-18 01:14:04 +00:00
|
|
|
//
|
|
|
|
|
2022-07-26 11:37:50 +00:00
|
|
|
static char const* whoami = nullptr;
|
2021-02-18 01:14:04 +00:00
|
|
|
|
2022-04-02 21:14:10 +00:00
|
|
|
static void
|
|
|
|
usage(std::string const& msg)
|
2021-02-18 01:14:04 +00:00
|
|
|
{
|
2022-04-02 21:14:10 +00:00
|
|
|
std::cerr << msg << std::endl
|
|
|
|
<< std::endl
|
2021-02-18 01:14:04 +00:00
|
|
|
<< "Usage: " << whoami << " options" << std::endl
|
|
|
|
<< "Options:" << std::endl
|
|
|
|
<< " --infile infile.pdf" << std::endl
|
|
|
|
<< " --outfile outfile.pdf" << std::endl
|
|
|
|
<< " --attachment attachment" << std::endl
|
2022-02-20 16:49:31 +00:00
|
|
|
<< " [--password infile-password]" << std::endl
|
|
|
|
<< " [--mimetype attachment mime type]" << std::endl;
|
2021-02-18 01:14:04 +00:00
|
|
|
exit(2);
|
|
|
|
}
|
|
|
|
|
2022-04-02 21:14:10 +00:00
|
|
|
static void
|
|
|
|
process(
|
|
|
|
char const* infilename,
|
|
|
|
char const* password,
|
|
|
|
char const* attachment,
|
|
|
|
char const* mimetype,
|
|
|
|
char const* outfilename)
|
2021-02-18 01:14:04 +00:00
|
|
|
{
|
|
|
|
QPDF q;
|
|
|
|
q.processFile(infilename, password);
|
|
|
|
|
2023-05-27 17:19:52 +00:00
|
|
|
// Create an indirect object for the built-in Helvetica font. This uses the qpdf literal syntax
|
|
|
|
// introduced in qpdf 10.6.
|
2022-04-03 20:10:27 +00:00
|
|
|
auto f1 = q.makeIndirectObject(
|
|
|
|
// force line-break
|
|
|
|
"<<"
|
|
|
|
" /Type /Font"
|
|
|
|
" /Subtype /Type1"
|
|
|
|
" /Name /F1"
|
|
|
|
" /BaseFont /Helvetica"
|
|
|
|
" /Encoding /WinAnsiEncoding"
|
|
|
|
">>"_qpdf);
|
2021-02-18 01:14:04 +00:00
|
|
|
|
2023-05-27 17:19:52 +00:00
|
|
|
// Create a resources dictionary with fonts. This uses the new parse introduced in qpdf 10.2
|
|
|
|
// that takes a QPDF* and allows indirect object references.
|
2022-04-03 20:10:27 +00:00
|
|
|
auto resources = q.makeIndirectObject(
|
|
|
|
// line-break
|
|
|
|
QPDFObjectHandle::parse(
|
|
|
|
&q,
|
|
|
|
("<<"
|
|
|
|
" /Font <<"
|
|
|
|
" /F1 " +
|
|
|
|
f1.unparse() +
|
|
|
|
" >>"
|
|
|
|
">>")));
|
2021-02-18 01:14:04 +00:00
|
|
|
|
|
|
|
// Create a file spec.
|
2021-02-18 13:03:32 +00:00
|
|
|
std::string key = QUtil::path_basename(attachment);
|
2023-05-21 17:35:09 +00:00
|
|
|
std::cout << whoami << ": attaching " << attachment << " as " << key << std::endl;
|
2021-02-18 01:14:04 +00:00
|
|
|
auto fs = QPDFFileSpecObjectHelper::createFileSpec(q, key, attachment);
|
|
|
|
|
2022-04-02 21:14:10 +00:00
|
|
|
if (mimetype) {
|
2021-02-18 01:14:04 +00:00
|
|
|
// Get an embedded file stream and set mimetype
|
|
|
|
auto ef = QPDFEFStreamObjectHelper(fs.getEmbeddedFileStream());
|
|
|
|
ef.setSubtype(mimetype);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the embedded file at the document level as an attachment.
|
|
|
|
auto efdh = QPDFEmbeddedFileDocumentHelper(q);
|
|
|
|
efdh.replaceEmbeddedFile(key, fs);
|
|
|
|
|
|
|
|
// Create a file attachment annotation.
|
|
|
|
|
|
|
|
// Create appearance stream for the attachment.
|
|
|
|
|
2022-09-26 18:29:26 +00:00
|
|
|
auto ap = q.newStream("0 10 m\n"
|
|
|
|
"10 0 l\n"
|
|
|
|
"20 10 l\n"
|
|
|
|
"10 0 m\n"
|
|
|
|
"10 20 l\n"
|
|
|
|
"0 0 20 20 re\n"
|
|
|
|
"S\n");
|
2021-02-18 01:14:04 +00:00
|
|
|
auto apdict = ap.getDict();
|
2022-02-20 16:49:31 +00:00
|
|
|
|
2023-05-27 17:19:52 +00:00
|
|
|
// The following four lines demonstrate the use of the qpdf literal syntax introduced in
|
|
|
|
// qpdf 10.6. They could have been written as:
|
2022-02-20 16:49:31 +00:00
|
|
|
// apdict.replaceKey("/Resources", QPDFObjectHandle::newDictionary());
|
|
|
|
// apdict.replaceKey("/Type", QPDFObjectHandle::newName("/XObject"));
|
|
|
|
// apdict.replaceKey("/Subtype", QPDFObjectHandle::newName("/Form"));
|
|
|
|
// apdict.replaceKey("/BBox", QPDFObjectHandle::parse("[ 0 0 20 20 ]"));
|
2022-05-17 22:28:50 +00:00
|
|
|
apdict.replaceKey("/Resources", "<< >>"_qpdf);
|
|
|
|
apdict.replaceKey("/Type", "/XObject"_qpdf);
|
|
|
|
apdict.replaceKey("/Subtype", "/Form"_qpdf);
|
|
|
|
apdict.replaceKey("/BBox", "[ 0 0 20 20 ]"_qpdf);
|
2022-04-02 21:14:10 +00:00
|
|
|
auto annot = q.makeIndirectObject(QPDFObjectHandle::parse(
|
|
|
|
&q,
|
2022-04-03 20:10:27 +00:00
|
|
|
("<<"
|
|
|
|
" /AP <<"
|
|
|
|
" /N " +
|
|
|
|
ap.unparse() +
|
|
|
|
" >>"
|
|
|
|
" /Contents " +
|
|
|
|
QPDFObjectHandle::newUnicodeString(attachment).unparse() + " /FS " +
|
|
|
|
fs.getObjectHandle().unparse() + " /NM " +
|
|
|
|
QPDFObjectHandle::newUnicodeString(attachment).unparse() +
|
|
|
|
" /Rect [ 72 700 92 720 ]"
|
|
|
|
" /Subtype /FileAttachment"
|
|
|
|
" /Type /Annot"
|
|
|
|
">>")));
|
2021-02-18 01:14:04 +00:00
|
|
|
|
|
|
|
// Generate contents for the page.
|
2022-09-26 18:29:26 +00:00
|
|
|
auto contents = q.newStream(("q\n"
|
|
|
|
"BT\n"
|
|
|
|
" 102 700 Td\n"
|
|
|
|
" /F1 16 Tf\n"
|
|
|
|
" (Here is an attachment.) Tj\n"
|
|
|
|
"ET\n"
|
|
|
|
"Q\n"));
|
2021-02-18 01:14:04 +00:00
|
|
|
|
|
|
|
// Create the page object.
|
|
|
|
auto page = QPDFObjectHandle::parse(
|
|
|
|
&q,
|
2022-04-03 20:10:27 +00:00
|
|
|
("<<"
|
|
|
|
" /Annots [ " +
|
|
|
|
annot.unparse() +
|
|
|
|
" ]"
|
|
|
|
" /Contents " +
|
|
|
|
contents.unparse() +
|
|
|
|
" /MediaBox [0 0 612 792]"
|
|
|
|
" /Resources " +
|
|
|
|
resources.unparse() +
|
|
|
|
" /Type /Page"
|
|
|
|
">>"));
|
2021-02-18 01:14:04 +00:00
|
|
|
|
|
|
|
// Add the page.
|
|
|
|
q.addPage(page, true);
|
|
|
|
|
|
|
|
QPDFWriter w(q, outfilename);
|
|
|
|
w.setQDFMode(true);
|
|
|
|
w.setSuppressOriginalObjectIDs(true);
|
|
|
|
w.setDeterministicID(true);
|
|
|
|
w.write();
|
|
|
|
}
|
|
|
|
|
2022-04-02 21:14:10 +00:00
|
|
|
int
|
|
|
|
main(int argc, char* argv[])
|
2021-02-18 01:14:04 +00:00
|
|
|
{
|
|
|
|
whoami = QUtil::getWhoami(argv[0]);
|
|
|
|
|
2022-07-26 11:37:50 +00:00
|
|
|
char const* infilename = nullptr;
|
|
|
|
char const* password = nullptr;
|
|
|
|
char const* attachment = nullptr;
|
|
|
|
char const* outfilename = nullptr;
|
|
|
|
char const* mimetype = nullptr;
|
2021-02-18 01:14:04 +00:00
|
|
|
|
|
|
|
auto check_arg = [](char const* arg, std::string const& msg) {
|
2022-04-02 21:14:10 +00:00
|
|
|
if (arg == nullptr) {
|
2021-02-18 01:14:04 +00:00
|
|
|
usage(msg);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-04-02 21:14:10 +00:00
|
|
|
for (int i = 1; i < argc; ++i) {
|
2021-02-18 01:14:04 +00:00
|
|
|
char* arg = argv[i];
|
2022-04-02 21:14:10 +00:00
|
|
|
char* next = argv[i + 1];
|
|
|
|
if (strcmp(arg, "--infile") == 0) {
|
2021-02-18 01:14:04 +00:00
|
|
|
check_arg(next, "--infile takes an argument");
|
|
|
|
infilename = next;
|
|
|
|
++i;
|
2022-04-02 21:14:10 +00:00
|
|
|
} else if (strcmp(arg, "--password") == 0) {
|
2021-02-18 01:14:04 +00:00
|
|
|
check_arg(next, "--password takes an argument");
|
|
|
|
password = next;
|
|
|
|
++i;
|
2022-04-02 21:14:10 +00:00
|
|
|
} else if (strcmp(arg, "--attachment") == 0) {
|
2021-02-18 01:14:04 +00:00
|
|
|
check_arg(next, "--attachment takes an argument");
|
|
|
|
attachment = next;
|
|
|
|
++i;
|
2022-04-02 21:14:10 +00:00
|
|
|
} else if (strcmp(arg, "--outfile") == 0) {
|
2021-02-18 01:14:04 +00:00
|
|
|
check_arg(next, "--outfile takes an argument");
|
|
|
|
outfilename = next;
|
|
|
|
++i;
|
2022-04-02 21:14:10 +00:00
|
|
|
} else if (strcmp(arg, "--mimetype") == 0) {
|
2021-02-18 01:14:04 +00:00
|
|
|
check_arg(next, "--mimetype takes an argument");
|
|
|
|
mimetype = next;
|
|
|
|
++i;
|
2022-04-02 21:14:10 +00:00
|
|
|
} else {
|
2021-02-18 01:14:04 +00:00
|
|
|
usage("unknown argument " + std::string(arg));
|
|
|
|
}
|
|
|
|
}
|
2022-04-02 21:14:10 +00:00
|
|
|
if (!(infilename && attachment && outfilename)) {
|
2021-02-18 01:14:04 +00:00
|
|
|
usage("required arguments were not provided");
|
|
|
|
}
|
|
|
|
|
2022-04-02 21:14:10 +00:00
|
|
|
try {
|
2021-02-18 01:14:04 +00:00
|
|
|
process(infilename, password, attachment, mimetype, outfilename);
|
2022-04-02 21:14:10 +00:00
|
|
|
} catch (std::exception& e) {
|
|
|
|
std::cerr << whoami << " exception: " << e.what() << std::endl;
|
2022-02-08 14:18:08 +00:00
|
|
|
exit(2);
|
2021-02-18 01:14:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|