aboutsummaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
authorJay Berkenbilt <ejb@ql.org>2021-02-18 02:14:04 +0100
committerJay Berkenbilt <ejb@ql.org>2021-02-18 15:59:03 +0100
commitf21e4f264ab285817bfad1698dbb8b8b300c9ece (patch)
tree42d4c7e4bd7198535b1317cdc100f82dae0d3241 /examples
parent8873466a5d86decc3600af2df45552f2ee26ec80 (diff)
downloadqpdf-f21e4f264ab285817bfad1698dbb8b8b300c9ece.tar.zst
Add file attachment example
Diffstat (limited to 'examples')
-rw-r--r--examples/build.mk1
-rw-r--r--examples/pdf-attach-file.cc234
-rw-r--r--examples/qtest/attach-file.test35
-rw-r--r--examples/qtest/attach-file/input.pdf79
-rw-r--r--examples/qtest/attach-file/output.pdfbin0 -> 5287 bytes
-rw-r--r--examples/qtest/attach-file/potato.pngbin0 -> 2692 bytes
6 files changed, 349 insertions, 0 deletions
diff --git a/examples/build.mk b/examples/build.mk
index bf730023..0d2c1132 100644
--- a/examples/build.mk
+++ b/examples/build.mk
@@ -1,4 +1,5 @@
BINS_examples = \
+ pdf-attach-file \
pdf-bookmarks \
pdf-count-strings \
pdf-create \
diff --git a/examples/pdf-attach-file.cc b/examples/pdf-attach-file.cc
new file mode 100644
index 00000000..903f30eb
--- /dev/null
+++ b/examples/pdf-attach-file.cc
@@ -0,0 +1,234 @@
+#include <qpdf/QPDF.hh>
+#include <qpdf/QUtil.hh>
+#include <qpdf/QPDFWriter.hh>
+#include <qpdf/QPDFEmbeddedFileDocumentHelper.hh>
+#include <qpdf/QPDFFileSpecObjectHelper.hh>
+
+#include <iostream>
+#include <cstring>
+
+//
+// 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.
+//
+
+static char const* whoami = 0;
+
+static void usage(std::string const& msg)
+{
+ std::cerr << msg << std::endl << std::endl
+ << "Usage: " << whoami << " options" << std::endl
+ << "Options:" << std::endl
+ << " --infile infile.pdf" << std::endl
+ << " --outfile outfile.pdf" << std::endl
+ << " --attachment attachment" << std::endl
+ << " [ --password infile-password ]" << std::endl
+ << " [ --mimetype attachment mime type ]" << std::endl;
+ 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.
+ auto f1 = q.makeIndirectObject(
+ QPDFObjectHandle::parse(
+ "<<"
+ " /Type /Font"
+ " /Subtype /Type1"
+ " /Name /F1"
+ " /BaseFont /Helvetica"
+ " /Encoding /WinAnsiEncoding"
+ ">>"));
+
+ // 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(
+ QPDFObjectHandle::parse(
+ &q,
+ "<<"
+ " /Font <<"
+ " /F1 " + f1.unparse() +
+ " >>"
+ ">>"));
+
+ // Create a file spec.
+ std::string key(attachment);
+ size_t pos = key.find_last_of("/\\");
+ if (pos != std::string::npos)
+ {
+ key = key.substr(pos + 1);
+ }
+ if (key.empty())
+ {
+ throw std::runtime_error("can't get last path element of attachment");
+ }
+ std::cout << whoami << ": attaching " << attachment << " as " << key
+ << std::endl;
+ 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(q);
+ efdh.replaceEmbeddedFile(key, fs);
+
+ // Create a file attachment annotation.
+
+ // Create appearance stream for the attachment.
+
+ auto ap = QPDFObjectHandle::newStream(
+ &q,
+ "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();
+ 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 ]"));
+ 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 = QPDFObjectHandle::newStream(
+ &q,
+ "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]);
+
+ // For libtool's sake....
+ if (strncmp(whoami, "lt-", 3) == 0)
+ {
+ whoami += 3;
+ }
+
+ char const* infilename = 0;
+ char const* password = 0;
+ char const* attachment = 0;
+ char const* outfilename = 0;
+ char const* mimetype = 0;
+
+ 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() << std::endl;
+ exit(2);
+ }
+
+ return 0;
+}
diff --git a/examples/qtest/attach-file.test b/examples/qtest/attach-file.test
new file mode 100644
index 00000000..9d8e7504
--- /dev/null
+++ b/examples/qtest/attach-file.test
@@ -0,0 +1,35 @@
+#!/usr/bin/env perl
+require 5.008;
+use warnings;
+use strict;
+
+chdir("attach-file") or die "chdir testdir failed: $!\n";
+
+require TestDriver;
+
+cleanup();
+
+my $td = new TestDriver('attach-file');
+
+$td->runtest("attach file",
+ {$td->COMMAND =>
+ "pdf-attach-file --infile input.pdf" .
+ " --attachment ./potato.png" .
+ " --outfile a.pdf" .
+ " --mimetype image/png"},
+ {$td->STRING =>
+ "pdf-attach-file: attaching ./potato.png as potato.png\n",
+ $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("check output",
+ {$td->FILE => "a.pdf"},
+ {$td->FILE => "output.pdf"});
+
+cleanup();
+
+$td->report(2);
+
+sub cleanup
+{
+ unlink "a.pdf";
+}
diff --git a/examples/qtest/attach-file/input.pdf b/examples/qtest/attach-file/input.pdf
new file mode 100644
index 00000000..a7e01f91
--- /dev/null
+++ b/examples/qtest/attach-file/input.pdf
@@ -0,0 +1,79 @@
+%PDF-1.3
+1 0 obj
+<<
+ /Type /Catalog
+ /Pages 2 0 R
+>>
+endobj
+
+2 0 obj
+<<
+ /Type /Pages
+ /Kids [
+ 3 0 R
+ ]
+ /Count 1
+>>
+endobj
+
+3 0 obj
+<<
+ /Type /Page
+ /Parent 2 0 R
+ /MediaBox [0 0 612 792]
+ /Contents 4 0 R
+ /Resources <<
+ /ProcSet 5 0 R
+ /Font <<
+ /F1 6 0 R
+ >>
+ >>
+>>
+endobj
+
+4 0 obj
+<<
+ /Length 44
+>>
+stream
+BT
+ /F1 24 Tf
+ 72 720 Td
+ (Potato) Tj
+ET
+endstream
+endobj
+
+5 0 obj
+[
+ /PDF
+ /Text
+]
+endobj
+
+6 0 obj
+<<
+ /Type /Font
+ /Subtype /Type1
+ /Name /F1
+ /BaseFont /Helvetica
+ /Encoding /WinAnsiEncoding
+>>
+endobj
+
+xref
+0 7
+0000000000 65535 f
+0000000009 00000 n
+0000000063 00000 n
+0000000135 00000 n
+0000000307 00000 n
+0000000403 00000 n
+0000000438 00000 n
+trailer <<
+ /Size 7
+ /Root 1 0 R
+>>
+startxref
+556
+%%EOF
diff --git a/examples/qtest/attach-file/output.pdf b/examples/qtest/attach-file/output.pdf
new file mode 100644
index 00000000..d41cef4f
--- /dev/null
+++ b/examples/qtest/attach-file/output.pdf
Binary files differ
diff --git a/examples/qtest/attach-file/potato.png b/examples/qtest/attach-file/potato.png
new file mode 100644
index 00000000..da425324
--- /dev/null
+++ b/examples/qtest/attach-file/potato.png
Binary files differ