Commit f21e4f264ab285817bfad1698dbb8b8b300c9ece

Authored by Jay Berkenbilt
1 parent 8873466a

Add file attachment example

ChangeLog
  1 +2021-02-18 Jay Berkenbilt <ejb@ql.org>
  2 +
  3 + * Add examples/pdf-attach-file.cc to illustrate new file
  4 + attachment method and also new parse that takes indirect objects.
  5 +
1 6 2021-02-17 Jay Berkenbilt <ejb@ql.org>
2 7  
3 8 * Allow optional numeric argument to --collate. If --collate=n is
... ...
... ... @@ -249,6 +249,10 @@ directory or that are otherwise not publicly accessible. This includes
249 249 things sent to me by email that are specifically not public. Even so,
250 250 I find it useful to make reference to them in this list.
251 251  
  252 + * Add code for creation of a file attachment annotation. It should
  253 + also be possible to create a widget annotation and a form field.
  254 + Update the pdf-attach-file.cc example with new APIs when ready.
  255 +
252 256 * If I do more with json, take a look at this C++ header-only JSON
253 257 library: https://github.com/nlohmann/json/releases
254 258  
... ...
examples/build.mk
1 1 BINS_examples = \
  2 + pdf-attach-file \
2 3 pdf-bookmarks \
3 4 pdf-count-strings \
4 5 pdf-create \
... ...
examples/pdf-attach-file.cc 0 → 100644
  1 +#include <qpdf/QPDF.hh>
  2 +#include <qpdf/QUtil.hh>
  3 +#include <qpdf/QPDFWriter.hh>
  4 +#include <qpdf/QPDFEmbeddedFileDocumentHelper.hh>
  5 +#include <qpdf/QPDFFileSpecObjectHelper.hh>
  6 +
  7 +#include <iostream>
  8 +#include <cstring>
  9 +
  10 +//
  11 +// This example attaches a file to an input file, adds a page to the
  12 +// beginning of the file that includes a file attachment annotation,
  13 +// and writes the result to an output file. It also illustrates a
  14 +// number of new API calls that were added in qpdf 10.2.
  15 +//
  16 +
  17 +static char const* whoami = 0;
  18 +
  19 +static void usage(std::string const& msg)
  20 +{
  21 + std::cerr << msg << std::endl << std::endl
  22 + << "Usage: " << whoami << " options" << std::endl
  23 + << "Options:" << std::endl
  24 + << " --infile infile.pdf" << std::endl
  25 + << " --outfile outfile.pdf" << std::endl
  26 + << " --attachment attachment" << std::endl
  27 + << " [ --password infile-password ]" << std::endl
  28 + << " [ --mimetype attachment mime type ]" << std::endl;
  29 + exit(2);
  30 +}
  31 +
  32 +static void process(char const* infilename, char const* password,
  33 + char const* attachment, char const* mimetype,
  34 + char const* outfilename)
  35 +{
  36 + QPDF q;
  37 + q.processFile(infilename, password);
  38 +
  39 + // Create an indirect object for the built-in Helvetica font.
  40 + auto f1 = q.makeIndirectObject(
  41 + QPDFObjectHandle::parse(
  42 + "<<"
  43 + " /Type /Font"
  44 + " /Subtype /Type1"
  45 + " /Name /F1"
  46 + " /BaseFont /Helvetica"
  47 + " /Encoding /WinAnsiEncoding"
  48 + ">>"));
  49 +
  50 + // Create a resources dictionary with fonts. This uses the new
  51 + // parse introduced in qpdf 10.2 that takes a QPDF* and allows
  52 + // indirect object references.
  53 + auto resources = q.makeIndirectObject(
  54 + QPDFObjectHandle::parse(
  55 + &q,
  56 + "<<"
  57 + " /Font <<"
  58 + " /F1 " + f1.unparse() +
  59 + " >>"
  60 + ">>"));
  61 +
  62 + // Create a file spec.
  63 + std::string key(attachment);
  64 + size_t pos = key.find_last_of("/\\");
  65 + if (pos != std::string::npos)
  66 + {
  67 + key = key.substr(pos + 1);
  68 + }
  69 + if (key.empty())
  70 + {
  71 + throw std::runtime_error("can't get last path element of attachment");
  72 + }
  73 + std::cout << whoami << ": attaching " << attachment << " as " << key
  74 + << std::endl;
  75 + auto fs = QPDFFileSpecObjectHelper::createFileSpec(q, key, attachment);
  76 +
  77 + if (mimetype)
  78 + {
  79 + // Get an embedded file stream and set mimetype
  80 + auto ef = QPDFEFStreamObjectHelper(fs.getEmbeddedFileStream());
  81 + ef.setSubtype(mimetype);
  82 + }
  83 +
  84 + // Add the embedded file at the document level as an attachment.
  85 + auto efdh = QPDFEmbeddedFileDocumentHelper(q);
  86 + efdh.replaceEmbeddedFile(key, fs);
  87 +
  88 + // Create a file attachment annotation.
  89 +
  90 + // Create appearance stream for the attachment.
  91 +
  92 + auto ap = QPDFObjectHandle::newStream(
  93 + &q,
  94 + "0 10 m\n"
  95 + "10 0 l\n"
  96 + "20 10 l\n"
  97 + "10 0 m\n"
  98 + "10 20 l\n"
  99 + "0 0 20 20 re\n"
  100 + "S\n");
  101 + auto apdict = ap.getDict();
  102 + apdict.replaceKey("/Resources", QPDFObjectHandle::newDictionary());
  103 + apdict.replaceKey("/Type", QPDFObjectHandle::newName("/XObject"));
  104 + apdict.replaceKey("/Subtype", QPDFObjectHandle::newName("/Form"));
  105 + apdict.replaceKey("/BBox", QPDFObjectHandle::parse("[ 0 0 20 20 ]"));
  106 + auto annot = q.makeIndirectObject(
  107 + QPDFObjectHandle::parse(
  108 + &q,
  109 + "<<"
  110 + " /AP <<"
  111 + " /N " + ap.unparse() +
  112 + " >>"
  113 + " /Contents "
  114 + + QPDFObjectHandle::newUnicodeString(attachment).unparse() +
  115 + " /FS " + fs.getObjectHandle().unparse() +
  116 + " /NM " +
  117 + QPDFObjectHandle::newUnicodeString(attachment).unparse() +
  118 + " /Rect [ 72 700 92 720 ]"
  119 + " /Subtype /FileAttachment"
  120 + " /Type /Annot"
  121 + ">>"));
  122 +
  123 + // Generate contents for the page.
  124 + auto contents = QPDFObjectHandle::newStream(
  125 + &q,
  126 + "q\n"
  127 + "BT\n"
  128 + " 102 700 Td\n"
  129 + " /F1 16 Tf\n"
  130 + " (Here is an attachment.) Tj\n"
  131 + "ET\n"
  132 + "Q\n");
  133 +
  134 + // Create the page object.
  135 + auto page = QPDFObjectHandle::parse(
  136 + &q,
  137 + "<<"
  138 + " /Annots [ " + annot.unparse() + " ]"
  139 + " /Contents " + contents.unparse() +
  140 + " /MediaBox [0 0 612 792]"
  141 + " /Resources " + resources.unparse() +
  142 + " /Type /Page"
  143 + ">>");
  144 +
  145 + // Add the page.
  146 + q.addPage(page, true);
  147 +
  148 + QPDFWriter w(q, outfilename);
  149 + w.setQDFMode(true);
  150 + w.setSuppressOriginalObjectIDs(true);
  151 + w.setDeterministicID(true);
  152 + w.write();
  153 +}
  154 +
  155 +int main(int argc, char* argv[])
  156 +{
  157 + whoami = QUtil::getWhoami(argv[0]);
  158 +
  159 + // For libtool's sake....
  160 + if (strncmp(whoami, "lt-", 3) == 0)
  161 + {
  162 + whoami += 3;
  163 + }
  164 +
  165 + char const* infilename = 0;
  166 + char const* password = 0;
  167 + char const* attachment = 0;
  168 + char const* outfilename = 0;
  169 + char const* mimetype = 0;
  170 +
  171 + auto check_arg = [](char const* arg, std::string const& msg) {
  172 + if (arg == nullptr)
  173 + {
  174 + usage(msg);
  175 + }
  176 + };
  177 +
  178 + for (int i = 1; i < argc; ++i)
  179 + {
  180 + char* arg = argv[i];
  181 + char* next = argv[i+1];
  182 + if (strcmp(arg, "--infile") == 0)
  183 + {
  184 + check_arg(next, "--infile takes an argument");
  185 + infilename = next;
  186 + ++i;
  187 + }
  188 + else if (strcmp(arg, "--password") == 0)
  189 + {
  190 + check_arg(next, "--password takes an argument");
  191 + password = next;
  192 + ++i;
  193 + }
  194 + else if (strcmp(arg, "--attachment") == 0)
  195 + {
  196 + check_arg(next, "--attachment takes an argument");
  197 + attachment = next;
  198 + ++i;
  199 + }
  200 + else if (strcmp(arg, "--outfile") == 0)
  201 + {
  202 + check_arg(next, "--outfile takes an argument");
  203 + outfilename = next;
  204 + ++i;
  205 + }
  206 + else if (strcmp(arg, "--mimetype") == 0)
  207 + {
  208 + check_arg(next, "--mimetype takes an argument");
  209 + mimetype = next;
  210 + ++i;
  211 + }
  212 + else
  213 + {
  214 + usage("unknown argument " + std::string(arg));
  215 + }
  216 + }
  217 + if (! (infilename && attachment && outfilename))
  218 + {
  219 + usage("required arguments were not provided");
  220 + }
  221 +
  222 + try
  223 + {
  224 + process(infilename, password, attachment, mimetype, outfilename);
  225 + }
  226 + catch (std::exception &e)
  227 + {
  228 + std::cerr << whoami << " exception: "
  229 + << e.what() << std::endl;
  230 + exit(2);
  231 + }
  232 +
  233 + return 0;
  234 +}
