2008-04-29 12:55:25 +00:00
|
|
|
// This program tests miscellaneous functionality in the qpdf library
|
|
|
|
// that we don't want to pollute the qpdf program with.
|
|
|
|
|
|
|
|
#include <qpdf/QPDF.hh>
|
|
|
|
|
2018-06-18 20:38:15 +00:00
|
|
|
#include <qpdf/QPDFPageDocumentHelper.hh>
|
|
|
|
#include <qpdf/QPDFPageObjectHelper.hh>
|
2018-06-19 13:26:41 +00:00
|
|
|
#include <qpdf/QPDFAcroFormDocumentHelper.hh>
|
2018-12-18 18:08:55 +00:00
|
|
|
#include <qpdf/QPDFNumberTreeObjectHelper.hh>
|
2018-12-19 02:56:56 +00:00
|
|
|
#include <qpdf/QPDFNameTreeObjectHelper.hh>
|
2018-12-18 16:29:00 +00:00
|
|
|
#include <qpdf/QPDFPageLabelDocumentHelper.hh>
|
2018-12-19 02:08:53 +00:00
|
|
|
#include <qpdf/QPDFOutlineDocumentHelper.hh>
|
2008-04-29 12:55:25 +00:00
|
|
|
#include <qpdf/QUtil.hh>
|
|
|
|
#include <qpdf/QTC.hh>
|
|
|
|
#include <qpdf/Pl_StdioFile.hh>
|
2009-10-18 19:54:24 +00:00
|
|
|
#include <qpdf/Pl_Buffer.hh>
|
2010-08-03 02:29:15 +00:00
|
|
|
#include <qpdf/Pl_Flate.hh>
|
2008-04-29 12:55:25 +00:00
|
|
|
#include <qpdf/QPDFWriter.hh>
|
2019-02-08 00:21:26 +00:00
|
|
|
#include <qpdf/QPDFSystemError.hh>
|
2019-06-21 03:35:23 +00:00
|
|
|
#include <qpdf/QIntC.hh>
|
2008-04-29 12:55:25 +00:00
|
|
|
#include <iostream>
|
2010-10-01 11:02:35 +00:00
|
|
|
#include <sstream>
|
2013-11-29 15:20:50 +00:00
|
|
|
#include <algorithm>
|
2009-10-19 23:09:19 +00:00
|
|
|
#include <stdio.h>
|
2008-04-29 12:55:25 +00:00
|
|
|
#include <string.h>
|
2008-05-04 16:02:53 +00:00
|
|
|
#include <stdlib.h>
|
2012-06-21 16:21:34 +00:00
|
|
|
#include <assert.h>
|
2019-06-20 17:04:57 +00:00
|
|
|
#include <limits.h>
|
2008-04-29 12:55:25 +00:00
|
|
|
#include <map>
|
|
|
|
|
|
|
|
static char const* whoami = 0;
|
|
|
|
|
|
|
|
void usage()
|
|
|
|
{
|
2012-12-30 14:27:49 +00:00
|
|
|
std::cerr << "Usage: " << whoami << " n filename1 [arg2]"
|
2012-07-14 12:48:30 +00:00
|
|
|
<< std::endl;
|
2008-04-29 12:55:25 +00:00
|
|
|
exit(2);
|
|
|
|
}
|
|
|
|
|
2010-08-05 19:04:22 +00:00
|
|
|
class Provider: public QPDFObjectHandle::StreamDataProvider
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
Provider(PointerHolder<Buffer> b) :
|
|
|
|
b(b),
|
|
|
|
bad_length(false)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
virtual ~Provider()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
virtual void provideStreamData(int objid, int generation,
|
|
|
|
Pipeline* p)
|
|
|
|
{
|
2010-09-24 20:45:18 +00:00
|
|
|
p->write(b->getBuffer(), b->getSize());
|
2010-08-05 19:04:22 +00:00
|
|
|
if (this->bad_length)
|
|
|
|
{
|
|
|
|
unsigned char ch = ' ';
|
|
|
|
p->write(&ch, 1);
|
|
|
|
}
|
|
|
|
p->finish();
|
|
|
|
}
|
|
|
|
void badLength(bool v)
|
|
|
|
{
|
|
|
|
this->bad_length = v;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
PointerHolder<Buffer> b;
|
|
|
|
bool bad_length;
|
|
|
|
};
|
|
|
|
|
2013-01-20 20:26:45 +00:00
|
|
|
class ParserCallbacks: public QPDFObjectHandle::ParserCallbacks
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
virtual ~ParserCallbacks()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2019-08-22 23:16:25 +00:00
|
|
|
virtual void contentSize(size_t size);
|
|
|
|
virtual void handleObject(QPDFObjectHandle, size_t, size_t);
|
2013-01-20 20:26:45 +00:00
|
|
|
virtual void handleEOF();
|
|
|
|
};
|
|
|
|
|
|
|
|
void
|
2019-08-22 23:16:25 +00:00
|
|
|
ParserCallbacks::contentSize(size_t size)
|
|
|
|
{
|
|
|
|
std::cout << "content size: " << size << std::endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ParserCallbacks::handleObject(QPDFObjectHandle obj,
|
|
|
|
size_t offset, size_t length)
|
2013-01-20 20:26:45 +00:00
|
|
|
{
|
2013-03-03 20:48:31 +00:00
|
|
|
if (obj.isName() && (obj.getName() == "/Abort"))
|
|
|
|
{
|
|
|
|
std::cout << "test suite: terminating parsing" << std::endl;
|
|
|
|
terminateParsing();
|
|
|
|
}
|
2019-08-22 23:16:25 +00:00
|
|
|
std::cout << obj.getTypeName() << ", offset=" << offset
|
|
|
|
<< ", length=" << length << ": ";
|
2013-01-20 20:26:45 +00:00
|
|
|
if (obj.isInlineImage())
|
|
|
|
{
|
2013-01-25 13:59:55 +00:00
|
|
|
// Exercise getTypeCode
|
2013-01-22 14:57:07 +00:00
|
|
|
assert(obj.getTypeCode() == QPDFObject::ot_inlineimage);
|
2013-01-25 13:59:55 +00:00
|
|
|
std::cout << QUtil::hex_encode(obj.getInlineImageValue()) << std::endl;
|
2013-01-20 20:26:45 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
std::cout << obj.unparse() << std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ParserCallbacks::handleEOF()
|
|
|
|
{
|
|
|
|
std::cout << "-EOF-" << std::endl;
|
|
|
|
}
|
|
|
|
|
2018-02-02 23:21:34 +00:00
|
|
|
class TokenFilter: public QPDFObjectHandle::TokenFilter
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
TokenFilter()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
virtual ~TokenFilter()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
virtual void handleToken(QPDFTokenizer::Token const& t)
|
|
|
|
{
|
|
|
|
if (t == QPDFTokenizer::Token(QPDFTokenizer::tt_string, "Potato"))
|
|
|
|
{
|
|
|
|
// Exercise unparsing of strings by token constructor
|
|
|
|
writeToken(
|
|
|
|
QPDFTokenizer::Token(QPDFTokenizer::tt_string, "Salad"));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
writeToken(t);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
virtual void handleEOF()
|
|
|
|
{
|
|
|
|
writeToken(QPDFTokenizer::Token(QPDFTokenizer::tt_name, "/bye"));
|
|
|
|
write("\n");
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-07-16 01:15:24 +00:00
|
|
|
static std::string getPageContents(QPDFObjectHandle page)
|
2012-06-21 16:21:34 +00:00
|
|
|
{
|
|
|
|
PointerHolder<Buffer> b1 =
|
|
|
|
page.getKey("/Contents").getStreamData();
|
2013-02-24 02:46:21 +00:00
|
|
|
return std::string(
|
|
|
|
reinterpret_cast<char *>(b1->getBuffer()), b1->getSize()) + "\0";
|
2012-07-16 01:15:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void checkPageContents(QPDFObjectHandle page,
|
|
|
|
std::string const& wanted_string)
|
|
|
|
{
|
|
|
|
std::string contents = getPageContents(page);
|
2012-06-21 16:21:34 +00:00
|
|
|
if (contents.find(wanted_string) == std::string::npos)
|
|
|
|
{
|
|
|
|
std::cout << "didn't find " << wanted_string << " in "
|
|
|
|
<< contents << std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static QPDFObjectHandle createPageContents(QPDF& pdf, std::string const& text)
|
|
|
|
{
|
|
|
|
std::string contents = "BT /F1 15 Tf 72 720 Td (" + text + ") Tj ET\n";
|
2012-06-27 04:00:58 +00:00
|
|
|
return QPDFObjectHandle::newStream(&pdf, contents);
|
2012-06-21 16:21:34 +00:00
|
|
|
}
|
|
|
|
|
2018-06-19 13:26:41 +00:00
|
|
|
static void print_rect(std::ostream& out,
|
|
|
|
QPDFObjectHandle::Rectangle const& r)
|
|
|
|
{
|
|
|
|
out << "[" << r.llx << ", " << r.lly << ", "
|
|
|
|
<< r.urx << ", " << r.ury << "]";
|
|
|
|
}
|
|
|
|
|
2019-06-20 17:04:57 +00:00
|
|
|
#define assert_compare_numbers(expected, expr) \
|
|
|
|
compare_numbers(#expr, expected, expr)
|
|
|
|
|
|
|
|
template <typename T1, typename T2>
|
|
|
|
static void compare_numbers(
|
|
|
|
char const* description, T1 const& expected, T2 const& actual)
|
|
|
|
{
|
|
|
|
if (expected != actual)
|
|
|
|
{
|
|
|
|
std::cerr << description << ": expected = " << expected
|
|
|
|
<< "; actual = " << actual << std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-30 14:27:49 +00:00
|
|
|
void runtest(int n, char const* filename1, char const* arg2)
|
2008-04-29 12:55:25 +00:00
|
|
|
{
|
2012-06-21 16:21:34 +00:00
|
|
|
// Most tests here are crafted to work on specific files. Look at
|
|
|
|
// the test suite to see how the test is invoked to find the file
|
|
|
|
// that the test is supposed to operate on.
|
|
|
|
|
2012-12-28 17:21:11 +00:00
|
|
|
if (n == 0)
|
|
|
|
{
|
|
|
|
// Throw in some random test cases that don't fit anywhere
|
|
|
|
// else. This is in addition to whatever else is going on in
|
|
|
|
// test 0.
|
|
|
|
|
|
|
|
// The code to trim user passwords looks for 0x28 (which is
|
|
|
|
// "(") since it marks the beginning of the padding. Exercise
|
|
|
|
// the code to make sure it skips over 0x28 characters that
|
|
|
|
// aren't part of padding.
|
|
|
|
std::string password(
|
|
|
|
"1234567890123456789012(45678\x28\xbf\x4e\x5e");
|
|
|
|
assert(password.length() == 32);
|
|
|
|
QPDF::trim_user_password(password);
|
|
|
|
assert(password == "1234567890123456789012(45678");
|
2013-01-22 14:57:07 +00:00
|
|
|
|
|
|
|
QPDFObjectHandle uninitialized;
|
|
|
|
assert(uninitialized.getTypeCode() == QPDFObject::ot_uninitialized);
|
|
|
|
assert(strcmp(uninitialized.getTypeName(), "uninitialized") == 0);
|
2012-12-28 17:21:11 +00:00
|
|
|
}
|
|
|
|
|
2008-04-29 12:55:25 +00:00
|
|
|
QPDF pdf;
|
2010-10-01 10:20:38 +00:00
|
|
|
PointerHolder<char> file_buf;
|
2012-06-21 11:59:56 +00:00
|
|
|
FILE* filep = 0;
|
2008-04-29 12:55:25 +00:00
|
|
|
if (n == 0)
|
|
|
|
{
|
|
|
|
pdf.setAttemptRecovery(false);
|
|
|
|
}
|
2012-12-30 18:54:12 +00:00
|
|
|
if (((n == 35) || (n == 36)) && (arg2 != 0))
|
2012-12-30 02:31:21 +00:00
|
|
|
{
|
|
|
|
// arg2 is password
|
|
|
|
pdf.processFile(filename1, arg2);
|
|
|
|
}
|
2018-08-13 23:26:20 +00:00
|
|
|
else if (n == 45)
|
|
|
|
{
|
|
|
|
// Decode obfuscated files. To obfuscated, run the input file
|
|
|
|
// through this perl script, and save the result to
|
|
|
|
// filename.obfuscated. This pretends that the input was
|
|
|
|
// called filename.pdf and that that file contained the
|
|
|
|
// deobfuscated version.
|
|
|
|
|
|
|
|
// undef $/;
|
|
|
|
// my @str = split('', <STDIN>);
|
|
|
|
// for (my $i = 0; $i < scalar(@str); ++$i)
|
|
|
|
// {
|
|
|
|
// $str[$i] = chr(ord($str[$i]) ^ 0xcc);
|
|
|
|
// }
|
|
|
|
// print(join('', @str));
|
|
|
|
|
|
|
|
std::string filename(std::string(filename1) + ".obfuscated");
|
|
|
|
size_t size = 0;
|
2019-06-22 14:12:10 +00:00
|
|
|
QUtil::read_file_into_memory(filename.c_str(), file_buf, size);
|
2018-08-13 23:26:20 +00:00
|
|
|
char* p = file_buf.getPointer();
|
|
|
|
for (size_t i = 0; i < size; ++i)
|
|
|
|
{
|
2019-06-21 03:35:23 +00:00
|
|
|
p[i] = static_cast<char>(p[i] ^ 0xcc);
|
2018-08-13 23:26:20 +00:00
|
|
|
}
|
|
|
|
pdf.processMemoryFile((std::string(filename1) + ".pdf").c_str(),
|
|
|
|
p, size);
|
|
|
|
}
|
2019-02-08 00:21:26 +00:00
|
|
|
else if (n == 61)
|
|
|
|
{
|
|
|
|
// Ignore filename argument entirely
|
|
|
|
}
|
2012-12-30 02:31:21 +00:00
|
|
|
else if (n % 2 == 0)
|
2010-10-01 10:20:38 +00:00
|
|
|
{
|
2012-06-21 11:59:56 +00:00
|
|
|
if (n % 4 == 0)
|
|
|
|
{
|
|
|
|
QTC::TC("qpdf", "exercise processFile(name)");
|
2012-07-14 12:48:30 +00:00
|
|
|
pdf.processFile(filename1);
|
2012-06-21 11:59:56 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
QTC::TC("qpdf", "exercise processFile(FILE*)");
|
2013-02-28 21:45:11 +00:00
|
|
|
filep = QUtil::safe_fopen(filename1, "rb");
|
2012-07-14 12:48:30 +00:00
|
|
|
pdf.processFile(filename1, filep, false);
|
2012-06-21 11:59:56 +00:00
|
|
|
}
|
2010-10-01 10:20:38 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2012-06-21 11:59:56 +00:00
|
|
|
QTC::TC("qpdf", "exercise processMemoryFile");
|
2018-08-13 23:26:20 +00:00
|
|
|
size_t size = 0;
|
2019-06-22 14:12:10 +00:00
|
|
|
QUtil::read_file_into_memory(filename1, file_buf, size);
|
2018-08-13 23:26:20 +00:00
|
|
|
pdf.processMemoryFile(filename1, file_buf.getPointer(), size);
|
2010-10-01 10:20:38 +00:00
|
|
|
}
|
2008-04-29 12:55:25 +00:00
|
|
|
|
|
|
|
if ((n == 0) || (n == 1))
|
|
|
|
{
|
|
|
|
QPDFObjectHandle trailer = pdf.getTrailer();
|
|
|
|
QPDFObjectHandle qtest = trailer.getKey("/QTest");
|
|
|
|
|
|
|
|
if (! trailer.hasKey("/QTest"))
|
|
|
|
{
|
|
|
|
// This will always happen when /QTest is null because
|
|
|
|
// hasKey returns false for null keys regardless of
|
|
|
|
// whether the key exists or not. That way there's never
|
|
|
|
// any difference between a key that is present and null
|
|
|
|
// and a key that is absent.
|
|
|
|
QTC::TC("qpdf", "main QTest implicit");
|
|
|
|
std::cout << "/QTest is implicit" << std::endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
QTC::TC("qpdf", "main QTest indirect",
|
|
|
|
qtest.isIndirect() ? 1 : 0);
|
|
|
|
std::cout << "/QTest is "
|
|
|
|
<< (qtest.isIndirect() ? "in" : "")
|
2013-03-04 15:59:20 +00:00
|
|
|
<< "direct and has type "
|
|
|
|
<< qtest.getTypeName()
|
|
|
|
<< " (" << qtest.getTypeCode() << ")" << std::endl;
|
2008-04-29 12:55:25 +00:00
|
|
|
|
|
|
|
if (qtest.isNull())
|
|
|
|
{
|
|
|
|
QTC::TC("qpdf", "main QTest null");
|
|
|
|
std::cout << "/QTest is null" << std::endl;
|
|
|
|
}
|
|
|
|
else if (qtest.isBool())
|
|
|
|
{
|
|
|
|
QTC::TC("qpdf", "main QTest bool",
|
|
|
|
qtest.getBoolValue() ? 1 : 0);
|
|
|
|
std::cout << "/QTest is Boolean with value "
|
|
|
|
<< (qtest.getBoolValue() ? "true" : "false")
|
|
|
|
<< std::endl;
|
|
|
|
}
|
|
|
|
else if (qtest.isInteger())
|
|
|
|
{
|
|
|
|
QTC::TC("qpdf", "main QTest int");
|
|
|
|
std::cout << "/QTest is an integer with value "
|
|
|
|
<< qtest.getIntValue() << std::endl;
|
|
|
|
}
|
|
|
|
else if (qtest.isReal())
|
|
|
|
{
|
|
|
|
QTC::TC("qpdf", "main QTest real");
|
|
|
|
std::cout << "/QTest is a real number with value "
|
|
|
|
<< qtest.getRealValue() << std::endl;
|
|
|
|
}
|
|
|
|
else if (qtest.isName())
|
|
|
|
{
|
|
|
|
QTC::TC("qpdf", "main QTest name");
|
|
|
|
std::cout << "/QTest is a name with value "
|
|
|
|
<< qtest.getName() << std::endl;
|
|
|
|
}
|
|
|
|
else if (qtest.isString())
|
|
|
|
{
|
|
|
|
QTC::TC("qpdf", "main QTest string");
|
|
|
|
std::cout << "/QTest is a string with value "
|
|
|
|
<< qtest.getStringValue() << std::endl;
|
|
|
|
}
|
|
|
|
else if (qtest.isArray())
|
|
|
|
{
|
|
|
|
QTC::TC("qpdf", "main QTest array");
|
2020-04-16 15:43:37 +00:00
|
|
|
int nitems = qtest.getArrayNItems();
|
2008-04-29 12:55:25 +00:00
|
|
|
std::cout << "/QTest is an array with "
|
2020-04-16 15:43:37 +00:00
|
|
|
<< nitems << " items" << std::endl;
|
|
|
|
for (int i = 0; i < nitems; ++i)
|
2008-04-29 12:55:25 +00:00
|
|
|
{
|
|
|
|
QTC::TC("qpdf", "main QTest array indirect",
|
|
|
|
qtest.getArrayItem(i).isIndirect() ? 1 : 0);
|
|
|
|
std::cout << " item " << i << " is "
|
|
|
|
<< (qtest.getArrayItem(i).isIndirect() ? "in" : "")
|
|
|
|
<< "direct" << std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (qtest.isDictionary())
|
|
|
|
{
|
|
|
|
QTC::TC("qpdf", "main QTest dictionary");
|
|
|
|
std::cout << "/QTest is a dictionary" << std::endl;
|
|
|
|
std::set<std::string> keys = qtest.getKeys();
|
|
|
|
for (std::set<std::string>::iterator iter = keys.begin();
|
|
|
|
iter != keys.end(); ++iter)
|
|
|
|
{
|
|
|
|
QTC::TC("qpdf", "main QTest dictionary indirect",
|
|
|
|
(qtest.getKey(*iter).isIndirect() ? 1 : 0));
|
|
|
|
std::cout << " " << *iter << " is "
|
|
|
|
<< (qtest.getKey(*iter).isIndirect() ? "in" : "")
|
|
|
|
<< "direct" << std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (qtest.isStream())
|
|
|
|
{
|
|
|
|
QTC::TC("qpdf", "main QTest stream");
|
|
|
|
std::cout << "/QTest is a stream. Dictionary: "
|
|
|
|
<< qtest.getDict().unparse() << std::endl;
|
|
|
|
|
|
|
|
std::cout << "Raw stream data:" << std::endl;
|
|
|
|
std::cout.flush();
|
2009-07-15 03:47:44 +00:00
|
|
|
QUtil::binary_stdout();
|
2008-04-29 12:55:25 +00:00
|
|
|
PointerHolder<Pl_StdioFile> out = new Pl_StdioFile("raw", stdout);
|
2017-08-19 13:18:14 +00:00
|
|
|
qtest.pipeStreamData(out.getPointer(), 0, qpdf_dl_none);
|
2008-04-29 12:55:25 +00:00
|
|
|
|
|
|
|
std::cout << std::endl << "Uncompressed stream data:" << std::endl;
|
2017-08-19 13:18:14 +00:00
|
|
|
if (qtest.pipeStreamData(0, 0, qpdf_dl_all))
|
2008-04-29 12:55:25 +00:00
|
|
|
{
|
|
|
|
std::cout.flush();
|
2009-07-15 03:47:44 +00:00
|
|
|
QUtil::binary_stdout();
|
2008-04-29 12:55:25 +00:00
|
|
|
out = new Pl_StdioFile("filtered", stdout);
|
2017-08-19 13:18:14 +00:00
|
|
|
qtest.pipeStreamData(out.getPointer(), 0, qpdf_dl_all);
|
2008-04-29 12:55:25 +00:00
|
|
|
std::cout << std::endl << "End of stream data" << std::endl;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
std::cout << "Stream data is not filterable." << std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Should not happen!
|
|
|
|
std::cout << "/QTest is an unknown object" << std::endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::cout << "unparse: " << qtest.unparse() << std::endl
|
|
|
|
<< "unparseResolved: " << qtest.unparseResolved()
|
|
|
|
<< std::endl;
|
|
|
|
}
|
|
|
|
else if (n == 2)
|
|
|
|
{
|
|
|
|
// Encrypted file. This test case is designed for a specific
|
|
|
|
// PDF file.
|
|
|
|
|
|
|
|
QPDFObjectHandle trailer = pdf.getTrailer();
|
|
|
|
std::cout << trailer.getKey("/Info").
|
|
|
|
getKey("/CreationDate").getStringValue() << std::endl;
|
|
|
|
std::cout << trailer.getKey("/Info").
|
|
|
|
getKey("/Producer").getStringValue() << std::endl;
|
|
|
|
|
|
|
|
QPDFObjectHandle encrypt = trailer.getKey("/Encrypt");
|
|
|
|
std::cout << encrypt.getKey("/O").unparse() << std::endl;
|
|
|
|
std::cout << encrypt.getKey("/U").unparse() << std::endl;
|
|
|
|
|
|
|
|
QPDFObjectHandle root = pdf.getRoot();
|
|
|
|
QPDFObjectHandle pages = root.getKey("/Pages");
|
|
|
|
QPDFObjectHandle kids = pages.getKey("/Kids");
|
|
|
|
QPDFObjectHandle page = kids.getArrayItem(1); // second page
|
|
|
|
QPDFObjectHandle contents = page.getKey("/Contents");
|
2009-07-15 03:47:44 +00:00
|
|
|
QUtil::binary_stdout();
|
2008-04-29 12:55:25 +00:00
|
|
|
PointerHolder<Pl_StdioFile> out = new Pl_StdioFile("filtered", stdout);
|
2017-08-19 13:18:14 +00:00
|
|
|
contents.pipeStreamData(out.getPointer(), 0, qpdf_dl_generalized);
|
2008-04-29 12:55:25 +00:00
|
|
|
}
|
|
|
|
else if (n == 3)
|
|
|
|
{
|
|
|
|
QPDFObjectHandle streams = pdf.getTrailer().getKey("/QStreams");
|
|
|
|
for (int i = 0; i < streams.getArrayNItems(); ++i)
|
|
|
|
{
|
|
|
|
QPDFObjectHandle stream = streams.getArrayItem(i);
|
|
|
|
std::cout << "-- stream " << i << " --" << std::endl;
|
|
|
|
std::cout.flush();
|
2009-07-15 03:47:44 +00:00
|
|
|
QUtil::binary_stdout();
|
2008-04-29 12:55:25 +00:00
|
|
|
PointerHolder<Pl_StdioFile> out =
|
|
|
|
new Pl_StdioFile("tokenized stream", stdout);
|
2017-08-19 13:18:14 +00:00
|
|
|
stream.pipeStreamData(out.getPointer(),
|
|
|
|
qpdf_ef_normalize, qpdf_dl_generalized);
|
2008-04-29 12:55:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (n == 4)
|
|
|
|
{
|
|
|
|
// Mutability testing: Make /QTest direct recursively, then
|
|
|
|
// copy to /Info. Also make some other mutations so we can
|
|
|
|
// tell the difference and ensure that the original /QTest
|
|
|
|
// isn't effected.
|
|
|
|
QPDFObjectHandle trailer = pdf.getTrailer();
|
|
|
|
QPDFObjectHandle qtest = trailer.getKey("/QTest");
|
|
|
|
qtest.makeDirect();
|
|
|
|
qtest.removeKey("/Subject");
|
|
|
|
qtest.replaceKey("/Author",
|
|
|
|
QPDFObjectHandle::newString("Mr. Potato Head"));
|
|
|
|
// qtest.A and qtest.B.A were originally the same object.
|
|
|
|
// They no longer are after makeDirect(). Mutate one of them
|
2012-06-20 12:20:51 +00:00
|
|
|
// and ensure the other is not changed. These test cases are
|
|
|
|
// crafted around a specific set of input files.
|
|
|
|
QPDFObjectHandle A = qtest.getKey("/A");
|
|
|
|
if (A.getArrayItem(0).getIntValue() == 1)
|
|
|
|
{
|
|
|
|
// Test mutators
|
|
|
|
A.setArrayItem(1, QPDFObjectHandle::newInteger(5)); // 1 5 3
|
|
|
|
A.insertItem(2, QPDFObjectHandle::newInteger(10)); // 1 5 10 3
|
|
|
|
A.appendItem(QPDFObjectHandle::newInteger(12)); // 1 5 10 3 12
|
|
|
|
A.eraseItem(3); // 1 5 10 12
|
|
|
|
A.insertItem(4, QPDFObjectHandle::newInteger(6)); // 1 5 10 12 6
|
|
|
|
A.insertItem(0, QPDFObjectHandle::newInteger(9)); // 9 1 5 10 12 6
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
std::vector<QPDFObjectHandle> items;
|
|
|
|
items.push_back(QPDFObjectHandle::newInteger(14));
|
|
|
|
items.push_back(QPDFObjectHandle::newInteger(15));
|
|
|
|
items.push_back(QPDFObjectHandle::newInteger(9));
|
|
|
|
A.setArrayFromVector(items);
|
|
|
|
}
|
|
|
|
|
2020-12-22 14:31:26 +00:00
|
|
|
QPDFObjectHandle qtest2 = trailer.getKey("/QTest2");
|
|
|
|
if (! qtest2.isNull())
|
|
|
|
{
|
|
|
|
// Test allow_streams=true
|
|
|
|
qtest2.makeDirect(true);
|
|
|
|
trailer.replaceKey("/QTest2", qtest2);
|
|
|
|
}
|
|
|
|
|
2008-04-29 12:55:25 +00:00
|
|
|
trailer.replaceKey("/Info", pdf.makeIndirectObject(qtest));
|
|
|
|
QPDFWriter w(pdf, 0);
|
|
|
|
w.setQDFMode(true);
|
|
|
|
w.setStaticID(true);
|
|
|
|
w.write();
|
|
|
|
|
|
|
|
// Prevent "done" message from getting appended
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
else if (n == 5)
|
|
|
|
{
|
2018-06-18 20:38:15 +00:00
|
|
|
QPDFPageDocumentHelper dh(pdf);
|
|
|
|
std::vector<QPDFPageObjectHelper> pages = dh.getAllPages();
|
2008-04-29 12:55:25 +00:00
|
|
|
int pageno = 0;
|
2018-06-18 20:38:15 +00:00
|
|
|
for (std::vector<QPDFPageObjectHelper>::iterator iter = pages.begin();
|
2008-04-29 12:55:25 +00:00
|
|
|
iter != pages.end(); ++iter)
|
|
|
|
{
|
2018-06-18 20:38:15 +00:00
|
|
|
QPDFPageObjectHelper& page(*iter);
|
2008-04-29 12:55:25 +00:00
|
|
|
++pageno;
|
|
|
|
|
|
|
|
std::cout << "page " << pageno << ":" << std::endl;
|
|
|
|
|
|
|
|
std::cout << " images:" << std::endl;
|
|
|
|
std::map<std::string, QPDFObjectHandle> images =
|
|
|
|
page.getPageImages();
|
2020-04-16 15:43:37 +00:00
|
|
|
for (auto const& iter2: images)
|
2008-04-29 12:55:25 +00:00
|
|
|
{
|
2020-04-16 15:43:37 +00:00
|
|
|
std::string const& name = iter2.first;
|
|
|
|
QPDFObjectHandle image = iter2.second;
|
2008-04-29 12:55:25 +00:00
|
|
|
QPDFObjectHandle dict = image.getDict();
|
2019-06-21 03:35:23 +00:00
|
|
|
long long width = dict.getKey("/Width").getIntValue();
|
|
|
|
long long height = dict.getKey("/Height").getIntValue();
|
2008-04-29 12:55:25 +00:00
|
|
|
std::cout << " " << name
|
|
|
|
<< ": " << width << " x " << height
|
|
|
|
<< std::endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::cout << " content:" << std::endl;
|
|
|
|
std::vector<QPDFObjectHandle> content = page.getPageContents();
|
2020-04-16 15:43:37 +00:00
|
|
|
for (auto& iter2: content)
|
2008-04-29 12:55:25 +00:00
|
|
|
{
|
2020-04-16 15:43:37 +00:00
|
|
|
std::cout << " " << iter2.unparse() << std::endl;
|
2008-04-29 12:55:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::cout << "end page " << pageno << std::endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
QPDFObjectHandle root = pdf.getRoot();
|
|
|
|
QPDFObjectHandle qstrings = root.getKey("/QStrings");
|
|
|
|
if (qstrings.isArray())
|
|
|
|
{
|
|
|
|
std::cout << "QStrings:" << std::endl;
|
2020-04-16 15:43:37 +00:00
|
|
|
int nitems = qstrings.getArrayNItems();
|
|
|
|
for (int i = 0; i < nitems; ++i)
|
2008-04-29 12:55:25 +00:00
|
|
|
{
|
|
|
|
std::cout << qstrings.getArrayItem(i).getUTF8Value()
|
|
|
|
<< std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QPDFObjectHandle qnumbers = root.getKey("/QNumbers");
|
|
|
|
if (qnumbers.isArray())
|
|
|
|
{
|
|
|
|
std::cout << "QNumbers:" << std::endl;
|
2020-04-16 15:43:37 +00:00
|
|
|
int nitems = qnumbers.getArrayNItems();
|
|
|
|
for (int i = 0; i < nitems; ++i)
|
2008-04-29 12:55:25 +00:00
|
|
|
{
|
|
|
|
std::cout << QUtil::double_to_string(
|
|
|
|
qnumbers.getArrayItem(i).getNumericValue(), 3)
|
|
|
|
<< std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2009-10-18 19:54:24 +00:00
|
|
|
else if (n == 6)
|
|
|
|
{
|
|
|
|
QPDFObjectHandle root = pdf.getRoot();
|
|
|
|
QPDFObjectHandle metadata = root.getKey("/Metadata");
|
|
|
|
if (! metadata.isStream())
|
|
|
|
{
|
|
|
|
throw std::logic_error("test 6 run on file with no metadata");
|
|
|
|
}
|
|
|
|
Pl_Buffer bufpl("buffer");
|
2017-08-19 13:18:14 +00:00
|
|
|
metadata.pipeStreamData(&bufpl, 0, qpdf_dl_none);
|
2009-10-18 19:54:24 +00:00
|
|
|
Buffer* buf = bufpl.getBuffer();
|
|
|
|
unsigned char const* data = buf->getBuffer();
|
|
|
|
bool cleartext = false;
|
|
|
|
if ((buf->getSize() > 9) &&
|
2013-02-24 02:46:21 +00:00
|
|
|
(strncmp(reinterpret_cast<char const*>(data),
|
|
|
|
"<?xpacket", 9) == 0))
|
2009-10-18 19:54:24 +00:00
|
|
|
{
|
|
|
|
cleartext = true;
|
|
|
|
}
|
|
|
|
delete buf;
|
|
|
|
std::cout << "encrypted="
|
|
|
|
<< (pdf.isEncrypted() ? 1 : 0)
|
|
|
|
<< "; cleartext="
|
|
|
|
<< (cleartext ? 1 : 0)
|
|
|
|
<< std::endl;
|
|
|
|
}
|
2010-08-03 02:29:15 +00:00
|
|
|
else if (n == 7)
|
|
|
|
{
|
|
|
|
QPDFObjectHandle root = pdf.getRoot();
|
|
|
|
QPDFObjectHandle qstream = root.getKey("/QStream");
|
|
|
|
if (! qstream.isStream())
|
|
|
|
{
|
|
|
|
throw std::logic_error("test 7 run on file with no QStream");
|
|
|
|
}
|
|
|
|
qstream.replaceStreamData(
|
2012-07-26 02:32:14 +00:00
|
|
|
"new data for stream\n",
|
|
|
|
QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull());
|
2010-08-03 02:29:15 +00:00
|
|
|
QPDFWriter w(pdf, "a.pdf");
|
|
|
|
w.setStaticID(true);
|
|
|
|
w.setStreamDataMode(qpdf_s_preserve);
|
|
|
|
w.write();
|
|
|
|
}
|
|
|
|
else if (n == 8)
|
|
|
|
{
|
|
|
|
QPDFObjectHandle root = pdf.getRoot();
|
|
|
|
QPDFObjectHandle qstream = root.getKey("/QStream");
|
|
|
|
if (! qstream.isStream())
|
|
|
|
{
|
|
|
|
throw std::logic_error("test 7 run on file with no QStream");
|
|
|
|
}
|
|
|
|
Pl_Buffer p1("buffer");
|
|
|
|
Pl_Flate p2("compress", &p1, Pl_Flate::a_deflate);
|
2013-02-24 02:46:21 +00:00
|
|
|
p2.write(QUtil::unsigned_char_pointer("new data for stream\n"),
|
|
|
|
20); // no null!
|
2010-08-03 02:29:15 +00:00
|
|
|
p2.finish();
|
|
|
|
PointerHolder<Buffer> b = p1.getBuffer();
|
2010-08-05 19:04:22 +00:00
|
|
|
// This is a bogus way to use StreamDataProvider, but it does
|
|
|
|
// adequately test its functionality.
|
|
|
|
Provider* provider = new Provider(b);
|
|
|
|
PointerHolder<QPDFObjectHandle::StreamDataProvider> p = provider;
|
2010-08-03 02:29:15 +00:00
|
|
|
qstream.replaceStreamData(
|
2010-08-05 19:04:22 +00:00
|
|
|
p, QPDFObjectHandle::newName("/FlateDecode"),
|
2012-07-07 21:33:45 +00:00
|
|
|
QPDFObjectHandle::newNull());
|
|
|
|
provider->badLength(false);
|
|
|
|
QPDFWriter w(pdf, "a.pdf");
|
|
|
|
w.setStaticID(true);
|
|
|
|
// Linearize to force the provider to be called multiple times.
|
|
|
|
w.setLinearization(true);
|
|
|
|
w.setStreamDataMode(qpdf_s_preserve);
|
|
|
|
w.write();
|
|
|
|
|
|
|
|
// Every time a provider pipes stream data, it has to provide
|
|
|
|
// the same amount of data.
|
2010-08-05 19:04:22 +00:00
|
|
|
provider->badLength(true);
|
|
|
|
try
|
|
|
|
{
|
|
|
|
qstream.getStreamData();
|
|
|
|
std::cout << "oops -- getStreamData didn't throw" << std::endl;
|
|
|
|
}
|
2017-07-27 22:18:18 +00:00
|
|
|
catch (std::exception const& e)
|
2010-08-05 19:04:22 +00:00
|
|
|
{
|
|
|
|
std::cout << "exception: " << e.what() << std::endl;
|
|
|
|
}
|
2010-08-03 02:29:15 +00:00
|
|
|
}
|
2010-08-05 20:20:52 +00:00
|
|
|
else if (n == 9)
|
|
|
|
{
|
|
|
|
QPDFObjectHandle root = pdf.getRoot();
|
2012-07-26 02:32:14 +00:00
|
|
|
// Explicitly exercise the Buffer version of newStream
|
|
|
|
PointerHolder<Buffer> buf = new Buffer(20);
|
|
|
|
unsigned char* bp = buf->getBuffer();
|
2013-02-24 02:46:21 +00:00
|
|
|
memcpy(bp, "data for new stream\n", 20); // no null!
|
2012-07-11 02:24:09 +00:00
|
|
|
QPDFObjectHandle qstream = QPDFObjectHandle::newStream(
|
2012-07-26 02:32:14 +00:00
|
|
|
&pdf, buf);
|
2010-08-05 20:20:52 +00:00
|
|
|
QPDFObjectHandle rstream = QPDFObjectHandle::newStream(&pdf);
|
|
|
|
try
|
|
|
|
{
|
|
|
|
rstream.getStreamData();
|
|
|
|
std::cout << "oops -- getStreamData didn't throw" << std::endl;
|
|
|
|
}
|
|
|
|
catch (std::logic_error const& e)
|
|
|
|
{
|
|
|
|
std::cout << "exception: " << e.what() << std::endl;
|
|
|
|
}
|
|
|
|
rstream.replaceStreamData(
|
2012-07-26 02:32:14 +00:00
|
|
|
"data for other stream\n",
|
|
|
|
QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull());
|
2010-08-05 20:20:52 +00:00
|
|
|
root.replaceKey("/QStream", qstream);
|
|
|
|
root.replaceKey("/RStream", rstream);
|
|
|
|
QPDFWriter w(pdf, "a.pdf");
|
|
|
|
w.setStaticID(true);
|
|
|
|
w.setStreamDataMode(qpdf_s_preserve);
|
|
|
|
w.write();
|
|
|
|
}
|
2010-08-05 21:06:49 +00:00
|
|
|
else if (n == 10)
|
|
|
|
{
|
2018-06-18 20:38:15 +00:00
|
|
|
std::vector<QPDFPageObjectHelper> pages =
|
|
|
|
QPDFPageDocumentHelper(pdf).getAllPages();
|
|
|
|
QPDFPageObjectHelper& ph(pages.at(0));
|
|
|
|
ph.addPageContents(
|
2012-07-11 02:24:09 +00:00
|
|
|
QPDFObjectHandle::newStream(
|
|
|
|
&pdf, "BT /F1 12 Tf 72 620 Td (Baked) Tj ET\n"), true);
|
2018-06-18 20:38:15 +00:00
|
|
|
ph.addPageContents(
|
2012-07-11 02:24:09 +00:00
|
|
|
QPDFObjectHandle::newStream(
|
|
|
|
&pdf, "BT /F1 18 Tf 72 520 Td (Mashed) Tj ET\n"), false);
|
2010-08-05 21:06:49 +00:00
|
|
|
|
|
|
|
QPDFWriter w(pdf, "a.pdf");
|
|
|
|
w.setStaticID(true);
|
|
|
|
w.setStreamDataMode(qpdf_s_preserve);
|
|
|
|
w.write();
|
|
|
|
}
|
2010-08-09 23:33:40 +00:00
|
|
|
else if (n == 11)
|
|
|
|
{
|
|
|
|
QPDFObjectHandle root = pdf.getRoot();
|
|
|
|
QPDFObjectHandle qstream = root.getKey("/QStream");
|
|
|
|
PointerHolder<Buffer> b1 = qstream.getStreamData();
|
|
|
|
PointerHolder<Buffer> b2 = qstream.getRawStreamData();
|
2010-09-24 20:45:18 +00:00
|
|
|
if ((b1->getSize() == 7) &&
|
|
|
|
(memcmp(b1->getBuffer(), "potato\n", 7) == 0))
|
2010-08-09 23:33:40 +00:00
|
|
|
{
|
|
|
|
std::cout << "filtered stream data okay" << std::endl;
|
|
|
|
}
|
2010-09-24 20:45:18 +00:00
|
|
|
if ((b2->getSize() == 15) &&
|
|
|
|
(memcmp(b2->getBuffer(), "706F7461746F0A\n", 15) == 0))
|
2010-08-09 23:33:40 +00:00
|
|
|
{
|
|
|
|
std::cout << "raw stream data okay" << std::endl;
|
|
|
|
}
|
|
|
|
}
|
2010-10-01 11:02:35 +00:00
|
|
|
else if (n == 12)
|
|
|
|
{
|
|
|
|
pdf.setOutputStreams(0, 0);
|
|
|
|
pdf.showLinearizationData();
|
|
|
|
}
|
|
|
|
else if (n == 13)
|
|
|
|
{
|
|
|
|
std::ostringstream out;
|
|
|
|
std::ostringstream err;
|
|
|
|
pdf.setOutputStreams(&out, &err);
|
|
|
|
pdf.showLinearizationData();
|
|
|
|
std::cout << "---output---" << std::endl
|
|
|
|
<< out.str()
|
|
|
|
<< "---error---" << std::endl
|
|
|
|
<< err.str();
|
|
|
|
}
|
2011-08-10 16:42:48 +00:00
|
|
|
else if (n == 14)
|
|
|
|
{
|
|
|
|
// Exercise swap and replace. This test case is designed for
|
|
|
|
// a specific file.
|
|
|
|
std::vector<QPDFObjectHandle> pages = pdf.getAllPages();
|
|
|
|
if (pages.size() != 4)
|
|
|
|
{
|
|
|
|
throw std::logic_error("test " + QUtil::int_to_string(n) +
|
|
|
|
" not called 4-page file");
|
|
|
|
}
|
|
|
|
// Swap pages 2 and 3
|
2013-10-05 23:42:39 +00:00
|
|
|
pdf.swapObjects(pages.at(1).getObjGen(), pages.at(2).getObjGen());
|
2011-08-10 16:42:48 +00:00
|
|
|
// Replace object and swap objects
|
|
|
|
QPDFObjectHandle trailer = pdf.getTrailer();
|
|
|
|
QPDFObjectHandle qdict = trailer.getKey("/QDict");
|
|
|
|
QPDFObjectHandle qarray = trailer.getKey("/QArray");
|
|
|
|
// Force qdict but not qarray to resolve
|
|
|
|
qdict.isDictionary();
|
2012-06-22 14:09:42 +00:00
|
|
|
QPDFObjectHandle new_dict = QPDFObjectHandle::newDictionary();
|
|
|
|
new_dict.replaceKey("/NewDict", QPDFObjectHandle::newInteger(2));
|
2011-08-10 16:42:48 +00:00
|
|
|
try
|
|
|
|
{
|
|
|
|
// Do it wrong first...
|
2013-06-14 15:58:37 +00:00
|
|
|
pdf.replaceObject(qdict.getObjGen(), qdict);
|
2011-08-10 16:42:48 +00:00
|
|
|
}
|
2018-08-13 01:57:52 +00:00
|
|
|
catch (std::logic_error const&)
|
2011-08-10 16:42:48 +00:00
|
|
|
{
|
|
|
|
std::cout << "caught logic error as expected" << std::endl;
|
|
|
|
}
|
2013-06-14 15:58:37 +00:00
|
|
|
pdf.replaceObject(qdict.getObjGen(), new_dict);
|
2011-08-10 16:42:48 +00:00
|
|
|
// Now qdict still points to the old dictionary
|
|
|
|
std::cout << "old dict: " << qdict.getKey("/Dict").getIntValue()
|
|
|
|
<< std::endl;
|
|
|
|
// Swap dict and array
|
2013-06-14 15:58:37 +00:00
|
|
|
pdf.swapObjects(qdict.getObjGen(), qarray.getObjGen());
|
2011-08-10 16:42:48 +00:00
|
|
|
// Now qarray will resolve to new object but qdict is still
|
|
|
|
// the old object
|
|
|
|
std::cout << "old dict: " << qdict.getKey("/Dict").getIntValue()
|
|
|
|
<< std::endl;
|
|
|
|
std::cout << "new dict: " << qarray.getKey("/NewDict").getIntValue()
|
|
|
|
<< std::endl;
|
|
|
|
// Reread qdict, now pointing to an array
|
2013-06-14 15:58:37 +00:00
|
|
|
qdict = pdf.getObjectByObjGen(qdict.getObjGen());
|
2011-08-10 16:42:48 +00:00
|
|
|
std::cout << "swapped array: " << qdict.getArrayItem(0).getName()
|
|
|
|
<< std::endl;
|
|
|
|
|
2011-08-10 17:33:58 +00:00
|
|
|
// Exercise getAsMap and getAsArray
|
|
|
|
std::vector<QPDFObjectHandle> array_elements =
|
|
|
|
qdict.getArrayAsVector();
|
|
|
|
std::map<std::string, QPDFObjectHandle> dict_items =
|
|
|
|
qarray.getDictAsMap();
|
|
|
|
if ((array_elements.size() == 1) &&
|
2013-10-05 23:42:39 +00:00
|
|
|
(array_elements.at(0).getName() == "/Array") &&
|
2011-08-10 17:33:58 +00:00
|
|
|
(dict_items.size() == 1) &&
|
|
|
|
(dict_items["/NewDict"].getIntValue() == 2))
|
|
|
|
{
|
|
|
|
std::cout << "array and dictionary contents are correct"
|
|
|
|
<< std::endl;
|
|
|
|
}
|
|
|
|
|
2011-08-10 20:34:29 +00:00
|
|
|
// Exercise writing to memory buffer
|
2012-09-06 18:39:06 +00:00
|
|
|
for (int i = 0; i < 2; ++i)
|
|
|
|
{
|
|
|
|
QPDFWriter w(pdf);
|
|
|
|
w.setOutputMemory();
|
|
|
|
// Exercise setOutputMemory with and without static ID
|
|
|
|
w.setStaticID(i == 0);
|
|
|
|
w.setStreamDataMode(qpdf_s_preserve);
|
|
|
|
w.write();
|
|
|
|
Buffer* b = w.getBuffer();
|
|
|
|
std::string const filename = (i == 0 ? "a.pdf" : "b.pdf");
|
2013-02-28 21:45:11 +00:00
|
|
|
FILE* f = QUtil::safe_fopen(filename.c_str(), "wb");
|
2012-09-06 18:39:06 +00:00
|
|
|
fwrite(b->getBuffer(), b->getSize(), 1, f);
|
|
|
|
fclose(f);
|
|
|
|
delete b;
|
|
|
|
}
|
2011-08-10 16:42:48 +00:00
|
|
|
}
|
2012-06-21 16:21:34 +00:00
|
|
|
else if (n == 15)
|
|
|
|
{
|
|
|
|
std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
|
|
|
|
// Reference to original page numbers for this test case are
|
|
|
|
// numbered from 0.
|
|
|
|
|
|
|
|
// Remove pages from various places, checking to make sure
|
|
|
|
// that our pages reference is getting updated.
|
|
|
|
assert(pages.size() == 10);
|
|
|
|
pdf.removePage(pages.back()); // original page 9
|
|
|
|
assert(pages.size() == 9);
|
|
|
|
pdf.removePage(*pages.begin()); // original page 0
|
|
|
|
assert(pages.size() == 8);
|
2013-10-05 23:42:39 +00:00
|
|
|
checkPageContents(pages.at(4), "Original page 5");
|
|
|
|
pdf.removePage(pages.at(4)); // original page 5
|
2012-06-21 16:21:34 +00:00
|
|
|
assert(pages.size() == 7);
|
2013-10-05 23:42:39 +00:00
|
|
|
checkPageContents(pages.at(4), "Original page 6");
|
|
|
|
checkPageContents(pages.at(0), "Original page 1");
|
|
|
|
checkPageContents(pages.at(6), "Original page 8");
|
2012-06-21 16:21:34 +00:00
|
|
|
|
|
|
|
// Insert pages
|
|
|
|
|
|
|
|
// Create some content streams.
|
|
|
|
std::vector<QPDFObjectHandle> contents;
|
|
|
|
contents.push_back(createPageContents(pdf, "New page 1"));
|
|
|
|
contents.push_back(createPageContents(pdf, "New page 0"));
|
|
|
|
contents.push_back(createPageContents(pdf, "New page 5"));
|
|
|
|
contents.push_back(createPageContents(pdf, "New page 6"));
|
|
|
|
contents.push_back(createPageContents(pdf, "New page 11"));
|
|
|
|
contents.push_back(createPageContents(pdf, "New page 12"));
|
|
|
|
|
|
|
|
// Create some page objects. Start with an existing
|
|
|
|
// dictionary and modify it. Using the results of
|
|
|
|
// getDictAsMap to create a new dictionary effectively creates
|
|
|
|
// a shallow copy.
|
2013-10-05 23:42:39 +00:00
|
|
|
QPDFObjectHandle page_template = pages.at(0);
|
2012-06-21 16:21:34 +00:00
|
|
|
std::vector<QPDFObjectHandle> new_pages;
|
|
|
|
for (std::vector<QPDFObjectHandle>::iterator iter = contents.begin();
|
|
|
|
iter != contents.end(); ++iter)
|
|
|
|
{
|
|
|
|
// We will retain indirect object references to other
|
|
|
|
// indirect objects other than page content.
|
2012-07-11 02:24:25 +00:00
|
|
|
QPDFObjectHandle page = page_template.shallowCopy();
|
2012-06-21 20:14:34 +00:00
|
|
|
page.replaceKey("/Contents", *iter);
|
2012-06-21 16:21:34 +00:00
|
|
|
if (iter == contents.begin())
|
|
|
|
{
|
|
|
|
// leave direct
|
|
|
|
new_pages.push_back(page);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
new_pages.push_back(pdf.makeIndirectObject(page));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now insert the pages
|
2013-10-05 23:42:39 +00:00
|
|
|
pdf.addPage(new_pages.at(0), true);
|
|
|
|
checkPageContents(pages.at(0), "New page 1");
|
|
|
|
pdf.addPageAt(new_pages.at(1), true, pages.at(0));
|
|
|
|
assert(pages.at(0).getObjGen() == new_pages.at(1).getObjGen());
|
|
|
|
pdf.addPageAt(new_pages.at(2), true, pages.at(5));
|
|
|
|
assert(pages.at(5).getObjGen() == new_pages.at(2).getObjGen());
|
|
|
|
pdf.addPageAt(new_pages.at(3), false, pages.at(5));
|
|
|
|
assert(pages.at(6).getObjGen() == new_pages.at(3).getObjGen());
|
2012-06-21 16:21:34 +00:00
|
|
|
assert(pages.size() == 11);
|
2013-10-05 23:42:39 +00:00
|
|
|
pdf.addPage(new_pages.at(4), false);
|
|
|
|
assert(pages.at(11).getObjGen() == new_pages.at(4).getObjGen());
|
|
|
|
pdf.addPageAt(new_pages.at(5), false, pages.back());
|
2012-06-21 16:21:34 +00:00
|
|
|
assert(pages.size() == 13);
|
2013-10-05 23:42:39 +00:00
|
|
|
checkPageContents(pages.at(0), "New page 0");
|
|
|
|
checkPageContents(pages.at(1), "New page 1");
|
|
|
|
checkPageContents(pages.at(5), "New page 5");
|
|
|
|
checkPageContents(pages.at(6), "New page 6");
|
|
|
|
checkPageContents(pages.at(11), "New page 11");
|
|
|
|
checkPageContents(pages.at(12), "New page 12");
|
2012-06-21 16:21:34 +00:00
|
|
|
|
2012-07-06 01:22:27 +00:00
|
|
|
// Exercise writing to FILE*
|
2013-02-28 21:45:11 +00:00
|
|
|
FILE* out = QUtil::safe_fopen("a.pdf", "wb");
|
2012-07-06 01:22:27 +00:00
|
|
|
QPDFWriter w(pdf, "FILE* a.pdf", out, true);
|
2012-06-21 16:21:34 +00:00
|
|
|
w.setStaticID(true);
|
|
|
|
w.setStreamDataMode(qpdf_s_preserve);
|
|
|
|
w.write();
|
|
|
|
}
|
|
|
|
else if (n == 16)
|
|
|
|
{
|
|
|
|
// Insert a page manually and then update the cache.
|
|
|
|
std::vector<QPDFObjectHandle> const& all_pages = pdf.getAllPages();
|
|
|
|
|
|
|
|
QPDFObjectHandle contents = createPageContents(pdf, "New page 10");
|
|
|
|
QPDFObjectHandle page =
|
|
|
|
pdf.makeIndirectObject(
|
2013-10-05 23:42:39 +00:00
|
|
|
QPDFObjectHandle(all_pages.at(0)).shallowCopy());
|
2012-06-21 20:14:34 +00:00
|
|
|
page.replaceKey("/Contents", contents);
|
2012-06-21 16:21:34 +00:00
|
|
|
|
|
|
|
// Insert the page manually.
|
|
|
|
QPDFObjectHandle root = pdf.getRoot();
|
|
|
|
QPDFObjectHandle pages = root.getKey("/Pages");
|
|
|
|
QPDFObjectHandle kids = pages.getKey("/Kids");
|
|
|
|
page.replaceKey("/Parent", pages);
|
|
|
|
pages.replaceKey(
|
|
|
|
"/Count",
|
2019-06-21 03:35:23 +00:00
|
|
|
QPDFObjectHandle::newInteger(
|
|
|
|
1 + QIntC::to_longlong(all_pages.size())));
|
2012-06-21 16:21:34 +00:00
|
|
|
kids.appendItem(page);
|
|
|
|
assert(all_pages.size() == 10);
|
|
|
|
pdf.updateAllPagesCache();
|
|
|
|
assert(all_pages.size() == 11);
|
2013-06-14 15:58:37 +00:00
|
|
|
assert(all_pages.back().getObjGen() == page.getObjGen());
|
2012-06-21 16:21:34 +00:00
|
|
|
|
|
|
|
QPDFWriter w(pdf, "a.pdf");
|
|
|
|
w.setStaticID(true);
|
|
|
|
w.setStreamDataMode(qpdf_s_preserve);
|
|
|
|
w.write();
|
|
|
|
}
|
|
|
|
else if (n == 17)
|
|
|
|
{
|
2019-01-29 01:13:10 +00:00
|
|
|
// The input file to this test case has a duplicated page.
|
|
|
|
QPDFObjectHandle page_kids =
|
|
|
|
pdf.getRoot().getKey("/Pages").getKey("/Kids");
|
|
|
|
assert(page_kids.getArrayItem(0).getObjGen() ==
|
|
|
|
page_kids.getArrayItem(1).getObjGen());
|
2012-06-21 16:21:34 +00:00
|
|
|
std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
|
2019-01-29 01:13:10 +00:00
|
|
|
assert(pages.size() == 3);
|
|
|
|
assert(! (pages.at(0).getObjGen() == pages.at(1).getObjGen()));
|
|
|
|
assert(QPDFObjectHandle(pages.at(0)).getKey("/Contents").getObjGen() ==
|
|
|
|
QPDFObjectHandle(pages.at(1)).getKey("/Contents").getObjGen());
|
2013-10-05 23:42:39 +00:00
|
|
|
pdf.removePage(pages.at(0));
|
2019-01-29 01:13:10 +00:00
|
|
|
assert(pages.size() == 2);
|
|
|
|
PointerHolder<Buffer> b = QPDFObjectHandle(pages.at(0)).
|
|
|
|
getKey("/Contents").getStreamData();
|
|
|
|
std::string contents = std::string(
|
|
|
|
reinterpret_cast<char const*>(b->getBuffer()),
|
|
|
|
b->getSize());
|
|
|
|
assert(contents.find("page 0") != std::string::npos);
|
2012-06-21 16:21:34 +00:00
|
|
|
}
|
2012-06-21 19:27:32 +00:00
|
|
|
else if (n == 18)
|
|
|
|
{
|
|
|
|
// Remove a page and re-insert it in the same file.
|
|
|
|
std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
|
|
|
|
|
|
|
|
// Remove pages from various places, checking to make sure
|
|
|
|
// that our pages reference is getting updated.
|
|
|
|
assert(pages.size() == 10);
|
2013-10-05 23:42:39 +00:00
|
|
|
QPDFObjectHandle page5 = pages.at(5);
|
2012-06-21 19:27:32 +00:00
|
|
|
pdf.removePage(page5);
|
2018-06-18 20:38:15 +00:00
|
|
|
assert(pages.size() == 9);
|
2012-06-21 19:27:32 +00:00
|
|
|
pdf.addPage(page5, false);
|
|
|
|
assert(pages.size() == 10);
|
2013-06-14 15:58:37 +00:00
|
|
|
assert(pages.back().getObjGen() == page5.getObjGen());
|
2012-06-21 19:27:32 +00:00
|
|
|
|
|
|
|
QPDFWriter w(pdf, "a.pdf");
|
|
|
|
w.setStaticID(true);
|
|
|
|
w.setStreamDataMode(qpdf_s_preserve);
|
|
|
|
w.write();
|
|
|
|
}
|
|
|
|
else if (n == 19)
|
|
|
|
{
|
|
|
|
// Remove a page and re-insert it in the same file.
|
2018-06-18 20:38:15 +00:00
|
|
|
QPDFPageDocumentHelper dh(pdf);
|
|
|
|
std::vector<QPDFPageObjectHelper> pages = dh.getAllPages();
|
2012-06-21 19:27:32 +00:00
|
|
|
|
|
|
|
// Try to insert a page that's already there.
|
2018-06-18 20:38:15 +00:00
|
|
|
dh.addPage(pages.at(5), false);
|
2012-06-21 19:27:32 +00:00
|
|
|
std::cout << "you can't see this" << std::endl;
|
|
|
|
}
|
2012-06-21 20:14:34 +00:00
|
|
|
else if (n == 20)
|
|
|
|
{
|
|
|
|
// Shallow copy an array
|
|
|
|
QPDFObjectHandle trailer = pdf.getTrailer();
|
|
|
|
QPDFObjectHandle qtest = trailer.getKey("/QTest");
|
|
|
|
QPDFObjectHandle copy = qtest.shallowCopy();
|
|
|
|
// Append shallow copy of a scalar
|
|
|
|
copy.appendItem(trailer.getKey("/Size").shallowCopy());
|
|
|
|
trailer.replaceKey("/QTest2", copy);
|
|
|
|
|
|
|
|
QPDFWriter w(pdf, "a.pdf");
|
|
|
|
w.setStaticID(true);
|
|
|
|
w.setStreamDataMode(qpdf_s_preserve);
|
|
|
|
w.write();
|
|
|
|
}
|
|
|
|
else if (n == 21)
|
|
|
|
{
|
|
|
|
// Try to shallow copy a stream
|
|
|
|
std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
|
2013-10-05 23:42:39 +00:00
|
|
|
QPDFObjectHandle page = pages.at(0);
|
2012-06-21 20:14:34 +00:00
|
|
|
QPDFObjectHandle contents = page.getKey("/Contents");
|
|
|
|
contents.shallowCopy();
|
|
|
|
std::cout << "you can't see this" << std::endl;
|
|
|
|
}
|
2012-06-23 22:32:14 +00:00
|
|
|
else if (n == 22)
|
|
|
|
{
|
|
|
|
// Try to remove a page we don't have
|
2018-06-18 20:38:15 +00:00
|
|
|
QPDFPageDocumentHelper dh(pdf);
|
|
|
|
std::vector<QPDFPageObjectHelper> pages = dh.getAllPages();
|
|
|
|
QPDFPageObjectHelper& page = pages.at(0);
|
|
|
|
dh.removePage(page);
|
|
|
|
dh.removePage(page);
|
2012-06-23 22:32:14 +00:00
|
|
|
std::cout << "you can't see this" << std::endl;
|
|
|
|
}
|
2012-07-05 03:19:32 +00:00
|
|
|
else if (n == 23)
|
|
|
|
{
|
2018-06-18 20:38:15 +00:00
|
|
|
QPDFPageDocumentHelper dh(pdf);
|
|
|
|
std::vector<QPDFPageObjectHelper> pages = dh.getAllPages();
|
|
|
|
dh.removePage(pages.back());
|
2012-07-05 03:19:32 +00:00
|
|
|
}
|
2012-07-08 18:19:19 +00:00
|
|
|
else if (n == 24)
|
|
|
|
{
|
|
|
|
// Test behavior of reserved objects
|
|
|
|
QPDFObjectHandle res1 = QPDFObjectHandle::newReserved(&pdf);
|
|
|
|
QPDFObjectHandle res2 = QPDFObjectHandle::newReserved(&pdf);
|
|
|
|
QPDFObjectHandle trailer = pdf.getTrailer();
|
|
|
|
trailer.replaceKey("Array1", res1);
|
|
|
|
trailer.replaceKey("Array2", res2);
|
|
|
|
|
|
|
|
QPDFObjectHandle array1 = QPDFObjectHandle::newArray();
|
|
|
|
QPDFObjectHandle array2 = QPDFObjectHandle::newArray();
|
|
|
|
array1.appendItem(res2);
|
|
|
|
array1.appendItem(QPDFObjectHandle::newInteger(1));
|
|
|
|
array2.appendItem(res1);
|
|
|
|
array2.appendItem(QPDFObjectHandle::newInteger(2));
|
|
|
|
// Make sure trying to ask questions about a reserved object
|
|
|
|
// doesn't break it.
|
|
|
|
if (res1.isArray())
|
|
|
|
{
|
|
|
|
std::cout << "oops -- res1 is an array" << std::endl;
|
|
|
|
}
|
|
|
|
if (res1.isReserved())
|
|
|
|
{
|
|
|
|
std::cout << "res1 is still reserved after checking if array"
|
|
|
|
<< std::endl;
|
|
|
|
}
|
|
|
|
pdf.replaceReserved(res1, array1);
|
|
|
|
if (res1.isReserved())
|
|
|
|
{
|
|
|
|
std::cout << "oops -- res1 is still reserved" << std::endl;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
std::cout << "res1 is no longer reserved" << std::endl;
|
|
|
|
}
|
|
|
|
res1.assertArray();
|
|
|
|
std::cout << "res1 is an array" << std::endl;
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
res2.unparseResolved();
|
|
|
|
std::cout << "oops -- didn't throw" << std::endl;
|
|
|
|
}
|
2018-08-13 01:57:52 +00:00
|
|
|
catch (std::logic_error const& e)
|
2012-07-08 18:19:19 +00:00
|
|
|
{
|
|
|
|
std::cout << "logic error: " << e.what() << std::endl;
|
|
|
|
}
|
|
|
|
try
|
|
|
|
{
|
|
|
|
res2.makeDirect();
|
|
|
|
std::cout << "oops -- didn't throw" << std::endl;
|
|
|
|
}
|
2018-08-13 01:57:52 +00:00
|
|
|
catch (std::logic_error const& e)
|
2012-07-08 18:19:19 +00:00
|
|
|
{
|
|
|
|
std::cout << "logic error: " << e.what() << std::endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
pdf.replaceReserved(res2, array2);
|
|
|
|
|
|
|
|
res2.assertArray();
|
|
|
|
std::cout << "res2 is an array" << std::endl;
|
|
|
|
|
|
|
|
// Verify that the previously added reserved keys can be
|
|
|
|
// dereferenced properly now
|
2019-06-21 03:35:23 +00:00
|
|
|
int i1 = res1.getArrayItem(0).getArrayItem(1).getIntValueAsInt();
|
|
|
|
int i2 = res2.getArrayItem(0).getArrayItem(1).getIntValueAsInt();
|
2012-07-08 18:19:19 +00:00
|
|
|
if ((i1 == 2) && (i2 == 1))
|
|
|
|
{
|
|
|
|
std::cout << "circular access and lazy resolution worked" << std::endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
QPDFWriter w(pdf, "a.pdf");
|
|
|
|
w.setStaticID(true);
|
|
|
|
w.setStreamDataMode(qpdf_s_preserve);
|
|
|
|
w.write();
|
|
|
|
}
|
2012-07-11 19:29:41 +00:00
|
|
|
else if (n == 25)
|
|
|
|
{
|
|
|
|
// The copy object tests are designed to work with a specific
|
|
|
|
// file. Look at the test suite for the file, and look at the
|
|
|
|
// file for comments about the file's structure.
|
|
|
|
|
|
|
|
// Copy qtest without crossing page boundaries. Should get O1
|
|
|
|
// and O2 and their streams but not O3 or any other pages.
|
|
|
|
|
2012-12-30 14:27:49 +00:00
|
|
|
assert(arg2 != 0);
|
2019-01-07 02:52:30 +00:00
|
|
|
{
|
|
|
|
// Make sure original PDF is out of scope when we write.
|
|
|
|
QPDF oldpdf;
|
|
|
|
oldpdf.processFile(arg2);
|
|
|
|
QPDFObjectHandle qtest = oldpdf.getTrailer().getKey("/QTest");
|
|
|
|
pdf.getTrailer().replaceKey(
|
|
|
|
"/QTest", pdf.copyForeignObject(qtest));
|
|
|
|
}
|
2012-07-11 19:29:41 +00:00
|
|
|
|
2019-01-07 02:52:30 +00:00
|
|
|
QPDFWriter w(pdf, "a.pdf");
|
2012-07-11 19:29:41 +00:00
|
|
|
w.setStaticID(true);
|
|
|
|
w.setStreamDataMode(qpdf_s_preserve);
|
|
|
|
w.write();
|
|
|
|
}
|
|
|
|
else if (n == 26)
|
|
|
|
{
|
|
|
|
// Copy the O3 page using addPage. Copy qtest without
|
|
|
|
// crossing page boundaries. In addition to previous results,
|
|
|
|
// should get page O3 but no other pages including the page
|
|
|
|
// that O3 points to. Also, inherited object will have been
|
|
|
|
// pushed down and will be preserved.
|
|
|
|
|
2019-01-07 02:52:30 +00:00
|
|
|
{
|
|
|
|
// Make sure original PDF is out of scope when we write.
|
|
|
|
assert(arg2 != 0);
|
|
|
|
QPDF oldpdf;
|
|
|
|
oldpdf.processFile(arg2);
|
|
|
|
QPDFObjectHandle qtest = oldpdf.getTrailer().getKey("/QTest");
|
|
|
|
QPDFObjectHandle O3 = qtest.getKey("/O3");
|
|
|
|
QPDFPageDocumentHelper(pdf).addPage(O3, false);
|
|
|
|
pdf.getTrailer().replaceKey(
|
|
|
|
"/QTest", pdf.copyForeignObject(qtest));
|
|
|
|
}
|
|
|
|
|
|
|
|
QPDFWriter w(pdf, "a.pdf");
|
2012-07-11 19:29:41 +00:00
|
|
|
w.setStaticID(true);
|
|
|
|
w.setStreamDataMode(qpdf_s_preserve);
|
|
|
|
w.write();
|
|
|
|
}
|
|
|
|
else if (n == 27)
|
|
|
|
{
|
|
|
|
// Copy O3 and the page O3 refers to before copying qtest.
|
|
|
|
// Should get qtest plus only the O3 page and the page that O3
|
2019-01-07 02:52:30 +00:00
|
|
|
// points to. Inherited objects should be preserved. This test
|
|
|
|
// also exercises copying from a stream that has a buffer and
|
2019-01-11 03:11:38 +00:00
|
|
|
// a provider, including copying a provider multiple times. We
|
|
|
|
// also exercise setImmediateCopyFrom.
|
2012-07-11 19:29:41 +00:00
|
|
|
|
2019-01-11 03:11:38 +00:00
|
|
|
// Create a provider. The provider stays in scope.
|
|
|
|
PointerHolder<QPDFObjectHandle::StreamDataProvider> p1;
|
|
|
|
{
|
|
|
|
// Local scope
|
|
|
|
Pl_Buffer pl("buffer");
|
|
|
|
pl.write(QUtil::unsigned_char_pointer("new data for stream\n"),
|
|
|
|
20); // no null!
|
|
|
|
pl.finish();
|
|
|
|
PointerHolder<Buffer> b = pl.getBuffer();
|
|
|
|
Provider* provider = new Provider(b);
|
|
|
|
p1 = provider;
|
|
|
|
}
|
|
|
|
// Create a stream that uses a provider in empty1 and copy it
|
|
|
|
// to empty2. It is copied from empty2 to the final pdf.
|
2019-01-07 02:52:30 +00:00
|
|
|
QPDF empty1;
|
|
|
|
empty1.emptyPDF();
|
|
|
|
QPDFObjectHandle s1 = QPDFObjectHandle::newStream(&empty1);
|
|
|
|
s1.replaceStreamData(
|
2019-01-11 03:11:38 +00:00
|
|
|
p1, QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull());
|
2019-01-07 02:52:30 +00:00
|
|
|
QPDF empty2;
|
|
|
|
empty2.emptyPDF();
|
|
|
|
s1 = empty2.copyForeignObject(s1);
|
|
|
|
{
|
2019-01-11 03:11:38 +00:00
|
|
|
// Make sure some source PDFs are out of scope when we
|
|
|
|
// write.
|
|
|
|
|
|
|
|
PointerHolder<QPDFObjectHandle::StreamDataProvider> p2;
|
|
|
|
// Create another provider. This one will go out of scope
|
|
|
|
// along with its containing qpdf, which has
|
|
|
|
// setImmediateCopyFrom(true).
|
|
|
|
{
|
|
|
|
// Local scope
|
|
|
|
Pl_Buffer pl("buffer");
|
|
|
|
pl.write(QUtil::unsigned_char_pointer(
|
|
|
|
"more data for stream\n"),
|
|
|
|
21); // no null!
|
|
|
|
pl.finish();
|
|
|
|
PointerHolder<Buffer> b = pl.getBuffer();
|
|
|
|
Provider* provider = new Provider(b);
|
|
|
|
p2 = provider;
|
|
|
|
}
|
|
|
|
QPDF empty3;
|
|
|
|
empty3.emptyPDF();
|
|
|
|
empty3.setImmediateCopyFrom(true);
|
|
|
|
QPDFObjectHandle s3 = QPDFObjectHandle::newStream(&empty3);
|
|
|
|
s3.replaceStreamData(
|
|
|
|
p2, QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull());
|
2019-01-07 02:52:30 +00:00
|
|
|
assert(arg2 != 0);
|
|
|
|
QPDF oldpdf;
|
|
|
|
oldpdf.processFile(arg2);
|
|
|
|
QPDFObjectHandle qtest = oldpdf.getTrailer().getKey("/QTest");
|
|
|
|
QPDFObjectHandle O3 = qtest.getKey("/O3");
|
|
|
|
QPDFPageDocumentHelper dh(pdf);
|
|
|
|
dh.addPage(O3.getKey("/OtherPage"), false);
|
|
|
|
dh.addPage(O3, false);
|
|
|
|
QPDFObjectHandle s2 = QPDFObjectHandle::newStream(
|
|
|
|
&oldpdf, "potato\n");
|
|
|
|
pdf.getTrailer().replaceKey(
|
|
|
|
"/QTest", pdf.copyForeignObject(qtest));
|
|
|
|
pdf.getTrailer().replaceKey(
|
|
|
|
"/QTest2", QPDFObjectHandle::newArray());
|
|
|
|
pdf.getTrailer().getKey("/QTest2").appendItem(
|
|
|
|
pdf.copyForeignObject(s1));
|
|
|
|
pdf.getTrailer().getKey("/QTest2").appendItem(
|
|
|
|
pdf.copyForeignObject(s2));
|
2019-01-11 03:11:38 +00:00
|
|
|
pdf.getTrailer().getKey("/QTest2").appendItem(
|
|
|
|
pdf.copyForeignObject(s3));
|
2019-01-07 02:52:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QPDFWriter w(pdf, "a.pdf");
|
2012-07-11 19:29:41 +00:00
|
|
|
w.setStaticID(true);
|
2020-04-05 03:35:35 +00:00
|
|
|
w.setCompressStreams(false);
|
|
|
|
w.setDecodeLevel(qpdf_dl_generalized);
|
2012-07-11 19:29:41 +00:00
|
|
|
w.write();
|
|
|
|
}
|
|
|
|
else if (n == 28)
|
|
|
|
{
|
|
|
|
// Copy foreign object errors
|
|
|
|
try
|
|
|
|
{
|
|
|
|
pdf.copyForeignObject(pdf.getTrailer().getKey("/QTest"));
|
|
|
|
std::cout << "oops -- didn't throw" << std::endl;
|
|
|
|
}
|
2018-08-13 01:57:52 +00:00
|
|
|
catch (std::logic_error const& e)
|
2012-07-11 19:29:41 +00:00
|
|
|
{
|
|
|
|
std::cout << "logic error: " << e.what() << std::endl;
|
|
|
|
}
|
|
|
|
try
|
|
|
|
{
|
|
|
|
pdf.copyForeignObject(QPDFObjectHandle::newInteger(1));
|
|
|
|
std::cout << "oops -- didn't throw" << std::endl;
|
|
|
|
}
|
2018-08-13 01:57:52 +00:00
|
|
|
catch (std::logic_error const& e)
|
2012-07-11 19:29:41 +00:00
|
|
|
{
|
|
|
|
std::cout << "logic error: " << e.what() << std::endl;
|
|
|
|
}
|
|
|
|
}
|
2012-07-14 12:07:23 +00:00
|
|
|
else if (n == 29)
|
|
|
|
{
|
|
|
|
// Detect mixed objects in QPDFWriter
|
2012-12-30 14:27:49 +00:00
|
|
|
assert(arg2 != 0);
|
2012-07-14 12:07:23 +00:00
|
|
|
QPDF other;
|
2012-12-30 14:27:49 +00:00
|
|
|
other.processFile(arg2);
|
2012-07-14 12:07:23 +00:00
|
|
|
// Should use copyForeignObject instead
|
|
|
|
other.getTrailer().replaceKey(
|
|
|
|
"/QTest", pdf.getTrailer().getKey("/QTest"));
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
QPDFWriter w(other, "a.pdf");
|
|
|
|
w.write();
|
|
|
|
std::cout << "oops -- didn't throw" << std::endl;
|
|
|
|
}
|
2018-08-13 01:57:52 +00:00
|
|
|
catch (std::logic_error const& e)
|
2012-07-14 12:07:23 +00:00
|
|
|
{
|
|
|
|
std::cout << "logic error: " << e.what() << std::endl;
|
|
|
|
}
|
|
|
|
}
|
2012-07-14 13:13:49 +00:00
|
|
|
else if (n == 30)
|
|
|
|
{
|
2012-12-30 14:27:49 +00:00
|
|
|
assert(arg2 != 0);
|
2012-07-14 13:13:49 +00:00
|
|
|
QPDF encrypted;
|
2012-12-30 14:27:49 +00:00
|
|
|
encrypted.processFile(arg2, "user");
|
2012-07-14 13:13:49 +00:00
|
|
|
QPDFWriter w(pdf, "b.pdf");
|
|
|
|
w.setStreamDataMode(qpdf_s_preserve);
|
|
|
|
w.copyEncryptionParameters(encrypted);
|
|
|
|
w.write();
|
2012-07-16 01:15:24 +00:00
|
|
|
|
|
|
|
// Make sure the contents are actually the same
|
|
|
|
QPDF final;
|
|
|
|
final.processFile("b.pdf", "user");
|
|
|
|
std::vector<QPDFObjectHandle> pages = pdf.getAllPages();
|
2013-10-05 23:42:39 +00:00
|
|
|
std::string orig_contents = getPageContents(pages.at(0));
|
2012-07-16 01:15:24 +00:00
|
|
|
pages = final.getAllPages();
|
2013-10-05 23:42:39 +00:00
|
|
|
std::string new_contents = getPageContents(pages.at(0));
|
2012-07-16 01:15:24 +00:00
|
|
|
if (orig_contents != new_contents)
|
|
|
|
{
|
|
|
|
std::cout << "oops -- page contents don't match" << std::endl
|
|
|
|
<< "original:\n" << orig_contents
|
|
|
|
<< "new:\n" << new_contents
|
|
|
|
<< std::endl;
|
|
|
|
}
|
2012-07-14 13:13:49 +00:00
|
|
|
}
|
2012-07-21 13:00:06 +00:00
|
|
|
else if (n == 31)
|
|
|
|
{
|
|
|
|
// Test object parsing from a string. The input file is not used.
|
|
|
|
|
|
|
|
QPDFObjectHandle o1 =
|
|
|
|
QPDFObjectHandle::parse(
|
|
|
|
"[/name 16059 3.14159 false\n"
|
|
|
|
" << /key true /other [ (string1) (string2) ] >> null]");
|
|
|
|
std::cout << o1.unparse() << std::endl;
|
|
|
|
QPDFObjectHandle o2 = QPDFObjectHandle::parse(" 12345 \f ");
|
|
|
|
assert(o2.isInteger() && (o2.getIntValue() == 12345));
|
|
|
|
try
|
|
|
|
{
|
|
|
|
QPDFObjectHandle::parse("[1 0 R]", "indirect test");
|
|
|
|
std::cout << "oops -- didn't throw" << std::endl;
|
|
|
|
}
|
2018-08-13 01:57:52 +00:00
|
|
|
catch (std::logic_error const& e)
|
2012-07-21 13:00:06 +00:00
|
|
|
{
|
|
|
|
std::cout << "logic error parsing indirect: " << e.what()
|
|
|
|
<< std::endl;
|
|
|
|
}
|
|
|
|
try
|
|
|
|
{
|
|
|
|
QPDFObjectHandle::parse("0 trailing", "trailing test");
|
|
|
|
std::cout << "oops -- didn't throw" << std::endl;
|
|
|
|
}
|
2018-08-13 01:57:52 +00:00
|
|
|
catch (std::runtime_error const& e)
|
2012-07-21 13:00:06 +00:00
|
|
|
{
|
|
|
|
std::cout << "trailing data: " << e.what()
|
|
|
|
<< std::endl;
|
|
|
|
}
|
|
|
|
}
|
2012-09-06 19:30:13 +00:00
|
|
|
else if (n == 32)
|
|
|
|
{
|
|
|
|
// Extra header text
|
|
|
|
char const* filenames[] = {"a.pdf", "b.pdf", "c.pdf", "d.pdf"};
|
|
|
|
for (int i = 0; i < 4; ++i)
|
|
|
|
{
|
|
|
|
bool linearized = ((i & 1) != 0);
|
|
|
|
bool newline = ((i & 2) != 0);
|
|
|
|
QPDFWriter w(pdf, filenames[i]);
|
|
|
|
w.setStaticID(true);
|
|
|
|
std::cout
|
|
|
|
<< "file: " << filenames[i] << std::endl
|
|
|
|
<< "linearized: " << (linearized ? "yes" : "no") << std::endl
|
|
|
|
<< "newline: " << (newline ? "yes" : "no") << std::endl;
|
|
|
|
w.setLinearization(linearized);
|
|
|
|
w.setExtraHeaderText(newline
|
|
|
|
? "%% Comment with newline\n"
|
|
|
|
: "%% Comment\n% No newline");
|
|
|
|
w.write();
|
|
|
|
}
|
|
|
|
}
|
2012-09-23 21:42:26 +00:00
|
|
|
else if (n == 33)
|
|
|
|
{
|
|
|
|
// Test writing to a custom pipeline
|
|
|
|
Pl_Buffer p("buffer");
|
|
|
|
QPDFWriter w(pdf);
|
|
|
|
w.setStaticID(true);
|
|
|
|
w.setOutputPipeline(&p);
|
|
|
|
w.write();
|
|
|
|
PointerHolder<Buffer> b = p.getBuffer();
|
2013-02-28 21:45:11 +00:00
|
|
|
FILE* f = QUtil::safe_fopen("a.pdf", "wb");
|
2012-09-23 21:42:26 +00:00
|
|
|
fwrite(b->getBuffer(), b->getSize(), 1, f);
|
|
|
|
fclose(f);
|
|
|
|
}
|
2012-12-27 20:17:39 +00:00
|
|
|
else if (n == 34)
|
|
|
|
{
|
|
|
|
// Look at Extensions dictionary
|
|
|
|
std::cout << "version: " << pdf.getPDFVersion() << std::endl
|
|
|
|
<< "extension level: " << pdf.getExtensionLevel() << std::endl
|
|
|
|
<< pdf.getRoot().getKey("/Extensions").unparse() << std::endl;
|
|
|
|
}
|
2012-12-30 02:31:21 +00:00
|
|
|
else if (n == 35)
|
|
|
|
{
|
|
|
|
// Extract attachments
|
|
|
|
|
|
|
|
std::map<std::string, PointerHolder<Buffer> > attachments;
|
|
|
|
QPDFObjectHandle root = pdf.getRoot();
|
|
|
|
QPDFObjectHandle names = root.getKey("/Names");
|
|
|
|
QPDFObjectHandle embeddedFiles = names.getKey("/EmbeddedFiles");
|
|
|
|
names = embeddedFiles.getKey("/Names");
|
|
|
|
for (int i = 0; i < names.getArrayNItems(); ++i)
|
|
|
|
{
|
|
|
|
QPDFObjectHandle item = names.getArrayItem(i);
|
|
|
|
if (item.isDictionary() &&
|
|
|
|
item.getKey("/Type").isName() &&
|
|
|
|
(item.getKey("/Type").getName() == "/Filespec") &&
|
|
|
|
item.getKey("/EF").isDictionary() &&
|
|
|
|
item.getKey("/EF").getKey("/F").isStream())
|
|
|
|
{
|
|
|
|
std::string filename = item.getKey("/F").getStringValue();
|
|
|
|
QPDFObjectHandle stream = item.getKey("/EF").getKey("/F");
|
|
|
|
attachments[filename] = stream.getStreamData();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (std::map<std::string, PointerHolder<Buffer> >::iterator iter =
|
|
|
|
attachments.begin(); iter != attachments.end(); ++iter)
|
|
|
|
{
|
|
|
|
std::string const& filename = (*iter).first;
|
|
|
|
std::string data = std::string(
|
2013-02-24 02:46:21 +00:00
|
|
|
reinterpret_cast<char const*>((*iter).second->getBuffer()),
|
2012-12-30 02:31:21 +00:00
|
|
|
(*iter).second->getSize());
|
|
|
|
bool is_binary = false;
|
|
|
|
for (size_t i = 0; i < data.size(); ++i)
|
|
|
|
{
|
2013-10-05 23:42:39 +00:00
|
|
|
if ((data.at(i) < 0) || (data.at(i) > 126))
|
2012-12-30 02:31:21 +00:00
|
|
|
{
|
|
|
|
is_binary = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (is_binary)
|
|
|
|
{
|
|
|
|
std::string t;
|
2013-02-24 02:46:21 +00:00
|
|
|
for (size_t i = 0;
|
2019-06-21 03:35:23 +00:00
|
|
|
i < std::min(data.size(), QIntC::to_size(20));
|
2013-02-24 02:46:21 +00:00
|
|
|
++i)
|
2012-12-30 02:31:21 +00:00
|
|
|
{
|
2013-10-05 23:42:39 +00:00
|
|
|
if ((data.at(i) >= 32) && (data.at(i) <= 126))
|
2012-12-30 02:31:21 +00:00
|
|
|
{
|
2013-10-05 23:42:39 +00:00
|
|
|
t += data.at(i);
|
2012-12-30 02:31:21 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
t += ".";
|
|
|
|
}
|
|
|
|
}
|
2019-06-21 03:35:23 +00:00
|
|
|
t += " (" + QUtil::uint_to_string(data.size()) + " bytes)";
|
2012-12-30 02:31:21 +00:00
|
|
|
data = t;
|
|
|
|
}
|
|
|
|
std::cout << filename << ":\n" << data << "--END--\n";
|
|
|
|
}
|
|
|
|
}
|
2012-12-30 18:54:12 +00:00
|
|
|
else if (n == 36)
|
|
|
|
{
|
|
|
|
// Extract raw unfilterable attachment
|
|
|
|
|
|
|
|
QPDFObjectHandle root = pdf.getRoot();
|
|
|
|
QPDFObjectHandle names = root.getKey("/Names");
|
|
|
|
QPDFObjectHandle embeddedFiles = names.getKey("/EmbeddedFiles");
|
|
|
|
names = embeddedFiles.getKey("/Names");
|
|
|
|
for (int i = 0; i < names.getArrayNItems(); ++i)
|
|
|
|
{
|
|
|
|
QPDFObjectHandle item = names.getArrayItem(i);
|
|
|
|
if (item.isDictionary() &&
|
|
|
|
item.getKey("/Type").isName() &&
|
|
|
|
(item.getKey("/Type").getName() == "/Filespec") &&
|
|
|
|
item.getKey("/EF").isDictionary() &&
|
|
|
|
item.getKey("/EF").getKey("/F").isStream() &&
|
|
|
|
(item.getKey("/F").getStringValue() == "attachment1.txt"))
|
|
|
|
{
|
|
|
|
std::string filename = item.getKey("/F").getStringValue();
|
|
|
|
QPDFObjectHandle stream = item.getKey("/EF").getKey("/F");
|
|
|
|
Pl_Buffer p1("buffer");
|
|
|
|
Pl_Flate p2("compress", &p1, Pl_Flate::a_inflate);
|
2017-08-19 13:18:14 +00:00
|
|
|
stream.pipeStreamData(&p2, 0, qpdf_dl_none);
|
2012-12-30 18:54:12 +00:00
|
|
|
PointerHolder<Buffer> buf = p1.getBuffer();
|
|
|
|
std::string data = std::string(
|
2013-02-24 02:46:21 +00:00
|
|
|
reinterpret_cast<char const*>(buf->getBuffer()),
|
|
|
|
buf->getSize());
|
2012-12-30 18:54:12 +00:00
|
|
|
std::cout << stream.getDict().unparse()
|
|
|
|
<< filename << ":\n" << data << "--END--\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-01-20 20:26:45 +00:00
|
|
|
else if (n == 37)
|
|
|
|
{
|
|
|
|
// Parse content streams of all pages
|
2018-06-18 20:38:15 +00:00
|
|
|
std::vector<QPDFPageObjectHelper> pages =
|
|
|
|
QPDFPageDocumentHelper(pdf).getAllPages();
|
|
|
|
for (std::vector<QPDFPageObjectHelper>::iterator iter = pages.begin();
|
2013-01-20 20:26:45 +00:00
|
|
|
iter != pages.end(); ++iter)
|
|
|
|
{
|
2018-06-18 20:38:15 +00:00
|
|
|
QPDFPageObjectHelper& page(*iter);
|
2013-01-20 20:26:45 +00:00
|
|
|
ParserCallbacks cb;
|
2018-01-31 02:39:38 +00:00
|
|
|
page.parsePageContents(&cb);
|
2013-01-20 20:26:45 +00:00
|
|
|
}
|
|
|
|
}
|
2013-02-23 22:41:27 +00:00
|
|
|
else if (n == 38)
|
|
|
|
{
|
|
|
|
// Designed for override-compressed-object.pdf
|
|
|
|
QPDFObjectHandle qtest = pdf.getRoot().getKey("/QTest");
|
|
|
|
for (int i = 0; i < qtest.getArrayNItems(); ++i)
|
|
|
|
{
|
|
|
|
std::cout << qtest.getArrayItem(i).unparseResolved() << std::endl;
|
|
|
|
}
|
|
|
|
}
|
2017-08-19 13:18:14 +00:00
|
|
|
else if (n == 39)
|
|
|
|
{
|
|
|
|
// Display image filter and color set for each image on each page
|
2018-06-18 20:38:15 +00:00
|
|
|
std::vector<QPDFPageObjectHelper> pages =
|
|
|
|
QPDFPageDocumentHelper(pdf).getAllPages();
|
2017-08-19 13:18:14 +00:00
|
|
|
int pageno = 0;
|
2018-06-18 20:38:15 +00:00
|
|
|
for (std::vector<QPDFPageObjectHelper>::iterator p_iter =
|
2017-08-19 13:18:14 +00:00
|
|
|
pages.begin();
|
|
|
|
p_iter != pages.end(); ++p_iter)
|
|
|
|
{
|
|
|
|
std::cout << "page " << ++pageno << std::endl;
|
|
|
|
std::map<std::string, QPDFObjectHandle> images =
|
|
|
|
(*p_iter).getPageImages();
|
|
|
|
for (std::map<std::string, QPDFObjectHandle>::iterator i_iter =
|
|
|
|
images.begin(); i_iter != images.end(); ++i_iter)
|
|
|
|
{
|
|
|
|
QPDFObjectHandle image_dict = (*i_iter).second.getDict();
|
|
|
|
std::cout << "filter: "
|
|
|
|
<< image_dict.getKey("/Filter").unparseResolved()
|
|
|
|
<< ", color space: "
|
|
|
|
<< image_dict.getKey("/ColorSpace").unparseResolved()
|
|
|
|
<< std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-08-22 00:13:39 +00:00
|
|
|
else if (n == 40)
|
|
|
|
{
|
|
|
|
// Write PCLm. This requires specially crafted PDF files. This
|
|
|
|
// feature was implemented by Sahil Arora
|
|
|
|
// <sahilarora.535@gmail.com> as part of a Google Summer of
|
|
|
|
// Code project in 2017.
|
|
|
|
assert(arg2 != 0);
|
|
|
|
QPDFWriter w(pdf, arg2);
|
|
|
|
w.setPCLm(true);
|
|
|
|
w.setStaticID(true);
|
|
|
|
w.write();
|
|
|
|
}
|
2018-02-02 23:21:34 +00:00
|
|
|
else if (n == 41)
|
|
|
|
{
|
|
|
|
// Apply a token filter. This test case is crafted to work
|
|
|
|
// with coalesce.pdf.
|
2018-06-18 20:38:15 +00:00
|
|
|
std::vector<QPDFPageObjectHelper> pages =
|
|
|
|
QPDFPageDocumentHelper(pdf).getAllPages();
|
|
|
|
for (std::vector<QPDFPageObjectHelper>::iterator iter =
|
2018-02-02 23:21:34 +00:00
|
|
|
pages.begin();
|
|
|
|
iter != pages.end(); ++iter)
|
|
|
|
{
|
|
|
|
(*iter).addContentTokenFilter(new TokenFilter);
|
|
|
|
}
|
|
|
|
QPDFWriter w(pdf, "a.pdf");
|
|
|
|
w.setQDFMode(true);
|
|
|
|
w.setStaticID(true);
|
|
|
|
w.write();
|
|
|
|
}
|
2018-02-16 22:25:27 +00:00
|
|
|
else if (n == 42)
|
|
|
|
{
|
|
|
|
// Access objects as wrong type. This test case is crafted to
|
|
|
|
// work with object-types.pdf.
|
|
|
|
QPDFObjectHandle qtest = pdf.getTrailer().getKey("/QTest");
|
|
|
|
QPDFObjectHandle array = qtest.getKey("/Dictionary").getKey("/Key2");
|
|
|
|
QPDFObjectHandle dictionary = qtest.getKey("/Dictionary");
|
|
|
|
QPDFObjectHandle integer = qtest.getKey("/Integer");
|
|
|
|
QPDFObjectHandle null = QPDFObjectHandle::newNull();
|
|
|
|
assert(array.isArray());
|
|
|
|
assert(dictionary.isDictionary());
|
|
|
|
assert("" == qtest.getStringValue());
|
|
|
|
array.getArrayItem(-1).assertNull();
|
|
|
|
array.getArrayItem(16059).assertNull();
|
|
|
|
integer.getArrayItem(0).assertNull();
|
|
|
|
integer.appendItem(null);
|
|
|
|
array.eraseItem(-1);
|
|
|
|
array.eraseItem(16059);
|
|
|
|
integer.eraseItem(0);
|
|
|
|
integer.insertItem(0, null);
|
|
|
|
integer.setArrayFromVector(std::vector<QPDFObjectHandle>());
|
|
|
|
integer.setArrayItem(0, null);
|
|
|
|
assert(0 == integer.getArrayNItems());
|
|
|
|
assert(integer.getArrayAsVector().empty());
|
|
|
|
assert(false == integer.getBoolValue());
|
|
|
|
assert(integer.getDictAsMap().empty());
|
|
|
|
assert(integer.getKeys().empty());
|
|
|
|
assert(false == integer.hasKey("/Potato"));
|
|
|
|
integer.removeKey("/Potato");
|
|
|
|
integer.replaceOrRemoveKey("/Potato", null);
|
|
|
|
integer.replaceOrRemoveKey("/Potato", QPDFObjectHandle::newInteger(1));
|
|
|
|
integer.replaceKey("/Potato", QPDFObjectHandle::newInteger(1));
|
|
|
|
qtest.getKey("/Integer").getKey("/Potato");
|
|
|
|
assert(integer.getInlineImageValue().empty());
|
|
|
|
assert(0 == dictionary.getIntValue());
|
|
|
|
assert("/QPDFFakeName" == integer.getName());
|
|
|
|
assert("QPDFFAKE" == integer.getOperatorValue());
|
|
|
|
assert("0.0" == dictionary.getRealValue());
|
|
|
|
assert(integer.getStringValue().empty());
|
|
|
|
assert(integer.getUTF8Value().empty());
|
|
|
|
assert(0.0 == dictionary.getNumericValue());
|
|
|
|
// Make sure error messages are okay for nested values
|
|
|
|
std::cerr << "One error\n";
|
|
|
|
assert(array.getArrayItem(0).getStringValue().empty());
|
|
|
|
std::cerr << "One error\n";
|
|
|
|
assert(dictionary.getKey("/Quack").getStringValue().empty());
|
|
|
|
assert(array.getArrayItem(1).isDictionary());
|
|
|
|
assert(array.getArrayItem(1).getKey("/K").isArray());
|
|
|
|
assert(array.getArrayItem(1).getKey("/K").getArrayItem(0).isName());
|
|
|
|
assert("/V" ==
|
|
|
|
array.getArrayItem(1).getKey("/K").getArrayItem(0).getName());
|
|
|
|
std::cerr << "Two errors\n";
|
|
|
|
assert(array.getArrayItem(16059).getStringValue().empty());
|
|
|
|
std::cerr << "One error\n";
|
|
|
|
array.getArrayItem(1).getKey("/K").getArrayItem(0).getStringValue();
|
|
|
|
// Stream dictionary
|
2018-06-18 20:38:15 +00:00
|
|
|
QPDFObjectHandle page = pdf.getAllPages().at(0);
|
2018-02-16 22:25:27 +00:00
|
|
|
assert("/QPDFFakeName" ==
|
|
|
|
page.getKey("/Contents").getDict().getKey("/Potato").getName());
|
2018-06-19 15:00:15 +00:00
|
|
|
// Rectangles
|
|
|
|
QPDFObjectHandle::Rectangle r0 = integer.getArrayAsRectangle();
|
|
|
|
assert((r0.llx == 0) && (r0.lly == 0) &&
|
|
|
|
(r0.urx == 0) && (r0.ury == 0));
|
|
|
|
QPDFObjectHandle rect = QPDFObjectHandle::newFromRectangle(
|
|
|
|
QPDFObjectHandle::Rectangle(1.2, 3.4, 5.6, 7.8));
|
|
|
|
QPDFObjectHandle::Rectangle r1 = rect.getArrayAsRectangle();
|
|
|
|
assert((r1.llx > 1.19) && (r1.llx < 1.21) &&
|
|
|
|
(r1.lly > 3.39) && (r1.lly < 3.41) &&
|
|
|
|
(r1.urx > 5.59) && (r1.urx < 5.61) &&
|
|
|
|
(r1.ury > 7.79) && (r1.ury < 7.81));
|
2018-02-16 22:25:27 +00:00
|
|
|
}
|
2018-06-19 13:26:41 +00:00
|
|
|
else if (n == 43)
|
|
|
|
{
|
|
|
|
// Forms
|
|
|
|
QPDFAcroFormDocumentHelper afdh(pdf);
|
|
|
|
if (! afdh.hasAcroForm())
|
|
|
|
{
|
|
|
|
std::cout << "no forms\n";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
std::cout << "iterating over form fields\n";
|
|
|
|
std::vector<QPDFFormFieldObjectHelper> form_fields =
|
|
|
|
afdh.getFormFields();
|
|
|
|
for (std::vector<QPDFFormFieldObjectHelper>::iterator iter =
|
|
|
|
form_fields.begin();
|
|
|
|
iter != form_fields.end(); ++iter)
|
|
|
|
{
|
|
|
|
QPDFFormFieldObjectHelper ffh(*iter);
|
|
|
|
std::cout << "Field: " << ffh.getObjectHandle().unparse()
|
|
|
|
<< std::endl;
|
|
|
|
QPDFFormFieldObjectHelper node = ffh;
|
|
|
|
while (! node.isNull())
|
|
|
|
{
|
|
|
|
QPDFFormFieldObjectHelper parent(node.getParent());
|
|
|
|
std::cout << " Parent: "
|
|
|
|
<< (parent.isNull()
|
2019-02-14 08:29:38 +00:00
|
|
|
? std::string("none")
|
2018-06-19 13:26:41 +00:00
|
|
|
: parent.getObjectHandle().unparse())
|
|
|
|
<< std::endl;
|
|
|
|
node = parent;
|
|
|
|
}
|
|
|
|
std::cout << " Fully qualified name: "
|
|
|
|
<< ffh.getFullyQualifiedName() << std::endl;
|
|
|
|
std::cout << " Partial name: "
|
|
|
|
<< ffh.getPartialName() << std::endl;
|
|
|
|
std::cout << " Alternative name: "
|
|
|
|
<< ffh.getAlternativeName() << std::endl;
|
|
|
|
std::cout << " Mapping name: "
|
|
|
|
<< ffh.getMappingName() << std::endl;
|
|
|
|
std::cout << " Field type: "
|
|
|
|
<< ffh.getFieldType() << std::endl;
|
|
|
|
std::cout << " Value: "
|
|
|
|
<< ffh.getValue().unparse() << std::endl;
|
|
|
|
std::cout << " Value as string: "
|
|
|
|
<< ffh.getValueAsString() << std::endl;
|
|
|
|
std::cout << " Default value: "
|
|
|
|
<< ffh.getDefaultValue().unparse() << std::endl;
|
|
|
|
std::cout << " Default value as string: "
|
|
|
|
<< ffh.getDefaultValueAsString() << std::endl;
|
|
|
|
std::cout << " Default appearance: "
|
|
|
|
<< ffh.getDefaultAppearance() << std::endl;
|
|
|
|
std::cout << " Quadding: "
|
|
|
|
<< ffh.getQuadding() << std::endl;
|
|
|
|
std::vector<QPDFAnnotationObjectHelper> annotations =
|
|
|
|
afdh.getAnnotationsForField(ffh);
|
|
|
|
for (std::vector<QPDFAnnotationObjectHelper>::iterator i2 =
|
|
|
|
annotations.begin();
|
|
|
|
i2 != annotations.end(); ++i2)
|
|
|
|
{
|
|
|
|
std::cout << " Annotation: "
|
|
|
|
<< (*i2).getObjectHandle().unparse() << std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
std::cout << "iterating over annotations per page\n";
|
|
|
|
std::vector<QPDFPageObjectHelper> pages =
|
|
|
|
QPDFPageDocumentHelper(pdf).getAllPages();
|
|
|
|
for (std::vector<QPDFPageObjectHelper>::iterator iter = pages.begin();
|
|
|
|
iter != pages.end(); ++iter)
|
|
|
|
{
|
|
|
|
std::cout << "Page: " << (*iter).getObjectHandle().unparse()
|
|
|
|
<< std::endl;
|
|
|
|
std::vector<QPDFAnnotationObjectHelper> annotations =
|
|
|
|
afdh.getWidgetAnnotationsForPage(*iter);
|
|
|
|
for (std::vector<QPDFAnnotationObjectHelper>::iterator i2 =
|
|
|
|
annotations.begin();
|
|
|
|
i2 != annotations.end(); ++i2)
|
|
|
|
{
|
|
|
|
QPDFAnnotationObjectHelper ah(*i2);
|
|
|
|
std::cout << " Annotation: " << ah.getObjectHandle().unparse()
|
|
|
|
<< std::endl;
|
|
|
|
std::cout << " Field: "
|
|
|
|
<< (afdh.getFieldForAnnotation(ah).
|
|
|
|
getObjectHandle().unparse())
|
|
|
|
<< std::endl;
|
|
|
|
std::cout << " Subtype: " << ah.getSubtype() << std::endl;
|
|
|
|
std::cout << " Rect: ";
|
|
|
|
print_rect(std::cout, ah.getRect());
|
|
|
|
std::cout << std::endl;
|
|
|
|
std::string state = ah.getAppearanceState();
|
|
|
|
if (! state.empty())
|
|
|
|
{
|
|
|
|
std::cout << " Appearance state: " << state
|
|
|
|
<< std::endl;
|
|
|
|
}
|
|
|
|
std::cout << " Appearance stream (/N): "
|
|
|
|
<< ah.getAppearanceStream("/N").unparse()
|
|
|
|
<< std::endl;
|
|
|
|
std::cout << " Appearance stream (/N, /3): "
|
|
|
|
<< ah.getAppearanceStream("/N", "/3").unparse()
|
|
|
|
<< std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-06-21 19:00:00 +00:00
|
|
|
else if (n == 44)
|
|
|
|
{
|
|
|
|
// Set form fields.
|
|
|
|
QPDFAcroFormDocumentHelper afdh(pdf);
|
|
|
|
std::vector<QPDFFormFieldObjectHelper> fields = afdh.getFormFields();
|
|
|
|
for (std::vector<QPDFFormFieldObjectHelper>::iterator iter =
|
|
|
|
fields.begin();
|
|
|
|
iter != fields.end(); ++iter)
|
|
|
|
{
|
|
|
|
QPDFFormFieldObjectHelper& field(*iter);
|
|
|
|
QPDFObjectHandle ft = field.getInheritableFieldValue("/FT");
|
|
|
|
if (ft.isName() && (ft.getName() == "/Tx"))
|
|
|
|
{
|
|
|
|
// \xc3\xb7 is utf-8 for U+00F7 (divided by)
|
|
|
|
field.setV("3.14 \xc3\xb7 0");
|
|
|
|
std::cout << "Set field value: "
|
|
|
|
<< field.getFullyQualifiedName()
|
|
|
|
<< " -> "
|
|
|
|
<< field.getValueAsString()
|
|
|
|
<< std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
QPDFWriter w(pdf, "a.pdf");
|
|
|
|
w.setQDFMode(true);
|
|
|
|
w.setStaticID(true);
|
|
|
|
w.setSuppressOriginalObjectIDs(true);
|
|
|
|
w.write();
|
|
|
|
}
|
2018-08-13 23:26:20 +00:00
|
|
|
else if (n == 45)
|
|
|
|
{
|
|
|
|
// Decode obfuscated files. This is here to help test with
|
|
|
|
// files that trigger anti-virus warnings. See comments in
|
|
|
|
// qpdf.test for details.
|
|
|
|
QPDFWriter w(pdf, "a.pdf");
|
|
|
|
w.setStaticID(true);
|
|
|
|
w.write();
|
|
|
|
if (! pdf.getWarnings().empty())
|
|
|
|
{
|
|
|
|
exit(3);
|
|
|
|
}
|
|
|
|
}
|
2018-12-18 18:08:55 +00:00
|
|
|
else if (n == 46)
|
|
|
|
{
|
|
|
|
// Test number tree. This test is crafted to work with
|
|
|
|
// number-tree.pdf
|
|
|
|
QPDFObjectHandle qtest = pdf.getTrailer().getKey("/QTest");
|
|
|
|
QPDFNumberTreeObjectHelper ntoh(qtest);
|
|
|
|
QPDFNumberTreeObjectHelper::idx_map ntoh_map = ntoh.getAsMap();
|
|
|
|
for (QPDFNumberTreeObjectHelper::idx_map::iterator iter =
|
|
|
|
ntoh_map.begin();
|
|
|
|
iter != ntoh_map.end(); ++iter)
|
|
|
|
{
|
|
|
|
std::cout << (*iter).first << " "
|
|
|
|
<< (*iter).second.getStringValue()
|
|
|
|
<< std::endl;
|
|
|
|
}
|
|
|
|
assert(1 == ntoh.getMin());
|
|
|
|
assert(29 == ntoh.getMax());
|
|
|
|
assert(ntoh.hasIndex(6));
|
|
|
|
assert(! ntoh.hasIndex(500));
|
|
|
|
QPDFObjectHandle oh;
|
|
|
|
assert(! ntoh.findObject(4, oh));
|
|
|
|
assert(ntoh.findObject(3, oh));
|
|
|
|
assert("three" == oh.getStringValue());
|
|
|
|
QPDFNumberTreeObjectHelper::numtree_number offset = 0;
|
|
|
|
assert(! ntoh.findObjectAtOrBelow(0, oh, offset));
|
|
|
|
assert(ntoh.findObjectAtOrBelow(8, oh, offset));
|
|
|
|
assert("six" == oh.getStringValue());
|
|
|
|
assert(2 == offset);
|
|
|
|
}
|
2018-12-18 16:29:00 +00:00
|
|
|
else if (n == 47)
|
|
|
|
{
|
|
|
|
// Test page labels.
|
|
|
|
QPDFPageLabelDocumentHelper pldh(pdf);
|
2019-06-21 03:35:23 +00:00
|
|
|
long long npages = pdf.getRoot().getKey("/Pages").
|
2018-12-18 16:29:00 +00:00
|
|
|
getKey("/Count").getIntValue();
|
|
|
|
std::vector<QPDFObjectHandle> labels;
|
|
|
|
pldh.getLabelsForPageRange(0, npages - 1, 1, labels);
|
|
|
|
assert(labels.size() % 2 == 0);
|
|
|
|
for (size_t i = 0; i < labels.size(); i+= 2)
|
|
|
|
{
|
|
|
|
std::cout << labels.at(i).getIntValue() << " "
|
|
|
|
<< labels.at(i+1).unparse() << std::endl;
|
|
|
|
}
|
|
|
|
}
|
2018-12-19 02:56:56 +00:00
|
|
|
else if (n == 48)
|
|
|
|
{
|
|
|
|
// Test name tree. This test is crafted to work with
|
|
|
|
// name-tree.pdf
|
|
|
|
QPDFObjectHandle qtest = pdf.getTrailer().getKey("/QTest");
|
|
|
|
QPDFNameTreeObjectHelper ntoh(qtest);
|
|
|
|
std::map<std::string, QPDFObjectHandle> ntoh_map = ntoh.getAsMap();
|
|
|
|
for (std::map<std::string, QPDFObjectHandle>::iterator iter =
|
|
|
|
ntoh_map.begin();
|
|
|
|
iter != ntoh_map.end(); ++iter)
|
|
|
|
{
|
|
|
|
std::cout << (*iter).first << " -> "
|
|
|
|
<< (*iter).second.getStringValue()
|
|
|
|
<< std::endl;
|
|
|
|
}
|
|
|
|
assert(ntoh.hasName("11 elephant"));
|
|
|
|
assert(ntoh.hasName("07 sev\xe2\x80\xa2n"));
|
|
|
|
assert(! ntoh.hasName("potato"));
|
|
|
|
QPDFObjectHandle oh;
|
|
|
|
assert(! ntoh.findObject("potato", oh));
|
|
|
|
assert(ntoh.findObject("07 sev\xe2\x80\xa2n", oh));
|
|
|
|
assert("seven!" == oh.getStringValue());
|
|
|
|
}
|
2018-12-19 02:08:53 +00:00
|
|
|
else if (n == 49)
|
|
|
|
{
|
|
|
|
// Outlines
|
|
|
|
std::vector<QPDFPageObjectHelper> pages =
|
|
|
|
QPDFPageDocumentHelper(pdf).getAllPages();
|
|
|
|
QPDFOutlineDocumentHelper odh(pdf);
|
|
|
|
int pageno = 0;
|
|
|
|
for (std::vector<QPDFPageObjectHelper>::iterator iter = pages.begin();
|
|
|
|
iter != pages.end(); ++iter, ++pageno)
|
|
|
|
{
|
2019-07-03 17:34:02 +00:00
|
|
|
std::vector<QPDFOutlineObjectHelper> outlines =
|
2018-12-19 02:08:53 +00:00
|
|
|
odh.getOutlinesForPage((*iter).getObjectHandle().getObjGen());
|
2019-07-03 17:34:02 +00:00
|
|
|
for (std::vector<QPDFOutlineObjectHelper>::iterator oiter =
|
2018-12-19 02:08:53 +00:00
|
|
|
outlines.begin();
|
|
|
|
oiter != outlines.end(); ++oiter)
|
|
|
|
{
|
|
|
|
std::cout
|
|
|
|
<< "page " << pageno << ": "
|
|
|
|
<< (*oiter).getTitle() << " -> "
|
|
|
|
<< (*oiter).getDest().unparseResolved() << std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-12-31 22:12:38 +00:00
|
|
|
else if (n == 50)
|
|
|
|
{
|
|
|
|
// Test dictionary merge. This test is crafted to work with
|
|
|
|
// merge-dict.pdf
|
|
|
|
QPDFObjectHandle d1 = pdf.getTrailer().getKey("/Dict1");
|
|
|
|
QPDFObjectHandle d2 = pdf.getTrailer().getKey("/Dict2");
|
2019-01-03 02:44:10 +00:00
|
|
|
d1.mergeResources(d2);
|
2018-12-31 22:12:38 +00:00
|
|
|
std::cout << d1.getJSON().unparse() << std::endl;
|
|
|
|
// Top-level type mismatch
|
2019-01-03 02:44:10 +00:00
|
|
|
d1.mergeResources(d2.getKey("/k1"));
|
|
|
|
std::set<std::string> names = d1.getResourceNames();
|
|
|
|
for (std::set<std::string>::iterator iter = names.begin();
|
|
|
|
iter != names.end(); ++iter)
|
|
|
|
{
|
|
|
|
std::cout << *iter << std::endl;
|
|
|
|
}
|
2018-12-31 22:12:38 +00:00
|
|
|
}
|
2019-01-03 22:00:56 +00:00
|
|
|
else if (n == 51)
|
|
|
|
{
|
|
|
|
// Test radio button and checkbox field setting. The input
|
|
|
|
// files must have radios button called r1 and r2 and
|
|
|
|
// checkboxes called checkbox1 and checkbox2. The files
|
|
|
|
// button-set*.pdf are designed for this test case.
|
|
|
|
QPDFObjectHandle acroform = pdf.getRoot().getKey("/AcroForm");
|
|
|
|
QPDFObjectHandle fields = acroform.getKey("/Fields");
|
2020-04-16 15:43:37 +00:00
|
|
|
int nitems = fields.getArrayNItems();
|
|
|
|
for (int i = 0; i < nitems; ++i)
|
2019-01-03 22:00:56 +00:00
|
|
|
{
|
|
|
|
QPDFObjectHandle field = fields.getArrayItem(i);
|
|
|
|
QPDFObjectHandle T = field.getKey("/T");
|
|
|
|
if (! T.isString())
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
std::string Tval = T.getUTF8Value();
|
|
|
|
if (Tval == "r1")
|
|
|
|
{
|
|
|
|
std::cout << "setting r1 via parent\n";
|
|
|
|
QPDFFormFieldObjectHelper foh(field);
|
|
|
|
foh.setV(QPDFObjectHandle::newName("/2"));
|
|
|
|
}
|
|
|
|
else if (Tval == "r2")
|
|
|
|
{
|
|
|
|
std::cout << "setting r2 via child\n";
|
|
|
|
field = field.getKey("/Kids").getArrayItem(1);
|
|
|
|
QPDFFormFieldObjectHelper foh(field);
|
|
|
|
foh.setV(QPDFObjectHandle::newName("/3"));
|
|
|
|
}
|
|
|
|
else if (Tval == "checkbox1")
|
|
|
|
{
|
|
|
|
std::cout << "turning checkbox1 on\n";
|
|
|
|
QPDFFormFieldObjectHelper foh(field);
|
|
|
|
foh.setV(QPDFObjectHandle::newName("/Yes"));
|
|
|
|
}
|
|
|
|
else if (Tval == "checkbox2")
|
|
|
|
{
|
|
|
|
std::cout << "turning checkbox2 off\n";
|
|
|
|
QPDFFormFieldObjectHelper foh(field);
|
|
|
|
foh.setV(QPDFObjectHandle::newName("/Off"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
QPDFWriter w(pdf, "a.pdf");
|
|
|
|
w.setQDFMode(true);
|
|
|
|
w.setStaticID(true);
|
|
|
|
w.write();
|
|
|
|
}
|
2019-01-04 00:09:00 +00:00
|
|
|
else if (n == 52)
|
|
|
|
{
|
|
|
|
// This test just sets a field value for appearance stream
|
|
|
|
// generating testing.
|
|
|
|
QPDFObjectHandle acroform = pdf.getRoot().getKey("/AcroForm");
|
|
|
|
QPDFObjectHandle fields = acroform.getKey("/Fields");
|
2020-04-16 15:43:37 +00:00
|
|
|
int nitems = fields.getArrayNItems();
|
|
|
|
for (int i = 0; i < nitems; ++i)
|
2019-01-04 00:09:00 +00:00
|
|
|
{
|
|
|
|
QPDFObjectHandle field = fields.getArrayItem(i);
|
|
|
|
QPDFObjectHandle T = field.getKey("/T");
|
|
|
|
if (! T.isString())
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
std::string Tval = T.getUTF8Value();
|
|
|
|
if (Tval == "list1")
|
|
|
|
{
|
|
|
|
std::cout << "setting list1 value\n";
|
|
|
|
QPDFFormFieldObjectHelper foh(field);
|
|
|
|
foh.setV(QPDFObjectHandle::newString(arg2));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
QPDFWriter w(pdf, "a.pdf");
|
|
|
|
w.write();
|
|
|
|
}
|
2019-01-04 15:17:33 +00:00
|
|
|
else if (n == 53)
|
|
|
|
{
|
|
|
|
// Test get all objects and dangling ref handling
|
|
|
|
QPDFObjectHandle root = pdf.getRoot();
|
|
|
|
root.replaceKey(
|
|
|
|
"/Q1",
|
|
|
|
pdf.makeIndirectObject(QPDFObjectHandle::newString("potato")));
|
|
|
|
std::cout << "all objects" << std::endl;
|
|
|
|
std::vector<QPDFObjectHandle> all = pdf.getAllObjects();
|
|
|
|
for (std::vector<QPDFObjectHandle>::iterator iter = all.begin();
|
|
|
|
iter != all.end(); ++iter)
|
|
|
|
{
|
|
|
|
std::cout << (*iter).unparse() << std::endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
QPDFWriter w(pdf, "a.pdf");
|
|
|
|
w.setStaticID(true);
|
|
|
|
w.write();
|
|
|
|
}
|
2019-01-04 17:32:02 +00:00
|
|
|
else if (n == 54)
|
|
|
|
{
|
|
|
|
// Test getFinalVersion. This must be invoked with a file
|
|
|
|
// whose final version is not 1.5.
|
|
|
|
QPDFWriter w(pdf, "a.pdf");
|
|
|
|
assert(pdf.getPDFVersion() != "1.5");
|
|
|
|
w.setObjectStreamMode(qpdf_o_generate);
|
|
|
|
if (w.getFinalVersion() != "1.5")
|
|
|
|
{
|
|
|
|
std::cout << "oops: " << w.getFinalVersion() << std::endl;
|
|
|
|
}
|
|
|
|
}
|
2019-01-26 21:48:48 +00:00
|
|
|
else if (n == 55)
|
|
|
|
{
|
|
|
|
// Form XObjects
|
|
|
|
std::vector<QPDFPageObjectHelper> pages =
|
|
|
|
QPDFPageDocumentHelper(pdf).getAllPages();
|
|
|
|
QPDFObjectHandle qtest = QPDFObjectHandle::newArray();
|
|
|
|
for (std::vector<QPDFPageObjectHelper>::iterator iter = pages.begin();
|
|
|
|
iter != pages.end(); ++iter)
|
|
|
|
{
|
|
|
|
QPDFPageObjectHelper& ph(*iter);
|
|
|
|
qtest.appendItem(ph.getFormXObjectForPage());
|
|
|
|
qtest.appendItem(ph.getFormXObjectForPage(false));
|
|
|
|
}
|
|
|
|
pdf.getTrailer().replaceKey("/QTest", qtest);
|
|
|
|
QPDFWriter w(pdf, "a.pdf");
|
|
|
|
w.setQDFMode(true);
|
|
|
|
w.setStaticID(true);
|
|
|
|
w.write();
|
|
|
|
}
|
|
|
|
else if ((n >= 56) && (n <= 59))
|
|
|
|
{
|
|
|
|
// Placing form XObjects
|
|
|
|
assert(arg2);
|
|
|
|
QPDF pdf2;
|
|
|
|
pdf2.processFile(arg2);
|
|
|
|
|
|
|
|
// red pages are from pdf, blue pages are from pdf2
|
|
|
|
// red pages always have stated rotation absolutely
|
2019-02-01 02:40:06 +00:00
|
|
|
// 56: blue pages are overlaid exactly on top of red pages
|
2019-01-26 21:48:48 +00:00
|
|
|
// 57: blue pages have stated rotation relative to red pages
|
2019-02-01 02:40:06 +00:00
|
|
|
// 58: blue pages have no rotation (absolutely upright)
|
2019-01-26 21:48:48 +00:00
|
|
|
// 59: blue pages have stated rotation absolutely
|
|
|
|
bool handle_from_transformation = ((n == 57) || (n == 59));
|
|
|
|
bool invert_to_transformation = ((n == 58) || (n == 59));
|
|
|
|
|
|
|
|
std::vector<QPDFPageObjectHelper> pages1 =
|
|
|
|
QPDFPageDocumentHelper(pdf).getAllPages();
|
|
|
|
std::vector<QPDFPageObjectHelper> pages2 =
|
|
|
|
QPDFPageDocumentHelper(pdf2).getAllPages();
|
|
|
|
size_t npages = (pages1.size() < pages2.size()
|
|
|
|
? pages1.size() : pages2.size());
|
|
|
|
for (size_t i = 0; i < npages; ++i)
|
|
|
|
{
|
|
|
|
QPDFPageObjectHelper& ph1 = pages1.at(i);
|
|
|
|
QPDFPageObjectHelper& ph2 = pages2.at(i);
|
|
|
|
QPDFObjectHandle fo = pdf.copyForeignObject(
|
|
|
|
ph2.getFormXObjectForPage(handle_from_transformation));
|
|
|
|
int min_suffix = 1;
|
|
|
|
QPDFObjectHandle resources = ph1.getAttribute("/Resources", true);
|
|
|
|
std::string name = resources.getUniqueResourceName(
|
|
|
|
"/Fx", min_suffix);
|
|
|
|
std::string content =
|
|
|
|
ph1.placeFormXObject(
|
|
|
|
fo, name, ph1.getTrimBox().getArrayAsRectangle(),
|
|
|
|
invert_to_transformation);
|
|
|
|
if (! content.empty())
|
|
|
|
{
|
|
|
|
resources.mergeResources(
|
|
|
|
QPDFObjectHandle::parse("<< /XObject << >> >>"));
|
|
|
|
resources.getKey("/XObject").replaceKey(name, fo);
|
|
|
|
ph1.addPageContents(
|
|
|
|
QPDFObjectHandle::newStream(&pdf, "q\n"), true);
|
|
|
|
ph1.addPageContents(
|
|
|
|
QPDFObjectHandle::newStream(&pdf, "\nQ\n" + content),
|
|
|
|
false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
QPDFWriter w(pdf, "a.pdf");
|
|
|
|
w.setQDFMode(true);
|
|
|
|
w.setStaticID(true);
|
|
|
|
w.write();
|
|
|
|
}
|
2019-01-27 13:00:32 +00:00
|
|
|
else if (n == 60)
|
|
|
|
{
|
|
|
|
// Boundary condition testing for getUniqueResourceName
|
|
|
|
QPDFObjectHandle r1 = QPDFObjectHandle::newDictionary();
|
|
|
|
int min_suffix = 1;
|
|
|
|
for (int i = 1; i < 3; ++i)
|
|
|
|
{
|
|
|
|
std::string name = r1.getUniqueResourceName("/Quack", min_suffix);
|
|
|
|
r1.mergeResources(QPDFObjectHandle::parse("<< /Z << >> >>"));
|
|
|
|
r1.getKey("/Z").replaceKey(
|
|
|
|
name, QPDFObjectHandle::newString("moo"));
|
|
|
|
}
|
|
|
|
pdf.getTrailer().replaceKey("/QTest", r1);
|
|
|
|
QPDFWriter w(pdf, "a.pdf");
|
|
|
|
w.setStaticID(true);
|
|
|
|
w.write();
|
|
|
|
}
|
2019-02-08 00:21:26 +00:00
|
|
|
else if (n == 61)
|
|
|
|
{
|
|
|
|
// Test to make sure exceptions can be caught properly across
|
|
|
|
// shared library boundaries.
|
|
|
|
pdf.setAttemptRecovery(false);
|
|
|
|
pdf.setSuppressWarnings(true);
|
|
|
|
try
|
|
|
|
{
|
|
|
|
pdf.processMemoryFile("empty", "", 0);
|
|
|
|
}
|
2019-06-21 03:35:23 +00:00
|
|
|
catch (QPDFExc const&)
|
2019-02-08 00:21:26 +00:00
|
|
|
{
|
|
|
|
std::cout << "Caught QPDFExc as expected" << std::endl;
|
|
|
|
}
|
|
|
|
try
|
|
|
|
{
|
|
|
|
QUtil::safe_fopen("/does/not/exist", "r");
|
|
|
|
}
|
2019-06-21 03:35:23 +00:00
|
|
|
catch (QPDFSystemError const&)
|
2019-02-08 00:21:26 +00:00
|
|
|
{
|
|
|
|
std::cout << "Caught QPDFSystemError as expected" << std::endl;
|
|
|
|
}
|
|
|
|
try
|
|
|
|
{
|
|
|
|
QUtil::int_to_string_base(0, 12);
|
|
|
|
}
|
2019-06-21 03:35:23 +00:00
|
|
|
catch (std::logic_error const&)
|
2019-02-08 00:21:26 +00:00
|
|
|
{
|
|
|
|
std::cout << "Caught logic_error as expected" << std::endl;
|
|
|
|
}
|
|
|
|
try
|
|
|
|
{
|
|
|
|
QUtil::toUTF8(0xffffffff);
|
|
|
|
}
|
2019-06-21 03:35:23 +00:00
|
|
|
catch (std::runtime_error const&)
|
2019-02-08 00:21:26 +00:00
|
|
|
{
|
|
|
|
std::cout << "Caught runtime_error as expected" << std::endl;
|
|
|
|
}
|
|
|
|
}
|
2019-06-20 17:04:57 +00:00
|
|
|
else if (n == 62)
|
|
|
|
{
|
|
|
|
// Test int size checks. This test will fail if int and long
|
|
|
|
// long are the same size.
|
|
|
|
QPDFObjectHandle t = pdf.getTrailer();
|
2019-06-21 03:35:23 +00:00
|
|
|
unsigned long long q1_l = 3ULL * QIntC::to_ulonglong(INT_MAX);
|
|
|
|
long long q1 = QIntC::to_longlong(q1_l);
|
|
|
|
long long q2_l = 3LL * QIntC::to_longlong(INT_MIN);
|
|
|
|
long long q2 = QIntC::to_longlong(q2_l);
|
2019-06-20 17:04:57 +00:00
|
|
|
unsigned int q3_i = UINT_MAX;
|
2019-06-21 03:35:23 +00:00
|
|
|
long long q3 = QIntC::to_longlong(q3_i);
|
2019-06-20 17:04:57 +00:00
|
|
|
t.replaceKey("/Q1", QPDFObjectHandle::newInteger(q1));
|
|
|
|
t.replaceKey("/Q2", QPDFObjectHandle::newInteger(q2));
|
|
|
|
t.replaceKey("/Q3", QPDFObjectHandle::newInteger(q3));
|
|
|
|
assert_compare_numbers(q1, t.getKey("/Q1").getIntValue());
|
|
|
|
assert_compare_numbers(q1_l, t.getKey("/Q1").getUIntValue());
|
|
|
|
assert_compare_numbers(INT_MAX, t.getKey("/Q1").getIntValueAsInt());
|
|
|
|
assert_compare_numbers(UINT_MAX, t.getKey("/Q1").getUIntValueAsUInt());
|
|
|
|
assert_compare_numbers(q2_l, t.getKey("/Q2").getIntValue());
|
|
|
|
assert_compare_numbers(0U, t.getKey("/Q2").getUIntValue());
|
|
|
|
assert_compare_numbers(INT_MIN, t.getKey("/Q2").getIntValueAsInt());
|
|
|
|
assert_compare_numbers(0U, t.getKey("/Q2").getUIntValueAsUInt());
|
|
|
|
assert_compare_numbers(INT_MAX, t.getKey("/Q3").getIntValueAsInt());
|
|
|
|
assert_compare_numbers(UINT_MAX, t.getKey("/Q3").getUIntValueAsUInt());
|
|
|
|
}
|
2019-06-23 00:05:18 +00:00
|
|
|
else if (n == 63)
|
|
|
|
{
|
|
|
|
QPDFWriter w(pdf);
|
|
|
|
// Exercise setting encryption parameters before setting the
|
|
|
|
// output filename. The previous bug does not happen if static
|
|
|
|
// or deterministic ID is used because the filename is not
|
|
|
|
// used as part of the input data for ID generation in those
|
|
|
|
// cases.
|
|
|
|
w.setR6EncryptionParameters(
|
|
|
|
"u", "o", true, true, true, true, true, true, qpdf_r3p_full, true);
|
|
|
|
w.setOutputFilename("a.pdf");
|
|
|
|
w.write();
|
|
|
|
}
|
2020-04-03 20:48:20 +00:00
|
|
|
else if ((n >= 64) && (n <= 67))
|
|
|
|
{
|
|
|
|
// Placing form XObjects: expand, shrink
|
|
|
|
assert(arg2);
|
|
|
|
QPDF pdf2;
|
|
|
|
pdf2.processFile(arg2);
|
|
|
|
|
|
|
|
// Overlay file2 on file1.
|
|
|
|
// 64: allow neither shrink nor shrink
|
|
|
|
// 65: allow shrink but not expand
|
|
|
|
// 66: allow expand but not shrink
|
|
|
|
// 67: allow both shrink and expand
|
|
|
|
bool allow_shrink = ((n == 65) || (n == 67));
|
|
|
|
bool allow_expand = ((n == 66) || (n == 67));
|
|
|
|
std::vector<QPDFPageObjectHelper> pages1 =
|
|
|
|
QPDFPageDocumentHelper(pdf).getAllPages();
|
|
|
|
std::vector<QPDFPageObjectHelper> pages2 =
|
|
|
|
QPDFPageDocumentHelper(pdf2).getAllPages();
|
|
|
|
size_t npages = (pages1.size() < pages2.size()
|
|
|
|
? pages1.size() : pages2.size());
|
|
|
|
for (size_t i = 0; i < npages; ++i)
|
|
|
|
{
|
|
|
|
QPDFPageObjectHelper& ph1 = pages1.at(i);
|
|
|
|
QPDFPageObjectHelper& ph2 = pages2.at(i);
|
|
|
|
QPDFObjectHandle fo = pdf.copyForeignObject(
|
|
|
|
ph2.getFormXObjectForPage());
|
|
|
|
int min_suffix = 1;
|
|
|
|
QPDFObjectHandle resources = ph1.getAttribute("/Resources", true);
|
|
|
|
std::string name = resources.getUniqueResourceName(
|
|
|
|
"/Fx", min_suffix);
|
|
|
|
std::string content =
|
|
|
|
ph1.placeFormXObject(
|
|
|
|
fo, name, ph1.getTrimBox().getArrayAsRectangle(),
|
|
|
|
false, allow_shrink, allow_expand);
|
|
|
|
if (! content.empty())
|
|
|
|
{
|
|
|
|
resources.mergeResources(
|
|
|
|
QPDFObjectHandle::parse("<< /XObject << >> >>"));
|
|
|
|
resources.getKey("/XObject").replaceKey(name, fo);
|
|
|
|
ph1.addPageContents(
|
|
|
|
QPDFObjectHandle::newStream(&pdf, "q\n"), true);
|
|
|
|
ph1.addPageContents(
|
|
|
|
QPDFObjectHandle::newStream(&pdf, "\nQ\n" + content),
|
|
|
|
false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
QPDFWriter w(pdf, "a.pdf");
|
|
|
|
w.setQDFMode(true);
|
|
|
|
w.setStaticID(true);
|
|
|
|
w.write();
|
|
|
|
}
|
2020-04-08 22:47:29 +00:00
|
|
|
else if (n == 68)
|
|
|
|
{
|
|
|
|
QPDFObjectHandle root = pdf.getRoot();
|
|
|
|
QPDFObjectHandle qstream = root.getKey("/QStream");
|
|
|
|
try
|
|
|
|
{
|
|
|
|
qstream.getStreamData();
|
|
|
|
std::cout << "oops -- didn't throw" << std::endl;
|
|
|
|
}
|
|
|
|
catch (std::exception& e)
|
|
|
|
{
|
|
|
|
std::cout << "get unfilterable stream: " << e.what()
|
|
|
|
<< std::endl;
|
|
|
|
}
|
|
|
|
PointerHolder<Buffer> b1 = qstream.getStreamData(qpdf_dl_all);
|
|
|
|
if ((b1->getSize() > 10) &&
|
|
|
|
(memcmp(b1->getBuffer(),
|
|
|
|
"wwwwwwwww", 9) == 0))
|
|
|
|
{
|
|
|
|
std::cout << "filtered stream data okay" << std::endl;
|
|
|
|
}
|
|
|
|
PointerHolder<Buffer> b2 = qstream.getRawStreamData();
|
|
|
|
if ((b2->getSize() > 10) &&
|
|
|
|
(memcmp(b2->getBuffer(),
|
|
|
|
"\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46", 10) == 0))
|
|
|
|
{
|
|
|
|
std::cout << "raw stream data okay" << std::endl;
|
|
|
|
}
|
|
|
|
}
|
2020-10-21 22:50:29 +00:00
|
|
|
else if (n == 69)
|
|
|
|
{
|
|
|
|
pdf.setImmediateCopyFrom(true);
|
|
|
|
auto pages = pdf.getAllPages();
|
|
|
|
for (size_t i = 0; i < pages.size(); ++i)
|
|
|
|
{
|
|
|
|
QPDF out;
|
|
|
|
out.emptyPDF();
|
|
|
|
out.addPage(pages.at(i), false);
|
|
|
|
std::string outname = std::string("auto-") +
|
|
|
|
QUtil::uint_to_string(i) + ".pdf";
|
|
|
|
QPDFWriter w(out, outname.c_str());
|
|
|
|
w.setStaticID(true);
|
|
|
|
w.write();
|
|
|
|
}
|
|
|
|
}
|
2020-12-27 00:45:01 +00:00
|
|
|
else if (n == 70)
|
|
|
|
{
|
|
|
|
auto trailer = pdf.getTrailer();
|
|
|
|
trailer.getKey("/S1").setFilterOnWrite(false);
|
|
|
|
trailer.getKey("/S2").setFilterOnWrite(false);
|
|
|
|
QPDFWriter w(pdf, "a.pdf");
|
|
|
|
w.setStaticID(true);
|
|
|
|
w.setDecodeLevel(qpdf_dl_specialized);
|
|
|
|
w.write();
|
|
|
|
}
|
2008-04-29 12:55:25 +00:00
|
|
|
else
|
|
|
|
{
|
2009-09-26 18:36:04 +00:00
|
|
|
throw std::runtime_error(std::string("invalid test ") +
|
|
|
|
QUtil::int_to_string(n));
|
2008-04-29 12:55:25 +00:00
|
|
|
}
|
|
|
|
|
2012-06-21 11:59:56 +00:00
|
|
|
if (filep)
|
|
|
|
{
|
|
|
|
fclose(filep);
|
|
|
|
}
|
2008-04-29 12:55:25 +00:00
|
|
|
std::cout << "test " << n << " done" << std::endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char* argv[])
|
|
|
|
{
|
2009-10-21 02:30:15 +00:00
|
|
|
QUtil::setLineBuf(stdout);
|
2008-04-29 12:55:25 +00:00
|
|
|
if ((whoami = strrchr(argv[0], '/')) == NULL)
|
|
|
|
{
|
|
|
|
whoami = argv[0];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
++whoami;
|
|
|
|
}
|
|
|
|
// For libtool's sake....
|
|
|
|
if (strncmp(whoami, "lt-", 3) == 0)
|
|
|
|
{
|
|
|
|
whoami += 3;
|
|
|
|
}
|
|
|
|
|
2012-07-14 12:48:30 +00:00
|
|
|
if ((argc < 3) || (argc > 4))
|
2008-04-29 12:55:25 +00:00
|
|
|
{
|
|
|
|
usage();
|
|
|
|
}
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
2017-08-29 16:27:59 +00:00
|
|
|
int n = QUtil::string_to_int(argv[1]);
|
2012-07-14 12:48:30 +00:00
|
|
|
char const* filename1 = argv[2];
|
2012-12-30 14:27:49 +00:00
|
|
|
char const* arg2 = argv[3];
|
|
|
|
runtest(n, filename1, arg2);
|
2008-04-29 12:55:25 +00:00
|
|
|
}
|
|
|
|
catch (std::exception& e)
|
|
|
|
{
|
|
|
|
std::cerr << e.what() << std::endl;
|
|
|
|
exit(2);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|