aboutsummaryrefslogtreecommitdiffstats
path: root/libqpdf
diff options
context:
space:
mode:
Diffstat (limited to 'libqpdf')
-rw-r--r--libqpdf/ClosedFileInputSource.cc26
-rw-r--r--libqpdf/JSON.cc396
-rw-r--r--libqpdf/MD5.cc2
-rw-r--r--libqpdf/Pl_Buffer.cc44
-rw-r--r--libqpdf/Pl_Flate.cc7
-rw-r--r--libqpdf/Pl_QPDFTokenizer.cc79
-rw-r--r--libqpdf/Pl_RC4.cc14
-rw-r--r--libqpdf/QPDF.cc403
-rw-r--r--libqpdf/QPDFAcroFormDocumentHelper.cc45
-rw-r--r--libqpdf/QPDFAnnotationObjectHelper.cc208
-rw-r--r--libqpdf/QPDFExc.cc4
-rw-r--r--libqpdf/QPDFFormFieldObjectHelper.cc686
-rw-r--r--libqpdf/QPDFMatrix.cc135
-rw-r--r--libqpdf/QPDFNameTreeObjectHelper.cc82
-rw-r--r--libqpdf/QPDFNumberTreeObjectHelper.cc121
-rw-r--r--libqpdf/QPDFObjectHandle.cc248
-rw-r--r--libqpdf/QPDFOutlineDocumentHelper.cc137
-rw-r--r--libqpdf/QPDFOutlineObjectHelper.cc117
-rw-r--r--libqpdf/QPDFPageDocumentHelper.cc151
-rw-r--r--libqpdf/QPDFPageLabelDocumentHelper.cc125
-rw-r--r--libqpdf/QPDFPageObjectHelper.cc611
-rw-r--r--libqpdf/QPDFSystemError.cc51
-rw-r--r--libqpdf/QPDFTokenizer.cc246
-rw-r--r--libqpdf/QPDFWriter.cc194
-rw-r--r--libqpdf/QPDF_Array.cc12
-rw-r--r--libqpdf/QPDF_Bool.cc6
-rw-r--r--libqpdf/QPDF_Dictionary.cc14
-rw-r--r--libqpdf/QPDF_InlineImage.cc6
-rw-r--r--libqpdf/QPDF_Integer.cc6
-rw-r--r--libqpdf/QPDF_Name.cc6
-rw-r--r--libqpdf/QPDF_Null.cc6
-rw-r--r--libqpdf/QPDF_Operator.cc6
-rw-r--r--libqpdf/QPDF_Real.cc6
-rw-r--r--libqpdf/QPDF_Reserved.cc7
-rw-r--r--libqpdf/QPDF_Stream.cc30
-rw-r--r--libqpdf/QPDF_String.cc147
-rw-r--r--libqpdf/QPDF_encryption.cc154
-rw-r--r--libqpdf/QPDF_linearization.cc94
-rw-r--r--libqpdf/QPDF_optimization.cc37
-rw-r--r--libqpdf/QPDF_pages.cc38
-rw-r--r--libqpdf/QUtil.cc1387
-rw-r--r--libqpdf/build.mk8
-rw-r--r--libqpdf/qpdf-c.cc70
-rw-r--r--libqpdf/qpdf/BitStream.hh6
-rw-r--r--libqpdf/qpdf/BitWriter.hh6
-rw-r--r--libqpdf/qpdf/ContentNormalizer.hh6
-rw-r--r--libqpdf/qpdf/InsecureRandomDataProvider.hh6
-rw-r--r--libqpdf/qpdf/MD5.hh6
-rw-r--r--libqpdf/qpdf/OffsetInputSource.hh6
-rw-r--r--libqpdf/qpdf/Pl_AES_PDF.hh6
-rw-r--r--libqpdf/qpdf/Pl_ASCII85Decoder.hh6
-rw-r--r--libqpdf/qpdf/Pl_ASCIIHexDecoder.hh6
-rw-r--r--libqpdf/qpdf/Pl_LZWDecoder.hh6
-rw-r--r--libqpdf/qpdf/Pl_MD5.hh6
-rw-r--r--libqpdf/qpdf/Pl_PNGFilter.hh6
-rw-r--r--libqpdf/qpdf/Pl_RC4.hh6
-rw-r--r--libqpdf/qpdf/Pl_SHA2.hh6
-rw-r--r--libqpdf/qpdf/Pl_TIFFPredictor.hh6
-rw-r--r--libqpdf/qpdf/QPDFMatrix.hh53
-rw-r--r--libqpdf/qpdf/QPDF_Array.hh7
-rw-r--r--libqpdf/qpdf/QPDF_Bool.hh7
-rw-r--r--libqpdf/qpdf/QPDF_Dictionary.hh7
-rw-r--r--libqpdf/qpdf/QPDF_InlineImage.hh7
-rw-r--r--libqpdf/qpdf/QPDF_Integer.hh7
-rw-r--r--libqpdf/qpdf/QPDF_Name.hh7
-rw-r--r--libqpdf/qpdf/QPDF_Null.hh7
-rw-r--r--libqpdf/qpdf/QPDF_Operator.hh7
-rw-r--r--libqpdf/qpdf/QPDF_Real.hh7
-rw-r--r--libqpdf/qpdf/QPDF_Reserved.hh7
-rw-r--r--libqpdf/qpdf/QPDF_Stream.hh13
-rw-r--r--libqpdf/qpdf/QPDF_String.hh7
-rw-r--r--libqpdf/qpdf/RC4.hh6
-rw-r--r--libqpdf/qpdf/SecureRandomDataProvider.hh6
-rw-r--r--libqpdf/qpdf/qpdf-config.h.in106
-rw-r--r--libqpdf/qpdf/rijndael.h4
-rw-r--r--libqpdf/sph/sph_sha2.h4
-rw-r--r--libqpdf/sph/sph_types.h4
77 files changed, 5948 insertions, 587 deletions
diff --git a/libqpdf/ClosedFileInputSource.cc b/libqpdf/ClosedFileInputSource.cc
index ea79a840..e9c9b3bd 100644
--- a/libqpdf/ClosedFileInputSource.cc
+++ b/libqpdf/ClosedFileInputSource.cc
@@ -4,16 +4,14 @@
ClosedFileInputSource::Members::Members(char const* filename) :
filename(filename),
offset(0),
- fis(0)
+ fis(0),
+ stay_open(false)
{
}
ClosedFileInputSource::Members::~Members()
{
- if (fis)
- {
- delete fis;
- }
+ delete fis;
}
ClosedFileInputSource::ClosedFileInputSource(char const* filename) :
@@ -42,6 +40,10 @@ ClosedFileInputSource::after()
{
this->last_offset = this->m->fis->getLastOffset();
this->m->offset = this->m->fis->tell();
+ if (this->m->stay_open)
+ {
+ return;
+ }
delete this->m->fis;
this->m->fis = 0;
}
@@ -82,6 +84,10 @@ void
ClosedFileInputSource::rewind()
{
this->m->offset = 0;
+ if (this->m->fis)
+ {
+ this->m->fis->rewind();
+ }
}
size_t
@@ -101,3 +107,13 @@ ClosedFileInputSource::unreadCh(char ch)
// Don't call after -- the file has to stay open after this
// operation.
}
+
+void
+ClosedFileInputSource::stayOpen(bool val)
+{
+ this->m->stay_open = val;
+ if ((! val) && this->m->fis)
+ {
+ after();
+ }
+}
diff --git a/libqpdf/JSON.cc b/libqpdf/JSON.cc
new file mode 100644
index 00000000..df753b69
--- /dev/null
+++ b/libqpdf/JSON.cc
@@ -0,0 +1,396 @@
+#include <qpdf/JSON.hh>
+#include <qpdf/QUtil.hh>
+#include <qpdf/QTC.hh>
+#include <stdexcept>
+
+JSON::Members::~Members()
+{
+}
+
+JSON::Members::Members(PointerHolder<JSON_value> value) :
+ value(value)
+{
+}
+
+JSON::JSON(PointerHolder<JSON_value> value) :
+ m(new Members(value))
+{
+}
+
+JSON::JSON_value::~JSON_value()
+{
+}
+
+JSON::JSON_dictionary::~JSON_dictionary()
+{
+}
+
+std::string JSON::JSON_dictionary::unparse(size_t depth) const
+{
+ std::string result = "{";
+ bool first = true;
+ for (std::map<std::string, PointerHolder<JSON_value> >::const_iterator
+ iter = members.begin();
+ iter != members.end(); ++iter)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ result.append(1, ',');
+ }
+ result.append(1, '\n');
+ result.append(2 * (1 + depth), ' ');
+ result += ("\"" + (*iter).first + "\": " +
+ (*iter).second->unparse(1 + depth));
+ }
+ if (! first)
+ {
+ result.append(1, '\n');
+ result.append(2 * depth, ' ');
+ }
+ result.append(1, '}');
+ return result;
+}
+
+JSON::JSON_array::~JSON_array()
+{
+}
+
+std::string JSON::JSON_array::unparse(size_t depth) const
+{
+ std::string result = "[";
+ bool first = true;
+ for (std::vector<PointerHolder<JSON_value> >::const_iterator iter =
+ elements.begin();
+ iter != elements.end(); ++iter)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ result.append(1, ',');
+ }
+ result.append(1, '\n');
+ result.append(2 * (1 + depth), ' ');
+ result += (*iter)->unparse(1 + depth);
+ }
+ if (! first)
+ {
+ result.append(1, '\n');
+ result.append(2 * depth, ' ');
+ }
+ result.append(1, ']');
+ return result;
+}
+
+JSON::JSON_string::JSON_string(std::string const& utf8) :
+ encoded(encode_string(utf8))
+{
+}
+
+JSON::JSON_string::~JSON_string()
+{
+}
+
+std::string JSON::JSON_string::unparse(size_t) const
+{
+ return "\"" + encoded + "\"";
+}
+
+JSON::JSON_number::JSON_number(long long value) :
+ encoded(QUtil::int_to_string(value))
+{
+}
+
+JSON::JSON_number::JSON_number(double value) :
+ encoded(QUtil::double_to_string(value, 6))
+{
+}
+
+JSON::JSON_number::JSON_number(std::string const& value) :
+ encoded(value)
+{
+}
+
+JSON::JSON_number::~JSON_number()
+{
+}
+
+std::string JSON::JSON_number::unparse(size_t) const
+{
+ return encoded;
+}
+
+JSON::JSON_bool::JSON_bool(bool val) :
+ value(val)
+{
+}
+
+JSON::JSON_bool::~JSON_bool()
+{
+}
+
+std::string JSON::JSON_bool::unparse(size_t) const
+{
+ return value ? "true" : "false";
+}
+
+JSON::JSON_null::~JSON_null()
+{
+}
+
+std::string JSON::JSON_null::unparse(size_t) const
+{
+ return "null";
+}
+
+std::string
+JSON::unparse() const
+{
+ if (0 == this->m->value.getPointer())
+ {
+ return "null";
+ }
+ else
+ {
+ return this->m->value->unparse(0);
+ }
+}
+
+std::string
+JSON::encode_string(std::string const& str)
+{
+ std::string result;
+ size_t len = str.length();
+ for (size_t i = 0; i < len; ++i)
+ {
+ unsigned char ch = static_cast<unsigned char>(str.at(i));
+ switch (ch)
+ {
+ case '\\':
+ result += "\\\\";
+ break;
+ case '\"':
+ result += "\\\"";
+ break;
+ case '\b':
+ result += "\\b";
+ break;
+ case '\n':
+ result += "\\n";
+ break;
+ case '\r':
+ result += "\\r";
+ break;
+ case '\t':
+ result += "\\t";
+ break;
+ default:
+ if (ch < 32)
+ {
+ result += "\\u" + QUtil::int_to_string_base(ch, 16, 4);
+ }
+ else
+ {
+ result.append(1, ch);
+ }
+ }
+ }
+ return result;
+}
+
+JSON
+JSON::makeDictionary()
+{
+ return JSON(new JSON_dictionary());
+}
+
+JSON
+JSON::addDictionaryMember(std::string const& key, JSON const& val)
+{
+ JSON_dictionary* obj = dynamic_cast<JSON_dictionary*>(
+ this->m->value.getPointer());
+ if (0 == obj)
+ {
+ throw std::runtime_error(
+ "JSON::addDictionaryMember called on non-dictionary");
+ }
+ if (val.m->value.getPointer())
+ {
+ obj->members[encode_string(key)] = val.m->value;
+ }
+ else
+ {
+ obj->members[encode_string(key)] = new JSON_null();
+ }
+ return obj->members[encode_string(key)];
+}
+
+JSON
+JSON::makeArray()
+{
+ return JSON(new JSON_array());
+}
+
+JSON
+JSON::addArrayElement(JSON const& val)
+{
+ JSON_array* arr = dynamic_cast<JSON_array*>(
+ this->m->value.getPointer());
+ if (0 == arr)
+ {
+ throw std::runtime_error("JSON::addArrayElement called on non-array");
+ }
+ if (val.m->value.getPointer())
+ {
+ arr->elements.push_back(val.m->value);
+ }
+ else
+ {
+ arr->elements.push_back(new JSON_null());
+ }
+ return arr->elements.back();
+}
+
+JSON
+JSON::makeString(std::string const& utf8)
+{
+ return JSON(new JSON_string(utf8));
+}
+
+JSON
+JSON::makeInt(long long int value)
+{
+ return JSON(new JSON_number(value));
+}
+
+JSON
+JSON::makeReal(double value)
+{
+ return JSON(new JSON_number(value));
+}
+
+JSON
+JSON::makeNumber(std::string const& encoded)
+{
+ return JSON(new JSON_number(encoded));
+}
+
+JSON
+JSON::makeBool(bool value)
+{
+ return JSON(new JSON_bool(value));
+}
+
+JSON
+JSON::makeNull()
+{
+ return JSON(new JSON_null());
+}
+
+bool
+JSON::checkSchema(JSON schema, std::list<std::string>& errors)
+{
+ return checkSchemaInternal(this->m->value.getPointer(),
+ schema.m->value.getPointer(),
+ errors, "");
+}
+
+
+bool
+JSON::checkSchemaInternal(JSON_value* this_v, JSON_value* sch_v,
+ std::list<std::string>& errors,
+ std::string prefix)
+{
+ JSON_array* this_arr = dynamic_cast<JSON_array*>(this_v);
+ JSON_dictionary* this_dict = dynamic_cast<JSON_dictionary*>(this_v);
+
+ JSON_array* sch_arr = dynamic_cast<JSON_array*>(sch_v);
+ JSON_dictionary* sch_dict = dynamic_cast<JSON_dictionary*>(sch_v);
+
+ std::string err_prefix;
+ if (prefix.empty())
+ {
+ err_prefix = "top-level object";
+ }
+ else
+ {
+ err_prefix = "json key \"" + prefix + "\"";
+ }
+
+ if (sch_dict)
+ {
+ if (! this_dict)
+ {
+ QTC::TC("libtests", "JSON wanted dictionary");
+ errors.push_back(err_prefix + " is supposed to be a dictionary");
+ return false;
+ }
+ for (std::map<std::string, PointerHolder<JSON_value> >::iterator iter =
+ sch_dict->members.begin();
+ iter != sch_dict->members.end(); ++iter)
+ {
+ std::string const& key = (*iter).first;
+ if (this_dict->members.count(key))
+ {
+ checkSchemaInternal(
+ this_dict->members[key].getPointer(),
+ (*iter).second.getPointer(),
+ errors, prefix + "." + key);
+ }
+ else
+ {
+ QTC::TC("libtests", "JSON key missing in object");
+ errors.push_back(
+ err_prefix + ": key \"" + key +
+ "\" is present in schema but missing in object");
+ }
+ }
+ for (std::map<std::string, PointerHolder<JSON_value> >::iterator iter =
+ this_dict->members.begin();
+ iter != this_dict->members.end(); ++iter)
+ {
+ std::string const& key = (*iter).first;
+ if (sch_dict->members.count(key) == 0)
+ {
+ QTC::TC("libtests", "JSON key extra in object");
+ errors.push_back(
+ err_prefix + ": key \"" + key +
+ "\" is not present in schema but appears in object");
+ }
+ }
+ }
+ else if (sch_arr)
+ {
+ if (! this_arr)
+ {
+ QTC::TC("libtests", "JSON wanted array");
+ errors.push_back(err_prefix + " is supposed to be an array");
+ return false;
+ }
+ if (sch_arr->elements.size() != 1)
+ {
+ QTC::TC("libtests", "JSON schema array error");
+ errors.push_back(err_prefix +
+ " schema array contains other than one item");
+ return false;
+ }
+ int i = 0;
+ for (std::vector<PointerHolder<JSON_value> >::iterator iter =
+ this_arr->elements.begin();
+ iter != this_arr->elements.end(); ++iter, ++i)
+ {
+ checkSchemaInternal(
+ (*iter).getPointer(),
+ sch_arr->elements.at(0).getPointer(),
+ errors, prefix + "." + QUtil::int_to_string(i));
+ }
+ }
+
+ return errors.empty();
+}
diff --git a/libqpdf/MD5.cc b/libqpdf/MD5.cc
index 0504e2d4..275567da 100644
--- a/libqpdf/MD5.cc
+++ b/libqpdf/MD5.cc
@@ -421,7 +421,7 @@ MD5::checkFileChecksum(char const* const checksum,
std::string actual_checksum = getFileChecksum(filename, up_to_size);
result = (checksum == actual_checksum);
}
- catch (std::runtime_error)
+ catch (std::runtime_error const&)
{
// Ignore -- return false
}
diff --git a/libqpdf/Pl_Buffer.cc b/libqpdf/Pl_Buffer.cc
index 45c0c862..d11959f4 100644
--- a/libqpdf/Pl_Buffer.cc
+++ b/libqpdf/Pl_Buffer.cc
@@ -1,5 +1,6 @@
#include <qpdf/Pl_Buffer.hh>
#include <stdexcept>
+#include <algorithm>
#include <assert.h>
#include <string.h>
@@ -17,11 +18,32 @@ Pl_Buffer::~Pl_Buffer()
void
Pl_Buffer::write(unsigned char* buf, size_t len)
{
- Buffer* b = new Buffer(len);
- memcpy(b->getBuffer(), buf, len);
- this->data.push_back(b);
+ PointerHolder<Buffer> cur_buf;
+ size_t cur_size = 0;
+ if (! this->data.empty())
+ {
+ cur_buf = this->data.back();
+ cur_size = cur_buf->getSize();
+ }
+ size_t left = cur_size - this->total_size;
+ if (left < len)
+ {
+ size_t new_size = std::max(this->total_size + len, 2 * cur_size);
+ Buffer* b = new Buffer(new_size);
+ if (cur_buf.getPointer())
+ {
+ memcpy(b->getBuffer(), cur_buf->getBuffer(), this->total_size);
+ }
+ this->data.clear();
+ cur_buf = b;
+ this->data.push_back(cur_buf);
+ }
+ if (len)
+ {
+ memcpy(cur_buf->getBuffer() + this->total_size, buf, len);
+ this->total_size += len;
+ }
this->ready = false;
- this->total_size += len;
if (getNext(true))
{
@@ -49,17 +71,13 @@ Pl_Buffer::getBuffer()
Buffer* b = new Buffer(this->total_size);
unsigned char* p = b->getBuffer();
- while (! this->data.empty())
+ if (! this->data.empty())
{
- PointerHolder<Buffer> bp = this->data.front();
- this->data.pop_front();
- size_t bytes = bp->getSize();
- memcpy(p, bp->getBuffer(), bytes);
- p += bytes;
- this->total_size -= bytes;
+ PointerHolder<Buffer> bp = this->data.back();
+ this->data.clear();
+ memcpy(p, bp->getBuffer(), this->total_size);
}
-
- assert(this->total_size == 0);
+ this->total_size = 0;
this->ready = false;
return b;
diff --git a/libqpdf/Pl_Flate.cc b/libqpdf/Pl_Flate.cc
index 3becc135..4cd48046 100644
--- a/libqpdf/Pl_Flate.cc
+++ b/libqpdf/Pl_Flate.cc
@@ -31,11 +31,8 @@ Pl_Flate::Pl_Flate(char const* identifier, Pipeline* next,
Pl_Flate::~Pl_Flate()
{
- if (this->outbuf)
- {
- delete [] this->outbuf;
- this->outbuf = 0;
- }
+ delete [] this->outbuf;
+ this->outbuf = 0;
if (this->initialized)
{
diff --git a/libqpdf/Pl_QPDFTokenizer.cc b/libqpdf/Pl_QPDFTokenizer.cc
index 577c5cc7..9a93f8f3 100644
--- a/libqpdf/Pl_QPDFTokenizer.cc
+++ b/libqpdf/Pl_QPDFTokenizer.cc
@@ -1,13 +1,13 @@
#include <qpdf/Pl_QPDFTokenizer.hh>
#include <qpdf/QTC.hh>
+#include <qpdf/QUtil.hh>
+#include <qpdf/BufferInputSource.hh>
#include <stdexcept>
#include <string.h>
Pl_QPDFTokenizer::Members::Members() :
filter(0),
- last_char_was_cr(false),
- unread_char(false),
- char_to_unread('\0')
+ buf("tokenizer buffer")
{
}
@@ -33,61 +33,42 @@ Pl_QPDFTokenizer::~Pl_QPDFTokenizer()
}
void
-Pl_QPDFTokenizer::processChar(char ch)
+Pl_QPDFTokenizer::write(unsigned char* data, size_t len)
{
- this->m->tokenizer.presentCharacter(ch);
- QPDFTokenizer::Token token;
- if (this->m->tokenizer.getToken(
- token, this->m->unread_char, this->m->char_to_unread))
- {
- this->m->filter->handleToken(token);
- if ((token.getType() == QPDFTokenizer::tt_word) &&
- (token.getValue() == "ID"))
- {
- QTC::TC("qpdf", "Pl_QPDFTokenizer found ID");
- this->m->tokenizer.expectInlineImage();
- }
- }
-}
-
-
-void
-Pl_QPDFTokenizer::checkUnread()
-{
- if (this->m->unread_char)
- {
- processChar(this->m->char_to_unread);
- if (this->m->unread_char)
- {
- throw std::logic_error(
- "INTERNAL ERROR: unread_char still true after processing "
- "unread character");
- }
- }
-}
-
-void
-Pl_QPDFTokenizer::write(unsigned char* buf, size_t len)
-{
- checkUnread();
- for (size_t i = 0; i < len; ++i)
- {
- processChar(buf[i]);
- checkUnread();
- }
+ this->m->buf.write(data, len);
}
void
Pl_QPDFTokenizer::finish()
{
- this->m->tokenizer.presentEOF();
- QPDFTokenizer::Token token;
- if (this->m->tokenizer.getToken(
- token, this->m->unread_char, this->m->char_to_unread))
+ this->m->buf.finish();
+ PointerHolder<InputSource> input =
+ new BufferInputSource("tokenizer data",
+ this->m->buf.getBuffer(), true);
+
+ while (true)
{
+ QPDFTokenizer::Token token = this->m->tokenizer.readToken(
+ input, "offset " + QUtil::int_to_string(input->tell()),
+ true);
this->m->filter->handleToken(token);
+ if (token.getType() == QPDFTokenizer::tt_eof)
+ {
+ break;
+ }
+ else if ((token.getType() == QPDFTokenizer::tt_word) &&
+ (token.getValue() == "ID"))
+ {
+ // Read the space after the ID.
+ char ch = ' ';
+ input->read(&ch, 1);
+ this->m->filter->handleToken(
+ QPDFTokenizer::Token(
+ QPDFTokenizer::tt_space, std::string(1, ch)));
+ QTC::TC("qpdf", "Pl_QPDFTokenizer found ID");
+ this->m->tokenizer.expectInlineImage(input);
+ }
}
-
this->m->filter->handleEOF();
QPDFObjectHandle::TokenFilter::PipelineAccessor::setPipeline(
m->filter, 0);
diff --git a/libqpdf/Pl_RC4.cc b/libqpdf/Pl_RC4.cc
index 87c17f83..407490ca 100644
--- a/libqpdf/Pl_RC4.cc
+++ b/libqpdf/Pl_RC4.cc
@@ -13,11 +13,8 @@ Pl_RC4::Pl_RC4(char const* identifier, Pipeline* next,
Pl_RC4::~Pl_RC4()
{
- if (this->outbuf)
- {
- delete [] this->outbuf;
- this->outbuf = 0;
- }
+ delete [] this->outbuf;
+ this->outbuf = 0;
}
void
@@ -47,10 +44,7 @@ Pl_RC4::write(unsigned char* data, size_t len)
void
Pl_RC4::finish()
{
- if (this->outbuf)
- {
- delete [] this->outbuf;
- this->outbuf = 0;
- }
+ delete [] this->outbuf;
+ this->outbuf = 0;
this->getNext()->finish();
}
diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc
index 308b3dd2..e9d0b77a 100644
--- a/libqpdf/QPDF.cc
+++ b/libqpdf/QPDF.cc
@@ -18,8 +18,9 @@
#include <qpdf/QPDFExc.hh>
#include <qpdf/QPDF_Null.hh>
#include <qpdf/QPDF_Dictionary.hh>
+#include <qpdf/QPDF_Stream.hh>
-std::string QPDF::qpdf_version = "8.1.0";
+std::string QPDF::qpdf_version = "8.4.0";
static char const* EMPTY_PDF =
"%PDF-1.3\n"
@@ -39,13 +40,50 @@ static char const* EMPTY_PDF =
"110\n"
"%%EOF\n";
+QPDF::ForeignStreamData::ForeignStreamData(
+ PointerHolder<EncryptionParameters> encp,
+ PointerHolder<InputSource> file,
+ int foreign_objid,
+ int foreign_generation,
+ qpdf_offset_t offset,
+ size_t length,
+ bool is_attachment_stream,
+ QPDFObjectHandle local_dict)
+ :
+ encp(encp),
+ file(file),
+ foreign_objid(foreign_objid),
+ foreign_generation(foreign_generation),
+ offset(offset),
+ length(length),
+ is_attachment_stream(is_attachment_stream),
+ local_dict(local_dict)
+{
+}
+
+QPDF::CopiedStreamDataProvider::CopiedStreamDataProvider(
+ QPDF& destination_qpdf) :
+ destination_qpdf(destination_qpdf)
+{
+}
+
void
QPDF::CopiedStreamDataProvider::provideStreamData(
int objid, int generation, Pipeline* pipeline)
{
- QPDFObjectHandle foreign_stream =
- this->foreign_streams[QPDFObjGen(objid, generation)];
- foreign_stream.pipeStreamData(pipeline, 0, qpdf_dl_none);
+ PointerHolder<ForeignStreamData> foreign_data =
+ this->foreign_stream_data[QPDFObjGen(objid, generation)];
+ if (foreign_data.getPointer())
+ {
+ destination_qpdf.pipeForeignStreamData(
+ foreign_data, pipeline, 0, qpdf_dl_none);
+ }
+ else
+ {
+ QPDFObjectHandle foreign_stream =
+ this->foreign_streams[QPDFObjGen(objid, generation)];
+ foreign_stream.pipeStreamData(pipeline, 0, qpdf_dl_none);
+ }
}
void
@@ -55,6 +93,14 @@ QPDF::CopiedStreamDataProvider::registerForeignStream(
this->foreign_streams[local_og] = foreign_stream;
}
+void
+QPDF::CopiedStreamDataProvider::registerForeignStream(
+ QPDFObjGen const& local_og,
+ PointerHolder<ForeignStreamData> foreign_stream)
+{
+ this->foreign_stream_data[local_og] = foreign_stream;
+}
+
QPDF::StringDecrypter::StringDecrypter(QPDF* qpdf, int objid, int gen) :
qpdf(qpdf),
objid(objid),
@@ -74,15 +120,9 @@ QPDF::QPDFVersion()
return QPDF::qpdf_version;
}
-QPDF::Members::Members() :
- provided_password_is_hex_key(false),
+QPDF::EncryptionParameters::EncryptionParameters() :
encrypted(false),
encryption_initialized(false),
- ignore_xref_streams(false),
- suppress_warnings(false),
- out_stream(&std::cout),
- err_stream(&std::cerr),
- attempt_recovery(true),
encryption_V(0),
encryption_R(0),
encrypt_metadata(true),
@@ -90,10 +130,24 @@ QPDF::Members::Members() :
cf_string(e_none),
cf_file(e_none),
cached_key_objid(0),
- cached_key_generation(0),
+ cached_key_generation(0)
+{
+}
+
+QPDF::Members::Members() :
+ unique_id(0),
+ provided_password_is_hex_key(false),
+ ignore_xref_streams(false),
+ suppress_warnings(false),
+ out_stream(&std::cout),
+ err_stream(&std::cerr),
+ attempt_recovery(true),
+ encp(new EncryptionParameters),
pushed_inherited_attributes_to_pages(false),
copied_stream_data_provider(0),
reconstructed_xref(false),
+ fixed_dangling_refs(false),
+ immediate_copy_from(false),
first_xref_item_offset(0),
uncompressed_after_compressed(false)
{
@@ -107,6 +161,12 @@ QPDF::QPDF() :
m(new Members())
{
m->tokenizer.allowEOF();
+ // Generate a unique ID. It just has to be unique among all QPDF
+ // objects allocated throughout the lifetime of this running
+ // application.
+ m->unique_id = static_cast<unsigned long>(QUtil::get_current_time());
+ m->unique_id <<= 32;
+ m->unique_id |= static_cast<unsigned long>(QUtil::random());
}
QPDF::~QPDF()
@@ -210,6 +270,12 @@ QPDF::setAttemptRecovery(bool val)
this->m->attempt_recovery = val;
}
+void
+QPDF::setImmediateCopyFrom(bool val)
+{
+ this->m->immediate_copy_from = val;
+}
+
std::vector<QPDFExc>
QPDF::getWarnings()
{
@@ -292,7 +358,7 @@ QPDF::parse(char const* password)
{
if (password)
{
- this->m->provided_password = password;
+ this->m->encp->provided_password = password;
}
// Find the header anywhere in the first 1024 bytes of the file.
@@ -1218,29 +1284,129 @@ QPDF::showXRefTable()
}
}
+void
+QPDF::fixDanglingReferences(bool force)
+{
+ if (this->m->fixed_dangling_refs && (! force))
+ {
+ return;
+ }
+ this->m->fixed_dangling_refs = true;
+
+ // Create a set of all known indirect objects including those
+ // we've previously resolved and those that we have created.
+ std::set<QPDFObjGen> to_process;
+ for (std::map<QPDFObjGen, ObjCache>::iterator iter =
+ this->m->obj_cache.begin();
+ iter != this->m->obj_cache.end(); ++iter)
+ {
+ to_process.insert((*iter).first);
+ }
+ for (std::map<QPDFObjGen, QPDFXRefEntry>::iterator iter =
+ this->m->xref_table.begin();
+ iter != this->m->xref_table.end(); ++iter)
+ {
+ to_process.insert((*iter).first);
+ }
+
+ // For each non-scalar item to process, put it in the queue.
+ std::list<QPDFObjectHandle> queue;
+ queue.push_back(this->m->trailer);
+ for (std::set<QPDFObjGen>::iterator iter = to_process.begin();
+ iter != to_process.end(); ++iter)
+ {
+ QPDFObjectHandle obj = QPDFObjectHandle::Factory::newIndirect(
+ this, (*iter).getObj(), (*iter).getGen());
+ if (obj.isDictionary() || obj.isArray())
+ {
+ queue.push_back(obj);
+ }
+ else if (obj.isStream())
+ {
+ queue.push_back(obj.getDict());
+ }
+ }
+
+ // Process the queue by recursively resolving all object
+ // references. We don't need to do loop detection because we don't
+ // traverse known indirect objects when processing the queue.
+ while (! queue.empty())
+ {
+ QPDFObjectHandle obj = queue.front();
+ queue.pop_front();
+ std::list<QPDFObjectHandle> to_check;
+ if (obj.isDictionary())
+ {
+ std::map<std::string, QPDFObjectHandle> members =
+ obj.getDictAsMap();
+ for (std::map<std::string, QPDFObjectHandle>::iterator iter =
+ members.begin();
+ iter != members.end(); ++iter)
+ {
+ to_check.push_back((*iter).second);
+ }
+ }
+ else if (obj.isArray())
+ {
+ std::vector<QPDFObjectHandle> elements = obj.getArrayAsVector();
+ for (std::vector<QPDFObjectHandle>::iterator iter =
+ elements.begin();
+ iter != elements.end(); ++iter)
+ {
+ to_check.push_back(*iter);
+ }
+ }
+ for (std::list<QPDFObjectHandle>::iterator iter = to_check.begin();
+ iter != to_check.end(); ++iter)
+ {
+ QPDFObjectHandle sub = *iter;
+ if (sub.isIndirect())
+ {
+ if (sub.getOwningQPDF() == this)
+ {
+ QPDFObjGen og(sub.getObjGen());
+ if (this->m->obj_cache.count(og) == 0)
+ {
+ QTC::TC("qpdf", "QPDF detected dangling ref");
+ queue.push_back(sub);
+ }
+ }
+ }
+ else
+ {
+ queue.push_back(sub);
+ }
+ }
+
+ }
+}
+
size_t
QPDF::getObjectCount()
{
// This method returns the next available indirect object number.
- // makeIndirectObject uses it for this purpose.
- QPDFObjGen o1(0, 0);
+ // makeIndirectObject uses it for this purpose. After
+ // fixDanglingReferences is called, all objects in the xref table
+ // will also be in obj_cache.
+ fixDanglingReferences();
+ QPDFObjGen og(0, 0);
if (! this->m->obj_cache.empty())
{
- o1 = (*(this->m->obj_cache.rbegin())).first;
+ og = (*(this->m->obj_cache.rbegin())).first;
}
- QPDFObjGen o2 = (*(this->m->xref_table.rbegin())).first;
- QTC::TC("qpdf", "QPDF indirect last obj from xref",
- (o2.getObj() > o1.getObj()) ? 1 : 0);
- return std::max(o1.getObj(), o2.getObj());
+ return og.getObj();
}
std::vector<QPDFObjectHandle>
QPDF::getAllObjects()
{
+ // After fixDanglingReferences is called, all objects are in the
+ // object cache.
+ fixDanglingReferences(true);
std::vector<QPDFObjectHandle> result;
- for (std::map<QPDFObjGen, QPDFXRefEntry>::iterator iter =
- this->m->xref_table.begin();
- iter != this->m->xref_table.end(); ++iter)
+ for (std::map<QPDFObjGen, ObjCache>::iterator iter =
+ this->m->obj_cache.begin();
+ iter != this->m->obj_cache.end(); ++iter)
{
QPDFObjGen const& og = (*iter).first;
@@ -1282,7 +1448,7 @@ QPDF::readObject(PointerHolder<InputSource> input,
bool empty = false;
PointerHolder<StringDecrypter> decrypter_ph;
StringDecrypter* decrypter = 0;
- if (this->m->encrypted && (! in_object_stream))
+ if (this->m->encp->encrypted && (! in_object_stream))
{
decrypter_ph = new StringDecrypter(this, objid, generation);
decrypter = decrypter_ph.getPointer();
@@ -1748,7 +1914,6 @@ QPDF::resolve(int objid, int generation)
}
ResolveRecorder rr(this, og);
- // PDF spec says unknown objects resolve to the null object.
if ((! this->m->obj_cache.count(og)) && this->m->xref_table.count(og))
{
QPDFXRefEntry const& entry = this->m->xref_table[og];
@@ -1796,6 +1961,7 @@ QPDF::resolve(int objid, int generation)
}
if (this->m->obj_cache.count(og) == 0)
{
+ // PDF spec says unknown objects resolve to the null object.
QTC::TC("qpdf", "QPDF resolve failure to null");
QPDFObjectHandle oh = QPDFObjectHandle::newNull();
this->m->obj_cache[og] =
@@ -1975,14 +2141,18 @@ QPDF::replaceReserved(QPDFObjectHandle reserved,
}
QPDFObjectHandle
-QPDF::copyForeignObject(QPDFObjectHandle foreign)
+QPDF::copyForeignObject(QPDFObjectHandle foreign, bool)
{
- return copyForeignObject(foreign, false);
+ // This method will be removed next time the ABI is changed.
+ return copyForeignObject(foreign);
}
QPDFObjectHandle
-QPDF::copyForeignObject(QPDFObjectHandle foreign, bool allow_page)
+QPDF::copyForeignObject(QPDFObjectHandle foreign)
{
+ // Do not preclude use of copyForeignObject on page objects. It is
+ // a documented use case to copy pages this way if the intention
+ // is to not update the pages tree.
if (! foreign.isIndirect())
{
QTC::TC("qpdf", "QPDF copyForeign direct");
@@ -1997,7 +2167,7 @@ QPDF::copyForeignObject(QPDFObjectHandle foreign, bool allow_page)
"QPDF::copyForeign called with object from this QPDF");
}
- ObjCopier& obj_copier = this->m->object_copiers[other];
+ ObjCopier& obj_copier = this->m->object_copiers[other->m->unique_id];
if (! obj_copier.visiting.empty())
{
throw std::logic_error("obj_copier.visiting is not empty"
@@ -2194,15 +2364,80 @@ QPDF::replaceForeignIndirectObjects(
if (this->m->copied_stream_data_provider == 0)
{
this->m->copied_stream_data_provider =
- new CopiedStreamDataProvider();
+ new CopiedStreamDataProvider(*this);
this->m->copied_streams = this->m->copied_stream_data_provider;
}
QPDFObjGen local_og(result.getObjGen());
- this->m->copied_stream_data_provider->registerForeignStream(
- local_og, foreign);
- result.replaceStreamData(this->m->copied_streams,
- dict.getKey("/Filter"),
- dict.getKey("/DecodeParms"));
+ // Copy information from the foreign stream so we can pipe its
+ // data later without keeping the original QPDF object around.
+ QPDF* foreign_stream_qpdf = foreign.getOwningQPDF();
+ if (! foreign_stream_qpdf)
+ {
+ throw std::logic_error("unable to retrieve owning qpdf"
+ " from foreign stream");
+ }
+ QPDF_Stream* stream =
+ dynamic_cast<QPDF_Stream*>(
+ QPDFObjectHandle::ObjAccessor::getObject(
+ foreign).getPointer());
+ if (! stream)
+ {
+ throw std::logic_error("unable to retrieve underlying"
+ " stream object from foreign stream");
+ }
+ PointerHolder<Buffer> stream_buffer =
+ stream->getStreamDataBuffer();
+ if ((foreign_stream_qpdf->m->immediate_copy_from) &&
+ (stream_buffer.getPointer() == 0))
+ {
+ // Pull the stream data into a buffer before attempting
+ // the copy operation. Do it on the source stream so that
+ // if the source stream is copied multiple times, we don't
+ // have to keep duplicating the memory.
+ QTC::TC("qpdf", "QPDF immediate copy stream data");
+ foreign.replaceStreamData(foreign.getRawStreamData(),
+ dict.getKey("/Filter"),
+ dict.getKey("/DecodeParms"));
+ stream_buffer = stream->getStreamDataBuffer();
+ }
+ PointerHolder<QPDFObjectHandle::StreamDataProvider> stream_provider =
+ stream->getStreamDataProvider();
+ if (stream_buffer.getPointer())
+ {
+ QTC::TC("qpdf", "QPDF copy foreign stream with buffer");
+ result.replaceStreamData(stream_buffer,
+ dict.getKey("/Filter"),
+ dict.getKey("/DecodeParms"));
+ }
+ else if (stream_provider.getPointer())
+ {
+ // In this case, the remote stream's QPDF must stay in scope.
+ QTC::TC("qpdf", "QPDF copy foreign stream with provider");
+ this->m->copied_stream_data_provider->registerForeignStream(
+ local_og, foreign);
+ result.replaceStreamData(this->m->copied_streams,
+ dict.getKey("/Filter"),
+ dict.getKey("/DecodeParms"));
+ }
+ else
+ {
+ PointerHolder<ForeignStreamData> foreign_stream_data =
+ new ForeignStreamData(
+ foreign_stream_qpdf->m->encp,
+ foreign_stream_qpdf->m->file,
+ foreign.getObjectID(),
+ foreign.getGeneration(),
+ stream->getOffset(),
+ stream->getLength(),
+ (foreign_stream_qpdf->m->attachment_streams.count(
+ foreign.getObjGen()) > 0),
+ dict);
+ this->m->copied_stream_data_provider->registerForeignStream(
+ local_og, foreign_stream_data);
+ result.replaceStreamData(this->m->copied_streams,
+ dict.getKey("/Filter"),
+ dict.getKey("/DecodeParms"));
+ }
}
else
{
@@ -2239,6 +2474,12 @@ QPDF::swapObjects(int objid1, int generation1, int objid2, int generation2)
this->m->obj_cache[og2] = t;
}
+unsigned long long
+QPDF::getUniqueId() const
+{
+ return this->m->unique_id;
+}
+
std::string
QPDF::getFilename() const
{
@@ -2399,34 +2640,40 @@ QPDF::getCompressibleObjGens()
}
bool
-QPDF::pipeStreamData(int objid, int generation,
+QPDF::pipeStreamData(PointerHolder<EncryptionParameters> encp,
+ PointerHolder<InputSource> file,
+ QPDF& qpdf_for_warning,
+ int objid, int generation,
qpdf_offset_t offset, size_t length,
QPDFObjectHandle stream_dict,
+ bool is_attachment_stream,
Pipeline* pipeline,
bool suppress_warnings,
bool will_retry)
{
- bool success = false;
std::vector<PointerHolder<Pipeline> > to_delete;
- if (this->m->encrypted)
+ if (encp->encrypted)
{
- decryptStream(pipeline, objid, generation, stream_dict, to_delete);
+ decryptStream(encp, file, qpdf_for_warning,
+ pipeline, objid, generation,
+ stream_dict, is_attachment_stream, to_delete);
}
+ bool success = false;
try
{
- this->m->file->seek(offset, SEEK_SET);
+ file->seek(offset, SEEK_SET);
char buf[10240];
while (length > 0)
{
size_t to_read = (sizeof(buf) < length ? sizeof(buf) : length);
- size_t len = this->m->file->read(buf, to_read);
+ size_t len = file->read(buf, to_read);
if (len == 0)
{
throw QPDFExc(qpdf_e_damaged_pdf,
- this->m->file->getName(),
- this->m->last_object_description,
- this->m->file->getLastOffset(),
+ file->getName(),
+ "",
+ file->getLastOffset(),
"unexpected EOF reading stream data");
}
length -= len;
@@ -2439,7 +2686,7 @@ QPDF::pipeStreamData(int objid, int generation,
{
if (! suppress_warnings)
{
- warn(e);
+ qpdf_for_warning.warn(e);
}
}
catch (std::exception& e)
@@ -2447,17 +2694,19 @@ QPDF::pipeStreamData(int objid, int generation,
if (! suppress_warnings)
{
QTC::TC("qpdf", "QPDF decoding error warning");
- warn(QPDFExc(qpdf_e_damaged_pdf, this->m->file->getName(),
- "", this->m->file->getLastOffset(),
- "error decoding stream data for object " +
- QUtil::int_to_string(objid) + " " +
- QUtil::int_to_string(generation) + ": " + e.what()));
+ qpdf_for_warning.warn(
+ QPDFExc(qpdf_e_damaged_pdf, file->getName(),
+ "", file->getLastOffset(),
+ "error decoding stream data for object " +
+ QUtil::int_to_string(objid) + " " +
+ QUtil::int_to_string(generation) + ": " + e.what()));
if (will_retry)
{
- warn(QPDFExc(qpdf_e_damaged_pdf, this->m->file->getName(),
- "", this->m->file->getLastOffset(),
- "stream will be re-processed without"
- " filtering to avoid data loss"));
+ qpdf_for_warning.warn(
+ QPDFExc(qpdf_e_damaged_pdf, file->getName(),
+ "", file->getLastOffset(),
+ "stream will be re-processed without"
+ " filtering to avoid data loss"));
}
}
}
@@ -2475,6 +2724,42 @@ QPDF::pipeStreamData(int objid, int generation,
return success;
}
+bool
+QPDF::pipeStreamData(int objid, int generation,
+ qpdf_offset_t offset, size_t length,
+ QPDFObjectHandle stream_dict,
+ Pipeline* pipeline,
+ bool suppress_warnings,
+ bool will_retry)
+{
+ bool is_attachment_stream = this->m->attachment_streams.count(
+ QPDFObjGen(objid, generation));
+ return pipeStreamData(
+ this->m->encp, this->m->file, *this,
+ objid, generation, offset, length,
+ stream_dict, is_attachment_stream,
+ pipeline, suppress_warnings, will_retry);
+}
+
+bool
+QPDF::pipeForeignStreamData(
+ PointerHolder<ForeignStreamData> foreign,
+ Pipeline* pipeline,
+ unsigned long encode_flags,
+ qpdf_stream_decode_level_e decode_level)
+{
+ if (foreign->encp->encrypted)
+ {
+ QTC::TC("qpdf", "QPDF pipe foreign encrypted stream");
+ }
+ return pipeStreamData(
+ foreign->encp, foreign->file, *this,
+ foreign->foreign_objid, foreign->foreign_generation,
+ foreign->offset, foreign->length,
+ foreign->local_dict, foreign->is_attachment_stream,
+ pipeline, false, false);
+}
+
void
QPDF::findAttachmentStreams()
{
@@ -2508,3 +2793,13 @@ QPDF::findAttachmentStreams()
}
}
}
+
+void
+QPDF::stopOnError(std::string const& message)
+{
+ // Throw a generic exception when we lack context for something
+ // more specific. New code should not use this. This method exists
+ // to improve somewhat from calling assert in very old code.
+ throw QPDFExc(qpdf_e_damaged_pdf, this->m->file->getName(),
+ "", this->m->file->getLastOffset(), message);
+}
diff --git a/libqpdf/QPDFAcroFormDocumentHelper.cc b/libqpdf/QPDFAcroFormDocumentHelper.cc
index 46648ed9..a55f7160 100644
--- a/libqpdf/QPDFAcroFormDocumentHelper.cc
+++ b/libqpdf/QPDFAcroFormDocumentHelper.cc
@@ -285,3 +285,48 @@ QPDFAcroFormDocumentHelper::setNeedAppearances(bool val)
acroform.removeKey("/NeedAppearances");
}
}
+
+void
+QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded()
+{
+ if (! getNeedAppearances())
+ {
+ return;
+ }
+
+ QPDFPageDocumentHelper pdh(this->qpdf);
+ std::vector<QPDFPageObjectHelper> pages = pdh.getAllPages();
+ for (std::vector<QPDFPageObjectHelper>::iterator page_iter =
+ pages.begin();
+ page_iter != pages.end(); ++page_iter)
+ {
+ std::vector<QPDFAnnotationObjectHelper> annotations =
+ getWidgetAnnotationsForPage(*page_iter);
+ for (std::vector<QPDFAnnotationObjectHelper>::iterator annot_iter =
+ annotations.begin();
+ annot_iter != annotations.end(); ++annot_iter)
+ {
+ QPDFAnnotationObjectHelper& aoh = *annot_iter;
+ QPDFFormFieldObjectHelper ffh =
+ getFieldForAnnotation(aoh);
+ if (ffh.getFieldType() == "/Btn")
+ {
+ // Rather than generating appearances for button
+ // fields, rely on what's already there. Just make
+ // sure /AS is consistent with /V, which we can do by
+ // resetting the value of the field back to itself.
+ // This code is referenced in a comment in
+ // QPDFFormFieldObjectHelper::generateAppearance.
+ if (ffh.isRadioButton() || ffh.isCheckbox())
+ {
+ ffh.setV(ffh.getValue());
+ }
+ }
+ else
+ {
+ ffh.generateAppearance(aoh);
+ }
+ }
+ }
+ setNeedAppearances(false);
+}
diff --git a/libqpdf/QPDFAnnotationObjectHelper.cc b/libqpdf/QPDFAnnotationObjectHelper.cc
index ee1d8180..698f80e1 100644
--- a/libqpdf/QPDFAnnotationObjectHelper.cc
+++ b/libqpdf/QPDFAnnotationObjectHelper.cc
@@ -1,5 +1,9 @@
#include <qpdf/QPDFAnnotationObjectHelper.hh>
#include <qpdf/QTC.hh>
+#include <qpdf/QPDFMatrix.hh>
+#include <qpdf/QUtil.hh>
+#include <qpdf/QPDF.hh>
+#include <qpdf/QPDFNameTreeObjectHelper.hh>
QPDFAnnotationObjectHelper::Members::~Members()
{
@@ -44,6 +48,13 @@ QPDFAnnotationObjectHelper::getAppearanceState()
return "";
}
+int
+QPDFAnnotationObjectHelper::getFlags()
+{
+ QPDFObjectHandle flags_obj = this->oh.getKey("/F");
+ return flags_obj.isInteger() ? flags_obj.getIntValue() : 0;
+}
+
QPDFObjectHandle
QPDFAnnotationObjectHelper::getAppearanceStream(
std::string const& which,
@@ -73,3 +84,200 @@ QPDFAnnotationObjectHelper::getAppearanceStream(
QTC::TC("qpdf", "QPDFAnnotationObjectHelper AN null");
return QPDFObjectHandle::newNull();
}
+
+std::string
+QPDFAnnotationObjectHelper::getPageContentForAppearance(
+ std::string const& name, int rotate,
+ int required_flags, int forbidden_flags)
+{
+ if (! getAppearanceStream("/N").isStream())
+ {
+ return "";
+ }
+
+ // The appearance matrix computed by this method is the
+ // transformation matrix that needs to be in effect when drawing
+ // this annotation's appearance stream on the page. The algorithm
+ // for computing the appearance matrix described in section 12.5.5
+ // of the ISO-32000 PDF spec is similar but not identical to what
+ // we are doing here.
+
+ // When rendering an appearance stream associated with an
+ // annotation, there are four relevant components:
+ //
+ // * The appearance stream's bounding box (/BBox)
+ // * The appearance stream's matrix (/Matrix)
+ // * The annotation's rectangle (/Rect)
+ // * In the case of form fields with the NoRotate flag, the
+ // page's rotation
+
+ // When rendering a form xobject in isolation, just drawn with a
+ // /Do operator, the is no form field, so page rotation is not
+ // relevant, and there is no annotation, so /Rect is not relevant,
+ // so only /BBox and /Matrix are relevant. The effect of these are
+ // as follows:
+
+ // * /BBox is treated as a clipping region
+ // * /Matrix is applied as a transformation prior to rendering the
+ // appearance stream.
+
+ // There is no relationship between /BBox and /Matrix in this
+ // case.
+
+ // When rendering a form xobject in the context of an annotation,
+ // things are a little different. In particular, a matrix is
+ // established such that /BBox, when transformed by /Matrix, would
+ // fit completely inside of /Rect. /BBox is no longer a clipping
+ // region. To illustrate the difference, consider a /Matrix of
+ // [2 0 0 2 0 0], which is scaling by a factor of two along both
+ // axes. If the appearance stream drew a rectangle equal to /BBox,
+ // in the case of the form xobject in isolation, this matrix would
+ // cause only the lower-left quadrant of the rectangle to be
+ // visible since the scaling would cause the rest of it to fall
+ // outside of the clipping region. In the case of the form xobject
+ // displayed in the context of an annotation, such a matrix would
+ // have no effect at all because it would be applied to the
+ // bounding box first, and then when the resulting enclosing
+ // quadrilateral was transformed to fit into /Rect, the effect of
+ // the scaling would be undone.
+
+ // Our job is to create a transformation matrix that compensates
+ // for these differences so that the appearance stream of an
+ // annotation can be drawn as a regular form xobject.
+
+ // To do this, we perform the following steps, which overlap
+ // significantly with the algorithm in 12.5.5:
+
+ // 1. Transform the four corners of /BBox by applying /Matrix to
+ // them, creating an arbitrarily transformed quadrilateral.
+
+ // 2. Find the minimum upright rectangle that encompasses the
+ // resulting quadrilateral. This is the "transformed appearance
+ // box", T.
+
+ // 3. Compute matrix A that maps the lower left and upper right
+ // corners of T to the annotation's /Rect. This can be done by
+ // scaling so that the sizes match and translating so that the
+ // scaled T exactly overlaps /Rect.
+
+ // If the annotation's /F flag has bit 4 set, this means that
+ // annotation is to be rotated about its upper left corner to
+ // counteract any rotation of the page so it remains upright. To
+ // achieve this effect, we do the following extra steps:
+
+ // 1. Perform the rotation on /BBox box prior to transforming it
+ // with /Matrix (by replacing matrix with concatenation of
+ // matrix onto the rotation)
+
+ // 2. Rotate the destination rectangle by the specified amount
+
+ // 3. Apply the rotation to A as computed above to get the final
+ // appearance matrix.
+
+ QPDFObjectHandle rect_obj = this->oh.getKey("/Rect");
+ QPDFObjectHandle as = getAppearanceStream("/N").getDict();
+ QPDFObjectHandle bbox_obj = as.getKey("/BBox");
+ QPDFObjectHandle matrix_obj = as.getKey("/Matrix");
+
+ int flags = getFlags();
+ if (flags & forbidden_flags)
+ {
+ QTC::TC("qpdf", "QPDFAnnotationObjectHelper forbidden flags");
+ return "";
+ }
+ if ((flags & required_flags) != required_flags)
+ {
+ QTC::TC("qpdf", "QPDFAnnotationObjectHelper missing required flags");
+ return "";
+ }
+
+ if (! (bbox_obj.isRectangle() && rect_obj.isRectangle()))
+ {
+ return "";
+ }
+ QPDFMatrix matrix;
+ if (matrix_obj.isMatrix())
+ {
+ QTC::TC("qpdf", "QPDFAnnotationObjectHelper explicit matrix");
+ matrix = QPDFMatrix(matrix_obj.getArrayAsMatrix());
+ }
+ else
+ {
+ QTC::TC("qpdf", "QPDFAnnotationObjectHelper default matrix");
+ }
+ QPDFObjectHandle::Rectangle rect = rect_obj.getArrayAsRectangle();
+ bool do_rotate = (rotate && (flags & an_no_rotate));
+ if (do_rotate)
+ {
+ // If the the annotation flags include the NoRotate bit and
+ // the page is rotated, we have to rotate the annotation about
+ // its upper left corner by the same amount in the opposite
+ // direction so that it will remain upright in absolute
+ // coordinates. Since the semantics of /Rotate for a page are
+ // to rotate the page, while the effect of rotating using a
+ // transformation matrix is to rotate the coordinate system,
+ // the opposite directionality is explicit in the code.
+ QPDFMatrix mr;
+ mr.rotatex90(rotate);
+ mr.concat(matrix);
+ matrix = mr;
+ double rect_w = rect.urx - rect.llx;
+ double rect_h = rect.ury - rect.lly;
+ switch (rotate)
+ {
+ case 90:
+ QTC::TC("qpdf", "QPDFAnnotationObjectHelper rotate 90");
+ rect = QPDFObjectHandle::Rectangle(
+ rect.llx,
+ rect.ury,
+ rect.llx + rect_h,
+ rect.ury + rect_w);
+ break;
+ case 180:
+ QTC::TC("qpdf", "QPDFAnnotationObjectHelper rotate 180");
+ rect = QPDFObjectHandle::Rectangle(
+ rect.llx - rect_w,
+ rect.ury,
+ rect.llx,
+ rect.ury + rect_h);
+ break;
+ case 270:
+ QTC::TC("qpdf", "QPDFAnnotationObjectHelper rotate 270");
+ rect = QPDFObjectHandle::Rectangle(
+ rect.llx - rect_h,
+ rect.ury - rect_w,
+ rect.llx,
+ rect.ury);
+ break;
+ default:
+ // ignore
+ break;
+ }
+ }
+
+ // Transform bounding box by matrix to get T
+ QPDFObjectHandle::Rectangle bbox = bbox_obj.getArrayAsRectangle();
+ QPDFObjectHandle::Rectangle T = matrix.transformRectangle(bbox);
+ if ((T.urx == T.llx) || (T.ury == T.lly))
+ {
+ // avoid division by zero
+ return "";
+ }
+ // Compute a matrix to transform the appearance box to the rectangle
+ QPDFMatrix AA;
+ AA.translate(rect.llx, rect.lly);
+ AA.scale((rect.urx - rect.llx) / (T.urx - T.llx),
+ (rect.ury - rect.lly) / (T.ury - T.lly));
+ AA.translate(-T.llx, -T.lly);
+ if (do_rotate)
+ {
+ AA.rotatex90(rotate);
+ }
+
+ as.replaceKey("/Subtype", QPDFObjectHandle::newName("/Form"));
+ return (
+ "q\n" +
+ AA.unparse() + " cm\n" +
+ name + " Do\n" +
+ "Q\n");
+}
diff --git a/libqpdf/QPDFExc.cc b/libqpdf/QPDFExc.cc
index b816e913..77de5bba 100644
--- a/libqpdf/QPDFExc.cc
+++ b/libqpdf/QPDFExc.cc
@@ -15,10 +15,6 @@ QPDFExc::QPDFExc(qpdf_error_code_e error_code,
{
}
-QPDFExc::~QPDFExc() throw ()
-{
-}
-
std::string
QPDFExc::createWhat(std::string const& filename,
std::string const& object,
diff --git a/libqpdf/QPDFFormFieldObjectHelper.cc b/libqpdf/QPDFFormFieldObjectHelper.cc
index 283b632d..38755388 100644
--- a/libqpdf/QPDFFormFieldObjectHelper.cc
+++ b/libqpdf/QPDFFormFieldObjectHelper.cc
@@ -1,6 +1,10 @@
#include <qpdf/QPDFFormFieldObjectHelper.hh>
#include <qpdf/QTC.hh>
#include <qpdf/QPDFAcroFormDocumentHelper.hh>
+#include <qpdf/QPDFAnnotationObjectHelper.hh>
+#include <qpdf/QUtil.hh>
+#include <qpdf/Pl_QPDFTokenizer.hh>
+#include <stdlib.h>
QPDFFormFieldObjectHelper::Members::~Members()
{
@@ -190,6 +194,70 @@ QPDFFormFieldObjectHelper::getQuadding()
return result;
}
+int
+QPDFFormFieldObjectHelper::getFlags()
+{
+ QPDFObjectHandle f = getInheritableFieldValue("/Ff");
+ return f.isInteger() ? f.getIntValue() : 0;
+}
+
+bool
+QPDFFormFieldObjectHelper::isText()
+{
+ return (getFieldType() == "/Tx");
+}
+
+bool
+QPDFFormFieldObjectHelper::isCheckbox()
+{
+ return ((getFieldType() == "/Btn") &&
+ ((getFlags() & (ff_btn_radio | ff_btn_pushbutton)) == 0));
+}
+
+bool
+QPDFFormFieldObjectHelper::isRadioButton()
+{
+ return ((getFieldType() == "/Btn") &&
+ ((getFlags() & ff_btn_radio) == ff_btn_radio));
+}
+
+bool
+QPDFFormFieldObjectHelper::isPushbutton()
+{
+ return ((getFieldType() == "/Btn") &&
+ ((getFlags() & ff_btn_pushbutton) == ff_btn_pushbutton));
+}
+
+bool
+QPDFFormFieldObjectHelper::isChoice()
+{
+ return (getFieldType() == "/Ch");
+}
+
+std::vector<std::string>
+QPDFFormFieldObjectHelper::getChoices()
+{
+ std::vector<std::string> result;
+ if (! isChoice())
+ {
+ return result;
+ }
+ QPDFObjectHandle opt = getInheritableFieldValue("/Opt");
+ if (opt.isArray())
+ {
+ size_t n = opt.getArrayNItems();
+ for (size_t i = 0; i < n; ++i)
+ {
+ QPDFObjectHandle item = opt.getArrayItem(i);
+ if (item.isString())
+ {
+ result.push_back(item.getUTF8Value());
+ }
+ }
+ }
+ return result;
+}
+
void
QPDFFormFieldObjectHelper::setFieldAttribute(
std::string const& key, QPDFObjectHandle value)
@@ -208,7 +276,56 @@ void
QPDFFormFieldObjectHelper::setV(
QPDFObjectHandle value, bool need_appearances)
{
- setFieldAttribute("/V", value);
+ if (getFieldType() == "/Btn")
+ {
+ if (isCheckbox())
+ {
+ bool okay = false;
+ if (value.isName())
+ {
+ std::string name = value.getName();
+ if ((name == "/Yes") || (name == "/Off"))
+ {
+ okay = true;
+ setCheckBoxValue((name == "/Yes"));
+ }
+ }
+ if (! okay)
+ {
+ this->oh.warnIfPossible(
+ "ignoring attempt to set a checkbox field to a"
+ " value of other than /Yes or /Off");
+ }
+ }
+ else if (isRadioButton())
+ {
+ if (value.isName())
+ {
+ setRadioButtonValue(value);
+ }
+ else
+ {
+ this->oh.warnIfPossible(
+ "ignoring attempt to set a radio button field to"
+ " an object that is not a name");
+ }
+ }
+ else if (isPushbutton())
+ {
+ this->oh.warnIfPossible(
+ "ignoring attempt set the value of a pushbutton field");
+ }
+ return;
+ }
+ if (value.isString())
+ {
+ setFieldAttribute(
+ "/V", QPDFObjectHandle::newUnicodeString(value.getUTF8Value()));
+ }
+ else
+ {
+ setFieldAttribute("/V", value);
+ }
if (need_appearances)
{
QPDF* qpdf = this->oh.getOwningQPDF();
@@ -230,3 +347,570 @@ QPDFFormFieldObjectHelper::setV(
setV(QPDFObjectHandle::newUnicodeString(utf8_value),
need_appearances);
}
+
+void
+QPDFFormFieldObjectHelper::setRadioButtonValue(QPDFObjectHandle name)
+{
+ // Set the value of a radio button field. This has the following
+ // specific behavior:
+ // * If this is a radio button field that has a parent that is
+ // also a radio button field and has no explicit /V, call itself
+ // on the parent
+ // * If this is a radio button field with children, set /V to the
+ // given value. Then, for each child, if the child has the
+ // specified value as one of its keys in the /N subdictionary of
+ // its /AP (i.e. its normal appearance stream dictionary), set
+ // /AS to name; otherwise, if /Off is a member, set /AS to /Off.
+ // Note that we never turn on /NeedAppearances when setting a
+ // radio button field.
+ QPDFObjectHandle parent = this->oh.getKey("/Parent");
+ if (parent.isDictionary() && parent.getKey("/Parent").isNull())
+ {
+ QPDFFormFieldObjectHelper ph(parent);
+ if (ph.isRadioButton())
+ {
+ // This is most likely one of the individual buttons. Try
+ // calling on the parent.
+ QTC::TC("qpdf", "QPDFFormFieldObjectHelper set parent radio button");
+ ph.setRadioButtonValue(name);
+ return;
+ }
+ }
+
+ QPDFObjectHandle kids = this->oh.getKey("/Kids");
+ if (! (isRadioButton() && parent.isNull() && kids.isArray()))
+ {
+ this->oh.warnIfPossible("don't know how to set the value"
+ " of this field as a radio button");
+ return;
+ }
+ setFieldAttribute("/V", name);
+ int nkids = kids.getArrayNItems();
+ for (int i = 0; i < nkids; ++i)
+ {
+ QPDFObjectHandle kid = kids.getArrayItem(i);
+ QPDFObjectHandle AP = kid.getKey("/AP");
+ QPDFObjectHandle annot;
+ if (AP.isNull())
+ {
+ // The widget may be below. If there is more than one,
+ // just find the first one.
+ QPDFObjectHandle grandkids = kid.getKey("/Kids");
+ if (grandkids.isArray())
+ {
+ int ngrandkids = grandkids.getArrayNItems();
+ for (int j = 0; j < ngrandkids; ++j)
+ {
+ QPDFObjectHandle grandkid = grandkids.getArrayItem(j);
+ AP = grandkid.getKey("/AP");
+ if (! AP.isNull())
+ {
+ QTC::TC("qpdf", "QPDFFormFieldObjectHelper radio button grandkid widget");
+ annot = grandkid;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ annot = kid;
+ }
+ if (! annot.isInitialized())
+ {
+ QTC::TC("qpdf", "QPDFObjectHandle broken radio button");
+ this->oh.warnIfPossible(
+ "unable to set the value of this radio button");
+ continue;
+ }
+ if (AP.isDictionary() &&
+ AP.getKey("/N").isDictionary() &&
+ AP.getKey("/N").hasKey(name.getName()))
+ {
+ QTC::TC("qpdf", "QPDFFormFieldObjectHelper turn on radio button");
+ annot.replaceKey("/AS", name);
+ }
+ else
+ {
+ QTC::TC("qpdf", "QPDFFormFieldObjectHelper turn off radio button");
+ annot.replaceKey("/AS", QPDFObjectHandle::newName("/Off"));
+ }
+ }
+}
+
+void
+QPDFFormFieldObjectHelper::setCheckBoxValue(bool value)
+{
+ // Set /AS to /Yes or /Off in addition to setting /V.
+ QPDFObjectHandle name =
+ QPDFObjectHandle::newName(value ? "/Yes" : "/Off");
+ setFieldAttribute("/V", name);
+ QPDFObjectHandle AP = this->oh.getKey("/AP");
+ QPDFObjectHandle annot;
+ if (AP.isNull())
+ {
+ // The widget may be below. If there is more than one, just
+ // find the first one.
+ QPDFObjectHandle kids = this->oh.getKey("/Kids");
+ if (kids.isArray())
+ {
+ int nkids = kids.getArrayNItems();
+ for (int i = 0; i < nkids; ++i)
+ {
+ QPDFObjectHandle kid = kids.getArrayItem(i);
+ AP = kid.getKey("/AP");
+ if (! AP.isNull())
+ {
+ QTC::TC("qpdf", "QPDFFormFieldObjectHelper checkbox kid widget");
+ annot = kid;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ annot = this->oh;
+ }
+ if (! annot.isInitialized())
+ {
+ QTC::TC("qpdf", "QPDFObjectHandle broken checkbox");
+ this->oh.warnIfPossible(
+ "unable to set the value of this checkbox");
+ return;
+ }
+ QTC::TC("qpdf", "QPDFFormFieldObjectHelper set checkbox AS");
+ annot.replaceKey("/AS", name);
+}
+
+void
+QPDFFormFieldObjectHelper::generateAppearance(QPDFAnnotationObjectHelper& aoh)
+{
+ std::string ft = getFieldType();
+ // Ignore field types we don't know how to generate appearances
+ // for. Button fields don't really need them -- see code in
+ // QPDFAcroFormDocumentHelper::generateAppearancesIfNeeded.
+ if ((ft == "/Tx") || (ft == "/Ch"))
+ {
+ generateTextAppearance(aoh);
+ }
+}
+
+class ValueSetter: public QPDFObjectHandle::TokenFilter
+{
+ public:
+ ValueSetter(std::string const& DA, std::string const& V,
+ std::vector<std::string> const& opt, double tf,
+ QPDFObjectHandle::Rectangle const& bbox);
+ virtual ~ValueSetter()
+ {
+ }
+ virtual void handleToken(QPDFTokenizer::Token const&);
+ virtual void handleEOF();
+ void writeAppearance();
+
+ private:
+ std::string DA;
+ std::string V;
+ std::vector<std::string> opt;
+ double tf;
+ QPDFObjectHandle::Rectangle bbox;
+ enum { st_top, st_bmc, st_emc, st_end } state;
+ bool replaced;
+};
+
+ValueSetter::ValueSetter(std::string const& DA, std::string const& V,
+ std::vector<std::string> const& opt, double tf,
+ QPDFObjectHandle::Rectangle const& bbox) :
+ DA(DA),
+ V(V),
+ opt(opt),
+ tf(tf),
+ bbox(bbox),
+ state(st_top),
+ replaced(false)
+{
+}
+
+void
+ValueSetter::handleToken(QPDFTokenizer::Token const& token)
+{
+ QPDFTokenizer::token_type_e ttype = token.getType();
+ std::string value = token.getValue();
+ bool do_replace = false;
+ switch (state)
+ {
+ case st_top:
+ writeToken(token);
+ if ((ttype == QPDFTokenizer::tt_word) && (value == "BMC"))
+ {
+ state = st_bmc;
+ }
+ break;
+
+ case st_bmc:
+ if ((ttype == QPDFTokenizer::tt_space) ||
+ (ttype == QPDFTokenizer::tt_comment))
+ {
+ writeToken(token);
+ }
+ else
+ {
+ state = st_emc;
+ }
+ // fall through to emc
+
+ case st_emc:
+ if ((ttype == QPDFTokenizer::tt_word) && (value == "EMC"))
+ {
+ do_replace = true;
+ state = st_end;
+ }
+ break;
+
+ case st_end:
+ writeToken(token);
+ break;
+ }
+ if (do_replace)
+ {
+ writeAppearance();
+ }
+}
+
+void
+ValueSetter::handleEOF()
+{
+ if (! this->replaced)
+ {
+ QTC::TC("qpdf", "QPDFFormFieldObjectHelper replaced BMC at EOF");
+ write("/Tx BMC\n");
+ writeAppearance();
+ }
+}
+
+void
+ValueSetter::writeAppearance()
+{
+ this->replaced = true;
+
+ // This code does not take quadding into consideration because
+ // doing so requires font metric information, which we don't
+ // have in many cases.
+
+ double tfh = 1.2 * tf;
+ int dx = 1;
+
+ // Write one or more lines, centered vertically, possibly with
+ // one row highlighted.
+
+ size_t max_rows = static_cast<size_t>((bbox.ury - bbox.lly) / tfh);
+ bool highlight = false;
+ size_t highlight_idx = 0;
+
+ std::vector<std::string> lines;
+ if (opt.empty() || (max_rows < 2))
+ {
+ lines.push_back(V);
+ }
+ else
+ {
+ // Figure out what rows to write
+ size_t nopt = opt.size();
+ size_t found_idx = 0;
+ bool found = false;
+ for (found_idx = 0; found_idx < nopt; ++found_idx)
+ {
+ if (opt.at(found_idx) == V)
+ {
+ found = true;
+ break;
+ }
+ }
+ if (found)
+ {
+ // Try to make the found item the second one, but
+ // adjust for under/overflow.
+ int wanted_first = found_idx - 1;
+ int wanted_last = found_idx + max_rows - 2;
+ QTC::TC("qpdf", "QPDFFormFieldObjectHelper list found");
+ while (wanted_first < 0)
+ {
+ QTC::TC("qpdf", "QPDFFormFieldObjectHelper list first too low");
+ ++wanted_first;
+ ++wanted_last;
+ }
+ while (wanted_last >= static_cast<int>(nopt))
+ {
+ QTC::TC("qpdf", "QPDFFormFieldObjectHelper list last too high");
+ if (wanted_first > 0)
+ {
+ --wanted_first;
+ }
+ --wanted_last;
+ }
+ highlight = true;
+ highlight_idx = found_idx - wanted_first;
+ for (int i = wanted_first; i <= wanted_last; ++i)
+ {
+ lines.push_back(opt.at(i));
+ }
+ }
+ else
+ {
+ QTC::TC("qpdf", "QPDFFormFieldObjectHelper list not found");
+ // include our value and the first n-1 rows
+ highlight_idx = 0;
+ highlight = true;
+ lines.push_back(V);
+ for (size_t i = 0; ((i < nopt) && (i < (max_rows - 1))); ++i)
+ {
+ lines.push_back(opt.at(i));
+ }
+ }
+ }
+
+ // Write the lines centered vertically, highlighting if needed
+ size_t nlines = lines.size();
+ double dy = bbox.ury - ((bbox.ury - bbox.lly - (nlines * tfh)) / 2.0);
+ if (highlight)
+ {
+ write("q\n0.85 0.85 0.85 rg\n" +
+ QUtil::int_to_string(bbox.llx) + " " +
+ QUtil::double_to_string(bbox.lly + dy -
+ (tfh * (highlight_idx + 1))) + " " +
+ QUtil::int_to_string(bbox.urx - bbox.llx) + " " +
+ QUtil::double_to_string(tfh) +
+ " re f\nQ\n");
+ }
+ dy -= tf;
+ write("q\nBT\n" + DA + "\n");
+ for (size_t i = 0; i < nlines; ++i)
+ {
+ // We could adjust Tm to translate to the beginning the first
+ // line, set TL to tfh, and use T* for each subsequent line,
+ // but doing this would require extracting any Tm from DA,
+ // which doesn't seem really worth the effort.
+ if (i == 0)
+ {
+ write(QUtil::int_to_string(bbox.llx + dx) + " " +
+ QUtil::double_to_string(bbox.lly + dy) + " Td\n");
+ }
+ else
+ {
+ write("0 " + QUtil::double_to_string(-tfh) + " Td\n");
+ }
+ write(QPDFObjectHandle::newString(lines.at(i)).unparse() + " Tj\n");
+ }
+ write("ET\nQ\nEMC");
+}
+
+class TfFinder: public QPDFObjectHandle::TokenFilter
+{
+ public:
+ TfFinder();
+ virtual ~TfFinder()
+ {
+ }
+ virtual void handleToken(QPDFTokenizer::Token const&);
+ double getTf();
+ std::string getFontName();
+ std::string getDA();
+
+ private:
+ double tf;
+ size_t tf_idx;
+ std::string font_name;
+ double last_num;
+ size_t last_num_idx;
+ std::string last_name;
+ std::vector<std::string> DA;
+};
+
+TfFinder::TfFinder() :
+ tf(11.0),
+ tf_idx(0),
+ last_num(0.0),
+ last_num_idx(0)
+{
+}
+
+void
+TfFinder::handleToken(QPDFTokenizer::Token const& token)
+{
+ QPDFTokenizer::token_type_e ttype = token.getType();
+ std::string value = token.getValue();
+ DA.push_back(token.getRawValue());
+ switch (ttype)
+ {
+ case QPDFTokenizer::tt_integer:
+ case QPDFTokenizer::tt_real:
+ last_num = strtod(value.c_str(), 0);
+ last_num_idx = DA.size() - 1;
+ break;
+
+ case QPDFTokenizer::tt_name:
+ last_name = value;
+ break;
+
+ case QPDFTokenizer::tt_word:
+ if ((value == "Tf") &&
+ (last_num > 1.0) &&
+ (last_num < 1000.0))
+ {
+ // These ranges are arbitrary but keep us from doing
+ // insane things or suffering from over/underflow
+ tf = last_num;
+ }
+ tf_idx = last_num_idx;
+ font_name = last_name;
+ break;
+
+ default:
+ break;
+ }
+}
+
+double
+TfFinder::getTf()
+{
+ return this->tf;
+}
+
+std::string
+TfFinder::getDA()
+{
+ std::string result;
+ size_t n = this->DA.size();
+ for (size_t i = 0; i < n; ++i)
+ {
+ std::string cur = this->DA.at(i);
+ if (i == tf_idx)
+ {
+ double delta = strtod(cur.c_str(), 0) - this->tf;
+ if ((delta > 0.001) || (delta < -0.001))
+ {
+ // tf doesn't match the font size passed to Tf, so
+ // substitute.
+ QTC::TC("qpdf", "QPDFFormFieldObjectHelper fallback Tf");
+ cur = QUtil::double_to_string(tf);
+ }
+ }
+ result += cur;
+ }
+ return result;
+}
+
+std::string
+TfFinder::getFontName()
+{
+ return this->font_name;
+}
+
+QPDFObjectHandle
+QPDFFormFieldObjectHelper::getFontFromResource(
+ QPDFObjectHandle resources, std::string const& name)
+{
+ QPDFObjectHandle result;
+ if (resources.isDictionary() &&
+ resources.getKey("/Font").isDictionary() &&
+ resources.getKey("/Font").hasKey(name))
+ {
+ result = resources.getKey("/Font").getKey(name);
+ }
+ return result;
+}
+
+void
+QPDFFormFieldObjectHelper::generateTextAppearance(
+ QPDFAnnotationObjectHelper& aoh)
+{
+ QPDFObjectHandle AS = aoh.getAppearanceStream("/N");
+ if (AS.isNull())
+ {
+ QTC::TC("qpdf", "QPDFFormFieldObjectHelper create AS from scratch");
+ QPDFObjectHandle::Rectangle rect = aoh.getRect();
+ QPDFObjectHandle::Rectangle bbox(
+ 0, 0, rect.urx - rect.llx, rect.ury - rect.lly);
+ QPDFObjectHandle dict = QPDFObjectHandle::parse(
+ "<< /Resources << /ProcSet [ /PDF /Text ] >>"
+ " /Type /XObject /Subtype /Form >>");
+ dict.replaceKey("/BBox", QPDFObjectHandle::newFromRectangle(bbox));
+ AS = QPDFObjectHandle::newStream(
+ this->oh.getOwningQPDF(), "/Tx BMC\nEMC\n");
+ AS.replaceDict(dict);
+ QPDFObjectHandle AP = aoh.getAppearanceDictionary();
+ if (AP.isNull())
+ {
+ QTC::TC("qpdf", "QPDFFormFieldObjectHelper create AP from scratch");
+ aoh.getObjectHandle().replaceKey(
+ "/AP", QPDFObjectHandle::newDictionary());
+ AP = aoh.getAppearanceDictionary();
+ }
+ AP.replaceKey("/N", AS);
+ }
+ if (! AS.isStream())
+ {
+ aoh.getObjectHandle().warnIfPossible(
+ "unable to get normal appearance stream for update");
+ return;
+ }
+ QPDFObjectHandle bbox_obj = AS.getDict().getKey("/BBox");
+ if (! bbox_obj.isRectangle())
+ {
+ aoh.getObjectHandle().warnIfPossible(
+ "unable to get appearance stream bounding box");
+ return;
+ }
+ QPDFObjectHandle::Rectangle bbox = bbox_obj.getArrayAsRectangle();
+ std::string DA = getDefaultAppearance();
+ std::string V = getValueAsString();
+ std::vector<std::string> opt;
+ if (isChoice() && ((getFlags() & ff_ch_combo) == 0))
+ {
+ opt = getChoices();
+ }
+
+ TfFinder tff;
+ Pl_QPDFTokenizer tok("tf", &tff);
+ tok.write(QUtil::unsigned_char_pointer(DA.c_str()), DA.length());
+ tok.finish();
+ double tf = tff.getTf();
+ DA = tff.getDA();
+
+ std::string (*encoder)(std::string const&, char) = &QUtil::utf8_to_ascii;
+ std::string font_name = tff.getFontName();
+ if (! font_name.empty())
+ {
+ // See if the font is encoded with something we know about.
+ QPDFObjectHandle resources = AS.getDict().getKey("/Resources");
+ QPDFObjectHandle font = getFontFromResource(resources, font_name);
+ if (! font.isInitialized())
+ {
+ QPDFObjectHandle dr = getInheritableFieldValue("/DR");
+ font = getFontFromResource(dr, font_name);
+ }
+ if (font.isDictionary() &&
+ font.getKey("/Encoding").isName())
+ {
+ std::string encoding = font.getKey("/Encoding").getName();
+ if (encoding == "/WinAnsiEncoding")
+ {
+ QTC::TC("qpdf", "QPDFFormFieldObjectHelper WinAnsi");
+ encoder = &QUtil::utf8_to_win_ansi;
+ }
+ else if (encoding == "/MacRomanEncoding")
+ {
+ encoder = &QUtil::utf8_to_mac_roman;
+ }
+ }
+ }
+
+ V = (*encoder)(V, '?');
+ for (size_t i = 0; i < opt.size(); ++i)
+ {
+ opt.at(i) = (*encoder)(opt.at(i), '?');
+ }
+
+ AS.addTokenFilter(new ValueSetter(DA, V, opt, tf, bbox));
+}
diff --git a/libqpdf/QPDFMatrix.cc b/libqpdf/QPDFMatrix.cc
new file mode 100644
index 00000000..c78154aa
--- /dev/null
+++ b/libqpdf/QPDFMatrix.cc
@@ -0,0 +1,135 @@
+#include <qpdf/QPDFMatrix.hh>
+#include <qpdf/QUtil.hh>
+#include <algorithm>
+
+QPDFMatrix::QPDFMatrix() :
+ a(1.0),
+ b(0.0),
+ c(0.0),
+ d(1.0),
+ e(0.0),
+ f(0.0)
+{
+}
+
+QPDFMatrix::QPDFMatrix(double a, double b, double c,
+ double d, double e, double f) :
+ a(a),
+ b(b),
+ c(c),
+ d(d),
+ e(e),
+ f(f)
+{
+}
+
+QPDFMatrix::QPDFMatrix(QPDFObjectHandle::Matrix const& m) :
+ a(m.a),
+ b(m.b),
+ c(m.c),
+ d(m.d),
+ e(m.e),
+ f(m.f)
+{
+}
+
+static double fix_rounding(double d)
+{
+ if ((d > -0.00001) && (d < 0.00001))
+ {
+ d = 0.0;
+ }
+ return d;
+}
+
+std::string
+QPDFMatrix::unparse() const
+{
+ return (QUtil::double_to_string(fix_rounding(a), 5) + " " +
+ QUtil::double_to_string(fix_rounding(b), 5) + " " +
+ QUtil::double_to_string(fix_rounding(c), 5) + " " +
+ QUtil::double_to_string(fix_rounding(d), 5) + " " +
+ QUtil::double_to_string(fix_rounding(e), 5) + " " +
+ QUtil::double_to_string(fix_rounding(f), 5));
+}
+
+QPDFObjectHandle::Matrix
+QPDFMatrix::getAsMatrix() const
+{
+ return QPDFObjectHandle::Matrix(a, b, c, d, e, f);
+}
+
+void
+QPDFMatrix::concat(QPDFMatrix const& other)
+{
+ double ap = (this->a * other.a) + (this->c * other.b);
+ double bp = (this->b * other.a) + (this->d * other.b);
+ double cp = (this->a * other.c) + (this->c * other.d);
+ double dp = (this->b * other.c) + (this->d * other.d);
+ double ep = (this->a * other.e) + (this->c * other.f) + this->e;
+ double fp = (this->b * other.e) + (this->d * other.f) + this->f;
+ this-> a = ap;
+ this-> b = bp;
+ this-> c = cp;
+ this-> d = dp;
+ this-> e = ep;
+ this-> f = fp;
+}
+
+void
+QPDFMatrix::scale(double sx, double sy)
+{
+ concat(QPDFMatrix(sx, 0, 0, sy, 0, 0));
+}
+
+void
+QPDFMatrix::translate(double tx, double ty)
+{
+ concat(QPDFMatrix(1, 0, 0, 1, tx, ty));
+}
+
+void
+QPDFMatrix::rotatex90(int angle)
+{
+ switch (angle)
+ {
+ case 90:
+ concat(QPDFMatrix(0, 1, -1, 0, 0, 0));
+ break;
+ case 180:
+ concat(QPDFMatrix(-1, 0, 0, -1, 0, 0));
+ break;
+ case 270:
+ concat(QPDFMatrix(0, -1, 1, 0, 0, 0));
+ break;
+ default:
+ // ignore
+ break;
+ }
+}
+
+void
+QPDFMatrix::transform(double x, double y, double& xp, double& yp)
+{
+ xp = (this->a * x) + (this->c * y) + this->e;
+ yp = (this->b * x) + (this->d * y) + this->f;
+}
+
+QPDFObjectHandle::Rectangle
+QPDFMatrix::transformRectangle(QPDFObjectHandle::Rectangle r)
+{
+ // Transform a rectangle by creating a new rectangle the tightly
+ // bounds the polygon resulting from transforming the four
+ // corners.
+ std::vector<double> tx(4);
+ std::vector<double> ty(4);
+ transform(r.llx, r.lly, tx.at(0), ty.at(0));
+ transform(r.llx, r.ury, tx.at(1), ty.at(1));
+ transform(r.urx, r.lly, tx.at(2), ty.at(2));
+ transform(r.urx, r.ury, tx.at(3), ty.at(3));
+ return QPDFObjectHandle::Rectangle(
+ *std::min_element(tx.begin(), tx.end()),
+ *std::min_element(ty.begin(), ty.end()),
+ *std::max_element(tx.begin(), tx.end()),
+ *std::max_element(ty.begin(), ty.end()));
+}
diff --git a/libqpdf/QPDFNameTreeObjectHelper.cc b/libqpdf/QPDFNameTreeObjectHelper.cc
new file mode 100644
index 00000000..4eb0fb15
--- /dev/null
+++ b/libqpdf/QPDFNameTreeObjectHelper.cc
@@ -0,0 +1,82 @@
+#include <qpdf/QPDFNameTreeObjectHelper.hh>
+
+QPDFNameTreeObjectHelper::Members::~Members()
+{
+}
+
+QPDFNameTreeObjectHelper::Members::Members()
+{
+}
+
+QPDFNameTreeObjectHelper::QPDFNameTreeObjectHelper(QPDFObjectHandle oh) :
+ QPDFObjectHelper(oh),
+ m(new Members())
+{
+ updateMap(oh);
+}
+
+QPDFNameTreeObjectHelper::~QPDFNameTreeObjectHelper()
+{
+}
+
+void
+QPDFNameTreeObjectHelper::updateMap(QPDFObjectHandle oh)
+{
+ if (this->m->seen.count(oh.getObjGen()))
+ {
+ return;
+ }
+ this->m->seen.insert(oh.getObjGen());
+ QPDFObjectHandle names = oh.getKey("/Names");
+ if (names.isArray())
+ {
+ size_t nitems = names.getArrayNItems();
+ size_t i = 0;
+ while (i < nitems - 1)
+ {
+ QPDFObjectHandle name = names.getArrayItem(i);
+ if (name.isString())
+ {
+ ++i;
+ QPDFObjectHandle obj = names.getArrayItem(i);
+ this->m->entries[name.getUTF8Value()] = obj;
+ }
+ ++i;
+ }
+ }
+ QPDFObjectHandle kids = oh.getKey("/Kids");
+ if (kids.isArray())
+ {
+ size_t nitems = kids.getArrayNItems();
+ for (size_t i = 0; i < nitems; ++i)
+ {
+ updateMap(kids.getArrayItem(i));
+ }
+ }
+}
+
+bool
+QPDFNameTreeObjectHelper::hasName(std::string const& name)
+{
+ return this->m->entries.count(name) != 0;
+}
+
+bool
+QPDFNameTreeObjectHelper::findObject(
+ std::string const& name, QPDFObjectHandle& oh)
+{
+ std::map<std::string, QPDFObjectHandle>::iterator i =
+ this->m->entries.find(name);
+ if (i == this->m->entries.end())
+ {
+ return false;
+ }
+ oh = (*i).second;
+ return true;
+}
+
+std::map<std::string, QPDFObjectHandle>
+QPDFNameTreeObjectHelper::getAsMap() const
+{
+ return this->m->entries;
+}
diff --git a/libqpdf/QPDFNumberTreeObjectHelper.cc b/libqpdf/QPDFNumberTreeObjectHelper.cc
new file mode 100644
index 00000000..40b1c6f9
--- /dev/null
+++ b/libqpdf/QPDFNumberTreeObjectHelper.cc
@@ -0,0 +1,121 @@
+#include <qpdf/QPDFNumberTreeObjectHelper.hh>
+
+QPDFNumberTreeObjectHelper::Members::~Members()
+{
+}
+
+QPDFNumberTreeObjectHelper::Members::Members()
+{
+}
+
+QPDFNumberTreeObjectHelper::QPDFNumberTreeObjectHelper(QPDFObjectHandle oh) :
+ QPDFObjectHelper(oh),
+ m(new Members())
+{
+ updateMap(oh);
+}
+
+void
+QPDFNumberTreeObjectHelper::updateMap(QPDFObjectHandle oh)
+{
+ if (this->m->seen.count(oh.getObjGen()))
+ {
+ return;
+ }
+ this->m->seen.insert(oh.getObjGen());
+ QPDFObjectHandle nums = oh.getKey("/Nums");
+ if (nums.isArray())
+ {
+ size_t nitems = nums.getArrayNItems();
+ size_t i = 0;
+ while (i < nitems - 1)
+ {
+ QPDFObjectHandle num = nums.getArrayItem(i);
+ if (num.isInteger())
+ {
+ ++i;
+ QPDFObjectHandle obj = nums.getArrayItem(i);
+ this->m->entries[num.getIntValue()] = obj;
+ }
+ ++i;
+ }
+ }
+ QPDFObjectHandle kids = oh.getKey("/Kids");
+ if (kids.isArray())
+ {
+ size_t nitems = kids.getArrayNItems();
+ for (size_t i = 0; i < nitems; ++i)
+ {
+ updateMap(kids.getArrayItem(i));
+ }
+ }
+}
+
+
+QPDFNumberTreeObjectHelper::numtree_number
+QPDFNumberTreeObjectHelper::getMin()
+{
+ if (this->m->entries.empty())
+ {
+ return 0;
+ }
+ // Our map is sorted in reverse.
+ return this->m->entries.rbegin()->first;
+}
+
+QPDFNumberTreeObjectHelper::numtree_number
+QPDFNumberTreeObjectHelper::getMax()
+{
+ if (this->m->entries.empty())
+ {
+ return 0;
+ }
+ // Our map is sorted in reverse.
+ return this->m->entries.begin()->first;
+}
+
+bool
+QPDFNumberTreeObjectHelper::hasIndex(numtree_number idx)
+{
+ return this->m->entries.count(idx) != 0;
+}
+
+bool
+QPDFNumberTreeObjectHelper::findObject(
+ numtree_number idx, QPDFObjectHandle& oh)
+{
+ Members::idx_map::iterator i = this->m->entries.find(idx);
+ if (i == this->m->entries.end())
+ {
+ return false;
+ }
+ oh = (*i).second;
+ return true;
+}
+
+bool
+QPDFNumberTreeObjectHelper::findObjectAtOrBelow(
+ numtree_number idx, QPDFObjectHandle& oh,
+ numtree_number& offset)
+{
+ Members::idx_map::iterator i = this->m->entries.lower_bound(idx);
+ if (i == this->m->entries.end())
+ {
+ return false;
+ }
+ oh = (*i).second;
+ offset = idx - (*i).first;
+ return true;
+}
+
+std::map<QPDFNumberTreeObjectHelper::numtree_number, QPDFObjectHandle>
+QPDFNumberTreeObjectHelper::getAsMap() const
+{
+ std::map<numtree_number, QPDFObjectHandle> result;
+ for (Members::idx_map::const_iterator iter = this->m->entries.begin();
+ iter != this->m->entries.end(); ++iter)
+ {
+ result[(*iter).first] = (*iter).second;
+ }
+ return result;
+}
diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc
index f4a8a0a4..a3a4d61d 100644
--- a/libqpdf/QPDFObjectHandle.cc
+++ b/libqpdf/QPDFObjectHandle.cc
@@ -18,6 +18,7 @@
#include <qpdf/Pl_QPDFTokenizer.hh>
#include <qpdf/BufferInputSource.hh>
#include <qpdf/QPDFExc.hh>
+#include <qpdf/QPDFPageObjectHelper.hh>
#include <qpdf/QTC.hh>
#include <qpdf/QUtil.hh>
@@ -575,6 +576,26 @@ QPDFObjectHandle::isRectangle()
return true;
}
+bool
+QPDFObjectHandle::isMatrix()
+{
+ if (! isArray())
+ {
+ return false;
+ }
+ if (getArrayNItems() != 6)
+ {
+ return false;
+ }
+ for (size_t i = 0; i < 6; ++i)
+ {
+ if (! getArrayItem(i).isNumber())
+ {
+ return false;
+ }
+ }
+ return true;
+}
QPDFObjectHandle::Rectangle
QPDFObjectHandle::getArrayAsRectangle()
@@ -590,6 +611,22 @@ QPDFObjectHandle::getArrayAsRectangle()
return result;
}
+QPDFObjectHandle::Matrix
+QPDFObjectHandle::getArrayAsMatrix()
+{
+ Matrix result;
+ if (isMatrix())
+ {
+ result = Matrix(getArrayItem(0).getNumericValue(),
+ getArrayItem(1).getNumericValue(),
+ getArrayItem(2).getNumericValue(),
+ getArrayItem(3).getNumericValue(),
+ getArrayItem(4).getNumericValue(),
+ getArrayItem(5).getNumericValue());
+ }
+ return result;
+}
+
std::vector<QPDFObjectHandle>
QPDFObjectHandle::getArrayAsVector()
{
@@ -789,6 +826,135 @@ QPDFObjectHandle::isOrHasName(std::string const& value)
return false;
}
+void
+QPDFObjectHandle::mergeResources(QPDFObjectHandle other)
+{
+ if (! (isDictionary() && other.isDictionary()))
+ {
+ QTC::TC("qpdf", "QPDFObjectHandle merge top type mismatch");
+ return;
+ }
+ std::set<std::string> other_keys = other.getKeys();
+ for (std::set<std::string>::iterator iter = other_keys.begin();
+ iter != other_keys.end(); ++iter)
+ {
+ std::string const& key = *iter;
+ QPDFObjectHandle other_val = other.getKey(key);
+ if (hasKey(key))
+ {
+ QPDFObjectHandle this_val = getKey(key);
+ if (this_val.isDictionary() && other_val.isDictionary())
+ {
+ if (this_val.isIndirect())
+ {
+ QTC::TC("qpdf", "QPDFObjectHandle replace with copy");
+ this_val = this_val.shallowCopy();
+ replaceKey(key, this_val);
+ }
+ std::set<std::string> other_val_keys = other_val.getKeys();
+ for (std::set<std::string>::iterator i2 =
+ other_val_keys.begin();
+ i2 != other_val_keys.end(); ++i2)
+ {
+ if (! this_val.hasKey(*i2))
+ {
+ QTC::TC("qpdf", "QPDFObjectHandle merge shallow copy");
+ this_val.replaceKey(
+ *i2, other_val.getKey(*i2).shallowCopy());
+ }
+ }
+ }
+ else if (this_val.isArray() && other_val.isArray())
+ {
+ std::set<std::string> scalars;
+ int n = this_val.getArrayNItems();
+ for (int i = 0; i < n; ++i)
+ {
+ QPDFObjectHandle this_item = this_val.getArrayItem(i);
+ if (this_item.isScalar())
+ {
+ scalars.insert(this_item.unparse());
+ }
+ }
+ n = other_val.getArrayNItems();
+ for (int i = 0; i < n; ++i)
+ {
+ QPDFObjectHandle other_item = other_val.getArrayItem(i);
+ if (other_item.isScalar())
+ {
+ if (scalars.count(other_item.unparse()) == 0)
+ {
+ QTC::TC("qpdf", "QPDFObjectHandle merge array");
+ this_val.appendItem(other_item);
+ }
+ else
+ {
+ QTC::TC("qpdf", "QPDFObjectHandle merge array dup");
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ QTC::TC("qpdf", "QPDFObjectHandle merge copy from other");
+ replaceKey(key, other_val.shallowCopy());
+ }
+ }
+}
+
+std::set<std::string>
+QPDFObjectHandle::getResourceNames()
+{
+ // Return second-level dictionary keys
+ std::set<std::string> result;
+ if (! isDictionary())
+ {
+ return result;
+ }
+ std::set<std::string> keys = getKeys();
+ for (std::set<std::string>::iterator iter = keys.begin();
+ iter != keys.end(); ++iter)
+ {
+ std::string const& key = *iter;
+ QPDFObjectHandle val = getKey(key);
+ if (val.isDictionary())
+ {
+ std::set<std::string> val_keys = val.getKeys();
+ for (std::set<std::string>::iterator i2 = val_keys.begin();
+ i2 != val_keys.end(); ++i2)
+ {
+ result.insert(*i2);
+ }
+ }
+ }
+ return result;
+}
+
+std::string
+QPDFObjectHandle::getUniqueResourceName(std::string const& prefix,
+ int& min_suffix)
+{
+ std::set<std::string> names = getResourceNames();
+ int max_suffix = min_suffix + names.size();
+ while (min_suffix <= max_suffix)
+ {
+ std::string candidate = prefix + QUtil::int_to_string(min_suffix);
+ if (names.count(candidate) == 0)
+ {
+ return candidate;
+ }
+ // Increment after return; min_suffix should be the value
+ // used, not the next value.
+ ++min_suffix;
+ }
+ // This could only happen if there is a coding error.
+ // The number of candidates we test is more than the
+ // number of keys we're checking against.
+ throw std::logic_error("unable to find unconflicting name in"
+ " QPDFObjectHandle::getUniqueResourceName");
+}
+
// Indirect object accessors
QPDF*
QPDFObjectHandle::getOwningQPDF()
@@ -968,24 +1134,11 @@ QPDFObjectHandle::getGeneration() const
std::map<std::string, QPDFObjectHandle>
QPDFObjectHandle::getPageImages()
{
- // Note: this code doesn't handle inherited resources. If this
- // page dictionary doesn't have a /Resources key or has one whose
- // value is null or an empty dictionary, you are supposed to walk
- // up the page tree until you find a /Resources dictionary. As of
- // this writing, I don't have any test files that use inherited
- // resources, and hand-generating one won't be a good test because
- // any mistakes in my understanding would be present in both the
- // code and the test file.
-
- // NOTE: If support of inherited resources (see above comment) is
- // implemented, edit comment in QPDFObjectHandle.hh for this
- // function. Also remove call to pushInheritedAttributesToPage
- // from qpdf.cc when show_page_images is true.
-
std::map<std::string, QPDFObjectHandle> result;
- if (this->hasKey("/Resources"))
+ QPDFObjectHandle resources =
+ QPDFPageObjectHelper(*this).getAttribute("/Resources", false);
+ if (resources.isDictionary())
{
- QPDFObjectHandle resources = this->getKey("/Resources");
if (resources.hasKey("/XObject"))
{
QPDFObjectHandle xobject = resources.getKey("/XObject");
@@ -1235,6 +1388,37 @@ QPDFObjectHandle::unparseBinary()
}
}
+JSON
+QPDFObjectHandle::getJSON(bool dereference_indirect)
+{
+ if ((! dereference_indirect) && this->isIndirect())
+ {
+ return JSON::makeString(unparse());
+ }
+ else
+ {
+ if (this->m->reserved)
+ {
+ throw std::logic_error(
+ "QPDFObjectHandle: attempting to unparse a reserved object");
+ }
+ dereference();
+ return this->m->obj->getJSON();
+ }
+}
+
+QPDFObjectHandle
+QPDFObjectHandle::wrapInArray()
+{
+ if (isArray())
+ {
+ return *this;
+ }
+ QPDFObjectHandle result = QPDFObjectHandle::newArray();
+ result.appendItem(*this);
+ return result;
+}
+
QPDFObjectHandle
QPDFObjectHandle::parse(std::string const& object_str,
std::string const& object_description)
@@ -1374,7 +1558,7 @@ QPDFObjectHandle::parseContentStream_data(
// terminated the token. Read until end of inline image.
char ch;
input->read(&ch, 1);
- tokenizer.expectInlineImage();
+ tokenizer.expectInlineImage(input);
QPDFTokenizer::Token t =
tokenizer.readToken(input, description, true);
if (t.getType() == QPDFTokenizer::tt_bad)
@@ -1386,16 +1570,7 @@ QPDFObjectHandle::parseContentStream_data(
}
else
{
- // Skip back over EI
- input->seek(-3, SEEK_CUR);
- std::string inline_image = t.getRawValue();
- for (int i = 0; i < 4; ++i)
- {
- if (inline_image.length() > 0)
- {
- inline_image.erase(inline_image.length() - 1);
- }
- }
+ std::string inline_image = t.getValue();
QTC::TC("qpdf", "QPDFObjectHandle inline image token");
callbacks->handleObject(
QPDFObjectHandle::newInlineImage(inline_image));
@@ -1900,12 +2075,31 @@ QPDFObjectHandle::newArray(Rectangle const& rect)
}
QPDFObjectHandle
+QPDFObjectHandle::newArray(Matrix const& matrix)
+{
+ std::vector<QPDFObjectHandle> items;
+ items.push_back(newReal(matrix.a));
+ items.push_back(newReal(matrix.b));
+ items.push_back(newReal(matrix.c));
+ items.push_back(newReal(matrix.d));
+ items.push_back(newReal(matrix.e));
+ items.push_back(newReal(matrix.f));
+ return newArray(items);
+}
+
+QPDFObjectHandle
QPDFObjectHandle::newFromRectangle(Rectangle const& rect)
{
return newArray(rect);
}
QPDFObjectHandle
+QPDFObjectHandle::newFromMatrix(Matrix const& rect)
+{
+ return newArray(rect);
+}
+
+QPDFObjectHandle
QPDFObjectHandle::newDictionary()
{
return newDictionary(std::map<std::string, QPDFObjectHandle>());
diff --git a/libqpdf/QPDFOutlineDocumentHelper.cc b/libqpdf/QPDFOutlineDocumentHelper.cc
new file mode 100644
index 00000000..411ccf34
--- /dev/null
+++ b/libqpdf/QPDFOutlineDocumentHelper.cc
@@ -0,0 +1,137 @@
+#include <qpdf/QPDFOutlineDocumentHelper.hh>
+#include <qpdf/QTC.hh>
+
+QPDFOutlineDocumentHelper::Members::~Members()
+{
+}
+
+QPDFOutlineDocumentHelper::Members::Members()
+{
+}
+
+QPDFOutlineDocumentHelper::QPDFOutlineDocumentHelper(QPDF& qpdf) :
+ QPDFDocumentHelper(qpdf),
+ m(new Members())
+{
+ QPDFObjectHandle root = qpdf.getRoot();
+ if (! root.hasKey("/Outlines"))
+ {
+ return;
+ }
+ QPDFObjectHandle outlines = root.getKey("/Outlines");
+ if (! (outlines.isDictionary() && outlines.hasKey("/First")))
+ {
+ return;
+ }
+ QPDFObjectHandle cur = outlines.getKey("/First");
+ while (! cur.isNull())
+ {
+ this->m->outlines.push_back(
+ QPDFOutlineObjectHelper::Accessor::create(cur, *this, 1));
+ cur = cur.getKey("/Next");
+ }
+}
+
+QPDFOutlineDocumentHelper::~QPDFOutlineDocumentHelper()
+{
+}
+
+bool
+QPDFOutlineDocumentHelper::hasOutlines()
+{
+ return ! this->m->outlines.empty();
+}
+
+std::list<QPDFOutlineObjectHelper>
+QPDFOutlineDocumentHelper::getTopLevelOutlines()
+{
+ return this->m->outlines;
+}
+
+void
+QPDFOutlineDocumentHelper::initializeByPage()
+{
+ std::list<QPDFOutlineObjectHelper> queue;
+ queue.insert(queue.end(), this->m->outlines.begin(), this->m->outlines.end());
+
+ while (! queue.empty())
+ {
+ QPDFOutlineObjectHelper oh = queue.front();
+ queue.pop_front();
+ this->m->by_page[oh.getDestPage().getObjGen()].push_back(oh);
+ std::list<QPDFOutlineObjectHelper> kids = oh.getKids();
+ queue.insert(queue.end(), kids.begin(), kids.end());
+ }
+}
+
+std::list<QPDFOutlineObjectHelper>
+QPDFOutlineDocumentHelper::getOutlinesForPage(QPDFObjGen const& og)
+{
+ if (this->m->by_page.empty())
+ {
+ initializeByPage();
+ }
+ std::list<QPDFOutlineObjectHelper> result;
+ if (this->m->by_page.count(og))
+ {
+ result = this->m->by_page[og];
+ }
+ return result;
+}
+
+QPDFObjectHandle
+QPDFOutlineDocumentHelper::resolveNamedDest(QPDFObjectHandle name)
+{
+ QPDFObjectHandle result;
+ if (name.isName())
+ {
+ if (! this->m->dest_dict.isInitialized())
+ {
+ this->m->dest_dict = this->qpdf.getRoot().getKey("/Dests");
+ }
+ if (this->m->dest_dict.isDictionary())
+ {
+ QTC::TC("qpdf", "QPDFOutlineDocumentHelper name named dest");
+ result = this->m->dest_dict.getKey(name.getName());
+ }
+ }
+ else if (name.isString())
+ {
+ if (0 == this->m->names_dest.getPointer())
+ {
+ QPDFObjectHandle names = this->qpdf.getRoot().getKey("/Names");
+ if (names.isDictionary())
+ {
+ QPDFObjectHandle dests = names.getKey("/Dests");
+ if (dests.isDictionary())
+ {
+ this->m->names_dest =
+ new QPDFNameTreeObjectHelper(dests);
+ }
+ }
+ }
+ if (this->m->names_dest.getPointer())
+ {
+ if (this->m->names_dest->findObject(name.getUTF8Value(), result))
+ {
+ QTC::TC("qpdf", "QPDFOutlineDocumentHelper string named dest");
+ }
+ }
+ }
+ if (! result.isInitialized())
+ {
+ result = QPDFObjectHandle::newNull();
+ }
+ return result;
+}
+
+bool
+QPDFOutlineDocumentHelper::checkSeen(QPDFObjGen const& og)
+{
+ if (this->m->seen.count(og) > 0)
+ {
+ return true;
+ }
+ this->m->seen.insert(og);
+ return false;
+}
diff --git a/libqpdf/QPDFOutlineObjectHelper.cc b/libqpdf/QPDFOutlineObjectHelper.cc
new file mode 100644
index 00000000..e8eb11d0
--- /dev/null
+++ b/libqpdf/QPDFOutlineObjectHelper.cc
@@ -0,0 +1,117 @@
+#include <qpdf/QPDFOutlineObjectHelper.hh>
+#include <qpdf/QPDFOutlineDocumentHelper.hh>
+#include <qpdf/QTC.hh>
+
+QPDFOutlineObjectHelper::Members::~Members()
+{
+}
+
+QPDFOutlineObjectHelper::Members::Members(QPDFOutlineDocumentHelper& dh) :
+ dh(dh)
+{
+}
+
+QPDFOutlineObjectHelper::QPDFOutlineObjectHelper(
+ QPDFObjectHandle oh, QPDFOutlineDocumentHelper& dh, int depth) :
+ QPDFObjectHelper(oh),
+ m(new Members(dh))
+{
+ if (depth > 50)
+ {
+ // Not exercised in test suite, but was tested manually by
+ // temporarily changing max depth to 1.
+ return;
+ }
+ if (QPDFOutlineDocumentHelper::Accessor::checkSeen(
+ this->m->dh, this->oh.getObjGen()))
+ {
+ QTC::TC("qpdf", "QPDFOutlineObjectHelper loop");
+ return;
+ }
+
+ QPDFObjectHandle cur = oh.getKey("/First");
+ while (! cur.isNull())
+ {
+ QPDFOutlineObjectHelper new_ooh(cur, dh, 1 + depth);
+ new_ooh.m->parent = new QPDFOutlineObjectHelper(*this);
+ this->m->kids.push_back(new_ooh);
+ cur = cur.getKey("/Next");
+ }
+}
+
+PointerHolder<QPDFOutlineObjectHelper>
+QPDFOutlineObjectHelper::getParent()
+{
+ return this->m->parent;
+}
+
+std::list<QPDFOutlineObjectHelper>
+QPDFOutlineObjectHelper::getKids()
+{
+ return this->m->kids;
+}
+
+QPDFObjectHandle
+QPDFOutlineObjectHelper::getDest()
+{
+ QPDFObjectHandle dest;
+ QPDFObjectHandle A;
+ if (this->oh.hasKey("/Dest"))
+ {
+ QTC::TC("qpdf", "QPDFOutlineObjectHelper direct dest");
+ dest = this->oh.getKey("/Dest");
+ }
+ else if ((A = this->oh.getKey("/A")).isDictionary() &&
+ A.getKey("/S").isName() &&
+ (A.getKey("/S").getName() == "/GoTo") &&
+ A.hasKey("/D"))
+ {
+ QTC::TC("qpdf", "QPDFOutlineObjectHelper action dest");
+ dest = A.getKey("/D");
+ }
+ if (! dest.isInitialized())
+ {
+ dest = QPDFObjectHandle::newNull();
+ }
+
+ if (dest.isName() || dest.isString())
+ {
+ QTC::TC("qpdf", "QPDFOutlineObjectHelper named dest");
+ dest = this->m->dh.resolveNamedDest(dest);
+ }
+
+ return dest;
+}
+
+QPDFObjectHandle
+QPDFOutlineObjectHelper::getDestPage()
+{
+ QPDFObjectHandle dest = getDest();
+ if ((dest.isArray()) && (dest.getArrayNItems() > 0))
+ {
+ return dest.getArrayItem(0);
+ }
+ return QPDFObjectHandle::newNull();
+}
+
+int
+QPDFOutlineObjectHelper::getCount()
+{
+ int count = 0;
+ if (this->oh.hasKey("/Count"))
+ {
+ count = this->oh.getKey("/Count").getIntValue();
+ }
+ return count;
+}
+
+std::string
+QPDFOutlineObjectHelper::getTitle()
+{
+ std::string result;
+ if (this->oh.hasKey("/Title"))
+ {
+ result = this->oh.getKey("/Title").getUTF8Value();
+ }
+ return result;
+}
diff --git a/libqpdf/QPDFPageDocumentHelper.cc b/libqpdf/QPDFPageDocumentHelper.cc
index f4774896..58d6ed22 100644
--- a/libqpdf/QPDFPageDocumentHelper.cc
+++ b/libqpdf/QPDFPageDocumentHelper.cc
@@ -1,4 +1,7 @@
#include <qpdf/QPDFPageDocumentHelper.hh>
+#include <qpdf/QPDFAcroFormDocumentHelper.hh>
+#include <qpdf/QUtil.hh>
+#include <qpdf/QTC.hh>
QPDFPageDocumentHelper::Members::~Members()
{
@@ -62,3 +65,151 @@ QPDFPageDocumentHelper::removePage(QPDFPageObjectHelper page)
{
this->qpdf.removePage(page.getObjectHandle());
}
+
+
+void
+QPDFPageDocumentHelper::flattenAnnotations(
+ int required_flags,
+ int forbidden_flags)
+{
+ QPDFAcroFormDocumentHelper afdh(this->qpdf);
+ if (afdh.getNeedAppearances())
+ {
+ this->qpdf.getRoot().getKey("/AcroForm").warnIfPossible(
+ "document does not have updated appearance streams,"
+ " so form fields will not be flattened");
+ }
+ std::vector<QPDFPageObjectHelper> pages = getAllPages();
+ for (std::vector<QPDFPageObjectHelper>::iterator iter = pages.begin();
+ iter != pages.end(); ++iter)
+ {
+ QPDFPageObjectHelper ph(*iter);
+ QPDFObjectHandle resources = ph.getAttribute("/Resources", true);
+ if (! resources.isDictionary())
+ {
+ // This should never happen and is not exercised in the
+ // test suite
+ resources = QPDFObjectHandle::newDictionary();
+ }
+ flattenAnnotationsForPage(ph, resources, afdh,
+ required_flags, forbidden_flags);
+ }
+ if (! afdh.getNeedAppearances())
+ {
+ this->qpdf.getRoot().removeKey("/AcroForm");
+ }
+}
+
+void
+QPDFPageDocumentHelper::flattenAnnotationsForPage(
+ QPDFPageObjectHelper& page,
+ QPDFObjectHandle& resources,
+ QPDFAcroFormDocumentHelper& afdh,
+ int required_flags,
+ int forbidden_flags)
+{
+ bool need_appearances = afdh.getNeedAppearances();
+ std::vector<QPDFAnnotationObjectHelper> annots = page.getAnnotations();
+ std::vector<QPDFObjectHandle> new_annots;
+ std::string new_content;
+ int rotate = 0;
+ QPDFObjectHandle rotate_obj =
+ page.getObjectHandle().getKey("/Rotate");
+ if (rotate_obj.isInteger() && rotate_obj.getIntValue())
+ {
+ rotate = rotate_obj.getIntValue();
+ }
+ int next_fx = 1;
+ for (std::vector<QPDFAnnotationObjectHelper>::iterator iter =
+ annots.begin();
+ iter != annots.end(); ++iter)
+ {
+ QPDFAnnotationObjectHelper& aoh(*iter);
+ QPDFObjectHandle as = aoh.getAppearanceStream("/N");
+ bool is_widget = (aoh.getSubtype() == "/Widget");
+ bool process = true;
+ if (need_appearances && is_widget)
+ {
+ QTC::TC("qpdf", "QPDFPageDocumentHelper skip widget need appearances");
+ process = false;
+ }
+ if (process && as.isStream())
+ {
+ if (is_widget)
+ {
+ QTC::TC("qpdf", "QPDFPageDocumentHelper merge DR");
+ QPDFFormFieldObjectHelper ff = afdh.getFieldForAnnotation(aoh);
+ QPDFObjectHandle as_resources =
+ as.getDict().getKey("/Resources");
+ if (as_resources.isIndirect())
+ {
+ QTC::TC("qpdf", "QPDFPageDocumentHelper indirect as resources");
+ as.getDict().replaceKey(
+ "/Resources", as_resources.shallowCopy());
+ as_resources = as.getDict().getKey("/Resources");
+ }
+ as_resources.mergeResources(
+ ff.getInheritableFieldValue("/DR"));
+ }
+ else
+ {
+ QTC::TC("qpdf", "QPDFPageDocumentHelper non-widget annotation");
+ }
+ std::string name = resources.getUniqueResourceName(
+ "/Fxo", next_fx);
+ std::string content = aoh.getPageContentForAppearance(
+ name, rotate, required_flags, forbidden_flags);
+ if (! content.empty())
+ {
+ resources.mergeResources(
+ QPDFObjectHandle::parse("<< /XObject << >> >>"));
+ resources.getKey("/XObject").replaceKey(name, as);
+ ++next_fx;
+ }
+ new_content += content;
+ }
+ else if (process)
+ {
+ // If an annotation has no appearance stream, just drop
+ // the annotation when flattening. This can happen for
+ // unchecked checkboxes and radio buttons, popup windows
+ // associated with comments that aren't visible, and other
+ // types of annotations that aren't visible.
+ QTC::TC("qpdf", "QPDFPageDocumentHelper ignore annotation with no appearance");
+ }
+ else
+ {
+ new_annots.push_back(aoh.getObjectHandle());
+ }
+ }
+ if (new_annots.size() != annots.size())
+ {
+ QPDFObjectHandle page_oh = page.getObjectHandle();
+ if (new_annots.empty())
+ {
+ QTC::TC("qpdf", "QPDFPageDocumentHelper remove annots");
+ page_oh.removeKey("/Annots");
+ }
+ else
+ {
+ QPDFObjectHandle old_annots = page_oh.getKey("/Annots");
+ QPDFObjectHandle new_annots_oh =
+ QPDFObjectHandle::newArray(new_annots);
+ if (old_annots.isIndirect())
+ {
+ QTC::TC("qpdf", "QPDFPageDocumentHelper replace indirect annots");
+ this->qpdf.replaceObject(
+ old_annots.getObjGen(), new_annots_oh);
+ }
+ else
+ {
+ QTC::TC("qpdf", "QPDFPageDocumentHelper replace direct annots");
+ page_oh.replaceKey("/Annots", new_annots_oh);
+ }
+ }
+ page.addPageContents(
+ QPDFObjectHandle::newStream(&qpdf, "q\n"), true);
+ page.addPageContents(
+ QPDFObjectHandle::newStream(&qpdf, "\nQ\n" + new_content), false);
+ }
+}
diff --git a/libqpdf/QPDFPageLabelDocumentHelper.cc b/libqpdf/QPDFPageLabelDocumentHelper.cc
new file mode 100644
index 00000000..a650fa9c
--- /dev/null
+++ b/libqpdf/QPDFPageLabelDocumentHelper.cc
@@ -0,0 +1,125 @@
+#include <qpdf/QPDFPageLabelDocumentHelper.hh>
+#include <qpdf/QTC.hh>
+
+QPDFPageLabelDocumentHelper::Members::~Members()
+{
+}
+
+QPDFPageLabelDocumentHelper::Members::Members()
+{
+}
+
+QPDFPageLabelDocumentHelper::QPDFPageLabelDocumentHelper(QPDF& qpdf) :
+ QPDFDocumentHelper(qpdf),
+ m(new Members())
+{
+ QPDFObjectHandle root = qpdf.getRoot();
+ if (root.hasKey("/PageLabels"))
+ {
+ this->m->labels = new QPDFNumberTreeObjectHelper(
+ root.getKey("/PageLabels"));
+ }
+}
+
+bool
+QPDFPageLabelDocumentHelper::hasPageLabels()
+{
+ return 0 != this->m->labels.getPointer();
+}
+
+QPDFObjectHandle
+QPDFPageLabelDocumentHelper::getLabelForPage(long long page_idx)
+{
+ QPDFObjectHandle result(QPDFObjectHandle::newNull());
+ if (! hasPageLabels())
+ {
+ return result;
+ }
+ QPDFNumberTreeObjectHelper::numtree_number offset = 0;
+ QPDFObjectHandle label;
+ if (! this->m->labels->findObjectAtOrBelow(page_idx, label, offset))
+ {
+ return result;
+ }
+ if (! label.isDictionary())
+ {
+ return result;
+ }
+ QPDFObjectHandle S = label.getKey("/S"); // type (D, R, r, A, a)
+ QPDFObjectHandle P = label.getKey("/P"); // prefix
+ QPDFObjectHandle St = label.getKey("/St"); // starting number
+ long long start = 1;
+ if (St.isInteger())
+ {
+ start = St.getIntValue();
+ }
+ start += offset;
+ result = QPDFObjectHandle::newDictionary();
+ result.replaceOrRemoveKey("/S", S);
+ result.replaceOrRemoveKey("/P", P);
+ result.replaceOrRemoveKey("/St", QPDFObjectHandle::newInteger(start));
+ return result;
+}
+
+void
+QPDFPageLabelDocumentHelper::getLabelsForPageRange(
+ long long start_idx, long long end_idx, long long new_start_idx,
+ std::vector<QPDFObjectHandle>& new_labels)
+{
+ // Start off with a suitable label for the first page. For every
+ // remaining page, if that page has an explicit entry, copy it.
+ // Otherwise, let the subsequent page just sequence from the prior
+ // entry. If there is no entry for the first page, fabricate one
+ // that would match how the page would look in a new file in which
+ // it also didn't have an explicit label.
+ QPDFObjectHandle label = getLabelForPage(start_idx);
+ if (label.isNull())
+ {
+ label = QPDFObjectHandle::newDictionary();
+ label.replaceKey(
+ "/St", QPDFObjectHandle::newInteger(1 + new_start_idx));
+ }
+ // See if the new label is redundant based on the previous entry
+ // in the vector. If so, don't add it.
+ size_t size = new_labels.size();
+ bool skip_first = false;
+ if (size >= 2)
+ {
+ QPDFObjectHandle last = new_labels.at(size - 1);
+ QPDFObjectHandle last_idx = new_labels.at(size - 2);
+ if (last_idx.isInteger() && last.isDictionary() &&
+ (label.getKey("/S").unparse() == last.getKey("/S").unparse()) &&
+ (label.getKey("/P").unparse() == last.getKey("/P").unparse()) &&
+ label.getKey("/St").isInteger() &&
+ last.getKey("/St").isInteger())
+ {
+ long long int st_delta =
+ label.getKey("/St").getIntValue() -
+ last.getKey("/St").getIntValue();
+ long long int idx_delta =
+ new_start_idx - last_idx.getIntValue();
+ if (st_delta == idx_delta)
+ {
+ QTC::TC("qpdf", "QPDFPageLabelDocumentHelper skip first");
+ skip_first = true;
+ }
+ }
+ }
+ if (! skip_first)
+ {
+ new_labels.push_back(QPDFObjectHandle::newInteger(new_start_idx));
+ new_labels.push_back(label);
+ }
+
+ long long int idx_offset = new_start_idx - start_idx;
+ for (long long i = start_idx + 1; i <= end_idx; ++i)
+ {
+ if (this->m->labels->hasIndex(i) &&
+ (label = getLabelForPage(i)).isDictionary())
+ {
+ new_labels.push_back(QPDFObjectHandle::newInteger(i + idx_offset));
+ new_labels.push_back(label);
+ }
+ }
+}
+
diff --git a/libqpdf/QPDFPageObjectHelper.cc b/libqpdf/QPDFPageObjectHelper.cc
index 4093622d..9543d294 100644
--- a/libqpdf/QPDFPageObjectHelper.cc
+++ b/libqpdf/QPDFPageObjectHelper.cc
@@ -1,5 +1,286 @@
#include <qpdf/QPDFPageObjectHelper.hh>
#include <qpdf/QTC.hh>
+#include <qpdf/QPDF.hh>
+#include <qpdf/Pl_Concatenate.hh>
+#include <qpdf/Pl_Buffer.hh>
+#include <qpdf/QUtil.hh>
+#include <qpdf/QPDFExc.hh>
+#include <qpdf/QPDFMatrix.hh>
+
+class ContentProvider: public QPDFObjectHandle::StreamDataProvider
+{
+ public:
+ ContentProvider(QPDFObjectHandle from_page) :
+ from_page(from_page)
+ {
+ }
+ virtual ~ContentProvider()
+ {
+ }
+ virtual void provideStreamData(int objid, int generation,
+ Pipeline* pipeline);
+
+ private:
+ QPDFObjectHandle from_page;
+};
+
+void
+ContentProvider::provideStreamData(int, int, Pipeline* p)
+{
+ Pl_Concatenate concat("concatenate", p);
+ std::string description = "contents from page object " +
+ QUtil::int_to_string(from_page.getObjectID()) + " " +
+ QUtil::int_to_string(from_page.getGeneration());
+ std::string all_description;
+ from_page.getKey("/Contents").pipeContentStreams(
+ &concat, description, all_description);
+ concat.manualFinish();
+}
+
+class InlineImageTracker: public QPDFObjectHandle::TokenFilter
+{
+ public:
+ InlineImageTracker(QPDF*, size_t min_size, QPDFObjectHandle resources);
+ virtual ~InlineImageTracker()
+ {
+ }
+ virtual void handleToken(QPDFTokenizer::Token const&);
+ QPDFObjectHandle convertIIDict(QPDFObjectHandle odict);
+
+ QPDF* qpdf;
+ size_t min_size;
+ QPDFObjectHandle resources;
+ std::string dict_str;
+ std::string bi_str;
+ int min_suffix;
+ bool any_images;
+ enum { st_top, st_bi } state;
+};
+
+InlineImageTracker::InlineImageTracker(QPDF* qpdf, size_t min_size,
+ QPDFObjectHandle resources) :
+ qpdf(qpdf),
+ min_size(min_size),
+ resources(resources),
+ min_suffix(1),
+ any_images(false),
+ state(st_top)
+{
+}
+
+QPDFObjectHandle
+InlineImageTracker::convertIIDict(QPDFObjectHandle odict)
+{
+ QPDFObjectHandle dict = QPDFObjectHandle::newDictionary();
+ dict.replaceKey("/Type", QPDFObjectHandle::newName("/XObject"));
+ dict.replaceKey("/Subtype", QPDFObjectHandle::newName("/Image"));
+ std::set<std::string> keys = odict.getKeys();
+ for (std::set<std::string>::iterator iter = keys.begin();
+ iter != keys.end(); ++iter)
+ {
+ std::string key = *iter;
+ QPDFObjectHandle value = odict.getKey(key);
+ if (key == "/BPC")
+ {
+ key = "/BitsPerComponent";
+ }
+ else if (key == "/CS")
+ {
+ key = "/ColorSpace";
+ }
+ else if (key == "/D")
+ {
+ key = "/Decode";
+ }
+ else if (key == "/DP")
+ {
+ key = "/DecodeParms";
+ }
+ else if (key == "/F")
+ {
+ key = "/Filter";
+ }
+ else if (key == "/H")
+ {
+ key = "/Height";
+ }
+ else if (key == "/IM")
+ {
+ key = "/ImageMask";
+ }
+ else if (key == "/I")
+ {
+ key = "/Interpolate";
+ }
+ else if (key == "/W")
+ {
+ key = "/Width";
+ }
+
+ if (key == "/ColorSpace")
+ {
+ if (value.isName())
+ {
+ std::string name = value.getName();
+ if (name == "/G")
+ {
+ name = "/DeviceGray";
+ }
+ else if (name == "/RGB")
+ {
+ name = "/DeviceRGB";
+ }
+ else if (name == "/CMYK")
+ {
+ name = "/DeviceCMYK";
+ }
+ else if (name == "/I")
+ {
+ name = "/Indexed";
+ }
+ else
+ {
+ name.clear();
+ }
+ if (! name.empty())
+ {
+ value = QPDFObjectHandle::newName(name);
+ }
+ }
+ }
+ else if (key == "/Filter")
+ {
+ std::vector<QPDFObjectHandle> filters;
+ if (value.isName())
+ {
+ filters.push_back(value);
+ }
+ else if (value.isArray())
+ {
+ filters = value.getArrayAsVector();
+ }
+ for (std::vector<QPDFObjectHandle>::iterator iter =
+ filters.begin();
+ iter != filters.end(); ++iter)
+ {
+ std::string name;
+ if ((*iter).isName())
+ {
+ name = (*iter).getName();
+ }
+ if (name == "/AHx")
+ {
+ name = "/ASCIIHexDecode";
+ }
+ else if (name == "/A85")
+ {
+ name = "/ASCII85Decode";
+ }
+ else if (name == "/LZW")
+ {
+ name = "/LZWDecode";
+ }
+ else if (name == "/Fl")
+ {
+ name = "/FlateDecode";
+ }
+ else if (name == "/RL")
+ {
+ name = "/RunLengthDecode";
+ }
+ else if (name == "/CCF")
+ {
+ name = "/CCITTFaxDecode";
+ }
+ else if (name == "/DCT")
+ {
+ name = "/DCTDecode";
+ }
+ else
+ {
+ name.clear();
+ }
+ if (! name.empty())
+ {
+ *iter = QPDFObjectHandle::newName(name);
+ }
+ }
+ if (value.isName() && (filters.size() == 1))
+ {
+ value = filters.at(0);
+ }
+ else if (value.isArray())
+ {
+ value = QPDFObjectHandle::newArray(filters);
+ }
+ }
+ dict.replaceKey(key, value);
+ }
+ return dict;
+}
+
+void
+InlineImageTracker::handleToken(QPDFTokenizer::Token const& token)
+{
+ if (state == st_bi)
+ {
+ if (token.getType() == QPDFTokenizer::tt_inline_image)
+ {
+ std::string image_data(token.getValue());
+ size_t len = image_data.length();
+ if (len >= this->min_size)
+ {
+ QTC::TC("qpdf", "QPDFPageObjectHelper externalize inline image");
+ Pl_Buffer b("image_data");
+ b.write(QUtil::unsigned_char_pointer(image_data), len);
+ b.finish();
+ QPDFObjectHandle dict =
+ convertIIDict(QPDFObjectHandle::parse(dict_str));
+ dict.replaceKey("/Length", QPDFObjectHandle::newInteger(len));
+ std::string name = resources.getUniqueResourceName(
+ "/IIm", this->min_suffix);
+ QPDFObjectHandle image = QPDFObjectHandle::newStream(
+ this->qpdf, b.getBuffer());
+ image.replaceDict(dict);
+ resources.getKey("/XObject").replaceKey(name, image);
+ write(name);
+ write(" Do\n");
+ any_images = true;
+ }
+ else
+ {
+ QTC::TC("qpdf", "QPDFPageObjectHelper keep inline image");
+ write(bi_str);
+ writeToken(token);
+ state = st_top;
+ }
+ }
+ else if (token == QPDFTokenizer::Token(QPDFTokenizer::tt_word, "ID"))
+ {
+ bi_str += token.getValue();
+ dict_str += " >>";
+ }
+ else if (token == QPDFTokenizer::Token(QPDFTokenizer::tt_word, "EI"))
+ {
+ state = st_top;
+ }
+ else
+ {
+ bi_str += token.getValue();
+ dict_str += token.getValue();
+ }
+ }
+ else if (token == QPDFTokenizer::Token(QPDFTokenizer::tt_word, "BI"))
+ {
+ bi_str = token.getValue();
+ dict_str = "<< ";
+ state = st_bi;
+ }
+ else
+ {
+ writeToken(token);
+ }
+}
QPDFPageObjectHelper::Members::~Members()
{
@@ -14,12 +295,95 @@ QPDFPageObjectHelper::QPDFPageObjectHelper(QPDFObjectHandle oh) :
{
}
+QPDFObjectHandle
+QPDFPageObjectHelper::getAttribute(std::string const& name,
+ bool copy_if_shared)
+{
+ bool inheritable = ((name == "/MediaBox") || (name == "/CropBox") ||
+ (name == "/Resources") || (name == "/Rotate"));
+
+ QPDFObjectHandle node = this->oh;
+ QPDFObjectHandle result(node.getKey(name));
+ std::set<QPDFObjGen> seen;
+ bool inherited = false;
+ while (inheritable && result.isNull() && node.hasKey("/Parent"))
+ {
+ seen.insert(node.getObjGen());
+ node = node.getKey("/Parent");
+ if (seen.count(node.getObjGen()))
+ {
+ break;
+ }
+ result = node.getKey(name);
+ if (! result.isNull())
+ {
+ QTC::TC("qpdf", "QPDFPageObjectHelper non-trivial inheritance");
+ inherited = true;
+ }
+ }
+ if (copy_if_shared && (inherited || result.isIndirect()))
+ {
+ QTC::TC("qpdf", "QPDFPageObjectHelper copy shared attribute");
+ result = result.shallowCopy();
+ this->oh.replaceKey(name, result);
+ }
+ return result;
+}
+
+QPDFObjectHandle
+QPDFPageObjectHelper::getTrimBox(bool copy_if_shared)
+{
+ QPDFObjectHandle result = getAttribute("/TrimBox", copy_if_shared);
+ if (result.isNull())
+ {
+ result = getCropBox(copy_if_shared);
+ }
+ return result;
+}
+
+QPDFObjectHandle
+QPDFPageObjectHelper::getCropBox(bool copy_if_shared)
+{
+ QPDFObjectHandle result = getAttribute("/CropBox", copy_if_shared);
+ if (result.isNull())
+ {
+ result = getMediaBox();
+ }
+ return result;
+}
+
+QPDFObjectHandle
+QPDFPageObjectHelper::getMediaBox(bool copy_if_shared)
+{
+ return getAttribute("/MediaBox", copy_if_shared);
+}
+
std::map<std::string, QPDFObjectHandle>
QPDFPageObjectHelper::getPageImages()
{
return this->oh.getPageImages();
}
+void
+QPDFPageObjectHelper::externalizeInlineImages(size_t min_size)
+{
+ QPDFObjectHandle resources = getAttribute("/Resources", true);
+ // Calling mergeResources also ensures that /XObject becomes
+ // direct and is not shared with other pages.
+ resources.mergeResources(
+ QPDFObjectHandle::parse("<< /XObject << >> >>"));
+ InlineImageTracker iit(this->oh.getOwningQPDF(), min_size, resources);
+ Pl_Buffer b("new page content");
+ filterPageContents(&iit, &b);
+ if (iit.any_images)
+ {
+ getObjectHandle().replaceKey(
+ "/Contents",
+ QPDFObjectHandle::newStream(
+ this->oh.getOwningQPDF(), b.getBuffer()));
+ }
+}
+
std::vector<QPDFAnnotationObjectHelper>
QPDFPageObjectHelper::getAnnotations(std::string const& only_subtype)
{
@@ -98,11 +462,16 @@ QPDFPageObjectHelper::addContentTokenFilter(
class NameWatcher: public QPDFObjectHandle::TokenFilter
{
public:
+ NameWatcher() :
+ saw_bad(false)
+ {
+ }
virtual ~NameWatcher()
{
}
virtual void handleToken(QPDFTokenizer::Token const&);
std::set<std::string> names;
+ bool saw_bad;
};
void
@@ -115,6 +484,10 @@ NameWatcher::handleToken(QPDFTokenizer::Token const& token)
this->names.insert(
QPDFObjectHandle::newName(token.getValue()).getName());
}
+ else if (token.getType() == QPDFTokenizer::tt_bad)
+ {
+ saw_bad = true;
+ }
writeToken(token);
}
@@ -133,6 +506,14 @@ QPDFPageObjectHelper::removeUnreferencedResources()
"; not attempting to remove unreferenced objects from this page");
return;
}
+ if (nw.saw_bad)
+ {
+ QTC::TC("qpdf", "QPDFPageObjectHelper bad token finding names");
+ this->oh.warnIfPossible(
+ "Bad token found while scanning content stream; "
+ "not attempting to remove unreferenced objects from this page");
+ return;
+ }
// Walk through /Font and /XObject dictionaries, removing any
// resources that are not referenced. We must make copies of
// resource dictionaries down into the dictionaries are mutating
@@ -141,12 +522,7 @@ QPDFPageObjectHelper::removeUnreferencedResources()
std::vector<std::string> to_filter;
to_filter.push_back("/Font");
to_filter.push_back("/XObject");
- QPDFObjectHandle resources = this->oh.getKey("/Resources");
- if (resources.isDictionary())
- {
- resources = resources.shallowCopy();
- this->oh.replaceKey("/Resources", resources);
- }
+ QPDFObjectHandle resources = getAttribute("/Resources", true);
for (std::vector<std::string>::iterator d_iter = to_filter.begin();
d_iter != to_filter.end(); ++d_iter)
{
@@ -155,6 +531,7 @@ QPDFPageObjectHelper::removeUnreferencedResources()
{
continue;
}
+ dict = dict.shallowCopy();
resources.replaceKey(*d_iter, dict);
std::set<std::string> keys = dict.getKeys();
for (std::set<std::string>::iterator k_iter = keys.begin();
@@ -167,3 +544,225 @@ QPDFPageObjectHelper::removeUnreferencedResources()
}
}
}
+
+QPDFPageObjectHelper
+QPDFPageObjectHelper::shallowCopyPage()
+{
+ QPDF* qpdf = this->oh.getOwningQPDF();
+ if (! qpdf)
+ {
+ throw std::runtime_error(
+ "QPDFPageObjectHelper::shallowCopyPage"
+ " called with a direct object");
+ }
+ QPDFObjectHandle new_page = this->oh.shallowCopy();
+ return QPDFPageObjectHelper(qpdf->makeIndirectObject(new_page));
+}
+
+QPDFObjectHandle::Matrix
+QPDFPageObjectHelper::getMatrixForTransformations(bool invert)
+{
+ QPDFObjectHandle::Matrix matrix(1, 0, 0, 1, 0, 0);
+ QPDFObjectHandle bbox = getTrimBox(false);
+ if (! bbox.isRectangle())
+ {
+ return matrix;
+ }
+ QPDFObjectHandle rotate_obj = getAttribute("/Rotate", false);
+ QPDFObjectHandle scale_obj = getAttribute("/UserUnit", false);
+ if (! (rotate_obj.isNull() && scale_obj.isNull()))
+ {
+ QPDFObjectHandle::Rectangle rect = bbox.getArrayAsRectangle();
+ double width = rect.urx - rect.llx;
+ double height = rect.ury - rect.lly;
+ double scale = (scale_obj.isNumber()
+ ? scale_obj.getNumericValue()
+ : 1.0);
+ int rotate = (rotate_obj.isInteger()
+ ? rotate_obj.getIntValue()
+ : 0);
+ if (invert)
+ {
+ if (scale == 0.0)
+ {
+ return matrix;
+ }
+ scale = 1.0 / scale;
+ rotate = 360 - rotate;
+ }
+
+ // Ignore invalid rotation angle
+ switch (rotate)
+ {
+ case 90:
+ matrix = QPDFObjectHandle::Matrix(
+ 0, -scale, scale, 0, 0, width * scale);
+ break;
+ case 180:
+ matrix = QPDFObjectHandle::Matrix(
+ -scale, 0, 0, -scale, width * scale, height * scale);
+ break;
+ case 270:
+ matrix = QPDFObjectHandle::Matrix(
+ 0, scale, -scale, 0, height * scale, 0);
+ break;
+ default:
+ matrix = QPDFObjectHandle::Matrix(
+ scale, 0, 0, scale, 0, 0);
+ break;
+ }
+ }
+ return matrix;
+}
+
+QPDFObjectHandle
+QPDFPageObjectHelper::getFormXObjectForPage(bool handle_transformations)
+{
+ QPDF* qpdf = this->oh.getOwningQPDF();
+ if (! qpdf)
+ {
+ throw std::runtime_error(
+ "QPDFPageObjectHelper::getFormXObjectForPage"
+ " called with a direct object");
+ }
+ QPDFObjectHandle result = QPDFObjectHandle::newStream(qpdf);
+ QPDFObjectHandle newdict = result.getDict();
+ newdict.replaceKey("/Type", QPDFObjectHandle::newName("/XObject"));
+ newdict.replaceKey("/Subtype", QPDFObjectHandle::newName("/Form"));
+ newdict.replaceKey("/Resources",
+ getAttribute("/Resources", false).shallowCopy());
+ newdict.replaceKey("/Group",
+ getAttribute("/Group", false).shallowCopy());
+ QPDFObjectHandle bbox = getTrimBox(false).shallowCopy();
+ if (! bbox.isRectangle())
+ {
+ this->oh.warnIfPossible(
+ "bounding box is invalid; form"
+ " XObject created from page will not work");
+ }
+ newdict.replaceKey("/BBox", bbox);
+ PointerHolder<QPDFObjectHandle::StreamDataProvider> provider =
+ new ContentProvider(this->oh);
+ result.replaceStreamData(
+ provider, QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull());
+ QPDFObjectHandle rotate_obj = getAttribute("/Rotate", false);
+ QPDFObjectHandle scale_obj = getAttribute("/UserUnit", false);
+ if (handle_transformations &&
+ (! (rotate_obj.isNull() && scale_obj.isNull())))
+ {
+ newdict.replaceKey("/Matrix",
+ QPDFObjectHandle::newArray(
+ getMatrixForTransformations()));
+ }
+
+ return result;
+}
+
+std::string
+QPDFPageObjectHelper::placeFormXObject(
+ QPDFObjectHandle fo, std::string name,
+ QPDFObjectHandle::Rectangle rect,
+ bool invert_transformations)
+{
+ // Calculate the transformation matrix that will place the given
+ // form XObject fully inside the given rectangle, shrinking and
+ // centering if needed.
+
+ // When rendering a form XObject, the transformation in the
+ // graphics state (cm) is applied first (of course -- when it is
+ // applied, the PDF interpreter doesn't even know we're going to
+ // be drawing a form XObject yet), and then the object's matrix
+ // (M) is applied. The resulting matrix, when applied to the form
+ // XObject's bounding box, will generate a new rectangle. We want
+ // to create a transformation matrix that make the form XObject's
+ // bounding box land in exactly the right spot.
+
+ QPDFObjectHandle fdict = fo.getDict();
+ QPDFObjectHandle bbox_obj = fdict.getKey("/BBox");
+ if (! bbox_obj.isRectangle())
+ {
+ return "";
+ }
+
+ QPDFMatrix wmatrix; // work matrix
+ QPDFMatrix tmatrix; // "to" matrix
+ QPDFMatrix fmatrix; // "from" matrix
+ if (invert_transformations)
+ {
+ // tmatrix inverts scaling and rotation of the destination
+ // page. Applying this matrix allows the overlaid form
+ // XObject's to be absolute rather than relative to properties
+ // of the destination page. tmatrix is part of the computed
+ // transformation matrix.
+ tmatrix = QPDFMatrix(getMatrixForTransformations(true));
+ wmatrix.concat(tmatrix);
+ }
+ if (fdict.getKey("/Matrix").isMatrix())
+ {
+ // fmatrix is the transformation matrix that is applied to the
+ // form XObject itself. We need this for calculations, but we
+ // don't explicitly use it in the final result because the PDF
+ // rendering system automatically applies this last before
+ // drawing the form XObject.
+ fmatrix = QPDFMatrix(fdict.getKey("/Matrix").getArrayAsMatrix());
+ wmatrix.concat(fmatrix);
+ }
+
+ // The current wmatrix handles transformation from the form
+ // xobject and, if requested, the destination page. Next, we have
+ // to adjust this for scale and position.
+
+ // Step 1: figure out what scale factor we need to make the form
+ // XObject's bounding box fit within the destination rectangle.
+
+ // Transform bounding box
+ QPDFObjectHandle::Rectangle bbox = bbox_obj.getArrayAsRectangle();
+ QPDFObjectHandle::Rectangle T = wmatrix.transformRectangle(bbox);
+
+ // Calculate a scale factor, if needed. If the transformed
+ // rectangle is too big, shrink it. Never expand it.
+ if ((T.urx == T.llx) || (T.ury == T.lly))
+ {
+ // avoid division by zero
+ return "";
+ }
+ double rect_w = rect.urx - rect.llx;
+ double rect_h = rect.ury - rect.lly;
+ double t_w = T.urx - T.llx;
+ double t_h = T.ury - T.lly;
+ double xscale = rect_w / t_w;
+ double yscale = rect_h / t_h;
+ double scale = (xscale < yscale ? xscale : yscale);
+ if (scale > 1.0)
+ {
+ scale = 1.0;
+ }
+
+ // Step 2: figure out what translation is required to get the
+ // rectangle to the right spot: centered within the destination.
+ wmatrix = QPDFMatrix();
+ wmatrix.scale(scale, scale);
+ wmatrix.concat(tmatrix);
+ wmatrix.concat(fmatrix);
+
+ T = wmatrix.transformRectangle(bbox);
+ double t_cx = (T.llx + T.urx) / 2.0;
+ double t_cy = (T.lly + T.ury) / 2.0;
+ double r_cx = (rect.llx + rect.urx) / 2.0;
+ double r_cy = (rect.lly + rect.ury) / 2.0;
+ double tx = r_cx - t_cx;
+ double ty = r_cy - t_cy;
+
+ // Now we can calculate the final matrix. The final matrix does
+ // not include fmatrix because that is applied automatically by
+ // the PDF interpreter.
+ QPDFMatrix cm;
+ cm.translate(tx, ty);
+ cm.scale(scale, scale);
+ cm.concat(tmatrix);
+ return (
+ "q\n" +
+ cm.unparse() + " cm\n" +
+ name + " Do\n" +
+ "Q\n");
+}
diff --git a/libqpdf/QPDFSystemError.cc b/libqpdf/QPDFSystemError.cc
new file mode 100644
index 00000000..65aeaab7
--- /dev/null
+++ b/libqpdf/QPDFSystemError.cc
@@ -0,0 +1,51 @@
+#include <qpdf/QPDFSystemError.hh>
+#include <qpdf/QUtil.hh>
+#include <string.h>
+
+QPDFSystemError::QPDFSystemError(std::string const& description,
+ int system_errno) :
+ std::runtime_error(createWhat(description, system_errno)),
+ description(description),
+ system_errno(system_errno)
+{
+}
+
+QPDFSystemError::~QPDFSystemError() throw ()
+{
+}
+
+std::string
+QPDFSystemError::createWhat(std::string const& description,
+ int system_errno)
+{
+ std::string message;
+#ifdef _MSC_VER
+ // "94" is mentioned in the MSVC docs, but it's still safe if the
+ // message is longer. strerror_s is a templated function that
+ // knows the size of buf and truncates.
+ char buf[94];
+ if (strerror_s(buf, system_errno) != 0)
+ {
+ message = description + ": failed with an unknown error";
+ }
+ else
+ {
+ message = description + ": " + buf;
+ }
+#else
+ message = description + ": " + strerror(system_errno);
+#endif
+ return message;
+}
+
+std::string const&
+QPDFSystemError::getDescription() const
+{
+ return this->description;
+}
+
+int
+QPDFSystemError::getErrno() const
+{
+ return this->system_errno;
+}
diff --git a/libqpdf/QPDFTokenizer.cc b/libqpdf/QPDFTokenizer.cc
index 9c2a1e05..80fcf347 100644
--- a/libqpdf/QPDFTokenizer.cc
+++ b/libqpdf/QPDFTokenizer.cc
@@ -13,6 +13,69 @@
#include <string.h>
#include <cstdlib>
+static bool is_delimiter(char ch)
+{
+ return (strchr(" \t\n\v\f\r()<>[]{}/%", ch) != 0);
+}
+
+class QPDFWordTokenFinder: public InputSource::Finder
+{
+ public:
+ QPDFWordTokenFinder(PointerHolder<InputSource> is,
+ std::string const& str) :
+ is(is),
+ str(str)
+ {
+ }
+ virtual ~QPDFWordTokenFinder()
+ {
+ }
+ virtual bool check();
+
+ private:
+ PointerHolder<InputSource> is;
+ std::string str;
+};
+
+bool
+QPDFWordTokenFinder::check()
+{
+ // Find a word token matching the given string, preceded by a
+ // delimiter, and followed by a delimiter or EOF.
+ QPDFTokenizer tokenizer;
+ QPDFTokenizer::Token t = tokenizer.readToken(is, "finder", true);
+ qpdf_offset_t pos = is->tell();
+ if (! (t == QPDFTokenizer::Token(QPDFTokenizer::tt_word, str)))
+ {
+ QTC::TC("qpdf", "QPDFTokenizer finder found wrong word");
+ return false;
+ }
+ qpdf_offset_t token_start = is->getLastOffset();
+ char next;
+ bool next_okay = false;
+ if (is->read(&next, 1) == 0)
+ {
+ QTC::TC("qpdf", "QPDFTokenizer inline image at EOF");
+ next_okay = true;
+ }
+ else
+ {
+ next_okay = is_delimiter(next);
+ }
+ is->seek(pos, SEEK_SET);
+ if (! next_okay)
+ {
+ return false;
+ }
+ if (token_start == 0)
+ {
+ // Can't actually happen...we never start the search at the
+ // beginning of the input.
+ return false;
+ }
+ return true;
+}
+
QPDFTokenizer::Members::Members() :
pound_special_in_name(true),
allow_eof(false),
@@ -31,9 +94,11 @@ QPDFTokenizer::Members::reset()
error_message = "";
unread_char = false;
char_to_unread = '\0';
+ inline_image_bytes = 0;
string_depth = 0;
string_ignoring_newline = false;
last_char_was_bs = false;
+ last_char_was_cr = false;
}
QPDFTokenizer::Members::~Members()
@@ -90,7 +155,7 @@ QPDFTokenizer::isSpace(char ch)
bool
QPDFTokenizer::isDelimiter(char ch)
{
- return (strchr(" \t\n\v\f\r()<>[]{}/%", ch) != 0);
+ return is_delimiter(ch);
}
void
@@ -217,6 +282,7 @@ QPDFTokenizer::presentCharacter(char ch)
memset(this->m->bs_num_register, '\0',
sizeof(this->m->bs_num_register));
this->m->last_char_was_bs = false;
+ this->m->last_char_was_cr = false;
this->m->state = st_in_string;
}
else if (ch == '<')
@@ -334,8 +400,7 @@ QPDFTokenizer::presentCharacter(char ch)
}
else if (this->m->state == st_in_string)
{
- if (this->m->string_ignoring_newline &&
- (! ((ch == '\r') || (ch == '\n'))))
+ if (this->m->string_ignoring_newline && (ch != '\n'))
{
this->m->string_ignoring_newline = false;
}
@@ -353,9 +418,10 @@ QPDFTokenizer::presentCharacter(char ch)
bs_num_count = 0;
}
- if (this->m->string_ignoring_newline && ((ch == '\r') || (ch == '\n')))
+ if (this->m->string_ignoring_newline && (ch == '\n'))
{
// ignore
+ this->m->string_ignoring_newline = false;
}
else if (ch_is_octal &&
(this->m->last_char_was_bs || (bs_num_count > 0)))
@@ -386,8 +452,10 @@ QPDFTokenizer::presentCharacter(char ch)
this->m->val += '\f';
break;
- case '\r':
case '\n':
+ break;
+
+ case '\r':
this->m->string_ignoring_newline = true;
break;
@@ -417,11 +485,26 @@ QPDFTokenizer::presentCharacter(char ch)
this->m->type = tt_string;
this->m->state = st_token_ready;
}
+ else if (ch == '\r')
+ {
+ // CR by itself is converted to LF
+ this->m->val += '\n';
+ }
+ else if (ch == '\n')
+ {
+ // CR LF is converted to LF
+ if (! this->m->last_char_was_cr)
+ {
+ this->m->val += ch;
+ }
+ }
else
{
this->m->val += ch;
}
+ this->m->last_char_was_cr =
+ ((! this->m->string_ignoring_newline) && (ch == '\r'));
this->m->last_char_was_bs =
((! this->m->last_char_was_bs) && (ch == '\\'));
}
@@ -449,21 +532,28 @@ QPDFTokenizer::presentCharacter(char ch)
}
else if (this->m->state == st_inline_image)
{
+ this->m->val += ch;
size_t len = this->m->val.length();
- if ((len >= 4) &&
- isDelimiter(this->m->val.at(len-4)) &&
- (this->m->val.at(len-3) == 'E') &&
- (this->m->val.at(len-2) == 'I') &&
- isDelimiter(this->m->val.at(len-1)))
+ if (len == this->m->inline_image_bytes)
{
+ QTC::TC("qpdf", "QPDFTokenizer found EI by byte count");
this->m->type = tt_inline_image;
- this->m->unread_char = true;
- this->m->char_to_unread = ch;
+ this->m->inline_image_bytes = 0;
this->m->state = st_token_ready;
}
- else
+ else if ((this->m->inline_image_bytes == 0) &&
+ (len >= 4) &&
+ isDelimiter(this->m->val.at(len-4)) &&
+ (this->m->val.at(len-3) == 'E') &&
+ (this->m->val.at(len-2) == 'I') &&
+ isDelimiter(this->m->val.at(len-1)))
{
- this->m->val += ch;
+ QTC::TC("qpdf", "QPDFTokenizer found EI the old way");
+ this->m->val.erase(len - 1);
+ this->m->type = tt_inline_image;
+ this->m->unread_char = true;
+ this->m->char_to_unread = ch;
+ this->m->state = st_token_ready;
}
}
else
@@ -471,7 +561,6 @@ QPDFTokenizer::presentCharacter(char ch)
handled = false;
}
-
if (handled)
{
// okay
@@ -546,7 +635,7 @@ QPDFTokenizer::presentEOF()
(this->m->val.at(len-2) == 'E') &&
(this->m->val.at(len-1) == 'I'))
{
- QTC::TC("qpdf", "QPDFTokenizer inline image at EOF");
+ QTC::TC("qpdf", "QPDFTokenizer inline image at EOF the old way");
this->m->type = tt_inline_image;
this->m->state = st_token_ready;
}
@@ -582,14 +671,139 @@ QPDFTokenizer::presentEOF()
void
QPDFTokenizer::expectInlineImage()
{
+ expectInlineImage(PointerHolder<InputSource>());
+}
+
+void
+QPDFTokenizer::expectInlineImage(PointerHolder<InputSource> input)
+{
if (this->m->state != st_top)
{
throw std::logic_error("QPDFTokenizer::expectInlineImage called"
" when tokenizer is in improper state");
}
+ findEI(input);
this->m->state = st_inline_image;
}
+void
+QPDFTokenizer::findEI(PointerHolder<InputSource> input)
+{
+ if (! input.getPointer())
+ {
+ return;
+ }
+
+ qpdf_offset_t last_offset = input->getLastOffset();
+ qpdf_offset_t pos = input->tell();
+
+ // Use QPDFWordTokenFinder to find EI surrounded by delimiters.
+ // Then read the next several tokens or up to EOF. If we find any
+ // suspicious-looking or tokens, this is probably still part of
+ // the image data, so keep looking for EI. Stop at the first EI
+ // that passes. If we get to the end without finding one, return
+ // the last EI we found. Store the number of bytes expected in the
+ // inline image including the EI and use that to break out of
+ // inline image, falling back to the old method if needed.
+
+ bool okay = false;
+ bool first_try = true;
+ while (! okay)
+ {
+ QPDFWordTokenFinder f(input, "EI");
+ if (! input->findFirst("EI", input->tell(), 0, f))
+ {
+ break;
+ }
+ this->m->inline_image_bytes = input->tell() - pos - 2;
+
+ QPDFTokenizer check;
+ bool found_bad = false;
+ // Look at the next 10 tokens or up to EOF. The next inline
+ // image's image data would look like bad tokens, but there
+ // will always be at least 10 tokens between one inline
+ // image's EI and the next valid one's ID since width, height,
+ // bits per pixel, and color space are all required as well as
+ // a BI and ID. If we get 10 good tokens in a row or hit EOF,
+ // we can be pretty sure we've found the actual EI.
+ for (int i = 0; i < 10; ++i)
+ {
+ QPDFTokenizer::Token t =
+ check.readToken(input, "checker", true);
+ token_type_e type = t.getType();
+ if (type == tt_eof)
+ {
+ okay = true;
+ }
+ else if (type == tt_bad)
+ {
+ found_bad = true;
+ }
+ else if (type == tt_word)
+ {
+ // The qpdf tokenizer lumps alphabetic and otherwise
+ // uncategorized characters into "words". We recognize
+ // strings of alphabetic characters as potential valid
+ // operators for purposes of telling whether we're in
+ // valid content or not. It's not perfect, but it
+ // should work more reliably than what we used to do,
+ // which was already good enough for the vast majority
+ // of files.
+ bool found_alpha = false;
+ bool found_non_printable = false;
+ bool found_other = false;
+ std::string value = t.getValue();
+ for (std::string::iterator iter = value.begin();
+ iter != value.end(); ++iter)
+ {
+ char ch = *iter;
+ if (((ch >= 'a') && (ch <= 'z')) ||
+ ((ch >= 'A') && (ch <= 'Z')) ||
+ (ch == '*'))
+ {
+ // Treat '*' as alpha since there are valid
+ // PDF operators that contain * along with
+ // alphabetic characters.
+ found_alpha = true;
+ }
+ else if (((ch < 32) && (! isSpace(ch))) || (ch > 127))
+ {
+ found_non_printable = true;
+ break;
+ }
+ else
+ {
+ found_other = true;
+ }
+ }
+ if (found_non_printable || (found_alpha && found_other))
+ {
+ found_bad = true;
+ }
+ }
+ if (okay || found_bad)
+ {
+ break;
+ }
+ }
+ if (! found_bad)
+ {
+ okay = true;
+ }
+ if (! okay)
+ {
+ first_try = false;
+ }
+ }
+ if (okay && (! first_try))
+ {
+ QTC::TC("qpdf", "QPDFTokenizer found EI after more than one try");
+ }
+
+ input->seek(pos, SEEK_SET);
+ input->setLastOffset(last_offset);
+}
+
bool
QPDFTokenizer::getToken(Token& token, bool& unread_char, char& ch)
{
diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc
index 447b6627..fee287bc 100644
--- a/libqpdf/QPDFWriter.cc
+++ b/libqpdf/QPDFWriter.cc
@@ -63,6 +63,7 @@ QPDFWriter::Members::Members(QPDF& pdf) :
max_ostream_index(0),
deterministic_id(false),
md5_pipeline(0),
+ did_write_setup(false),
events_expected(0),
events_seen(0),
next_progress_report(0)
@@ -75,10 +76,7 @@ QPDFWriter::Members::~Members()
{
fclose(file);
}
- if (output_buffer)
- {
- delete output_buffer;
- }
+ delete output_buffer;
}
QPDFWriter::QPDFWriter(QPDF& pdf) :
@@ -411,7 +409,26 @@ QPDFWriter::setR3EncryptionParameters(
std::set<int> clear;
interpretR3EncryptionParameters(
clear, user_password, owner_password,
- allow_accessibility, allow_extract, print, modify);
+ allow_accessibility, allow_extract,
+ true, true, true, true, print, modify);
+ setEncryptionParameters(user_password, owner_password, 2, 3, 16, clear);
+}
+
+void
+QPDFWriter::setR3EncryptionParameters(
+ char const* user_password, char const* owner_password,
+ bool allow_accessibility, bool allow_extract,
+ bool allow_assemble, bool allow_annotate_and_form,
+ bool allow_form_filling, bool allow_modify_other,
+ qpdf_r3_print_e print)
+{
+ std::set<int> clear;
+ interpretR3EncryptionParameters(
+ clear, user_password, owner_password,
+ allow_accessibility, allow_extract,
+ allow_assemble, allow_annotate_and_form,
+ allow_form_filling, allow_modify_other,
+ print, qpdf_r3m_all);
setEncryptionParameters(user_password, owner_password, 2, 3, 16, clear);
}
@@ -425,7 +442,29 @@ QPDFWriter::setR4EncryptionParameters(
std::set<int> clear;
interpretR3EncryptionParameters(
clear, user_password, owner_password,
- allow_accessibility, allow_extract, print, modify);
+ allow_accessibility, allow_extract,
+ true, true, true, true, print, modify);
+ this->m->encrypt_use_aes = use_aes;
+ this->m->encrypt_metadata = encrypt_metadata;
+ setEncryptionParameters(user_password, owner_password, 4, 4, 16, clear);
+}
+
+void
+QPDFWriter::setR4EncryptionParameters(
+ char const* user_password, char const* owner_password,
+ bool allow_accessibility, bool allow_extract,
+ bool allow_assemble, bool allow_annotate_and_form,
+ bool allow_form_filling, bool allow_modify_other,
+ qpdf_r3_print_e print,
+ bool encrypt_metadata, bool use_aes)
+{
+ std::set<int> clear;
+ interpretR3EncryptionParameters(
+ clear, user_password, owner_password,
+ allow_accessibility, allow_extract,
+ allow_assemble, allow_annotate_and_form,
+ allow_form_filling, allow_modify_other,
+ print, qpdf_r3m_all);
this->m->encrypt_use_aes = use_aes;
this->m->encrypt_metadata = encrypt_metadata;
setEncryptionParameters(user_password, owner_password, 4, 4, 16, clear);
@@ -441,7 +480,29 @@ QPDFWriter::setR5EncryptionParameters(
std::set<int> clear;
interpretR3EncryptionParameters(
clear, user_password, owner_password,
- allow_accessibility, allow_extract, print, modify);
+ allow_accessibility, allow_extract,
+ true, true, true, true, print, modify);
+ this->m->encrypt_use_aes = true;
+ this->m->encrypt_metadata = encrypt_metadata;
+ setEncryptionParameters(user_password, owner_password, 5, 5, 32, clear);
+}
+
+void
+QPDFWriter::setR5EncryptionParameters(
+ char const* user_password, char const* owner_password,
+ bool allow_accessibility, bool allow_extract,
+ bool allow_assemble, bool allow_annotate_and_form,
+ bool allow_form_filling, bool allow_modify_other,
+ qpdf_r3_print_e print,
+ bool encrypt_metadata)
+{
+ std::set<int> clear;
+ interpretR3EncryptionParameters(
+ clear, user_password, owner_password,
+ allow_accessibility, allow_extract,
+ allow_assemble, allow_annotate_and_form,
+ allow_form_filling, allow_modify_other,
+ print, qpdf_r3m_all);
this->m->encrypt_use_aes = true;
this->m->encrypt_metadata = encrypt_metadata;
setEncryptionParameters(user_password, owner_password, 5, 5, 32, clear);
@@ -457,7 +518,29 @@ QPDFWriter::setR6EncryptionParameters(
std::set<int> clear;
interpretR3EncryptionParameters(
clear, user_password, owner_password,
- allow_accessibility, allow_extract, print, modify);
+ allow_accessibility, allow_extract,
+ true, true, true, true, print, modify);
+ this->m->encrypt_use_aes = true;
+ this->m->encrypt_metadata = encrypt_metadata;
+ setEncryptionParameters(user_password, owner_password, 5, 6, 32, clear);
+}
+
+void
+QPDFWriter::setR6EncryptionParameters(
+ char const* user_password, char const* owner_password,
+ bool allow_accessibility, bool allow_extract,
+ bool allow_assemble, bool allow_annotate_and_form,
+ bool allow_form_filling, bool allow_modify_other,
+ qpdf_r3_print_e print,
+ bool encrypt_metadata)
+{
+ std::set<int> clear;
+ interpretR3EncryptionParameters(
+ clear, user_password, owner_password,
+ allow_accessibility, allow_extract,
+ allow_assemble, allow_annotate_and_form,
+ allow_form_filling, allow_modify_other,
+ print, qpdf_r3m_all);
this->m->encrypt_use_aes = true;
this->m->encrypt_metadata = encrypt_metadata;
setEncryptionParameters(user_password, owner_password, 5, 6, 32, clear);
@@ -468,6 +551,8 @@ QPDFWriter::interpretR3EncryptionParameters(
std::set<int>& clear,
char const* user_password, char const* owner_password,
bool allow_accessibility, bool allow_extract,
+ bool allow_assemble, bool allow_annotate_and_form,
+ bool allow_form_filling, bool allow_modify_other,
qpdf_r3_print_e print, qpdf_r3_modify_e modify)
{
// Acrobat 5 security options:
@@ -488,8 +573,21 @@ QPDFWriter::interpretR3EncryptionParameters(
// Low Resolution
// Full printing
+ // Meanings of bits in P when R >= 3
+ //
+ // 3: low-resolution printing
+ // 4: document modification except as controlled by 6, 9, and 11
+ // 5: extraction
+ // 6: add/modify annotations (comment), fill in forms
+ // if 4+6 are set, also allows modification of form fields
+ // 9: fill in forms even if 6 is clear
+ // 10: accessibility; ignored by readers, should always be set
+ // 11: document assembly even if 4 is clear
+ // 12: high-resolution printing
+
if (! allow_accessibility)
{
+ // setEncryptionParameters sets this if R > 3
clear.insert(10);
}
if (! allow_extract)
@@ -513,6 +611,13 @@ QPDFWriter::interpretR3EncryptionParameters(
// no default so gcc warns for missing cases
}
+ // Modify options. The qpdf_r3_modify_e options control groups of
+ // bits and lack the full flexibility of the spec. This is
+ // unfortunate, but it's been in the API for ages, and we're stuck
+ // with it. See also allow checks below to control the bits
+ // individually.
+
+ // NOT EXERCISED IN TEST SUITE
switch (modify)
{
case qpdf_r3m_none:
@@ -532,6 +637,24 @@ QPDFWriter::interpretR3EncryptionParameters(
// no default so gcc warns for missing cases
}
+ // END NOT EXERCISED IN TEST SUITE
+
+ if (! allow_assemble)
+ {
+ clear.insert(11);
+ }
+ if (! allow_annotate_and_form)
+ {
+ clear.insert(6);
+ }
+ if (! allow_form_filling)
+ {
+ clear.insert(9);
+ }
+ if (! allow_modify_other)
+ {
+ clear.insert(4);
+ }
}
void
@@ -1811,7 +1934,9 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
// Set up a stream to write the stream data into a buffer.
Pipeline* next = pushPipeline(new Pl_Buffer("object stream"));
- if (! (this->m->stream_decode_level || this->m->qdf_mode))
+ if ((this->m->compress_streams ||
+ (this->m->stream_decode_level == qpdf_dl_none)) &&
+ (! this->m->qdf_mode))
{
compressed = true;
next = pushPipeline(
@@ -2247,6 +2372,7 @@ QPDFWriter::prepareFileForWrite()
// includes stream lengths, stream filtering parameters, and
// document extension level information.
+ this->m->pdf.fixDanglingReferences(true);
std::list<QPDFObjectHandle> queue;
queue.push_back(getTrimmedTrailer());
std::set<int> visited;
@@ -2360,8 +2486,14 @@ QPDFWriter::prepareFileForWrite()
}
void
-QPDFWriter::write()
+QPDFWriter::doWriteSetup()
{
+ if (this->m->did_write_setup)
+ {
+ return;
+ }
+ this->m->did_write_setup = true;
+
// Do preliminary setup
if (this->m->linearized)
@@ -2509,6 +2641,23 @@ QPDFWriter::write()
setMinimumPDFVersion("1.5");
}
+ setMinimumPDFVersion(this->m->pdf.getPDFVersion(),
+ this->m->pdf.getExtensionLevel());
+ this->m->final_pdf_version = this->m->min_pdf_version;
+ this->m->final_extension_level = this->m->min_extension_level;
+ if (! this->m->forced_pdf_version.empty())
+ {
+ QTC::TC("qpdf", "QPDFWriter using forced PDF version");
+ this->m->final_pdf_version = this->m->forced_pdf_version;
+ this->m->final_extension_level = this->m->forced_extension_level;
+ }
+}
+
+void
+QPDFWriter::write()
+{
+ doWriteSetup();
+
// Set up progress reporting. We spent about equal amounts of time
// preparing and writing one pass. To get a rough estimate of
// progress, we track handling of indirect objects. For linearized
@@ -2571,20 +2720,16 @@ QPDFWriter::writeEncryptionDictionary()
closeObject(this->m->encryption_dict_objid);
}
+std::string
+QPDFWriter::getFinalVersion()
+{
+ doWriteSetup();
+ return this->m->final_pdf_version;
+}
+
void
QPDFWriter::writeHeader()
{
- setMinimumPDFVersion(this->m->pdf.getPDFVersion(),
- this->m->pdf.getExtensionLevel());
- this->m->final_pdf_version = this->m->min_pdf_version;
- this->m->final_extension_level = this->m->min_extension_level;
- if (! this->m->forced_pdf_version.empty())
- {
- QTC::TC("qpdf", "QPDFWriter using forced PDF version");
- this->m->final_pdf_version = this->m->forced_pdf_version;
- this->m->final_extension_level = this->m->forced_extension_level;
- }
-
writeString("%PDF-");
writeString(this->m->final_pdf_version);
if (this->m->pclm)
@@ -2733,7 +2878,9 @@ QPDFWriter::writeXRefStream(int xref_id, int max_id, qpdf_offset_t max_offset,
Pipeline* p = pushPipeline(new Pl_Buffer("xref stream"));
bool compressed = false;
- if (! (this->m->stream_decode_level || this->m->qdf_mode))
+ if ((this->m->compress_streams ||
+ (this->m->stream_decode_level == qpdf_dl_none)) &&
+ (! this->m->qdf_mode))
{
compressed = true;
if (! skip_compression)
@@ -3357,9 +3504,10 @@ QPDFWriter::indicateProgress(bool decrement, bool finished)
this->m->events_expected)));
this->m->progress_reporter->reportProgress(percentage);
}
+ int increment = std::max(1, (this->m->events_expected / 100));
while (this->m->events_seen >= this->m->next_progress_report)
{
- this->m->next_progress_report += (this->m->events_expected / 100);
+ this->m->next_progress_report += increment;
}
}
diff --git a/libqpdf/QPDF_Array.cc b/libqpdf/QPDF_Array.cc
index 1a4ba61d..15c2e93e 100644
--- a/libqpdf/QPDF_Array.cc
+++ b/libqpdf/QPDF_Array.cc
@@ -35,6 +35,18 @@ QPDF_Array::unparse()
return result;
}
+JSON
+QPDF_Array::getJSON()
+{
+ JSON j = JSON::makeArray();
+ for (std::vector<QPDFObjectHandle>::iterator iter = this->items.begin();
+ iter != this->items.end(); ++iter)
+ {
+ j.addArrayElement((*iter).getJSON());
+ }
+ return j;
+}
+
QPDFObject::object_type_e
QPDF_Array::getTypeCode() const
{
diff --git a/libqpdf/QPDF_Bool.cc b/libqpdf/QPDF_Bool.cc
index 781a7ad9..63d44adb 100644
--- a/libqpdf/QPDF_Bool.cc
+++ b/libqpdf/QPDF_Bool.cc
@@ -15,6 +15,12 @@ QPDF_Bool::unparse()
return (val ? "true" : "false");
}
+JSON
+QPDF_Bool::getJSON()
+{
+ return JSON::makeBool(this->val);
+}
+
QPDFObject::object_type_e
QPDF_Bool::getTypeCode() const
{
diff --git a/libqpdf/QPDF_Dictionary.cc b/libqpdf/QPDF_Dictionary.cc
index df640354..1301d46f 100644
--- a/libqpdf/QPDF_Dictionary.cc
+++ b/libqpdf/QPDF_Dictionary.cc
@@ -39,6 +39,20 @@ QPDF_Dictionary::unparse()
return result;
}
+JSON
+QPDF_Dictionary::getJSON()
+{
+ JSON j = JSON::makeDictionary();
+ for (std::map<std::string, QPDFObjectHandle>::iterator iter =
+ this->items.begin();
+ iter != this->items.end(); ++iter)
+ {
+ j.addDictionaryMember(QPDF_Name::normalizeName((*iter).first),
+ (*iter).second.getJSON());
+ }
+ return j;
+}
+
QPDFObject::object_type_e
QPDF_Dictionary::getTypeCode() const
{
diff --git a/libqpdf/QPDF_InlineImage.cc b/libqpdf/QPDF_InlineImage.cc
index 48be6387..9769630c 100644
--- a/libqpdf/QPDF_InlineImage.cc
+++ b/libqpdf/QPDF_InlineImage.cc
@@ -17,6 +17,12 @@ QPDF_InlineImage::unparse()
return this->val;
}
+JSON
+QPDF_InlineImage::getJSON()
+{
+ return JSON::makeNull();
+}
+
QPDFObject::object_type_e
QPDF_InlineImage::getTypeCode() const
{
diff --git a/libqpdf/QPDF_Integer.cc b/libqpdf/QPDF_Integer.cc
index 572b34e0..d0356dad 100644
--- a/libqpdf/QPDF_Integer.cc
+++ b/libqpdf/QPDF_Integer.cc
@@ -17,6 +17,12 @@ QPDF_Integer::unparse()
return QUtil::int_to_string(this->val);
}
+JSON
+QPDF_Integer::getJSON()
+{
+ return JSON::makeInt(this->val);
+}
+
QPDFObject::object_type_e
QPDF_Integer::getTypeCode() const
{
diff --git a/libqpdf/QPDF_Name.cc b/libqpdf/QPDF_Name.cc
index 0c2082c4..290fb067 100644
--- a/libqpdf/QPDF_Name.cc
+++ b/libqpdf/QPDF_Name.cc
@@ -44,6 +44,12 @@ QPDF_Name::unparse()
return normalizeName(this->name);
}
+JSON
+QPDF_Name::getJSON()
+{
+ return JSON::makeString(normalizeName(this->name));
+}
+
QPDFObject::object_type_e
QPDF_Name::getTypeCode() const
{
diff --git a/libqpdf/QPDF_Null.cc b/libqpdf/QPDF_Null.cc
index 05c35b09..e4193c6c 100644
--- a/libqpdf/QPDF_Null.cc
+++ b/libqpdf/QPDF_Null.cc
@@ -10,6 +10,12 @@ QPDF_Null::unparse()
return "null";
}
+JSON
+QPDF_Null::getJSON()
+{
+ return JSON::makeNull();
+}
+
QPDFObject::object_type_e
QPDF_Null::getTypeCode() const
{
diff --git a/libqpdf/QPDF_Operator.cc b/libqpdf/QPDF_Operator.cc
index c6c68816..b61d47b1 100644
--- a/libqpdf/QPDF_Operator.cc
+++ b/libqpdf/QPDF_Operator.cc
@@ -17,6 +17,12 @@ QPDF_Operator::unparse()
return this->val;
}
+JSON
+QPDF_Operator::getJSON()
+{
+ return JSON::makeNull();
+}
+
QPDFObject::object_type_e
QPDF_Operator::getTypeCode() const
{
diff --git a/libqpdf/QPDF_Real.cc b/libqpdf/QPDF_Real.cc
index fa0a60cb..b28c2f70 100644
--- a/libqpdf/QPDF_Real.cc
+++ b/libqpdf/QPDF_Real.cc
@@ -22,6 +22,12 @@ QPDF_Real::unparse()
return this->val;
}
+JSON
+QPDF_Real::getJSON()
+{
+ return JSON::makeNumber(this->val);
+}
+
QPDFObject::object_type_e
QPDF_Real::getTypeCode() const
{
diff --git a/libqpdf/QPDF_Reserved.cc b/libqpdf/QPDF_Reserved.cc
index ad8d94a3..6cce2f2a 100644
--- a/libqpdf/QPDF_Reserved.cc
+++ b/libqpdf/QPDF_Reserved.cc
@@ -12,6 +12,13 @@ QPDF_Reserved::unparse()
return "";
}
+JSON
+QPDF_Reserved::getJSON()
+{
+ throw std::logic_error("attempt to generate JSON from QPDF_Reserved");
+ return JSON::makeNull();
+}
+
QPDFObject::object_type_e
QPDF_Reserved::getTypeCode() const
{
diff --git a/libqpdf/QPDF_Stream.cc b/libqpdf/QPDF_Stream.cc
index 384652e2..3733940d 100644
--- a/libqpdf/QPDF_Stream.cc
+++ b/libqpdf/QPDF_Stream.cc
@@ -74,6 +74,12 @@ QPDF_Stream::unparse()
QUtil::int_to_string(this->generation) + " R";
}
+JSON
+QPDF_Stream::getJSON()
+{
+ return this->stream_dict.getJSON();
+}
+
QPDFObject::object_type_e
QPDF_Stream::getTypeCode() const
{
@@ -127,6 +133,30 @@ QPDF_Stream::isDataModified() const
return (! this->token_filters.empty());
}
+qpdf_offset_t
+QPDF_Stream::getOffset() const
+{
+ return this->offset;
+}
+
+size_t
+QPDF_Stream::getLength() const
+{
+ return this->length;
+}
+
+PointerHolder<Buffer>
+QPDF_Stream::getStreamDataBuffer() const
+{
+ return this->stream_data;
+}
+
+PointerHolder<QPDFObjectHandle::StreamDataProvider>
+QPDF_Stream::getStreamDataProvider() const
+{
+ return this->stream_provider;
+}
+
PointerHolder<Buffer>
QPDF_Stream::getStreamData(qpdf_stream_decode_level_e decode_level)
{
diff --git a/libqpdf/QPDF_String.cc b/libqpdf/QPDF_String.cc
index eb31a808..bf1141d1 100644
--- a/libqpdf/QPDF_String.cc
+++ b/libqpdf/QPDF_String.cc
@@ -8,43 +8,6 @@
// be used.
#include <string.h>
-// First element is 128
-static unsigned short pdf_doc_to_unicode[] = {
- 0x2022, // 0x80 BULLET
- 0x2020, // 0x81 DAGGER
- 0x2021, // 0x82 DOUBLE DAGGER
- 0x2026, // 0x83 HORIZONTAL ELLIPSIS
- 0x2014, // 0x84 EM DASH
- 0x2013, // 0x85 EN DASH
- 0x0192, // 0x86 SMALL LETTER F WITH HOOK
- 0x2044, // 0x87 FRACTION SLASH (solidus)
- 0x2039, // 0x88 SINGLE LEFT-POINTING ANGLE QUOTATION MARK
- 0x203a, // 0x89 SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
- 0x2212, // 0x8a MINUS SIGN
- 0x2030, // 0x8b PER MILLE SIGN
- 0x201e, // 0x8c DOUBLE LOW-9 QUOTATION MARK (quotedblbase)
- 0x201c, // 0x8d LEFT DOUBLE QUOTATION MARK (double quote left)
- 0x201d, // 0x8e RIGHT DOUBLE QUOTATION MARK (quotedblright)
- 0x2018, // 0x8f LEFT SINGLE QUOTATION MARK (quoteleft)
- 0x2019, // 0x90 RIGHT SINGLE QUOTATION MARK (quoteright)
- 0x201a, // 0x91 SINGLE LOW-9 QUOTATION MARK (quotesinglbase)
- 0x2122, // 0x92 TRADE MARK SIGN
- 0xfb01, // 0x93 LATIN SMALL LIGATURE FI
- 0xfb02, // 0x94 LATIN SMALL LIGATURE FL
- 0x0141, // 0x95 LATIN CAPITAL LETTER L WITH STROKE
- 0x0152, // 0x96 LATIN CAPITAL LIGATURE OE
- 0x0160, // 0x97 LATIN CAPITAL LETTER S WITH CARON
- 0x0178, // 0x98 LATIN CAPITAL LETTER Y WITH DIAERESIS
- 0x017d, // 0x99 LATIN CAPITAL LETTER Z WITH CARON
- 0x0131, // 0x9a LATIN SMALL LETTER DOTLESS I
- 0x0142, // 0x9b LATIN SMALL LETTER L WITH STROKE
- 0x0153, // 0x9c LATIN SMALL LIGATURE OE
- 0x0161, // 0x9d LATIN SMALL LETTER S WITH CARON
- 0x017e, // 0x9e LATIN SMALL LETTER Z WITH CARON
- 0xfffd, // 0x9f UNDEFINED
- 0x20ac, // 0xa0 EURO SIGN
-};
-
// See above about ctype.
static bool is_ascii_printable(unsigned char ch)
{
@@ -67,53 +30,7 @@ QPDF_String::~QPDF_String()
QPDF_String*
QPDF_String::new_utf16(std::string const& utf8_val)
{
- std::string result = "\xfe\xff";
- size_t len = utf8_val.length();
- for (size_t i = 0; i < len; ++i)
- {
- unsigned char ch = static_cast<unsigned char>(utf8_val.at(i));
- if (ch < 128)
- {
- result += QUtil::toUTF16(ch);
- }
- else
- {
- size_t bytes_needed = 0;
- unsigned bit_check = 0x40;
- unsigned char to_clear = 0x80;
- while (ch & bit_check)
- {
- ++bytes_needed;
- to_clear |= bit_check;
- bit_check >>= 1;
- }
-
- if (((bytes_needed > 5) || (bytes_needed < 1)) ||
- ((i + bytes_needed) >= len))
- {
- result += "\xff\xfd";
- }
- else
- {
- unsigned long codepoint = (ch & ~to_clear);
- while (bytes_needed > 0)
- {
- --bytes_needed;
- ch = utf8_val.at(++i);
- if ((ch & 0xc0) != 0x80)
- {
- --i;
- codepoint = 0xfffd;
- break;
- }
- codepoint <<= 6;
- codepoint += (ch & 0x3f);
- }
- result += QUtil::toUTF16(codepoint);
- }
- }
- }
- return new QPDF_String(result);
+ return new QPDF_String(QUtil::utf8_to_utf16(utf8_val));
}
std::string
@@ -122,6 +39,12 @@ QPDF_String::unparse()
return unparse(false);
}
+JSON
+QPDF_String::getJSON()
+{
+ return JSON::makeString(getUTF8Val());
+}
+
QPDFObject::object_type_e
QPDF_String::getTypeCode() const
{
@@ -250,62 +173,12 @@ QPDF_String::getVal() const
std::string
QPDF_String::getUTF8Val() const
{
- std::string result;
- size_t len = this->val.length();
- if ((len >= 2) && (len % 2 == 0) &&
- (this->val.at(0) == '\xfe') && (this->val.at(1) == '\xff'))
+ if (QUtil::is_utf16(this->val))
{
- // This is a Unicode string using big-endian UTF-16. This
- // code uses unsigned long and unsigned short to hold
- // codepoint values. It requires unsigned long to be at least
- // 32 bits and unsigned short to be at least 16 bits, but it
- // will work fine if they are larger.
- unsigned long codepoint = 0L;
- for (unsigned int i = 2; i < len; i += 2)
- {
- // Convert from UTF16-BE. If we get a malformed
- // codepoint, this code will generate incorrect output
- // without giving a warning. Specifically, a high
- // codepoint not followed by a low codepoint will be
- // discarded, and a low codepoint not preceded by a high
- // codepoint will just get its low 10 bits output.
- unsigned short bits =
- (static_cast<unsigned char>(this->val.at(i)) << 8) +
- static_cast<unsigned char>(this->val.at(i+1));
- if ((bits & 0xFC00) == 0xD800)
- {
- codepoint = 0x10000 + ((bits & 0x3FF) << 10);
- continue;
- }
- else if ((bits & 0xFC00) == 0xDC00)
- {
- if (codepoint != 0)
- {
- QTC::TC("qpdf", "QPDF_String non-trivial UTF-16");
- }
- codepoint += bits & 0x3FF;
- }
- else
- {
- codepoint = bits;
- }
-
- result += QUtil::toUTF8(codepoint);
- codepoint = 0;
- }
+ return QUtil::utf16_to_utf8(this->val);
}
else
{
- for (unsigned int i = 0; i < len; ++i)
- {
- unsigned char ch = static_cast<unsigned char>(this->val.at(i));
- unsigned short val = ch;
- if ((ch >= 128) && (ch <= 160))
- {
- val = pdf_doc_to_unicode[ch - 128];
- }
- result += QUtil::toUTF8(val);
- }
+ return QUtil::pdf_doc_to_utf8(this->val);
}
- return result;
}
diff --git a/libqpdf/QPDF_encryption.cc b/libqpdf/QPDF_encryption.cc
index 54d7ad03..c0778b6a 100644
--- a/libqpdf/QPDF_encryption.cc
+++ b/libqpdf/QPDF_encryption.cc
@@ -437,11 +437,10 @@ QPDF::compute_encryption_key_from_password(
md5.encodeDataIncrementally(bytes, 4);
}
MD5::Digest digest;
- iterate_md5_digest(md5, digest, ((data.getR() >= 3) ? 50 : 0),
- data.getLengthBytes());
- return std::string(reinterpret_cast<char*>(digest),
- std::min(static_cast<int>(sizeof(digest)),
- data.getLengthBytes()));
+ int key_len = std::min(static_cast<int>(sizeof(digest)),
+ data.getLengthBytes());
+ iterate_md5_digest(md5, digest, ((data.getR() >= 3) ? 50 : 0), key_len);
+ return std::string(reinterpret_cast<char*>(digest), key_len);
}
static void
@@ -464,8 +463,9 @@ compute_O_rc4_key(std::string const& user_password,
md5.encodeDataIncrementally(
pad_or_truncate_password_V4(password).c_str(), key_bytes);
MD5::Digest digest;
- iterate_md5_digest(md5, digest, ((data.getR() >= 3) ? 50 : 0),
- data.getLengthBytes());
+ int key_len = std::min(static_cast<int>(sizeof(digest)),
+ data.getLengthBytes());
+ iterate_md5_digest(md5, digest, ((data.getR() >= 3) ? 50 : 0), key_len);
memcpy(key, digest, OU_key_bytes_V4);
}
@@ -764,14 +764,15 @@ QPDF::recover_encryption_key_with_password(
}
QPDF::encryption_method_e
-QPDF::interpretCF(QPDFObjectHandle cf)
+QPDF::interpretCF(
+ PointerHolder<EncryptionParameters> encp, QPDFObjectHandle cf)
{
if (cf.isName())
{
std::string filter = cf.getName();
- if (this->m->crypt_filters.count(filter) != 0)
+ if (encp->crypt_filters.count(filter) != 0)
{
- return this->m->crypt_filters[filter];
+ return encp->crypt_filters[filter];
}
else if (filter == "/Identity")
{
@@ -792,11 +793,11 @@ QPDF::interpretCF(QPDFObjectHandle cf)
void
QPDF::initializeEncryption()
{
- if (this->m->encryption_initialized)
+ if (this->m->encp->encryption_initialized)
{
return;
}
- this->m->encryption_initialized = true;
+ this->m->encp->encryption_initialized = true;
// After we initialize encryption parameters, we must used stored
// key information and never look at /Encrypt again. Otherwise,
@@ -811,7 +812,7 @@ QPDF::initializeEncryption()
// Go ahead and set this->m->encrypted here. That way, isEncrypted
// will return true even if there were errors reading the
// encryption dictionary.
- this->m->encrypted = true;
+ this->m->encp->encrypted = true;
std::string id1;
QPDFObjectHandle id_obj = this->m->trailer.getKey("/ID");
@@ -885,8 +886,8 @@ QPDF::initializeEncryption()
QUtil::int_to_string(V) + " (max 5)");
}
- this->m->encryption_V = V;
- this->m->encryption_R = R;
+ this->m->encp->encryption_V = V;
+ this->m->encp->encryption_R = R;
// OE, UE, and Perms are only present if V >= 5.
std::string OE;
@@ -949,10 +950,10 @@ QPDF::initializeEncryption()
}
}
- this->m->encrypt_metadata = true;
+ this->m->encp->encrypt_metadata = true;
if ((V >= 4) && (encryption_dict.getKey("/EncryptMetadata").isBool()))
{
- this->m->encrypt_metadata =
+ this->m->encp->encrypt_metadata =
encryption_dict.getKey("/EncryptMetadata").getBoolValue();
}
@@ -993,40 +994,41 @@ QPDF::initializeEncryption()
method = e_unknown;
}
}
- this->m->crypt_filters[filter] = method;
+ this->m->encp->crypt_filters[filter] = method;
}
}
QPDFObjectHandle StmF = encryption_dict.getKey("/StmF");
QPDFObjectHandle StrF = encryption_dict.getKey("/StrF");
QPDFObjectHandle EFF = encryption_dict.getKey("/EFF");
- this->m->cf_stream = interpretCF(StmF);
- this->m->cf_string = interpretCF(StrF);
+ this->m->encp->cf_stream = interpretCF(this->m->encp, StmF);
+ this->m->encp->cf_string = interpretCF(this->m->encp, StrF);
if (EFF.isName())
{
- this->m->cf_file = interpretCF(EFF);
+ this->m->encp->cf_file = interpretCF(this->m->encp, EFF);
}
else
{
- this->m->cf_file = this->m->cf_stream;
+ this->m->encp->cf_file = this->m->encp->cf_stream;
}
}
EncryptionData data(V, R, Length / 8, P, O, U, OE, UE, Perms,
- id1, this->m->encrypt_metadata);
+ id1, this->m->encp->encrypt_metadata);
if (this->m->provided_password_is_hex_key)
{
// ignore passwords in file
}
else if (check_owner_password(
- this->m->user_password, this->m->provided_password, data))
+ this->m->encp->user_password,
+ this->m->encp->provided_password, data))
{
// password supplied was owner password; user_password has
// been initialized for V < 5
}
- else if (check_user_password(this->m->provided_password, data))
+ else if (check_user_password(this->m->encp->provided_password, data))
{
- this->m->user_password = this->m->provided_password;
+ this->m->encp->user_password = this->m->encp->provided_password;
}
else
{
@@ -1036,15 +1038,16 @@ QPDF::initializeEncryption()
if (this->m->provided_password_is_hex_key)
{
- this->m->encryption_key = QUtil::hex_decode(this->m->provided_password);
+ this->m->encp->encryption_key =
+ QUtil::hex_decode(this->m->encp->provided_password);
}
else if (V < 5)
{
// For V < 5, the user password is encrypted with the owner
// password, and the user password is always used for
// computing the encryption key.
- this->m->encryption_key = compute_encryption_key(
- this->m->user_password, data);
+ this->m->encp->encryption_key = compute_encryption_key(
+ this->m->encp->user_password, data);
}
else
{
@@ -1052,8 +1055,8 @@ QPDF::initializeEncryption()
// compute the encryption key, and neither password can be
// used to recover the other.
bool perms_valid;
- this->m->encryption_key = recover_encryption_key_with_password(
- this->m->provided_password, data, perms_valid);
+ this->m->encp->encryption_key = recover_encryption_key_with_password(
+ this->m->encp->provided_password, data, perms_valid);
if (! perms_valid)
{
warn(QPDFExc(qpdf_e_damaged_pdf, this->m->file->getName(),
@@ -1066,26 +1069,28 @@ QPDF::initializeEncryption()
}
std::string
-QPDF::getKeyForObject(int objid, int generation, bool use_aes)
+QPDF::getKeyForObject(
+ PointerHolder<EncryptionParameters> encp,
+ int objid, int generation, bool use_aes)
{
- if (! this->m->encrypted)
+ if (! encp->encrypted)
{
throw std::logic_error(
"request for encryption key in non-encrypted PDF");
}
- if (! ((objid == this->m->cached_key_objid) &&
- (generation == this->m->cached_key_generation)))
+ if (! ((objid == encp->cached_key_objid) &&
+ (generation == encp->cached_key_generation)))
{
- this->m->cached_object_encryption_key =
- compute_data_key(this->m->encryption_key, objid, generation,
- use_aes, this->m->encryption_V,
- this->m->encryption_R);
- this->m->cached_key_objid = objid;
- this->m->cached_key_generation = generation;
+ encp->cached_object_encryption_key =
+ compute_data_key(encp->encryption_key, objid, generation,
+ use_aes, encp->encryption_V,
+ encp->encryption_R);
+ encp->cached_key_objid = objid;
+ encp->cached_key_generation = generation;
}
- return this->m->cached_object_encryption_key;
+ return encp->cached_object_encryption_key;
}
void
@@ -1096,9 +1101,9 @@ QPDF::decryptString(std::string& str, int objid, int generation)
return;
}
bool use_aes = false;
- if (this->m->encryption_V >= 4)
+ if (this->m->encp->encryption_V >= 4)
{
- switch (this->m->cf_string)
+ switch (this->m->encp->cf_string)
{
case e_none:
return;
@@ -1123,12 +1128,14 @@ QPDF::decryptString(std::string& str, int objid, int generation)
" strings may be decrypted improperly"));
// To avoid repeated warnings, reset cf_string. Assume
// we'd want to use AES if V == 4.
- this->m->cf_string = e_aes;
+ this->m->encp->cf_string = e_aes;
+ use_aes = true;
break;
}
}
- std::string key = getKeyForObject(objid, generation, use_aes);
+ std::string key = getKeyForObject(
+ this->m->encp, objid, generation, use_aes);
try
{
if (use_aes)
@@ -1172,8 +1179,12 @@ QPDF::decryptString(std::string& str, int objid, int generation)
}
void
-QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation,
+QPDF::decryptStream(PointerHolder<EncryptionParameters> encp,
+ PointerHolder<InputSource> file,
+ QPDF& qpdf_for_warning, Pipeline*& pipeline,
+ int objid, int generation,
QPDFObjectHandle& stream_dict,
+ bool is_attachment_stream,
std::vector<PointerHolder<Pipeline> >& heap)
{
std::string type;
@@ -1187,7 +1198,7 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation,
return;
}
bool use_aes = false;
- if (this->m->encryption_V >= 4)
+ if (encp->encryption_V >= 4)
{
encryption_method_e method = e_unknown;
std::string method_source = "/StmF from /Encrypt dictionary";
@@ -1203,7 +1214,7 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation,
"/CryptFilterDecodeParms"))
{
QTC::TC("qpdf", "QPDF_encryption stream crypt filter");
- method = interpretCF(decode_parms.getKey("/Name"));
+ method = interpretCF(encp, decode_parms.getKey("/Name"));
method_source = "stream's Crypt decode parameters";
}
}
@@ -1226,7 +1237,7 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation,
{
QTC::TC("qpdf", "QPDF_encrypt crypt array");
method = interpretCF(
- crypt_params.getKey("/Name"));
+ encp, crypt_params.getKey("/Name"));
method_source = "stream's Crypt "
"decode parameters (array)";
}
@@ -1238,21 +1249,21 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation,
if (method == e_unknown)
{
- if ((! this->m->encrypt_metadata) && (type == "/Metadata"))
+ if ((! encp->encrypt_metadata) && (type == "/Metadata"))
{
QTC::TC("qpdf", "QPDF_encryption cleartext metadata");
method = e_none;
}
else
{
- if (this->m->attachment_streams.count(
- QPDFObjGen(objid, generation)) > 0)
+ if (is_attachment_stream)
{
- method = this->m->cf_file;
+ QTC::TC("qpdf", "QPDF_encryption attachment stream");
+ method = encp->cf_file;
}
else
{
- method = this->m->cf_stream;
+ method = encp->cf_stream;
}
}
}
@@ -1276,19 +1287,20 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation,
default:
// filter local to this stream.
- warn(QPDFExc(qpdf_e_damaged_pdf, this->m->file->getName(),
- this->m->last_object_description,
- this->m->file->getLastOffset(),
- "unknown encryption filter for streams"
- " (check " + method_source + ");"
- " streams may be decrypted improperly"));
+ qpdf_for_warning.warn(
+ QPDFExc(qpdf_e_damaged_pdf, file->getName(),
+ "", file->getLastOffset(),
+ "unknown encryption filter for streams"
+ " (check " + method_source + ");"
+ " streams may be decrypted improperly"));
// To avoid repeated warnings, reset cf_stream. Assume
// we'd want to use AES if V == 4.
- this->m->cf_stream = e_aes;
+ encp->cf_stream = e_aes;
+ use_aes = true;
break;
}
}
- std::string key = getKeyForObject(objid, generation, use_aes);
+ std::string key = getKeyForObject(encp, objid, generation, use_aes);
if (use_aes)
{
QTC::TC("qpdf", "QPDF_encryption aes decode stream");
@@ -1348,13 +1360,13 @@ QPDF::compute_encryption_parameters_V5(
std::string const&
QPDF::getPaddedUserPassword() const
{
- return this->m->user_password;
+ return this->m->encp->user_password;
}
std::string
QPDF::getTrimmedUserPassword() const
{
- std::string result = this->m->user_password;
+ std::string result = this->m->encp->user_password;
trim_user_password(result);
return result;
}
@@ -1362,13 +1374,13 @@ QPDF::getTrimmedUserPassword() const
std::string
QPDF::getEncryptionKey() const
{
- return this->m->encryption_key;
+ return this->m->encp->encryption_key;
}
bool
QPDF::isEncrypted() const
{
- return this->m->encrypted;
+ return this->m->encp->encrypted;
}
bool
@@ -1385,7 +1397,7 @@ QPDF::isEncrypted(int& R, int& P, int& V,
encryption_method_e& string_method,
encryption_method_e& file_method)
{
- if (this->m->encrypted)
+ if (this->m->encp->encrypted)
{
QPDFObjectHandle trailer = getTrailer();
QPDFObjectHandle encrypt = trailer.getKey("/Encrypt");
@@ -1395,9 +1407,9 @@ QPDF::isEncrypted(int& R, int& P, int& V,
P = Pkey.getIntValue();
R = Rkey.getIntValue();
V = Vkey.getIntValue();
- stream_method = this->m->cf_stream;
- string_method = this->m->cf_stream;
- file_method = this->m->cf_file;
+ stream_method = this->m->encp->cf_stream;
+ string_method = this->m->encp->cf_string;
+ file_method = this->m->encp->cf_file;
return true;
}
else
diff --git a/libqpdf/QPDF_linearization.cc b/libqpdf/QPDF_linearization.cc
index ecf81bee..5de1e8f6 100644
--- a/libqpdf/QPDF_linearization.cc
+++ b/libqpdf/QPDF_linearization.cc
@@ -675,14 +675,20 @@ QPDF::checkLinearizationInternal()
qpdf_offset_t
QPDF::maxEnd(ObjUser const& ou)
{
- assert(this->m->obj_user_to_objects.count(ou) > 0);
+ if (this->m->obj_user_to_objects.count(ou) == 0)
+ {
+ stopOnError("no entry in object user table for requested object user");
+ }
std::set<QPDFObjGen> const& ogs = this->m->obj_user_to_objects[ou];
qpdf_offset_t end = 0;
for (std::set<QPDFObjGen>::const_iterator iter = ogs.begin();
iter != ogs.end(); ++iter)
{
QPDFObjGen const& og = *iter;
- assert(this->m->obj_cache.count(og) > 0);
+ if (this->m->obj_cache.count(og) == 0)
+ {
+ stopOnError("unknown object referenced in object user table");
+ }
end = std::max(end, this->m->obj_cache[og].end_after_space);
}
return end;
@@ -745,7 +751,11 @@ QPDF::lengthNextN(int first_object, int n,
}
else
{
- assert(this->m->obj_cache.count(og) > 0);
+ if (this->m->obj_cache.count(og) == 0)
+ {
+ stopOnError("found unknown object while"
+ " calculating length for linearization data");
+ }
length += this->m->obj_cache[og].end_after_space -
getLinearizationOffset(og);
}
@@ -780,8 +790,11 @@ QPDF::checkHPageOffset(std::list<std::string>& errors,
int table_offset = adjusted_offset(
this->m->page_offset_hints.first_page_offset);
QPDFObjGen first_page_og(pages.at(0).getObjGen());
- assert(this->m->xref_table.count(first_page_og) > 0);
- int offset = getLinearizationOffset(first_page_og);
+ if (this->m->xref_table.count(first_page_og) == 0)
+ {
+ stopOnError("supposed first page object is not known");
+ }
+ qpdf_offset_t offset = getLinearizationOffset(first_page_og);
if (table_offset != offset)
{
warnings.push_back("first page object offset mismatch");
@@ -791,7 +804,10 @@ QPDF::checkHPageOffset(std::list<std::string>& errors,
{
QPDFObjGen page_og(pages.at(pageno).getObjGen());
int first_object = page_og.getObj();
- assert(this->m->xref_table.count(page_og) > 0);
+ if (this->m->xref_table.count(page_og) == 0)
+ {
+ stopOnError("unknown object in page offset hint table");
+ }
offset = getLinearizationOffset(page_og);
HPageOffsetEntry& he = this->m->page_offset_hints.entries.at(pageno);
@@ -955,7 +971,10 @@ QPDF::checkHSharedObject(std::list<std::string>& errors,
cur_object = so.first_shared_obj;
QPDFObjGen og(cur_object, 0);
- assert(this->m->xref_table.count(og) > 0);
+ if (this->m->xref_table.count(og) == 0)
+ {
+ stopOnError("unknown object in shared object hint table");
+ }
int offset = getLinearizationOffset(og);
int h_offset = adjusted_offset(so.first_shared_offset);
if (offset != h_offset)
@@ -1018,7 +1037,10 @@ QPDF::checkHOutlines(std::list<std::string>& warnings)
return;
}
QPDFObjGen og(outlines.getObjGen());
- assert(this->m->xref_table.count(og) > 0);
+ if (this->m->xref_table.count(og) == 0)
+ {
+ stopOnError("unknown object in outlines hint table");
+ }
int offset = getLinearizationOffset(og);
ObjUser ou(ObjUser::ou_root_key, "/Outlines");
int length = maxEnd(ou) - offset;
@@ -1513,7 +1535,11 @@ QPDF::calculateLinearizationData(std::map<int, int> const& object_stream_data)
// Part 4: open document objects. We don't care about the order.
- assert(lc_root.size() == 1);
+ if (lc_root.size() != 1)
+ {
+ stopOnError("found other than one root while"
+ " calculating linearization data");
+ }
this->m->part4.push_back(objGenToIndirect(*(lc_root.begin())));
for (std::set<QPDFObjGen>::iterator iter = lc_open_document.begin();
iter != lc_open_document.end(); ++iter)
@@ -1594,7 +1620,11 @@ QPDF::calculateLinearizationData(std::map<int, int> const& object_stream_data)
this->m->c_page_offset_data.entries.at(i).nobjects = 1;
ObjUser ou(ObjUser::ou_page, i);
- assert(this->m->obj_user_to_objects.count(ou) > 0);
+ if (this->m->obj_user_to_objects.count(ou) == 0)
+ {
+ stopOnError("found unreferenced page while"
+ " calculating linearization data");
+ }
std::set<QPDFObjGen> ogs = this->m->obj_user_to_objects[ou];
for (std::set<QPDFObjGen>::iterator iter = ogs.begin();
iter != ogs.end(); ++iter)
@@ -1638,7 +1668,11 @@ QPDF::calculateLinearizationData(std::map<int, int> const& object_stream_data)
// Place the pages tree.
std::set<QPDFObjGen> pages_ogs =
this->m->obj_user_to_objects[ObjUser(ObjUser::ou_root_key, "/Pages")];
- assert(! pages_ogs.empty());
+ if (pages_ogs.empty())
+ {
+ stopOnError("found empty pages tree while"
+ " calculating linearization data");
+ }
for (std::set<QPDFObjGen>::iterator iter = pages_ogs.begin();
iter != pages_ogs.end(); ++iter)
{
@@ -1790,7 +1824,11 @@ QPDF::calculateLinearizationData(std::map<int, int> const& object_stream_data)
{
CHPageOffsetEntry& pe = this->m->c_page_offset_data.entries.at(i);
ObjUser ou(ObjUser::ou_page, i);
- assert(this->m->obj_user_to_objects.count(ou) > 0);
+ if (this->m->obj_user_to_objects.count(ou) == 0)
+ {
+ stopOnError("found unreferenced page while"
+ " calculating linearization data");
+ }
std::set<QPDFObjGen> const& ogs = this->m->obj_user_to_objects[ou];
for (std::set<QPDFObjGen>::const_iterator iter = ogs.begin();
iter != ogs.end(); ++iter)
@@ -1869,12 +1907,20 @@ QPDF::outputLengthNextN(
// the output file starting with whatever object in_object from
// the input file mapped to.
- assert(obj_renumber.count(in_object) > 0);
+ if (obj_renumber.count(in_object) == 0)
+ {
+ stopOnError("found object that is not renumbered while"
+ " writing linearization data");
+ }
int first = (*(obj_renumber.find(in_object))).second;
int length = 0;
for (int i = 0; i < n; ++i)
{
- assert(lengths.count(first + i) > 0);
+ if (lengths.count(first + i) == 0)
+ {
+ stopOnError("found item with unknown length"
+ " while writing linearization data");
+ }
length += (*(lengths.find(first + i))).second;
}
return length;
@@ -1958,8 +2004,12 @@ QPDF::calculateHPageOffset(
for (unsigned int i = 0; i < npages; ++i)
{
// Adjust delta entries
- assert(phe.at(i).delta_nobjects >= min_nobjects);
- assert(phe.at(i).delta_page_length >= min_length);
+ if ((phe.at(i).delta_nobjects < min_nobjects) ||
+ (phe.at(i).delta_page_length < min_length))
+ {
+ stopOnError("found too small delta nobjects or delta page length"
+ " while writing linearization data");
+ }
phe.at(i).delta_nobjects -= min_nobjects;
phe.at(i).delta_page_length -= min_length;
phe.at(i).delta_content_length = phe.at(i).delta_page_length;
@@ -2019,7 +2069,11 @@ QPDF::calculateHSharedObject(
for (int i = 0; i < cso.nshared_total; ++i)
{
// Adjust deltas
- assert(soe.at(i).delta_group_length >= min_length);
+ if (soe.at(i).delta_group_length < min_length)
+ {
+ stopOnError("found too small group length while"
+ " writing linearization data");
+ }
soe.at(i).delta_group_length -= min_length;
}
}
@@ -2158,7 +2212,11 @@ QPDF::writeHSharedObject(BitWriter& w)
for (int i = 0; i < nitems; ++i)
{
// If signature were present, we'd have to write a 128-bit hash.
- assert(entries.at(i).signature_present == 0);
+ if (entries.at(i).signature_present != 0)
+ {
+ stopOnError("found unexpected signature present"
+ " while writing linearization data");
+ }
}
write_vector_int(w, nitems, entries,
t.nbits_nobjects,
diff --git a/libqpdf/QPDF_optimization.cc b/libqpdf/QPDF_optimization.cc
index 1e42865c..5d959f10 100644
--- a/libqpdf/QPDF_optimization.cc
+++ b/libqpdf/QPDF_optimization.cc
@@ -156,14 +156,24 @@ QPDF::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys)
return;
}
+ // Calling getAllPages() resolves any duplicated page objects.
+ getAllPages();
+
// key_ancestors is a mapping of page attribute keys to a stack of
// Pages nodes that contain values for them.
std::map<std::string, std::vector<QPDFObjectHandle> > key_ancestors;
this->m->all_pages.clear();
+ std::set<QPDFObjGen> visited;
pushInheritedAttributesToPageInternal(
this->m->trailer.getKey("/Root").getKey("/Pages"),
- key_ancestors, this->m->all_pages, allow_changes, warn_skipped_keys);
- assert(key_ancestors.empty());
+ key_ancestors, this->m->all_pages, allow_changes, warn_skipped_keys,
+ visited);
+ if (! key_ancestors.empty())
+ {
+ throw std::logic_error(
+ "key_ancestors not empty after"
+ " pushing inherited attributes to pages");
+ }
this->m->pushed_inherited_attributes_to_pages = true;
}
@@ -172,19 +182,6 @@ QPDF::pushInheritedAttributesToPageInternal(
QPDFObjectHandle cur_pages,
std::map<std::string, std::vector<QPDFObjectHandle> >& key_ancestors,
std::vector<QPDFObjectHandle>& pages,
- bool allow_changes, bool warn_skipped_keys)
-{
- std::set<QPDFObjGen> visited;
- pushInheritedAttributesToPageInternal2(
- cur_pages, key_ancestors, pages, allow_changes,
- warn_skipped_keys, visited);
-}
-
-void
-QPDF::pushInheritedAttributesToPageInternal2(
- QPDFObjectHandle cur_pages,
- std::map<std::string, std::vector<QPDFObjectHandle> >& key_ancestors,
- std::vector<QPDFObjectHandle>& pages,
bool allow_changes, bool warn_skipped_keys,
std::set<QPDFObjGen>& visited)
{
@@ -203,10 +200,10 @@ QPDF::pushInheritedAttributesToPageInternal2(
if (type == "/Pages")
{
- // Make a list of inheritable keys. Any key other than /Type,
- // /Parent, Kids, or /Count is an inheritable attribute. Push
- // this object onto the stack of pages nodes that have values
- // for this attribute.
+ // Make a list of inheritable keys. Only the keys /MediaBox,
+ // /CropBox, /Resources, and /Rotate are inheritable
+ // attributes. Push this object onto the stack of pages nodes
+ // that have values for this attribute.
std::set<std::string> inheritable_keys;
std::set<std::string> keys = cur_pages.getKeys();
@@ -283,7 +280,7 @@ QPDF::pushInheritedAttributesToPageInternal2(
int n = kids.getArrayNItems();
for (int i = 0; i < n; ++i)
{
- pushInheritedAttributesToPageInternal2(
+ pushInheritedAttributesToPageInternal(
kids.getArrayItem(i), key_ancestors, pages,
allow_changes, warn_skipped_keys, visited);
}
diff --git a/libqpdf/QPDF_pages.cc b/libqpdf/QPDF_pages.cc
index ea5afdb5..f4156d03 100644
--- a/libqpdf/QPDF_pages.cc
+++ b/libqpdf/QPDF_pages.cc
@@ -47,23 +47,19 @@ QPDF::getAllPages()
// initialize this->m->all_pages.
if (this->m->all_pages.empty())
{
- getAllPagesInternal(getRoot().getKey("/Pages"), this->m->all_pages);
+ std::set<QPDFObjGen> visited;
+ std::set<QPDFObjGen> seen;
+ getAllPagesInternal(getRoot().getKey("/Pages"), this->m->all_pages,
+ visited, seen);
}
return this->m->all_pages;
}
void
QPDF::getAllPagesInternal(QPDFObjectHandle cur_pages,
- std::vector<QPDFObjectHandle>& result)
-{
- std::set<QPDFObjGen> visited;
- getAllPagesInternal2(cur_pages, result, visited);
-}
-
-void
-QPDF::getAllPagesInternal2(QPDFObjectHandle cur_pages,
- std::vector<QPDFObjectHandle>& result,
- std::set<QPDFObjGen>& visited)
+ std::vector<QPDFObjectHandle>& result,
+ std::set<QPDFObjGen>& visited,
+ std::set<QPDFObjGen>& seen)
{
QPDFObjGen this_og = cur_pages.getObjGen();
if (visited.count(this_og) > 0)
@@ -94,11 +90,27 @@ QPDF::getAllPagesInternal2(QPDFObjectHandle cur_pages,
int n = kids.getArrayNItems();
for (int i = 0; i < n; ++i)
{
- getAllPagesInternal2(kids.getArrayItem(i), result, visited);
+ QPDFObjectHandle kid = kids.getArrayItem(i);
+ if (! kid.isIndirect())
+ {
+ QTC::TC("qpdf", "QPDF handle direct page object");
+ kid = makeIndirectObject(kid);
+ kids.setArrayItem(i, kid);
+ }
+ else if (seen.count(kid.getObjGen()))
+ {
+ // Make a copy of the page. This does the same as
+ // shallowCopyPage in QPDFPageObjectHelper.
+ QTC::TC("qpdf", "QPDF resolve duplicated page object");
+ kid = makeIndirectObject(QPDFObjectHandle(kid).shallowCopy());
+ kids.setArrayItem(i, kid);
+ }
+ getAllPagesInternal(kid, result, visited, seen);
}
}
else if (type == "/Page")
{
+ seen.insert(this_og);
result.push_back(cur_pages);
}
else
@@ -201,7 +213,7 @@ QPDF::insertPage(QPDFObjectHandle newpage, int pos)
{
QTC::TC("qpdf", "QPDF insert foreign page");
newpage.getOwningQPDF()->pushInheritedAttributesToPage();
- newpage = copyForeignObject(newpage, true);
+ newpage = copyForeignObject(newpage);
}
else
{
diff --git a/libqpdf/QUtil.cc b/libqpdf/QUtil.cc
index e2bc0bac..58646ade 100644
--- a/libqpdf/QUtil.cc
+++ b/libqpdf/QUtil.cc
@@ -7,12 +7,15 @@
# include <qpdf/InsecureRandomDataProvider.hh>
#endif
#include <qpdf/SecureRandomDataProvider.hh>
+#include <qpdf/QPDFSystemError.hh>
+#include <qpdf/QTC.hh>
#include <cmath>
#include <iomanip>
#include <sstream>
#include <fstream>
#include <stdexcept>
+#include <set>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
@@ -28,6 +31,208 @@
#include <sys/stat.h>
#endif
+// First element is 128
+static unsigned short pdf_doc_to_unicode[] = {
+ 0x2022, // 0x80 BULLET
+ 0x2020, // 0x81 DAGGER
+ 0x2021, // 0x82 DOUBLE DAGGER
+ 0x2026, // 0x83 HORIZONTAL ELLIPSIS
+ 0x2014, // 0x84 EM DASH
+ 0x2013, // 0x85 EN DASH
+ 0x0192, // 0x86 SMALL LETTER F WITH HOOK
+ 0x2044, // 0x87 FRACTION SLASH (solidus)
+ 0x2039, // 0x88 SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+ 0x203a, // 0x89 SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+ 0x2212, // 0x8a MINUS SIGN
+ 0x2030, // 0x8b PER MILLE SIGN
+ 0x201e, // 0x8c DOUBLE LOW-9 QUOTATION MARK (quotedblbase)
+ 0x201c, // 0x8d LEFT DOUBLE QUOTATION MARK (double quote left)
+ 0x201d, // 0x8e RIGHT DOUBLE QUOTATION MARK (quotedblright)
+ 0x2018, // 0x8f LEFT SINGLE QUOTATION MARK (quoteleft)
+ 0x2019, // 0x90 RIGHT SINGLE QUOTATION MARK (quoteright)
+ 0x201a, // 0x91 SINGLE LOW-9 QUOTATION MARK (quotesinglbase)
+ 0x2122, // 0x92 TRADE MARK SIGN
+ 0xfb01, // 0x93 LATIN SMALL LIGATURE FI
+ 0xfb02, // 0x94 LATIN SMALL LIGATURE FL
+ 0x0141, // 0x95 LATIN CAPITAL LETTER L WITH STROKE
+ 0x0152, // 0x96 LATIN CAPITAL LIGATURE OE
+ 0x0160, // 0x97 LATIN CAPITAL LETTER S WITH CARON
+ 0x0178, // 0x98 LATIN CAPITAL LETTER Y WITH DIAERESIS
+ 0x017d, // 0x99 LATIN CAPITAL LETTER Z WITH CARON
+ 0x0131, // 0x9a LATIN SMALL LETTER DOTLESS I
+ 0x0142, // 0x9b LATIN SMALL LETTER L WITH STROKE
+ 0x0153, // 0x9c LATIN SMALL LIGATURE OE
+ 0x0161, // 0x9d LATIN SMALL LETTER S WITH CARON
+ 0x017e, // 0x9e LATIN SMALL LETTER Z WITH CARON
+ 0xfffd, // 0x9f UNDEFINED
+ 0x20ac, // 0xa0 EURO SIGN
+};
+static unsigned short win_ansi_to_unicode[] = {
+ 0x20ac, // 0x80
+ 0xfffd, // 0x81
+ 0x201a, // 0x82
+ 0x0192, // 0x83
+ 0x201e, // 0x84
+ 0x2026, // 0x85
+ 0x2020, // 0x86
+ 0x2021, // 0x87
+ 0x02c6, // 0x88
+ 0x2030, // 0x89
+ 0x0160, // 0x8a
+ 0x2039, // 0x8b
+ 0x0152, // 0x8c
+ 0xfffd, // 0x8d
+ 0x017d, // 0x8e
+ 0xfffd, // 0x8f
+ 0xfffd, // 0x90
+ 0x2018, // 0x91
+ 0x2019, // 0x92
+ 0x201c, // 0x93
+ 0x201d, // 0x94
+ 0x2022, // 0x95
+ 0x2013, // 0x96
+ 0x2014, // 0x97
+ 0x0303, // 0x98
+ 0x2122, // 0x99
+ 0x0161, // 0x9a
+ 0x203a, // 0x9b
+ 0x0153, // 0x9c
+ 0xfffd, // 0x9d
+ 0x017e, // 0x9e
+ 0x0178, // 0x9f
+ 0x00a0, // 0xa0
+};
+static unsigned short mac_roman_to_unicode[] = {
+ 0x00c4, // 0x80
+ 0x00c5, // 0x81
+ 0x00c7, // 0x82
+ 0x00c9, // 0x83
+ 0x00d1, // 0x84
+ 0x00d6, // 0x85
+ 0x00dc, // 0x86
+ 0x00e1, // 0x87
+ 0x00e0, // 0x88
+ 0x00e2, // 0x89
+ 0x00e4, // 0x8a
+ 0x00e3, // 0x8b
+ 0x00e5, // 0x8c
+ 0x00e7, // 0x8d
+ 0x00e9, // 0x8e
+ 0x00e8, // 0x8f
+ 0x00ea, // 0x90
+ 0x00eb, // 0x91
+ 0x00ed, // 0x92
+ 0x00ec, // 0x93
+ 0x00ee, // 0x94
+ 0x00ef, // 0x95
+ 0x00f1, // 0x96
+ 0x00f3, // 0x97
+ 0x00f2, // 0x98
+ 0x00f4, // 0x99
+ 0x00f6, // 0x9a
+ 0x00f5, // 0x9b
+ 0x00fa, // 0x9c
+ 0x00f9, // 0x9d
+ 0x00fb, // 0x9e
+ 0x00fc, // 0x9f
+ 0x2020, // 0xa0
+ 0x00b0, // 0xa1
+ 0x00a2, // 0xa2
+ 0x00a3, // 0xa3
+ 0x00a7, // 0xa4
+ 0x2022, // 0xa5
+ 0x00b6, // 0xa6
+ 0x00df, // 0xa7
+ 0x00ae, // 0xa8
+ 0x00a9, // 0xa9
+ 0x2122, // 0xaa
+ 0x0301, // 0xab
+ 0x0308, // 0xac
+ 0xfffd, // 0xad
+ 0x00c6, // 0xae
+ 0x00d8, // 0xaf
+ 0xfffd, // 0xb0
+ 0x00b1, // 0xb1
+ 0xfffd, // 0xb2
+ 0xfffd, // 0xb3
+ 0x00a5, // 0xb4
+ 0x03bc, // 0xb5
+ 0xfffd, // 0xb6
+ 0xfffd, // 0xb7
+ 0xfffd, // 0xb8
+ 0xfffd, // 0xb9
+ 0xfffd, // 0xba
+ 0x1d43, // 0xbb
+ 0x1d52, // 0xbc
+ 0xfffd, // 0xbd
+ 0x00e6, // 0xbe
+ 0x00f8, // 0xbf
+ 0x00bf, // 0xc0
+ 0x00a1, // 0xc1
+ 0x00ac, // 0xc2
+ 0xfffd, // 0xc3
+ 0x0192, // 0xc4
+ 0xfffd, // 0xc5
+ 0xfffd, // 0xc6
+ 0x00ab, // 0xc7
+ 0x00bb, // 0xc8
+ 0x2026, // 0xc9
+ 0xfffd, // 0xca
+ 0x00c0, // 0xcb
+ 0x00c3, // 0xcc
+ 0x00d5, // 0xcd
+ 0x0152, // 0xce
+ 0x0153, // 0xcf
+ 0x2013, // 0xd0
+ 0x2014, // 0xd1
+ 0x201c, // 0xd2
+ 0x201d, // 0xd3
+ 0x2018, // 0xd4
+ 0x2019, // 0xd5
+ 0x00f7, // 0xd6
+ 0xfffd, // 0xd7
+ 0x00ff, // 0xd8
+ 0x0178, // 0xd9
+ 0x2044, // 0xda
+ 0x00a4, // 0xdb
+ 0x2039, // 0xdc
+ 0x203a, // 0xdd
+ 0xfb01, // 0xde
+ 0xfb02, // 0xdf
+ 0x2021, // 0xe0
+ 0x00b7, // 0xe1
+ 0x201a, // 0xe2
+ 0x201e, // 0xe3
+ 0x2030, // 0xe4
+ 0x00c2, // 0xe5
+ 0x00ca, // 0xe6
+ 0x00c1, // 0xe7
+ 0x00cb, // 0xe8
+ 0x00c8, // 0xe9
+ 0x00cd, // 0xea
+ 0x00ce, // 0xeb
+ 0x00cf, // 0xec
+ 0x00cc, // 0xed
+ 0x00d3, // 0xee
+ 0x00d4, // 0xef
+ 0xfffd, // 0xf0
+ 0x00d2, // 0xf1
+ 0x00da, // 0xf2
+ 0x00db, // 0xf3
+ 0x00d9, // 0xf4
+ 0x0131, // 0xf5
+ 0x02c6, // 0xf6
+ 0x0303, // 0xf7
+ 0x0304, // 0xf8
+ 0x0306, // 0xf9
+ 0x0307, // 0xfa
+ 0x030a, // 0xfb
+ 0x0327, // 0xfc
+ 0x030b, // 0xfd
+ 0x0328, // 0xfe
+ 0x02c7, // 0xff
+};
+
std::string
QUtil::int_to_string(long long num, int length)
{
@@ -132,22 +337,7 @@ QUtil::unsigned_char_pointer(char const* str)
void
QUtil::throw_system_error(std::string const& description)
{
-#ifdef _MSC_VER
- // "94" is mentioned in the MSVC docs, but it's still safe if the
- // message is longer. strerror_s is a templated function that
- // knows the size of buf and truncates.
- char buf[94];
- if (strerror_s(buf, errno) != 0)
- {
- throw std::runtime_error(description + ": failed with an unknown error");
- }
- else
- {
- throw std::runtime_error(description + ": " + buf);
- }
-#else
- throw std::runtime_error(description + ": " + strerror(errno));
-#endif
+ throw QPDFSystemError(description, errno);
}
int
@@ -228,13 +418,14 @@ QUtil::same_file(char const* name1, char const* name2)
return false;
}
#ifdef _WIN32
+ bool same = false;
+# ifndef AVOID_WINDOWS_HANDLE
HANDLE fh1 = CreateFile(name1, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
HANDLE fh2 = CreateFile(name2, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
BY_HANDLE_FILE_INFORMATION fi1;
BY_HANDLE_FILE_INFORMATION fi2;
- bool same = false;
if ((fh1 != INVALID_HANDLE_VALUE) &&
(fh2 != INVALID_HANDLE_VALUE) &&
GetFileInformationByHandle(fh1, &fi1) &&
@@ -253,6 +444,7 @@ QUtil::same_file(char const* name1, char const* name2)
{
CloseHandle(fh2);
}
+# endif
return same;
#else
struct stat st1;
@@ -732,3 +924,1164 @@ QUtil::strcasecmp(char const *s1, char const *s2)
return ::strcasecmp(s1, s2);
#endif
}
+
+static int maybe_from_end(int num, bool from_end, int max)
+{
+ if (from_end)
+ {
+ if (num > max)
+ {
+ num = 0;
+ }
+ else
+ {
+ num = max + 1 - num;
+ }
+ }
+ return num;
+}
+
+std::vector<int>
+QUtil::parse_numrange(char const* range, int max)
+{
+ std::vector<int> result;
+ char const* p = range;
+ try
+ {
+ std::vector<int> work;
+ static int const comma = -1;
+ static int const dash = -2;
+
+ enum { st_top,
+ st_in_number,
+ st_after_number } state = st_top;
+ bool last_separator_was_dash = false;
+ int cur_number = 0;
+ bool from_end = false;
+ while (*p)
+ {
+ char ch = *p;
+ if (isdigit(ch))
+ {
+ if (! ((state == st_top) || (state == st_in_number)))
+ {
+ throw std::runtime_error("digit not expected");
+ }
+ state = st_in_number;
+ cur_number *= 10;
+ cur_number += (ch - '0');
+ }
+ else if (ch == 'z')
+ {
+ // z represents max
+ if (! (state == st_top))
+ {
+ throw std::runtime_error("z not expected");
+ }
+ state = st_after_number;
+ cur_number = max;
+ }
+ else if (ch == 'r')
+ {
+ if (! (state == st_top))
+ {
+ throw std::runtime_error("r not expected");
+ }
+ state = st_in_number;
+ from_end = true;
+ }
+ else if ((ch == ',') || (ch == '-'))
+ {
+ if (! ((state == st_in_number) || (state == st_after_number)))
+ {
+ throw std::runtime_error("unexpected separator");
+ }
+ cur_number = maybe_from_end(cur_number, from_end, max);
+ work.push_back(cur_number);
+ cur_number = 0;
+ from_end = false;
+ if (ch == ',')
+ {
+ state = st_top;
+ last_separator_was_dash = false;
+ work.push_back(comma);
+ }
+ else if (ch == '-')
+ {
+ if (last_separator_was_dash)
+ {
+ throw std::runtime_error("unexpected dash");
+ }
+ state = st_top;
+ last_separator_was_dash = true;
+ work.push_back(dash);
+ }
+ }
+ else
+ {
+ throw std::runtime_error("unexpected character");
+ }
+ ++p;
+ }
+ if ((state == st_in_number) || (state == st_after_number))
+ {
+ cur_number = maybe_from_end(cur_number, from_end, max);
+ work.push_back(cur_number);
+ }
+ else
+ {
+ throw std::runtime_error("number expected");
+ }
+
+ p = 0;
+ for (size_t i = 0; i < work.size(); i += 2)
+ {
+ int num = work.at(i);
+ // max == 0 means we don't know the max and are just
+ // testing for valid syntax.
+ if ((max > 0) && ((num < 1) || (num > max)))
+ {
+ throw std::runtime_error(
+ "number " + QUtil::int_to_string(num) + " out of range");
+ }
+ if (i == 0)
+ {
+ result.push_back(work.at(i));
+ }
+ else
+ {
+ int separator = work.at(i-1);
+ if (separator == comma)
+ {
+ result.push_back(num);
+ }
+ else if (separator == dash)
+ {
+ int lastnum = result.back();
+ if (num > lastnum)
+ {
+ for (int j = lastnum + 1; j <= num; ++j)
+ {
+ result.push_back(j);
+ }
+ }
+ else
+ {
+ for (int j = lastnum - 1; j >= num; --j)
+ {
+ result.push_back(j);
+ }
+ }
+ }
+ else
+ {
+ throw std::logic_error(
+ "INTERNAL ERROR parsing numeric range");
+ }
+ }
+ }
+ }
+ catch (std::runtime_error const& e)
+ {
+ std::string message;
+ if (p)
+ {
+ message = "error at * in numeric range " +
+ std::string(range, p - range) + "*" + p + ": " + e.what();
+ }
+ else
+ {
+ message = "error in numeric range " +
+ std::string(range) + ": " + e.what();
+ }
+ throw std::runtime_error(message);
+ }
+ return result;
+}
+
+enum encoding_e { e_utf16, e_ascii, e_winansi, e_macroman, e_pdfdoc };
+
+static unsigned char
+encode_winansi(unsigned long codepoint)
+{
+ // Use this ugly switch statement to avoid a static, which is not
+ // thread-safe.
+ unsigned char ch = '\0';
+ switch (codepoint)
+ {
+ case 0x20ac:
+ ch = 0x80;
+ break;
+ case 0x201a:
+ ch = 0x82;
+ break;
+ case 0x192:
+ ch = 0x83;
+ break;
+ case 0x201e:
+ ch = 0x84;
+ break;
+ case 0x2026:
+ ch = 0x85;
+ break;
+ case 0x2020:
+ ch = 0x86;
+ break;
+ case 0x2021:
+ ch = 0x87;
+ break;
+ case 0x2c6:
+ ch = 0x88;
+ break;
+ case 0x2030:
+ ch = 0x89;
+ break;
+ case 0x160:
+ ch = 0x8a;
+ break;
+ case 0x2039:
+ ch = 0x8b;
+ break;
+ case 0x152:
+ ch = 0x8c;
+ break;
+ case 0x17d:
+ ch = 0x8e;
+ break;
+ case 0x2018:
+ ch = 0x91;
+ break;
+ case 0x2019:
+ ch = 0x92;
+ break;
+ case 0x201c:
+ ch = 0x93;
+ break;
+ case 0x201d:
+ ch = 0x94;
+ break;
+ case 0x2022:
+ ch = 0x95;
+ break;
+ case 0x2013:
+ ch = 0x96;
+ break;
+ case 0x2014:
+ ch = 0x97;
+ break;
+ case 0x303:
+ ch = 0x98;
+ break;
+ case 0x2122:
+ ch = 0x99;
+ break;
+ case 0x161:
+ ch = 0x9a;
+ break;
+ case 0x203a:
+ ch = 0x9b;
+ break;
+ case 0x153:
+ ch = 0x9c;
+ break;
+ case 0x17e:
+ ch = 0x9e;
+ break;
+ case 0x178:
+ ch = 0x9f;
+ break;
+ case 0xa0:
+ ch = 0xa0;
+ break;
+ default:
+ break;
+ }
+ return ch;
+}
+
+static unsigned char
+encode_macroman(unsigned long codepoint)
+{
+ // Use this ugly switch statement to avoid a static, which is not
+ // thread-safe.
+ unsigned char ch = '\0';
+ switch (codepoint)
+ {
+ case 0xc4:
+ ch = 0x80;
+ break;
+ case 0xc5:
+ ch = 0x81;
+ break;
+ case 0xc7:
+ ch = 0x82;
+ break;
+ case 0xc9:
+ ch = 0x83;
+ break;
+ case 0xd1:
+ ch = 0x84;
+ break;
+ case 0xd6:
+ ch = 0x85;
+ break;
+ case 0xdc:
+ ch = 0x86;
+ break;
+ case 0xe1:
+ ch = 0x87;
+ break;
+ case 0xe0:
+ ch = 0x88;
+ break;
+ case 0xe2:
+ ch = 0x89;
+ break;
+ case 0xe4:
+ ch = 0x8a;
+ break;
+ case 0xe3:
+ ch = 0x8b;
+ break;
+ case 0xe5:
+ ch = 0x8c;
+ break;
+ case 0xe7:
+ ch = 0x8d;
+ break;
+ case 0xe9:
+ ch = 0x8e;
+ break;
+ case 0xe8:
+ ch = 0x8f;
+ break;
+ case 0xea:
+ ch = 0x90;
+ break;
+ case 0xeb:
+ ch = 0x91;
+ break;
+ case 0xed:
+ ch = 0x92;
+ break;
+ case 0xec:
+ ch = 0x93;
+ break;
+ case 0xee:
+ ch = 0x94;
+ break;
+ case 0xef:
+ ch = 0x95;
+ break;
+ case 0xf1:
+ ch = 0x96;
+ break;
+ case 0xf3:
+ ch = 0x97;
+ break;
+ case 0xf2:
+ ch = 0x98;
+ break;
+ case 0xf4:
+ ch = 0x99;
+ break;
+ case 0xf6:
+ ch = 0x9a;
+ break;
+ case 0xf5:
+ ch = 0x9b;
+ break;
+ case 0xfa:
+ ch = 0x9c;
+ break;
+ case 0xf9:
+ ch = 0x9d;
+ break;
+ case 0xfb:
+ ch = 0x9e;
+ break;
+ case 0xfc:
+ ch = 0x9f;
+ break;
+ case 0x2020:
+ ch = 0xa0;
+ break;
+ case 0xb0:
+ ch = 0xa1;
+ break;
+ case 0xa2:
+ ch = 0xa2;
+ break;
+ case 0xa3:
+ ch = 0xa3;
+ break;
+ case 0xa7:
+ ch = 0xa4;
+ break;
+ case 0x2022:
+ ch = 0xa5;
+ break;
+ case 0xb6:
+ ch = 0xa6;
+ break;
+ case 0xdf:
+ ch = 0xa7;
+ break;
+ case 0xae:
+ ch = 0xa8;
+ break;
+ case 0xa9:
+ ch = 0xa9;
+ break;
+ case 0x2122:
+ ch = 0xaa;
+ break;
+ case 0x301:
+ ch = 0xab;
+ break;
+ case 0x308:
+ ch = 0xac;
+ break;
+ case 0xc6:
+ ch = 0xae;
+ break;
+ case 0xd8:
+ ch = 0xaf;
+ break;
+ case 0xb1:
+ ch = 0xb1;
+ break;
+ case 0xa5:
+ ch = 0xb4;
+ break;
+ case 0x3bc:
+ ch = 0xb5;
+ break;
+ case 0x1d43:
+ ch = 0xbb;
+ break;
+ case 0x1d52:
+ ch = 0xbc;
+ break;
+ case 0xe6:
+ ch = 0xbe;
+ break;
+ case 0xf8:
+ ch = 0xbf;
+ break;
+ case 0xbf:
+ ch = 0xc0;
+ break;
+ case 0xa1:
+ ch = 0xc1;
+ break;
+ case 0xac:
+ ch = 0xc2;
+ break;
+ case 0x192:
+ ch = 0xc4;
+ break;
+ case 0xab:
+ ch = 0xc7;
+ break;
+ case 0xbb:
+ ch = 0xc8;
+ break;
+ case 0x2026:
+ ch = 0xc9;
+ break;
+ case 0xc0:
+ ch = 0xcb;
+ break;
+ case 0xc3:
+ ch = 0xcc;
+ break;
+ case 0xd5:
+ ch = 0xcd;
+ break;
+ case 0x152:
+ ch = 0xce;
+ break;
+ case 0x153:
+ ch = 0xcf;
+ break;
+ case 0x2013:
+ ch = 0xd0;
+ break;
+ case 0x2014:
+ ch = 0xd1;
+ break;
+ case 0x201c:
+ ch = 0xd2;
+ break;
+ case 0x201d:
+ ch = 0xd3;
+ break;
+ case 0x2018:
+ ch = 0xd4;
+ break;
+ case 0x2019:
+ ch = 0xd5;
+ break;
+ case 0xf7:
+ ch = 0xd6;
+ break;
+ case 0xff:
+ ch = 0xd8;
+ break;
+ case 0x178:
+ ch = 0xd9;
+ break;
+ case 0x2044:
+ ch = 0xda;
+ break;
+ case 0xa4:
+ ch = 0xdb;
+ break;
+ case 0x2039:
+ ch = 0xdc;
+ break;
+ case 0x203a:
+ ch = 0xdd;
+ break;
+ case 0xfb01:
+ ch = 0xde;
+ break;
+ case 0xfb02:
+ ch = 0xdf;
+ break;
+ case 0x2021:
+ ch = 0xe0;
+ break;
+ case 0xb7:
+ ch = 0xe1;
+ break;
+ case 0x201a:
+ ch = 0xe2;
+ break;
+ case 0x201e:
+ ch = 0xe3;
+ break;
+ case 0x2030:
+ ch = 0xe4;
+ break;
+ case 0xc2:
+ ch = 0xe5;
+ break;
+ case 0xca:
+ ch = 0xe6;
+ break;
+ case 0xc1:
+ ch = 0xe7;
+ break;
+ case 0xcb:
+ ch = 0xe8;
+ break;
+ case 0xc8:
+ ch = 0xe9;
+ break;
+ case 0xcd:
+ ch = 0xea;
+ break;
+ case 0xce:
+ ch = 0xeb;
+ break;
+ case 0xcf:
+ ch = 0xec;
+ break;
+ case 0xcc:
+ ch = 0xed;
+ break;
+ case 0xd3:
+ ch = 0xee;
+ break;
+ case 0xd4:
+ ch = 0xef;
+ break;
+ case 0xd2:
+ ch = 0xf1;
+ break;
+ case 0xda:
+ ch = 0xf2;
+ break;
+ case 0xdb:
+ ch = 0xf3;
+ break;
+ case 0xd9:
+ ch = 0xf4;
+ break;
+ case 0x131:
+ ch = 0xf5;
+ break;
+ case 0x2c6:
+ ch = 0xf6;
+ break;
+ case 0x303:
+ ch = 0xf7;
+ break;
+ case 0x304:
+ ch = 0xf8;
+ break;
+ case 0x306:
+ ch = 0xf9;
+ break;
+ case 0x307:
+ ch = 0xfa;
+ break;
+ case 0x30a:
+ ch = 0xfb;
+ break;
+ case 0x327:
+ ch = 0xfc;
+ break;
+ case 0x30b:
+ ch = 0xfd;
+ break;
+ case 0x328:
+ ch = 0xfe;
+ break;
+ case 0x2c7:
+ ch = 0xff;
+ break;
+ default:
+ break;
+ }
+ return ch;
+}
+
+static unsigned char
+encode_pdfdoc(unsigned long codepoint)
+{
+ // Use this ugly switch statement to avoid a static, which is not
+ // thread-safe.
+ unsigned char ch = '\0';
+ switch (codepoint)
+ {
+ case 0x2022:
+ ch = 0x80;
+ break;
+ case 0x2020:
+ ch = 0x81;
+ break;
+ case 0x2021:
+ ch = 0x82;
+ break;
+ case 0x2026:
+ ch = 0x83;
+ break;
+ case 0x2014:
+ ch = 0x84;
+ break;
+ case 0x2013:
+ ch = 0x85;
+ break;
+ case 0x0192:
+ ch = 0x86;
+ break;
+ case 0x2044:
+ ch = 0x87;
+ break;
+ case 0x2039:
+ ch = 0x88;
+ break;
+ case 0x203a:
+ ch = 0x89;
+ break;
+ case 0x2212:
+ ch = 0x8a;
+ break;
+ case 0x2030:
+ ch = 0x8b;
+ break;
+ case 0x201e:
+ ch = 0x8c;
+ break;
+ case 0x201c:
+ ch = 0x8d;
+ break;
+ case 0x201d:
+ ch = 0x8e;
+ break;
+ case 0x2018:
+ ch = 0x8f;
+ break;
+ case 0x2019:
+ ch = 0x90;
+ break;
+ case 0x201a:
+ ch = 0x91;
+ break;
+ case 0x2122:
+ ch = 0x92;
+ break;
+ case 0xfb01:
+ ch = 0x93;
+ break;
+ case 0xfb02:
+ ch = 0x94;
+ break;
+ case 0x0141:
+ ch = 0x95;
+ break;
+ case 0x0152:
+ ch = 0x96;
+ break;
+ case 0x0160:
+ ch = 0x97;
+ break;
+ case 0x0178:
+ ch = 0x98;
+ break;
+ case 0x017d:
+ ch = 0x99;
+ break;
+ case 0x0131:
+ ch = 0x9a;
+ break;
+ case 0x0142:
+ ch = 0x9b;
+ break;
+ case 0x0153:
+ ch = 0x9c;
+ break;
+ case 0x0161:
+ ch = 0x9d;
+ break;
+ case 0x017e:
+ ch = 0x9e;
+ break;
+ case 0xfffd:
+ ch = 0x9f;
+ break;
+ case 0x20ac:
+ ch = 0xa0;
+ break;
+ default:
+ break;
+ }
+ return ch;
+}
+
+unsigned long get_next_utf8_codepoint(
+ std::string const& utf8_val, size_t& pos, bool& error)
+{
+ size_t len = utf8_val.length();
+ unsigned char ch = static_cast<unsigned char>(utf8_val.at(pos));
+ error = false;
+ if (ch < 128)
+ {
+ return static_cast<unsigned long>(ch);
+ }
+
+ size_t bytes_needed = 0;
+ unsigned bit_check = 0x40;
+ unsigned char to_clear = 0x80;
+ while (ch & bit_check)
+ {
+ ++bytes_needed;
+ to_clear |= bit_check;
+ bit_check >>= 1;
+ }
+ if (((bytes_needed > 5) || (bytes_needed < 1)) ||
+ ((pos + bytes_needed) >= len))
+ {
+ error = true;
+ return 0xfffd;
+ }
+
+ unsigned long codepoint = (ch & ~to_clear);
+ while (bytes_needed > 0)
+ {
+ --bytes_needed;
+ ch = utf8_val.at(++pos);
+ if ((ch & 0xc0) != 0x80)
+ {
+ --pos;
+ codepoint = 0xfffd;
+ break;
+ }
+ codepoint <<= 6;
+ codepoint += (ch & 0x3f);
+ }
+ return codepoint;
+}
+
+static bool
+transcode_utf8(std::string const& utf8_val, std::string& result,
+ encoding_e encoding, char unknown)
+{
+ bool okay = true;
+ result.clear();
+ if (encoding == e_utf16)
+ {
+ result += "\xfe\xff";
+ }
+ size_t len = utf8_val.length();
+ for (size_t i = 0; i < len; ++i)
+ {
+ bool error = false;
+ unsigned long codepoint = get_next_utf8_codepoint(utf8_val, i, error);
+ if (error)
+ {
+ okay = false;
+ if (encoding == e_utf16)
+ {
+ result += "\xff\xfd";
+ }
+ else
+ {
+ result.append(1, unknown);
+ }
+ }
+ else if (codepoint < 128)
+ {
+ char ch = static_cast<char>(codepoint);
+ if (encoding == e_utf16)
+ {
+ result += QUtil::toUTF16(ch);
+ }
+ else
+ {
+ result.append(1, ch);
+ }
+ }
+ else if (encoding == e_utf16)
+ {
+ result += QUtil::toUTF16(codepoint);
+ }
+ else if ((codepoint > 160) && (codepoint < 256) &&
+ ((encoding == e_winansi) || (encoding == e_pdfdoc)))
+ {
+ result.append(1, static_cast<unsigned char>(codepoint & 0xff));
+ }
+ else
+ {
+ unsigned char ch = '\0';
+ if (encoding == e_winansi)
+ {
+ ch = encode_winansi(codepoint);
+ }
+ else if (encoding == e_macroman)
+ {
+ ch = encode_macroman(codepoint);
+ }
+ else if (encoding == e_pdfdoc)
+ {
+ ch = encode_pdfdoc(codepoint);
+ }
+ if (ch == '\0')
+ {
+ okay = false;
+ ch = static_cast<unsigned char>(unknown);
+ }
+ result.append(1, ch);
+ }
+ }
+ return okay;
+}
+
+static std::string
+transcode_utf8(std::string const& utf8_val, encoding_e encoding,
+ char unknown)
+{
+ std::string result;
+ transcode_utf8(utf8_val, result, encoding, unknown);
+ return result;
+}
+
+std::string
+QUtil::utf8_to_utf16(std::string const& utf8)
+{
+ return transcode_utf8(utf8, e_utf16, 0);
+}
+
+std::string
+QUtil::utf8_to_ascii(std::string const& utf8, char unknown_char)
+{
+ return transcode_utf8(utf8, e_ascii, unknown_char);
+}
+
+std::string
+QUtil::utf8_to_win_ansi(std::string const& utf8, char unknown_char)
+{
+ return transcode_utf8(utf8, e_winansi, unknown_char);
+}
+
+std::string
+QUtil::utf8_to_mac_roman(std::string const& utf8, char unknown_char)
+{
+ return transcode_utf8(utf8, e_macroman, unknown_char);
+}
+
+std::string
+QUtil::utf8_to_pdf_doc(std::string const& utf8, char unknown_char)
+{
+ return transcode_utf8(utf8, e_pdfdoc, unknown_char);
+}
+
+bool
+QUtil::utf8_to_ascii(std::string const& utf8, std::string& ascii,
+ char unknown_char)
+{
+ return transcode_utf8(utf8, ascii, e_ascii, unknown_char);
+}
+
+bool
+QUtil::utf8_to_win_ansi(std::string const& utf8, std::string& win,
+ char unknown_char)
+{
+ return transcode_utf8(utf8, win, e_winansi, unknown_char);
+}
+
+bool
+QUtil::utf8_to_mac_roman(std::string const& utf8, std::string& mac,
+ char unknown_char)
+{
+ return transcode_utf8(utf8, mac, e_macroman, unknown_char);
+}
+
+bool
+QUtil::utf8_to_pdf_doc(std::string const& utf8, std::string& pdfdoc,
+ char unknown_char)
+{
+ return transcode_utf8(utf8, pdfdoc, e_pdfdoc, unknown_char);
+}
+
+bool
+QUtil::is_utf16(std::string const& val)
+{
+ return ((val.length() >= 2) &&
+ (val.at(0) == '\xfe') && (val.at(1) == '\xff'));
+}
+
+std::string
+QUtil::utf16_to_utf8(std::string const& val)
+{
+ std::string result;
+ // This code uses unsigned long and unsigned short to hold
+ // codepoint values. It requires unsigned long to be at least
+ // 32 bits and unsigned short to be at least 16 bits, but it
+ // will work fine if they are larger.
+ unsigned long codepoint = 0L;
+ size_t len = val.length();
+ size_t start = 0;
+ if (is_utf16(val))
+ {
+ start += 2;
+ }
+ // If the string has an odd number of bytes, the last byte is
+ // ignored.
+ for (unsigned int i = start; i < len; i += 2)
+ {
+ // Convert from UTF16-BE. If we get a malformed
+ // codepoint, this code will generate incorrect output
+ // without giving a warning. Specifically, a high
+ // codepoint not followed by a low codepoint will be
+ // discarded, and a low codepoint not preceded by a high
+ // codepoint will just get its low 10 bits output.
+ unsigned short bits =
+ (static_cast<unsigned char>(val.at(i)) << 8) +
+ static_cast<unsigned char>(val.at(i+1));
+ if ((bits & 0xFC00) == 0xD800)
+ {
+ codepoint = 0x10000 + ((bits & 0x3FF) << 10);
+ continue;
+ }
+ else if ((bits & 0xFC00) == 0xDC00)
+ {
+ if (codepoint != 0)
+ {
+ QTC::TC("qpdf", "QUtil non-trivial UTF-16");
+ }
+ codepoint += bits & 0x3FF;
+ }
+ else
+ {
+ codepoint = bits;
+ }
+
+ result += QUtil::toUTF8(codepoint);
+ codepoint = 0;
+ }
+ return result;
+}
+
+std::string
+QUtil::win_ansi_to_utf8(std::string const& val)
+{
+ std::string result;
+ size_t len = val.length();
+ for (unsigned int i = 0; i < len; ++i)
+ {
+ unsigned char ch = static_cast<unsigned char>(val.at(i));
+ unsigned short val = ch;
+ if ((ch >= 128) && (ch <= 160))
+ {
+ val = win_ansi_to_unicode[ch - 128];
+ }
+ result += QUtil::toUTF8(val);
+ }
+ return result;
+}
+
+std::string
+QUtil::mac_roman_to_utf8(std::string const& val)
+{
+ std::string result;
+ size_t len = val.length();
+ for (unsigned int i = 0; i < len; ++i)
+ {
+ unsigned char ch = static_cast<unsigned char>(val.at(i));
+ unsigned short val = ch;
+ if (ch >= 128)
+ {
+ val = mac_roman_to_unicode[ch - 128];
+ }
+ result += QUtil::toUTF8(val);
+ }
+ return result;
+}
+
+std::string
+QUtil::pdf_doc_to_utf8(std::string const& val)
+{
+ std::string result;
+ size_t len = val.length();
+ for (unsigned int i = 0; i < len; ++i)
+ {
+ unsigned char ch = static_cast<unsigned char>(val.at(i));
+ unsigned short val = ch;
+ if ((ch >= 128) && (ch <= 160))
+ {
+ val = pdf_doc_to_unicode[ch - 128];
+ }
+ result += QUtil::toUTF8(val);
+ }
+ return result;
+}
+
+void
+QUtil::analyze_encoding(std::string const& val,
+ bool& has_8bit_chars,
+ bool& is_valid_utf8,
+ bool& is_utf16)
+{
+ has_8bit_chars = is_utf16 = is_valid_utf8 = false;
+ if (QUtil::is_utf16(val))
+ {
+ has_8bit_chars = true;
+ is_utf16 = true;
+ return;
+ }
+ size_t len = val.length();
+ bool any_errors = false;
+ for (size_t i = 0; i < len; ++i)
+ {
+ bool error = false;
+ unsigned long codepoint = get_next_utf8_codepoint(val, i, error);
+ if (error)
+ {
+ any_errors = true;
+ }
+ if (codepoint >= 128)
+ {
+ has_8bit_chars = true;
+ }
+ }
+ if (has_8bit_chars && (! any_errors))
+ {
+ is_valid_utf8 = true;
+ }
+}
+
+std::vector<std::string>
+QUtil::possible_repaired_encodings(std::string supplied)
+{
+ std::vector<std::string> result;
+ // Always include the original string
+ result.push_back(supplied);
+ bool has_8bit_chars = false;
+ bool is_valid_utf8 = false;
+ bool is_utf16 = false;
+ analyze_encoding(supplied, has_8bit_chars, is_valid_utf8, is_utf16);
+ if (! has_8bit_chars)
+ {
+ return result;
+ }
+ if (is_utf16)
+ {
+ // Convert to UTF-8 and pretend we got a UTF-8 string.
+ is_utf16 = false;
+ is_valid_utf8 = true;
+ supplied = utf16_to_utf8(supplied);
+ }
+ std::string output;
+ if (is_valid_utf8)
+ {
+ // Maybe we were given UTF-8 but wanted one of the single-byte
+ // encodings.
+ if (utf8_to_pdf_doc(supplied, output))
+ {
+ result.push_back(output);
+ }
+ if (utf8_to_win_ansi(supplied, output))
+ {
+ result.push_back(output);
+ }
+ if (utf8_to_mac_roman(supplied, output))
+ {
+ result.push_back(output);
+ }
+ }
+ else
+ {
+ // Maybe we were given one of the single-byte encodings but
+ // wanted UTF-8.
+ std::string from_pdf_doc(pdf_doc_to_utf8(supplied));
+ result.push_back(from_pdf_doc);
+ std::string from_win_ansi(win_ansi_to_utf8(supplied));
+ result.push_back(from_win_ansi);
+ std::string from_mac_roman(mac_roman_to_utf8(supplied));
+ result.push_back(from_mac_roman);
+
+ // Maybe we were given one of the other single-byte encodings
+ // but wanted one of the other ones.
+ if (utf8_to_win_ansi(from_pdf_doc, output))
+ {
+ result.push_back(output);
+ }
+ if (utf8_to_mac_roman(from_pdf_doc, output))
+ {
+ result.push_back(output);
+ }
+ if (utf8_to_pdf_doc(from_win_ansi, output))
+ {
+ result.push_back(output);
+ }
+ if (utf8_to_mac_roman(from_win_ansi, output))
+ {
+ result.push_back(output);
+ }
+ if (utf8_to_pdf_doc(from_mac_roman, output))
+ {
+ result.push_back(output);
+ }
+ if (utf8_to_win_ansi(from_mac_roman, output))
+ {
+ result.push_back(output);
+ }
+ }
+ // De-duplicate
+ std::vector<std::string> t;
+ std::set<std::string> seen;
+ for (std::vector<std::string>::iterator iter = result.begin();
+ iter != result.end(); ++iter)
+ {
+ if (! seen.count(*iter))
+ {
+ seen.insert(*iter);
+ t.push_back(*iter);
+ }
+ }
+ return t;
+}
diff --git a/libqpdf/build.mk b/libqpdf/build.mk
index 528456f8..4ef0688d 100644
--- a/libqpdf/build.mk
+++ b/libqpdf/build.mk
@@ -14,6 +14,7 @@ SRCS_libqpdf = \
libqpdf/FileInputSource.cc \
libqpdf/InputSource.cc \
libqpdf/InsecureRandomDataProvider.cc \
+ libqpdf/JSON.cc \
libqpdf/MD5.cc \
libqpdf/OffsetInputSource.cc \
libqpdf/Pipeline.cc \
@@ -40,11 +41,18 @@ SRCS_libqpdf = \
libqpdf/QPDFAnnotationObjectHelper.cc \
libqpdf/QPDFExc.cc \
libqpdf/QPDFFormFieldObjectHelper.cc \
+ libqpdf/QPDFMatrix.cc \
+ libqpdf/QPDFNameTreeObjectHelper.cc \
+ libqpdf/QPDFNumberTreeObjectHelper.cc \
libqpdf/QPDFObjGen.cc \
libqpdf/QPDFObject.cc \
libqpdf/QPDFObjectHandle.cc \
+ libqpdf/QPDFOutlineDocumentHelper.cc \
+ libqpdf/QPDFOutlineObjectHelper.cc \
libqpdf/QPDFPageDocumentHelper.cc \
+ libqpdf/QPDFPageLabelDocumentHelper.cc \
libqpdf/QPDFPageObjectHelper.cc \
+ libqpdf/QPDFSystemError.cc \
libqpdf/QPDFTokenizer.cc \
libqpdf/QPDFWriter.cc \
libqpdf/QPDFXRefEntry.cc \
diff --git a/libqpdf/qpdf-c.cc b/libqpdf/qpdf-c.cc
index 1cc1e2b1..310bb7d0 100644
--- a/libqpdf/qpdf-c.cc
+++ b/libqpdf/qpdf-c.cc
@@ -603,12 +603,77 @@ void qpdf_set_r2_encryption_parameters(
allow_print, allow_modify, allow_extract, allow_annotate);
}
+void qpdf_set_r3_encryption_parameters2(
+ qpdf_data qpdf, char const* user_password, char const* owner_password,
+ QPDF_BOOL allow_accessibility, QPDF_BOOL allow_extract,
+ QPDF_BOOL allow_assemble, QPDF_BOOL allow_annotate_and_form,
+ QPDF_BOOL allow_form_filling, QPDF_BOOL allow_modify_other,
+ enum qpdf_r3_print_e print)
+{
+ QTC::TC("qpdf", "qpdf-c called qpdf_set_r3_encryption_parameters");
+ qpdf->qpdf_writer->setR3EncryptionParameters(
+ user_password, owner_password,
+ allow_accessibility, allow_extract,
+ allow_assemble, allow_annotate_and_form,
+ allow_form_filling, allow_modify_other,
+ print);
+}
+
+void qpdf_set_r4_encryption_parameters2(
+ qpdf_data qpdf, char const* user_password, char const* owner_password,
+ QPDF_BOOL allow_accessibility, QPDF_BOOL allow_extract,
+ QPDF_BOOL allow_assemble, QPDF_BOOL allow_annotate_and_form,
+ QPDF_BOOL allow_form_filling, QPDF_BOOL allow_modify_other,
+ enum qpdf_r3_print_e print,
+ QPDF_BOOL encrypt_metadata, QPDF_BOOL use_aes)
+{
+ QTC::TC("qpdf", "qpdf-c called qpdf_set_r4_encryption_parameters");
+ qpdf->qpdf_writer->setR4EncryptionParameters(
+ user_password, owner_password,
+ allow_accessibility, allow_extract,
+ allow_assemble, allow_annotate_and_form,
+ allow_form_filling, allow_modify_other,
+ print, encrypt_metadata, use_aes);
+}
+
+
+void qpdf_set_r5_encryption_parameters2(
+ qpdf_data qpdf, char const* user_password, char const* owner_password,
+ QPDF_BOOL allow_accessibility, QPDF_BOOL allow_extract,
+ QPDF_BOOL allow_assemble, QPDF_BOOL allow_annotate_and_form,
+ QPDF_BOOL allow_form_filling, QPDF_BOOL allow_modify_other,
+ enum qpdf_r3_print_e print, QPDF_BOOL encrypt_metadata)
+{
+ QTC::TC("qpdf", "qpdf-c called qpdf_set_r5_encryption_parameters");
+ qpdf->qpdf_writer->setR5EncryptionParameters(
+ user_password, owner_password,
+ allow_accessibility, allow_extract,
+ allow_assemble, allow_annotate_and_form,
+ allow_form_filling, allow_modify_other,
+ print, encrypt_metadata);
+}
+
+void qpdf_set_r6_encryption_parameters2(
+ qpdf_data qpdf, char const* user_password, char const* owner_password,
+ QPDF_BOOL allow_accessibility, QPDF_BOOL allow_extract,
+ QPDF_BOOL allow_assemble, QPDF_BOOL allow_annotate_and_form,
+ QPDF_BOOL allow_form_filling, QPDF_BOOL allow_modify_other,
+ enum qpdf_r3_print_e print, QPDF_BOOL encrypt_metadata)
+{
+ QTC::TC("qpdf", "qpdf-c called qpdf_set_r6_encryption_parameters");
+ qpdf->qpdf_writer->setR6EncryptionParameters(
+ user_password, owner_password,
+ allow_accessibility, allow_extract,
+ allow_assemble, allow_annotate_and_form,
+ allow_form_filling, allow_modify_other,
+ print, encrypt_metadata);
+}
+
void qpdf_set_r3_encryption_parameters(
qpdf_data qpdf, char const* user_password, char const* owner_password,
QPDF_BOOL allow_accessibility, QPDF_BOOL allow_extract,
qpdf_r3_print_e print, qpdf_r3_modify_e modify)
{
- QTC::TC("qpdf", "qpdf-c called qpdf_set_r3_encryption_parameters");
qpdf->qpdf_writer->setR3EncryptionParameters(
user_password, owner_password,
allow_accessibility, allow_extract, print, modify);
@@ -620,7 +685,6 @@ void qpdf_set_r4_encryption_parameters(
qpdf_r3_print_e print, qpdf_r3_modify_e modify,
QPDF_BOOL encrypt_metadata, QPDF_BOOL use_aes)
{
- QTC::TC("qpdf", "qpdf-c called qpdf_set_r4_encryption_parameters");
qpdf->qpdf_writer->setR4EncryptionParameters(
user_password, owner_password,
allow_accessibility, allow_extract, print, modify,
@@ -633,7 +697,6 @@ void qpdf_set_r5_encryption_parameters(
qpdf_r3_print_e print, qpdf_r3_modify_e modify,
QPDF_BOOL encrypt_metadata)
{
- QTC::TC("qpdf", "qpdf-c called qpdf_set_r5_encryption_parameters");
qpdf->qpdf_writer->setR5EncryptionParameters(
user_password, owner_password,
allow_accessibility, allow_extract, print, modify,
@@ -646,7 +709,6 @@ void qpdf_set_r6_encryption_parameters(
qpdf_r3_print_e print, qpdf_r3_modify_e modify,
QPDF_BOOL encrypt_metadata)
{
- QTC::TC("qpdf", "qpdf-c called qpdf_set_r6_encryption_parameters");
qpdf->qpdf_writer->setR6EncryptionParameters(
user_password, owner_password,
allow_accessibility, allow_extract, print, modify,
diff --git a/libqpdf/qpdf/BitStream.hh b/libqpdf/qpdf/BitStream.hh
index 8ea8bec4..bdbf2645 100644
--- a/libqpdf/qpdf/BitStream.hh
+++ b/libqpdf/qpdf/BitStream.hh
@@ -1,7 +1,7 @@
// Read bits from a bit stream. See BitWriter for writing.
-#ifndef __BITSTREAM_HH__
-#define __BITSTREAM_HH__
+#ifndef BITSTREAM_HH
+#define BITSTREAM_HH
#include <qpdf/DLL.h>
@@ -28,4 +28,4 @@ class BitStream
unsigned int bits_available;
};
-#endif // __BITSTREAM_HH__
+#endif // BITSTREAM_HH
diff --git a/libqpdf/qpdf/BitWriter.hh b/libqpdf/qpdf/BitWriter.hh
index 3d93c0b7..76e28b8e 100644
--- a/libqpdf/qpdf/BitWriter.hh
+++ b/libqpdf/qpdf/BitWriter.hh
@@ -1,7 +1,7 @@
// Write bits into a bit stream. See BitStream for reading.
-#ifndef __BITWRITER_HH__
-#define __BITWRITER_HH__
+#ifndef BITWRITER_HH
+#define BITWRITER_HH
#include <qpdf/DLL.h>
@@ -28,4 +28,4 @@ class BitWriter
unsigned int bit_offset;
};
-#endif // __BITWRITER_HH__
+#endif // BITWRITER_HH
diff --git a/libqpdf/qpdf/ContentNormalizer.hh b/libqpdf/qpdf/ContentNormalizer.hh
index 0d505a37..4d976d27 100644
--- a/libqpdf/qpdf/ContentNormalizer.hh
+++ b/libqpdf/qpdf/ContentNormalizer.hh
@@ -1,5 +1,5 @@
-#ifndef __CONTENTNORMALIZER_HH__
-#define __CONTENTNORMALIZER_HH__
+#ifndef CONTENTNORMALIZER_HH
+#define CONTENTNORMALIZER_HH
#include <qpdf/QPDFObjectHandle.hh>
@@ -18,4 +18,4 @@ class ContentNormalizer: public QPDFObjectHandle::TokenFilter
bool last_token_was_bad;
};
-#endif // __CONTENTNORMALIZER_HH__
+#endif // CONTENTNORMALIZER_HH
diff --git a/libqpdf/qpdf/InsecureRandomDataProvider.hh b/libqpdf/qpdf/InsecureRandomDataProvider.hh
index 530ee3cb..76f2703a 100644
--- a/libqpdf/qpdf/InsecureRandomDataProvider.hh
+++ b/libqpdf/qpdf/InsecureRandomDataProvider.hh
@@ -1,5 +1,5 @@
-#ifndef __INSECURERANDOMDATAPROVIDER_HH__
-#define __INSECURERANDOMDATAPROVIDER_HH__
+#ifndef INSECURERANDOMDATAPROVIDER_HH
+#define INSECURERANDOMDATAPROVIDER_HH
#include <qpdf/RandomDataProvider.hh>
#include <qpdf/DLL.h>
@@ -24,4 +24,4 @@ class InsecureRandomDataProvider: public RandomDataProvider
bool seeded_random;
};
-#endif // __INSECURERANDOMDATAPROVIDER_HH__
+#endif // INSECURERANDOMDATAPROVIDER_HH
diff --git a/libqpdf/qpdf/MD5.hh b/libqpdf/qpdf/MD5.hh
index 4cfe027e..5cefe08c 100644
--- a/libqpdf/qpdf/MD5.hh
+++ b/libqpdf/qpdf/MD5.hh
@@ -1,5 +1,5 @@
-#ifndef __MD5_HH__
-#define __MD5_HH__
+#ifndef MD5_HH
+#define MD5_HH
#include <qpdf/DLL.h>
#include <qpdf/qpdf-config.h>
@@ -89,4 +89,4 @@ class MD5
Digest digest_val;
};
-#endif // __MD5_HH__
+#endif // MD5_HH
diff --git a/libqpdf/qpdf/OffsetInputSource.hh b/libqpdf/qpdf/OffsetInputSource.hh
index aedc574a..d541db1a 100644
--- a/libqpdf/qpdf/OffsetInputSource.hh
+++ b/libqpdf/qpdf/OffsetInputSource.hh
@@ -1,5 +1,5 @@
-#ifndef __QPDF_OFFSETINPUTSOURCE_HH__
-#define __QPDF_OFFSETINPUTSOURCE_HH__
+#ifndef QPDF_OFFSETINPUTSOURCE_HH
+#define QPDF_OFFSETINPUTSOURCE_HH
// This class implements an InputSource that proxies for an underlying
// input source but offset a specific number of bytes.
@@ -26,4 +26,4 @@ class OffsetInputSource: public InputSource
qpdf_offset_t global_offset;
};
-#endif // __QPDF_OFFSETINPUTSOURCE_HH__
+#endif // QPDF_OFFSETINPUTSOURCE_HH
diff --git a/libqpdf/qpdf/Pl_AES_PDF.hh b/libqpdf/qpdf/Pl_AES_PDF.hh
index 9aa73ad6..ac56e59a 100644
--- a/libqpdf/qpdf/Pl_AES_PDF.hh
+++ b/libqpdf/qpdf/Pl_AES_PDF.hh
@@ -1,5 +1,5 @@
-#ifndef __PL_AES_PDF_HH__
-#define __PL_AES_PDF_HH__
+#ifndef PL_AES_PDF_HH
+#define PL_AES_PDF_HH
#include <qpdf/Pipeline.hh>
#include <qpdf/qpdf-config.h>
@@ -66,4 +66,4 @@ class Pl_AES_PDF: public Pipeline
bool disable_padding;
};
-#endif // __PL_AES_PDF_HH__
+#endif // PL_AES_PDF_HH
diff --git a/libqpdf/qpdf/Pl_ASCII85Decoder.hh b/libqpdf/qpdf/Pl_ASCII85Decoder.hh
index 001da867..4778f61a 100644
--- a/libqpdf/qpdf/Pl_ASCII85Decoder.hh
+++ b/libqpdf/qpdf/Pl_ASCII85Decoder.hh
@@ -1,5 +1,5 @@
-#ifndef __PL_ASCII85DECODER_HH__
-#define __PL_ASCII85DECODER_HH__
+#ifndef PL_ASCII85DECODER_HH
+#define PL_ASCII85DECODER_HH
#include <qpdf/Pipeline.hh>
@@ -23,4 +23,4 @@ class Pl_ASCII85Decoder: public Pipeline
size_t eod;
};
-#endif // __PL_ASCII85DECODER_HH__
+#endif // PL_ASCII85DECODER_HH
diff --git a/libqpdf/qpdf/Pl_ASCIIHexDecoder.hh b/libqpdf/qpdf/Pl_ASCIIHexDecoder.hh
index 1d33afde..766fd727 100644
--- a/libqpdf/qpdf/Pl_ASCIIHexDecoder.hh
+++ b/libqpdf/qpdf/Pl_ASCIIHexDecoder.hh
@@ -1,5 +1,5 @@
-#ifndef __PL_ASCIIHEXDECODER_HH__
-#define __PL_ASCIIHEXDECODER_HH__
+#ifndef PL_ASCIIHEXDECODER_HH
+#define PL_ASCIIHEXDECODER_HH
#include <qpdf/Pipeline.hh>
@@ -23,4 +23,4 @@ class Pl_ASCIIHexDecoder: public Pipeline
bool eod;
};
-#endif // __PL_ASCIIHEXDECODER_HH__
+#endif // PL_ASCIIHEXDECODER_HH
diff --git a/libqpdf/qpdf/Pl_LZWDecoder.hh b/libqpdf/qpdf/Pl_LZWDecoder.hh
index 4121f6f2..b4f517ff 100644
--- a/libqpdf/qpdf/Pl_LZWDecoder.hh
+++ b/libqpdf/qpdf/Pl_LZWDecoder.hh
@@ -1,5 +1,5 @@
-#ifndef __PL_LZWDECODER_HH__
-#define __PL_LZWDECODER_HH__
+#ifndef PL_LZWDECODER_HH
+#define PL_LZWDECODER_HH
#include <qpdf/Pipeline.hh>
@@ -40,4 +40,4 @@ class Pl_LZWDecoder: public Pipeline
int last_code;
};
-#endif // __PL_LZWDECODER_HH__
+#endif // PL_LZWDECODER_HH
diff --git a/libqpdf/qpdf/Pl_MD5.hh b/libqpdf/qpdf/Pl_MD5.hh
index c23e6b45..08a93706 100644
--- a/libqpdf/qpdf/Pl_MD5.hh
+++ b/libqpdf/qpdf/Pl_MD5.hh
@@ -1,5 +1,5 @@
-#ifndef __PL_MD5_HH__
-#define __PL_MD5_HH__
+#ifndef PL_MD5_HH
+#define PL_MD5_HH
// This pipeline sends its output to its successor unmodified. After
// calling finish, the MD5 checksum of the data that passed through
@@ -45,4 +45,4 @@ class Pl_MD5: public Pipeline
bool persist_across_finish;
};
-#endif // __PL_MD5_HH__
+#endif // PL_MD5_HH
diff --git a/libqpdf/qpdf/Pl_PNGFilter.hh b/libqpdf/qpdf/Pl_PNGFilter.hh
index f167c67c..f6745319 100644
--- a/libqpdf/qpdf/Pl_PNGFilter.hh
+++ b/libqpdf/qpdf/Pl_PNGFilter.hh
@@ -1,5 +1,5 @@
-#ifndef __PL_PNGFILTER_HH__
-#define __PL_PNGFILTER_HH__
+#ifndef PL_PNGFILTER_HH
+#define PL_PNGFILTER_HH
// This pipeline applies or reverses the application of a PNG filter
// as described in the PNG specification.
@@ -49,4 +49,4 @@ class Pl_PNGFilter: public Pipeline
size_t incoming;
};
-#endif // __PL_PNGFILTER_HH__
+#endif // PL_PNGFILTER_HH
diff --git a/libqpdf/qpdf/Pl_RC4.hh b/libqpdf/qpdf/Pl_RC4.hh
index bb892f12..27eab5d0 100644
--- a/libqpdf/qpdf/Pl_RC4.hh
+++ b/libqpdf/qpdf/Pl_RC4.hh
@@ -1,5 +1,5 @@
-#ifndef __PL_RC4_HH__
-#define __PL_RC4_HH__
+#ifndef PL_RC4_HH
+#define PL_RC4_HH
#include <qpdf/Pipeline.hh>
@@ -29,4 +29,4 @@ class Pl_RC4: public Pipeline
RC4 rc4;
};
-#endif // __PL_RC4_HH__
+#endif // PL_RC4_HH
diff --git a/libqpdf/qpdf/Pl_SHA2.hh b/libqpdf/qpdf/Pl_SHA2.hh
index 8ff4723a..0748dd0b 100644
--- a/libqpdf/qpdf/Pl_SHA2.hh
+++ b/libqpdf/qpdf/Pl_SHA2.hh
@@ -1,5 +1,5 @@
-#ifndef __PL_SHA2_HH__
-#define __PL_SHA2_HH__
+#ifndef PL_SHA2_HH
+#define PL_SHA2_HH
// Bits must be a supported number of bits, currently only 256, 384,
// or 512. Passing 0 as bits leaves the pipeline uncommitted, in
@@ -47,4 +47,4 @@ class Pl_SHA2: public Pipeline
unsigned char sha512sum[64];
};
-#endif // __PL_SHA2_HH__
+#endif // PL_SHA2_HH
diff --git a/libqpdf/qpdf/Pl_TIFFPredictor.hh b/libqpdf/qpdf/Pl_TIFFPredictor.hh
index 235068ee..b4391f8e 100644
--- a/libqpdf/qpdf/Pl_TIFFPredictor.hh
+++ b/libqpdf/qpdf/Pl_TIFFPredictor.hh
@@ -1,5 +1,5 @@
-#ifndef __PL_TIFFPREDICTOR_HH__
-#define __PL_TIFFPREDICTOR_HH__
+#ifndef PL_TIFFPREDICTOR_HH
+#define PL_TIFFPREDICTOR_HH
// This pipeline reverses the application of a TIFF predictor as
// described in the TIFF specification.
@@ -36,4 +36,4 @@ class Pl_TIFFPredictor: public Pipeline
size_t pos;
};
-#endif // __PL_TIFFPREDICTOR_HH__
+#endif // PL_TIFFPREDICTOR_HH
diff --git a/libqpdf/qpdf/QPDFMatrix.hh b/libqpdf/qpdf/QPDFMatrix.hh
new file mode 100644
index 00000000..7b19a665
--- /dev/null
+++ b/libqpdf/qpdf/QPDFMatrix.hh
@@ -0,0 +1,53 @@
+#ifndef QPDFMATRIX_HH
+#define QPDFMATRIX_HH
+
+#include <qpdf/QPDFObjectHandle.hh>
+#include <qpdf/DLL.h>
+#include <string>
+
+class QPDFMatrix
+{
+ public:
+ QPDF_DLL
+ QPDFMatrix();
+ QPDF_DLL
+ QPDFMatrix(double a, double b, double c,
+ double d, double e, double f);
+ QPDF_DLL
+ QPDFMatrix(QPDFObjectHandle::Matrix const&);
+
+ QPDF_DLL
+ std::string unparse() const;
+
+ QPDF_DLL
+ QPDFObjectHandle::Matrix getAsMatrix() const;
+
+ // This is not part of the public API. Just provide the methods we
+ // need as we need them.
+ QPDF_DLL
+ void concat(QPDFMatrix const& other);
+ QPDF_DLL
+ void scale(double sx, double sy);
+ QPDF_DLL
+ void translate(double tx, double ty);
+ // Any value other than 90, 180, or 270 is ignored
+ QPDF_DLL
+ void rotatex90(int angle);
+
+ QPDF_DLL
+ void transform(double x, double y, double& xp, double& yp);
+
+ QPDF_DLL
+ QPDFObjectHandle::Rectangle transformRectangle(
+ QPDFObjectHandle::Rectangle r);
+
+ private:
+ double a;
+ double b;
+ double c;
+ double d;
+ double e;
+ double f;
+};
+
+#endif // QPDFMATRIX_HH
diff --git a/libqpdf/qpdf/QPDF_Array.hh b/libqpdf/qpdf/QPDF_Array.hh
index 8a23da35..695e6587 100644
--- a/libqpdf/qpdf/QPDF_Array.hh
+++ b/libqpdf/qpdf/QPDF_Array.hh
@@ -1,5 +1,5 @@
-#ifndef __QPDF_ARRAY_HH__
-#define __QPDF_ARRAY_HH__
+#ifndef QPDF_ARRAY_HH
+#define QPDF_ARRAY_HH
#include <qpdf/QPDFObject.hh>
@@ -12,6 +12,7 @@ class QPDF_Array: public QPDFObject
QPDF_Array(std::vector<QPDFObjectHandle> const& items);
virtual ~QPDF_Array();
virtual std::string unparse();
+ virtual JSON getJSON();
virtual QPDFObject::object_type_e getTypeCode() const;
virtual char const* getTypeName() const;
virtual void setDescription(QPDF*, std::string const&);
@@ -33,4 +34,4 @@ class QPDF_Array: public QPDFObject
std::vector<QPDFObjectHandle> items;
};
-#endif // __QPDF_ARRAY_HH__
+#endif // QPDF_ARRAY_HH
diff --git a/libqpdf/qpdf/QPDF_Bool.hh b/libqpdf/qpdf/QPDF_Bool.hh
index 2ec8eb10..5da6e87c 100644
--- a/libqpdf/qpdf/QPDF_Bool.hh
+++ b/libqpdf/qpdf/QPDF_Bool.hh
@@ -1,5 +1,5 @@
-#ifndef __QPDF_BOOL_HH__
-#define __QPDF_BOOL_HH__
+#ifndef QPDF_BOOL_HH
+#define QPDF_BOOL_HH
#include <qpdf/QPDFObject.hh>
@@ -9,6 +9,7 @@ class QPDF_Bool: public QPDFObject
QPDF_Bool(bool val);
virtual ~QPDF_Bool();
virtual std::string unparse();
+ virtual JSON getJSON();
virtual QPDFObject::object_type_e getTypeCode() const;
virtual char const* getTypeName() const;
bool getVal() const;
@@ -17,4 +18,4 @@ class QPDF_Bool: public QPDFObject
bool val;
};
-#endif // __QPDF_BOOL_HH__
+#endif // QPDF_BOOL_HH
diff --git a/libqpdf/qpdf/QPDF_Dictionary.hh b/libqpdf/qpdf/QPDF_Dictionary.hh
index cea63835..4ea3ae7d 100644
--- a/libqpdf/qpdf/QPDF_Dictionary.hh
+++ b/libqpdf/qpdf/QPDF_Dictionary.hh
@@ -1,5 +1,5 @@
-#ifndef __QPDF_DICTIONARY_HH__
-#define __QPDF_DICTIONARY_HH__
+#ifndef QPDF_DICTIONARY_HH
+#define QPDF_DICTIONARY_HH
#include <qpdf/QPDFObject.hh>
@@ -14,6 +14,7 @@ class QPDF_Dictionary: public QPDFObject
QPDF_Dictionary(std::map<std::string, QPDFObjectHandle> const& items);
virtual ~QPDF_Dictionary();
virtual std::string unparse();
+ virtual JSON getJSON();
virtual QPDFObject::object_type_e getTypeCode() const;
virtual char const* getTypeName() const;
virtual void setDescription(QPDF*, std::string const&);
@@ -40,4 +41,4 @@ class QPDF_Dictionary: public QPDFObject
std::map<std::string, QPDFObjectHandle> items;
};
-#endif // __QPDF_DICTIONARY_HH__
+#endif // QPDF_DICTIONARY_HH
diff --git a/libqpdf/qpdf/QPDF_InlineImage.hh b/libqpdf/qpdf/QPDF_InlineImage.hh
index 6be034a9..bc544c39 100644
--- a/libqpdf/qpdf/QPDF_InlineImage.hh
+++ b/libqpdf/qpdf/QPDF_InlineImage.hh
@@ -1,5 +1,5 @@
-#ifndef __QPDF_INLINEIMAGE_HH__
-#define __QPDF_INLINEIMAGE_HH__
+#ifndef QPDF_INLINEIMAGE_HH
+#define QPDF_INLINEIMAGE_HH
#include <qpdf/QPDFObject.hh>
@@ -9,6 +9,7 @@ class QPDF_InlineImage: public QPDFObject
QPDF_InlineImage(std::string const& val);
virtual ~QPDF_InlineImage();
virtual std::string unparse();
+ virtual JSON getJSON();
virtual QPDFObject::object_type_e getTypeCode() const;
virtual char const* getTypeName() const;
std::string getVal() const;
@@ -17,4 +18,4 @@ class QPDF_InlineImage: public QPDFObject
std::string val;
};
-#endif // __QPDF_INLINEIMAGE_HH__
+#endif // QPDF_INLINEIMAGE_HH
diff --git a/libqpdf/qpdf/QPDF_Integer.hh b/libqpdf/qpdf/QPDF_Integer.hh
index 091392ab..35e11a94 100644
--- a/libqpdf/qpdf/QPDF_Integer.hh
+++ b/libqpdf/qpdf/QPDF_Integer.hh
@@ -1,5 +1,5 @@
-#ifndef __QPDF_INTEGER_HH__
-#define __QPDF_INTEGER_HH__
+#ifndef QPDF_INTEGER_HH
+#define QPDF_INTEGER_HH
#include <qpdf/QPDFObject.hh>
@@ -9,6 +9,7 @@ class QPDF_Integer: public QPDFObject
QPDF_Integer(long long val);
virtual ~QPDF_Integer();
virtual std::string unparse();
+ virtual JSON getJSON();
virtual QPDFObject::object_type_e getTypeCode() const;
virtual char const* getTypeName() const;
long long getVal() const;
@@ -17,4 +18,4 @@ class QPDF_Integer: public QPDFObject
long long val;
};
-#endif // __QPDF_INTEGER_HH__
+#endif // QPDF_INTEGER_HH
diff --git a/libqpdf/qpdf/QPDF_Name.hh b/libqpdf/qpdf/QPDF_Name.hh
index 5f50c41d..f87ca460 100644
--- a/libqpdf/qpdf/QPDF_Name.hh
+++ b/libqpdf/qpdf/QPDF_Name.hh
@@ -1,5 +1,5 @@
-#ifndef __QPDF_NAME_HH__
-#define __QPDF_NAME_HH__
+#ifndef QPDF_NAME_HH
+#define QPDF_NAME_HH
#include <qpdf/QPDFObject.hh>
@@ -9,6 +9,7 @@ class QPDF_Name: public QPDFObject
QPDF_Name(std::string const& name);
virtual ~QPDF_Name();
virtual std::string unparse();
+ virtual JSON getJSON();
virtual QPDFObject::object_type_e getTypeCode() const;
virtual char const* getTypeName() const;
std::string getName() const;
@@ -20,4 +21,4 @@ class QPDF_Name: public QPDFObject
std::string name;
};
-#endif // __QPDF_NAME_HH__
+#endif // QPDF_NAME_HH
diff --git a/libqpdf/qpdf/QPDF_Null.hh b/libqpdf/qpdf/QPDF_Null.hh
index 16ca4755..667c72f9 100644
--- a/libqpdf/qpdf/QPDF_Null.hh
+++ b/libqpdf/qpdf/QPDF_Null.hh
@@ -1,5 +1,5 @@
-#ifndef __QPDF_NULL_HH__
-#define __QPDF_NULL_HH__
+#ifndef QPDF_NULL_HH
+#define QPDF_NULL_HH
#include <qpdf/QPDFObject.hh>
@@ -8,8 +8,9 @@ class QPDF_Null: public QPDFObject
public:
virtual ~QPDF_Null();
virtual std::string unparse();
+ virtual JSON getJSON();
virtual QPDFObject::object_type_e getTypeCode() const;
virtual char const* getTypeName() const;
};
-#endif // __QPDF_NULL_HH__
+#endif // QPDF_NULL_HH
diff --git a/libqpdf/qpdf/QPDF_Operator.hh b/libqpdf/qpdf/QPDF_Operator.hh
index 9d18ad37..4b3787a0 100644
--- a/libqpdf/qpdf/QPDF_Operator.hh
+++ b/libqpdf/qpdf/QPDF_Operator.hh
@@ -1,5 +1,5 @@
-#ifndef __QPDF_OPERATOR_HH__
-#define __QPDF_OPERATOR_HH__
+#ifndef QPDF_OPERATOR_HH
+#define QPDF_OPERATOR_HH
#include <qpdf/QPDFObject.hh>
@@ -9,6 +9,7 @@ class QPDF_Operator: public QPDFObject
QPDF_Operator(std::string const& val);
virtual ~QPDF_Operator();
virtual std::string unparse();
+ virtual JSON getJSON();
virtual QPDFObject::object_type_e getTypeCode() const;
virtual char const* getTypeName() const;
std::string getVal() const;
@@ -17,4 +18,4 @@ class QPDF_Operator: public QPDFObject
std::string val;
};
-#endif // __QPDF_OPERATOR_HH__
+#endif // QPDF_OPERATOR_HH
diff --git a/libqpdf/qpdf/QPDF_Real.hh b/libqpdf/qpdf/QPDF_Real.hh
index d59ed982..dbc03de6 100644
--- a/libqpdf/qpdf/QPDF_Real.hh
+++ b/libqpdf/qpdf/QPDF_Real.hh
@@ -1,5 +1,5 @@
-#ifndef __QPDF_REAL_HH__
-#define __QPDF_REAL_HH__
+#ifndef QPDF_REAL_HH
+#define QPDF_REAL_HH
#include <qpdf/QPDFObject.hh>
@@ -10,6 +10,7 @@ class QPDF_Real: public QPDFObject
QPDF_Real(double value, int decimal_places = 0);
virtual ~QPDF_Real();
virtual std::string unparse();
+ virtual JSON getJSON();
virtual QPDFObject::object_type_e getTypeCode() const;
virtual char const* getTypeName() const;
std::string getVal();
@@ -19,4 +20,4 @@ class QPDF_Real: public QPDFObject
std::string val;
};
-#endif // __QPDF_REAL_HH__
+#endif // QPDF_REAL_HH
diff --git a/libqpdf/qpdf/QPDF_Reserved.hh b/libqpdf/qpdf/QPDF_Reserved.hh
index 8dbf4fe4..71adbf3a 100644
--- a/libqpdf/qpdf/QPDF_Reserved.hh
+++ b/libqpdf/qpdf/QPDF_Reserved.hh
@@ -1,5 +1,5 @@
-#ifndef __QPDF_RESERVED_HH__
-#define __QPDF_RESERVED_HH__
+#ifndef QPDF_RESERVED_HH
+#define QPDF_RESERVED_HH
#include <qpdf/QPDFObject.hh>
@@ -8,8 +8,9 @@ class QPDF_Reserved: public QPDFObject
public:
virtual ~QPDF_Reserved();
virtual std::string unparse();
+ virtual JSON getJSON();
virtual QPDFObject::object_type_e getTypeCode() const;
virtual char const* getTypeName() const;
};
-#endif // __QPDF_RESERVED_HH__
+#endif // QPDF_RESERVED_HH
diff --git a/libqpdf/qpdf/QPDF_Stream.hh b/libqpdf/qpdf/QPDF_Stream.hh
index 98b8c11f..647b600d 100644
--- a/libqpdf/qpdf/QPDF_Stream.hh
+++ b/libqpdf/qpdf/QPDF_Stream.hh
@@ -1,5 +1,5 @@
-#ifndef __QPDF_STREAM_HH__
-#define __QPDF_STREAM_HH__
+#ifndef QPDF_STREAM_HH
+#define QPDF_STREAM_HH
#include <qpdf/Types.h>
@@ -17,12 +17,19 @@ class QPDF_Stream: public QPDFObject
qpdf_offset_t offset, size_t length);
virtual ~QPDF_Stream();
virtual std::string unparse();
+ virtual JSON getJSON();
virtual QPDFObject::object_type_e getTypeCode() const;
virtual char const* getTypeName() const;
virtual void setDescription(QPDF*, std::string const&);
QPDFObjectHandle getDict() const;
bool isDataModified() const;
+ // Methods to help QPDF copy foreign streams
+ qpdf_offset_t getOffset() const;
+ size_t getLength() const;
+ PointerHolder<Buffer> getStreamDataBuffer() const;
+ PointerHolder<QPDFObjectHandle::StreamDataProvider> getStreamDataProvider() const;
+
// See comments in QPDFObjectHandle.hh for these methods.
bool pipeStreamData(Pipeline*,
unsigned long encode_flags,
@@ -82,4 +89,4 @@ class QPDF_Stream: public QPDFObject
PointerHolder<QPDFObjectHandle::TokenFilter> > token_filters;
};
-#endif // __QPDF_STREAM_HH__
+#endif // QPDF_STREAM_HH
diff --git a/libqpdf/qpdf/QPDF_String.hh b/libqpdf/qpdf/QPDF_String.hh
index b4858c49..1c7bb639 100644
--- a/libqpdf/qpdf/QPDF_String.hh
+++ b/libqpdf/qpdf/QPDF_String.hh
@@ -1,5 +1,5 @@
-#ifndef __QPDF_STRING_HH__
-#define __QPDF_STRING_HH__
+#ifndef QPDF_STRING_HH
+#define QPDF_STRING_HH
#include <qpdf/QPDFObject.hh>
@@ -15,6 +15,7 @@ class QPDF_String: public QPDFObject
virtual QPDFObject::object_type_e getTypeCode() const;
virtual char const* getTypeName() const;
std::string unparse(bool force_binary);
+ virtual JSON getJSON();
std::string getVal() const;
std::string getUTF8Val() const;
@@ -22,4 +23,4 @@ class QPDF_String: public QPDFObject
std::string val;
};
-#endif // __QPDF_STRING_HH__
+#endif // QPDF_STRING_HH
diff --git a/libqpdf/qpdf/RC4.hh b/libqpdf/qpdf/RC4.hh
index c26f3d0f..efd91069 100644
--- a/libqpdf/qpdf/RC4.hh
+++ b/libqpdf/qpdf/RC4.hh
@@ -1,5 +1,5 @@
-#ifndef __RC4_HH__
-#define __RC4_HH__
+#ifndef RC4_HH
+#define RC4_HH
class RC4
{
@@ -22,4 +22,4 @@ class RC4
RC4Key key;
};
-#endif // __RC4_HH__
+#endif // RC4_HH
diff --git a/libqpdf/qpdf/SecureRandomDataProvider.hh b/libqpdf/qpdf/SecureRandomDataProvider.hh
index 178c73c0..3f5ed19f 100644
--- a/libqpdf/qpdf/SecureRandomDataProvider.hh
+++ b/libqpdf/qpdf/SecureRandomDataProvider.hh
@@ -1,5 +1,5 @@
-#ifndef __SECURERANDOMDATAPROVIDER_HH__
-#define __SECURERANDOMDATAPROVIDER_HH__
+#ifndef SECURERANDOMDATAPROVIDER_HH
+#define SECURERANDOMDATAPROVIDER_HH
#include <qpdf/RandomDataProvider.hh>
#include <qpdf/DLL.h>
@@ -19,4 +19,4 @@ class SecureRandomDataProvider: public RandomDataProvider
static RandomDataProvider* getInstance();
};
-#endif // __SECURERANDOMDATAPROVIDER_HH__
+#endif // SECURERANDOMDATAPROVIDER_HH
diff --git a/libqpdf/qpdf/qpdf-config.h.in b/libqpdf/qpdf/qpdf-config.h.in
new file mode 100644
index 00000000..e50a00b8
--- /dev/null
+++ b/libqpdf/qpdf/qpdf-config.h.in
@@ -0,0 +1,106 @@
+/* libqpdf/qpdf/qpdf-config.h.in. Generated from configure.ac by autoheader. */
+
+/* Whether to avoid use of HANDLE in Windows */
+#undef AVOID_WINDOWS_HANDLE
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#undef HAVE_DLFCN_H
+
+/* Define to 1 if fseeko (and presumably ftello) exists and is declared. */
+#undef HAVE_FSEEKO
+
+/* Define to 1 if you have the `fseeko64' function. */
+#undef HAVE_FSEEKO64
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#undef HAVE_INTTYPES_H
+
+/* Define to 1 if you have the <memory.h> header file. */
+#undef HAVE_MEMORY_H
+
+/* Define to 1 if you have the `random' function. */
+#undef HAVE_RANDOM
+
+/* Define to 1 (and set RANDOM_DEVICE) if a random device is available */
+#undef HAVE_RANDOM_DEVICE
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#undef HAVE_STDINT_H
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#undef HAVE_STDLIB_H
+
+/* Define to 1 if you have the <strings.h> header file. */
+#undef HAVE_STRINGS_H
+
+/* Define to 1 if you have the <string.h> header file. */
+#undef HAVE_STRING_H
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#undef HAVE_SYS_STAT_H
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#undef HAVE_SYS_TYPES_H
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#undef HAVE_UNISTD_H
+
+/* Define to the sub-directory where libtool stores uninstalled libraries. */
+#undef LT_OBJDIR
+
+/* Define to the address where bug reports for this package should be sent. */
+#undef PACKAGE_BUGREPORT
+
+/* Define to the full name of this package. */
+#undef PACKAGE_NAME
+
+/* Define to the full name and version of this package. */
+#undef PACKAGE_STRING
+
+/* Define to the one symbol short name of this package. */
+#undef PACKAGE_TARNAME
+
+/* Define to the home page for this package. */
+#undef PACKAGE_URL
+
+/* Define to the version of this package. */
+#undef PACKAGE_VERSION
+
+/* Define to the filename of the random device (and set HAVE_RANDOM_DEVICE) */
+#undef RANDOM_DEVICE
+
+/* Whether to suppres use of OS-provided secure random numbers */
+#undef SKIP_OS_SECURE_RANDOM
+
+/* Define to 1 if you have the ANSI C header files. */
+#undef STDC_HEADERS
+
+/* Whether to use insecure random numbers */
+#undef USE_INSECURE_RANDOM
+
+/* Enable large inode numbers on Mac OS X 10.5. */
+#ifndef _DARWIN_USE_64_BIT_INODE
+# define _DARWIN_USE_64_BIT_INODE 1
+#endif
+
+/* Number of bits in a file offset, on hosts where this is settable. */
+#undef _FILE_OFFSET_BITS
+
+/* Define to 1 to make fseeko visible on some hosts (e.g. glibc 2.2). */
+#undef _LARGEFILE_SOURCE
+
+/* Define for large files, on AIX-style hosts. */
+#undef _LARGE_FILES
+
+/* Define for Solaris 2.5.1 so the uint32_t typedef from <sys/synch.h>,
+ <pthread.h>, or <semaphore.h> is not used. If the typedef were allowed, the
+ #define below would cause a syntax error. */
+#undef _UINT32_T
+
+/* Define to the type of an unsigned integer type of width exactly 16 bits if
+ such a type exists and the standard includes do not define it. */
+#undef uint16_t
+
+/* Define to the type of an unsigned integer type of width exactly 32 bits if
+ such a type exists and the standard includes do not define it. */
+#undef uint32_t
diff --git a/libqpdf/qpdf/rijndael.h b/libqpdf/qpdf/rijndael.h
index a9cd71da..8b4c88f6 100644
--- a/libqpdf/qpdf/rijndael.h
+++ b/libqpdf/qpdf/rijndael.h
@@ -1,5 +1,5 @@
-#ifndef H__RIJNDAEL
-#define H__RIJNDAEL
+#ifndef RIJNDAEL_H
+#define RIJNDAEL_H
#include <qpdf/qpdf-config.h>
#ifdef HAVE_INTTYPES_H
diff --git a/libqpdf/sph/sph_sha2.h b/libqpdf/sph/sph_sha2.h
index 4bff9cd8..dde62088 100644
--- a/libqpdf/sph/sph_sha2.h
+++ b/libqpdf/sph/sph_sha2.h
@@ -37,8 +37,8 @@
* @author Thomas Pornin <thomas.pornin@cryptolog.com>
*/
-#ifndef SPH_SHA2_H__
-#define SPH_SHA2_H__
+#ifndef SPH_SHA2_H
+#define SPH_SHA2_H
#ifdef __cplusplus
extern "C" {
diff --git a/libqpdf/sph/sph_types.h b/libqpdf/sph/sph_types.h
index eab09a26..f434d8bb 100644
--- a/libqpdf/sph/sph_types.h
+++ b/libqpdf/sph/sph_types.h
@@ -44,8 +44,8 @@
* @author Thomas Pornin <thomas.pornin@cryptolog.com>
*/
-#ifndef SPH_TYPES_H__
-#define SPH_TYPES_H__
+#ifndef SPH_TYPES_H
+#define SPH_TYPES_H
#include <limits.h>