diff options
Diffstat (limited to 'libqpdf')
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> |