1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
|
#include <qpdf/QPDF.hh>
#include <qpdf/QPDFEmbeddedFileDocumentHelper.hh>
#include <qpdf/QPDFFileSpecObjectHelper.hh>
#include <qpdf/QPDFWriter.hh>
#include <qpdf/QUtil.hh>
#include <cstring>
#include <iostream>
//
// 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.
//
static char const* whoami = nullptr;
static void
usage(std::string const& msg)
{
std::cerr << msg << '\n'
<< '\n'
<< "Usage: " << whoami << " options\n"
<< "Options:\n"
<< " --infile infile.pdf\n"
<< " --outfile outfile.pdf\n"
<< " --attachment attachment\n"
<< " [--password infile-password]\n"
<< " [--mimetype attachment mime type]\n";
exit(2);
}
static void
process(
char const* infilename,
char const* password,
char const* attachment,
char const* mimetype,
char const* outfilename)
{
QPDF q;
q.processFile(infilename, password);
// Create an indirect object for the built-in Helvetica font. This uses the qpdf literal syntax
// introduced in qpdf 10.6.
auto f1 = q.makeIndirectObject(
// force line-break
"<<"
" /Type /Font"
" /Subtype /Type1"
" /Name /F1"
" /BaseFont /Helvetica"
" /Encoding /WinAnsiEncoding"
">>"_qpdf);
// 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.
auto resources = q.makeIndirectObject(
// line-break
QPDFObjectHandle::parse(
&q,
("<<"
" /Font <<"
" /F1 " +
f1.unparse() +
" >>"
">>")));
// Create a file spec.
std::string key = QUtil::path_basename(attachment);
std::cout << whoami << ": attaching " << attachment << " as " << key << '\n';
auto fs = QPDFFileSpecObjectHelper::createFileSpec(q, key, attachment);
if (mimetype) {
// 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::get(q);
efdh.replaceEmbeddedFile(key, fs);
// Create a file attachment annotation.
// Create appearance stream for the attachment.
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");
auto apdict = ap.getDict();
// The following four lines demonstrate the use of the qpdf literal syntax introduced in
// qpdf 10.6. They could have been written as:
// 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 ]"));
apdict.replaceKey("/Resources", "<< >>"_qpdf);
apdict.replaceKey("/Type", "/XObject"_qpdf);
apdict.replaceKey("/Subtype", "/Form"_qpdf);
apdict.replaceKey("/BBox", "[ 0 0 20 20 ]"_qpdf);
auto annot = q.makeIndirectObject(
QPDFObjectHandle::parse(
&q,
("<<"
" /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"
">>")));
// Generate contents for the page.
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"));
// Create the page object.
auto page = QPDFObjectHandle::parse(
&q,
("<<"
" /Annots [ " +
annot.unparse() +
" ]"
" /Contents " +
contents.unparse() +
" /MediaBox [0 0 612 792]"
" /Resources " +
resources.unparse() +
" /Type /Page"
">>"));
// Add the page.
q.addPage(page, true);
QPDFWriter w(q, outfilename);
w.setQDFMode(true);
w.setSuppressOriginalObjectIDs(true);
w.setDeterministicID(true);
w.write();
}
int
main(int argc, char* argv[])
{
whoami = QUtil::getWhoami(argv[0]);
char const* infilename = nullptr;
char const* password = nullptr;
char const* attachment = nullptr;
char const* outfilename = nullptr;
char const* mimetype = nullptr;
auto check_arg = [](char const* arg, std::string const& msg) {
if (arg == nullptr) {
usage(msg);
}
};
for (int i = 1; i < argc; ++i) {
char* arg = argv[i];
char* next = argv[i + 1];
if (strcmp(arg, "--infile") == 0) {
check_arg(next, "--infile takes an argument");
infilename = next;
++i;
} else if (strcmp(arg, "--password") == 0) {
check_arg(next, "--password takes an argument");
password = next;
++i;
} else if (strcmp(arg, "--attachment") == 0) {
check_arg(next, "--attachment takes an argument");
attachment = next;
++i;
} else if (strcmp(arg, "--outfile") == 0) {
check_arg(next, "--outfile takes an argument");
outfilename = next;
++i;
} else if (strcmp(arg, "--mimetype") == 0) {
check_arg(next, "--mimetype takes an argument");
mimetype = next;
++i;
} else {
usage("unknown argument " + std::string(arg));
}
}
if (!(infilename && attachment && outfilename)) {
usage("required arguments were not provided");
}
try {
process(infilename, password, attachment, mimetype, outfilename);
} catch (std::exception& e) {
std::cerr << whoami << " exception: " << e.what() << '\n';
exit(2);
}
return 0;
}
|