mirror of
https://github.com/qpdf/qpdf.git
synced 2024-12-22 02:49:00 +00:00
Add file attachment example
This commit is contained in:
parent
8873466a5d
commit
f21e4f264a
@ -1,3 +1,8 @@
|
||||
2021-02-18 Jay Berkenbilt <ejb@ql.org>
|
||||
|
||||
* Add examples/pdf-attach-file.cc to illustrate new file
|
||||
attachment method and also new parse that takes indirect objects.
|
||||
|
||||
2021-02-17 Jay Berkenbilt <ejb@ql.org>
|
||||
|
||||
* Allow optional numeric argument to --collate. If --collate=n is
|
||||
|
4
TODO
4
TODO
@ -249,6 +249,10 @@ directory or that are otherwise not publicly accessible. This includes
|
||||
things sent to me by email that are specifically not public. Even so,
|
||||
I find it useful to make reference to them in this list.
|
||||
|
||||
* Add code for creation of a file attachment annotation. It should
|
||||
also be possible to create a widget annotation and a form field.
|
||||
Update the pdf-attach-file.cc example with new APIs when ready.
|
||||
|
||||
* If I do more with json, take a look at this C++ header-only JSON
|
||||
library: https://github.com/nlohmann/json/releases
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
BINS_examples = \
|
||||
pdf-attach-file \
|
||||
pdf-bookmarks \
|
||||
pdf-count-strings \
|
||||
pdf-create \
|
||||
|
234
examples/pdf-attach-file.cc
Normal file
234
examples/pdf-attach-file.cc
Normal file
@ -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;
|
||||
}
|
35
examples/qtest/attach-file.test
Normal file
35
examples/qtest/attach-file.test
Normal file
@ -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";
|
||||
}
|
79
examples/qtest/attach-file/input.pdf
Normal file
79
examples/qtest/attach-file/input.pdf
Normal file
@ -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
|
BIN
examples/qtest/attach-file/output.pdf
Normal file
BIN
examples/qtest/attach-file/output.pdf
Normal file
Binary file not shown.
BIN
examples/qtest/attach-file/potato.png
Normal file
BIN
examples/qtest/attach-file/potato.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
@ -5209,7 +5209,9 @@ print "\n";
|
||||
<classname>QPDFEmbeddedFileDocumentHelper</classname>,
|
||||
<classname>QPDFFileSpecObjectHelper</classname>, and
|
||||
<classname>QPDFEFStreamObjectHelper</classname>. See their
|
||||
respective headers for details.
|
||||
respective headers for details and
|
||||
<filename>examples/pdf-attach-file.cc</filename> for an
|
||||
example.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
@ -5231,6 +5233,8 @@ print "\n";
|
||||
<function>QPDFObjectHandle::parse</function> that takes a
|
||||
<classname>QPDF</classname> pointer as context so that it
|
||||
can parse strings containing indirect object references.
|
||||
This is illustrated in
|
||||
<filename>examples/pdf-attach-file.cc</filename>.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
|
Loading…
Reference in New Issue
Block a user