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 2021-02-17 Jay Berkenbilt <ejb@ql.org> 6 2021-02-17 Jay Berkenbilt <ejb@ql.org>
2 7
3 * Allow optional numeric argument to --collate. If --collate=n is 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,6 +249,10 @@ directory or that are otherwise not publicly accessible. This includes
249 things sent to me by email that are specifically not public. Even so, 249 things sent to me by email that are specifically not public. Even so,
250 I find it useful to make reference to them in this list. 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 * If I do more with json, take a look at this C++ header-only JSON 256 * If I do more with json, take a look at this C++ header-only JSON
253 library: https://github.com/nlohmann/json/releases 257 library: https://github.com/nlohmann/json/releases
254 258
examples/build.mk
1 BINS_examples = \ 1 BINS_examples = \
  2 + pdf-attach-file \
2 pdf-bookmarks \ 3 pdf-bookmarks \
3 pdf-count-strings \ 4 pdf-count-strings \
4 pdf-create \ 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,7 +5209,9 @@ print &quot;\n&quot;;
5209 <classname>QPDFEmbeddedFileDocumentHelper</classname>, 5209 <classname>QPDFEmbeddedFileDocumentHelper</classname>,
5210 <classname>QPDFFileSpecObjectHelper</classname>, and 5210 <classname>QPDFFileSpecObjectHelper</classname>, and
5211 <classname>QPDFEFStreamObjectHelper</classname>. See their 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 </para> 5215 </para>
5214 </listitem> 5216 </listitem>
5215 <listitem> 5217 <listitem>
@@ -5231,6 +5233,8 @@ print &quot;\n&quot;; @@ -5231,6 +5233,8 @@ print &quot;\n&quot;;
5231 <function>QPDFObjectHandle::parse</function> that takes a 5233 <function>QPDFObjectHandle::parse</function> that takes a
5232 <classname>QPDF</classname> pointer as context so that it 5234 <classname>QPDF</classname> pointer as context so that it
5233 can parse strings containing indirect object references. 5235 can parse strings containing indirect object references.
  5236 + This is illustrated in
  5237 + <filename>examples/pdf-attach-file.cc</filename>.
5234 </para> 5238 </para>
5235 </listitem> 5239 </listitem>
5236 <listitem> 5240 <listitem>