... ...
examples/qtest/attach-file.test 0 → 100644
  1 +#!/usr/bin/env perl
  2 +require 5.008;
  3 +use warnings;
  4 +use strict;
  5 +
  6 +chdir("attach-file") or die "chdir testdir failed: $!\n";
  7 +
  8 +require TestDriver;
  9 +
  10 +cleanup();
  11 +
  12 +my $td = new TestDriver('attach-file');
  13 +
  14 +$td->runtest("attach file",
  15 + {$td->COMMAND =>
  16 + "pdf-attach-file --infile input.pdf" .
  17 + " --attachment ./potato.png" .
  18 + " --outfile a.pdf" .
  19 + " --mimetype image/png"},
  20 + {$td->STRING =>
  21 + "pdf-attach-file: attaching ./potato.png as potato.png\n",
  22 + $td->EXIT_STATUS => 0},
  23 + $td->NORMALIZE_NEWLINES);
  24 +$td->runtest("check output",
  25 + {$td->FILE => "a.pdf"},
  26 + {$td->FILE => "output.pdf"});
  27 +
  28 +cleanup();
  29 +
  30 +$td->report(2);
  31 +
  32 +sub cleanup
  33 +{
  34 + unlink "a.pdf";
  35 +}
... ...
examples/qtest/attach-file/input.pdf 0 → 100644
  1 +%PDF-1.3
  2 +1 0 obj
  3 +<<
  4 + /Type /Catalog
  5 + /Pages 2 0 R
  6 +>>
  7 +endobj
  8 +
  9 +2 0 obj
  10 +<<
  11 + /Type /Pages
  12 + /Kids [
  13 + 3 0 R
  14 + ]
  15 + /Count 1
  16 +>>
  17 +endobj
  18 +
  19 +3 0 obj
  20 +<<
  21 + /Type /Page
  22 + /Parent 2 0 R
  23 + /MediaBox [0 0 612 792]
  24 + /Contents 4 0 R
  25 + /Resources <<
  26 + /ProcSet 5 0 R
  27 + /Font <<
  28 + /F1 6 0 R
  29 + >>
  30 + >>
  31 +>>
  32 +endobj
  33 +
  34 +4 0 obj
  35 +<<
  36 + /Length 44
  37 +>>
  38 +stream
  39 +BT
  40 + /F1 24 Tf
  41 + 72 720 Td
  42 + (Potato) Tj
  43 +ET
  44 +endstream
  45 +endobj
  46 +
  47 +5 0 obj
  48 +[
  49 + /PDF
  50 + /Text
  51 +]
  52 +endobj
  53 +
  54 +6 0 obj
  55 +<<
  56 + /Type /Font
  57 + /Subtype /Type1
  58 + /Name /F1
  59 + /BaseFont /Helvetica
  60 + /Encoding /WinAnsiEncoding
  61 +>>
  62 +endobj
  63 +
  64 +xref
  65 +0 7
  66 +0000000000 65535 f
  67 +0000000009 00000 n
  68 +0000000063 00000 n
  69 +0000000135 00000 n
  70 +0000000307 00000 n
  71 +0000000403 00000 n
  72 +0000000438 00000 n
  73 +trailer <<
  74 + /Size 7
  75 + /Root 1 0 R
  76 +>>
  77 +startxref
  78 +556
  79 +%%EOF
... ...
examples/qtest/attach-file/output.pdf 0 → 100644
No preview for this file type
examples/qtest/attach-file/potato.png 0 → 100644

2.63 KB

manual/qpdf-manual.xml
... ... @@ -5209,7 +5209,9 @@ print &quot;\n&quot;;
5209 5209 <classname>QPDFEmbeddedFileDocumentHelper</classname>,
5210 5210 <classname>QPDFFileSpecObjectHelper</classname>, and
5211 5211 <classname>QPDFEFStreamObjectHelper</classname>. See their
5212   - respective headers for details.
  5212 + respective headers for details and
  5213 + <filename>examples/pdf-attach-file.cc</filename> for an
  5214 + example.
5213 5215 </para>
5214 5216 </listitem>
5215 5217 <listitem>
... ... @@ -5231,6 +5233,8 @@ print &quot;\n&quot;;
5231 5233 <function>QPDFObjectHandle::parse</function> that takes a
5232 5234 <classname>QPDF</classname> pointer as context so that it
5233 5235 can parse strings containing indirect object references.
  5236 + This is illustrated in
  5237 + <filename>examples/pdf-attach-file.cc</filename>.
5234 5238 </para>
5235 5239 </listitem>
5236 5240 <listitem>
... ...