2
1
mirror of https://github.com/qpdf/qpdf.git synced 2024-11-17 18:15:09 +00:00
qpdf/libqpdf/QPDFWriter.cc
Jay Berkenbilt 5d4cad9c02 ABI change: fix use of off_t, size_t, and integer types
Significantly improve the code's use of off_t for file offsets, size_t
for memory sizes, and integer types in cases where there has to be
compatibility with external interfaces.  Rework sections of the code
that would have prevented qpdf from working on files larger than 2 (or
maybe 4) GB in size.
2012-06-20 15:20:26 -04:00

2378 lines
59 KiB
C++
Raw Blame History

#include <qpdf/QPDFWriter.hh>
#include <assert.h>
#include <qpdf/Pl_StdioFile.hh>
#include <qpdf/Pl_Count.hh>
#include <qpdf/Pl_Discard.hh>
#include <qpdf/Pl_RC4.hh>
#include <qpdf/Pl_AES_PDF.hh>
#include <qpdf/Pl_Flate.hh>
#include <qpdf/Pl_PNGFilter.hh>
#include <qpdf/QUtil.hh>
#include <qpdf/MD5.hh>
#include <qpdf/RC4.hh>
#include <qpdf/QTC.hh>
#include <qpdf/QPDF.hh>
#include <qpdf/QPDFObjectHandle.hh>
#include <qpdf/QPDF_Name.hh>
#include <qpdf/QPDF_String.hh>
#include <stdlib.h>
QPDFWriter::QPDFWriter(QPDF& pdf) :
pdf(pdf)
{
init();
}
QPDFWriter::QPDFWriter(QPDF& pdf, char const* filename) :
pdf(pdf)
{
init();
setOutputFilename(filename);
}
void
QPDFWriter::init()
{
filename = 0;
file = 0;
close_file = false;
buffer_pipeline = 0;
output_buffer = 0;
normalize_content_set = false;
normalize_content = false;
stream_data_mode_set = false;
stream_data_mode = qpdf_s_compress;
qdf_mode = false;
static_id = false;
suppress_original_object_ids = false;
direct_stream_lengths = true;
encrypted = false;
preserve_encryption = true;
linearized = false;
object_stream_mode = qpdf_o_preserve;
encrypt_metadata = true;
encrypt_use_aes = false;
encryption_dict_objid = 0;
next_objid = 1;
cur_stream_length_id = 0;
cur_stream_length = 0;
added_newline = false;
max_ostream_index = 0;
}
QPDFWriter::~QPDFWriter()
{
if (file && close_file)
{
fclose(file);
}
if (output_buffer)
{
delete output_buffer;
}
}
void
QPDFWriter::setOutputFilename(char const* filename)
{
this->filename = filename;
if (filename == 0)
{
this->filename = "standard output";
QTC::TC("qpdf", "QPDFWriter write to stdout");
file = stdout;
QUtil::binary_stdout();
}
else
{
QTC::TC("qpdf", "QPDFWriter write to file");
file = QUtil::fopen_wrapper(std::string("open ") + filename,
fopen(filename, "wb+"));
close_file = true;
}
Pipeline* p = new Pl_StdioFile("qpdf output", file);
to_delete.push_back(p);
initializePipelineStack(p);
}
void
QPDFWriter::setOutputMemory()
{
this->buffer_pipeline = new Pl_Buffer("qpdf output");
to_delete.push_back(this->buffer_pipeline);
initializePipelineStack(this->buffer_pipeline);
}
Buffer*
QPDFWriter::getBuffer()
{
Buffer* result = this->output_buffer;
this->output_buffer = 0;
return result;
}
void
QPDFWriter::setObjectStreamMode(qpdf_object_stream_e mode)
{
this->object_stream_mode = mode;
}
void
QPDFWriter::setStreamDataMode(qpdf_stream_data_e mode)
{
this->stream_data_mode_set = true;
this->stream_data_mode = mode;
}
void
QPDFWriter::setContentNormalization(bool val)
{
this->normalize_content_set = true;
this->normalize_content = val;
}
void
QPDFWriter::setQDFMode(bool val)
{
this->qdf_mode = val;
}
void
QPDFWriter::setMinimumPDFVersion(std::string const& version)
{
bool set_version = false;
if (this->min_pdf_version.empty())
{
set_version = true;
}
else
{
int old_major = 0;
int old_minor = 0;
int min_major = 0;
int min_minor = 0;
parseVersion(version, old_major, old_minor);
parseVersion(this->min_pdf_version, min_major, min_minor);
if (compareVersions(old_major, old_minor, min_major, min_minor) > 0)
{
QTC::TC("qpdf", "QPDFWriter increasing minimum version");
set_version = true;
}
}
if (set_version)
{
this->min_pdf_version = version;
}
}
void
QPDFWriter::forcePDFVersion(std::string const& version)
{
this->forced_pdf_version = version;
}
void
QPDFWriter::setStaticID(bool val)
{
this->static_id = val;
}
void
QPDFWriter::setStaticAesIV(bool val)
{
if (val)
{
Pl_AES_PDF::useStaticIV();
}
}
void
QPDFWriter::setSuppressOriginalObjectIDs(bool val)
{
this->suppress_original_object_ids = val;
}
void
QPDFWriter::setPreserveEncryption(bool val)
{
this->preserve_encryption = val;
}
void
QPDFWriter::setLinearization(bool val)
{
this->linearized = val;
}
void
QPDFWriter::setR2EncryptionParameters(
char const* user_password, char const* owner_password,
bool allow_print, bool allow_modify,
bool allow_extract, bool allow_annotate)
{
std::set<int> clear;
if (! allow_print)
{
clear.insert(3);
}
if (! allow_modify)
{
clear.insert(4);
}
if (! allow_extract)
{
clear.insert(5);
}
if (! allow_annotate)
{
clear.insert(6);
}
setMinimumPDFVersion("1.3");
setEncryptionParameters(user_password, owner_password, 1, 2, 5, clear);
}
void
QPDFWriter::setR3EncryptionParameters(
char const* user_password, char const* owner_password,
bool allow_accessibility, bool allow_extract,
qpdf_r3_print_e print, qpdf_r3_modify_e modify)
{
std::set<int> clear;
interpretR3EncryptionParameters(
clear, user_password, owner_password,
allow_accessibility, allow_extract, print, modify);
setMinimumPDFVersion("1.4");
setEncryptionParameters(user_password, owner_password, 2, 3, 16, clear);
}
void
QPDFWriter::setR4EncryptionParameters(
char const* user_password, char const* owner_password,
bool allow_accessibility, bool allow_extract,
qpdf_r3_print_e print, qpdf_r3_modify_e modify,
bool encrypt_metadata, bool use_aes)
{
std::set<int> clear;
interpretR3EncryptionParameters(
clear, user_password, owner_password,
allow_accessibility, allow_extract, print, modify);
this->encrypt_use_aes = use_aes;
this->encrypt_metadata = encrypt_metadata;
setMinimumPDFVersion(use_aes ? "1.6" : "1.5");
setEncryptionParameters(user_password, owner_password, 4, 4, 16, clear);
}
void
QPDFWriter::interpretR3EncryptionParameters(
std::set<int>& clear,
char const* user_password, char const* owner_password,
bool allow_accessibility, bool allow_extract,
qpdf_r3_print_e print, qpdf_r3_modify_e modify)
{
// Acrobat 5 security options:
// Checkboxes:
// Enable Content Access for the Visually Impaired
// Allow Content Copying and Extraction
// Allowed changes menu:
// None
// Only Document Assembly
// Only Form Field Fill-in or Signing
// Comment Authoring, Form Field Fill-in or Signing
// General Editing, Comment and Form Field Authoring
// Allowed printing menu:
// None
// Low Resolution
// Full printing
if (! allow_accessibility)
{
clear.insert(10);
}
if (! allow_extract)
{
clear.insert(5);
}
// Note: these switch statements all "fall through" (no break
// statements). Each option clears successively more access bits.
switch (print)
{
case qpdf_r3p_none:
clear.insert(3); // any printing
case qpdf_r3p_low:
clear.insert(12); // high resolution printing
case qpdf_r3p_full:
break;
// no default so gcc warns for missing cases
}
switch (modify)
{
case qpdf_r3m_none:
clear.insert(11); // document assembly
case qpdf_r3m_assembly:
clear.insert(9); // filling in form fields
case qpdf_r3m_form:
clear.insert(6); // modify annotations, fill in form fields
case qpdf_r3m_annotate:
clear.insert(4); // other modifications
case qpdf_r3m_all:
break;
// no default so gcc warns for missing cases
}
}
void
QPDFWriter::setEncryptionParameters(
char const* user_password, char const* owner_password,
int V, int R, int key_len, std::set<int>& bits_to_clear)
{
// PDF specification refers to bits with the low bit numbered 1.
// We have to convert this into a bit field.
// Specification always requires bits 1 and 2 to be cleared.
bits_to_clear.insert(1);
bits_to_clear.insert(2);
int P = 0;
// Create the complement of P, then invert.
for (std::set<int>::iterator iter = bits_to_clear.begin();
iter != bits_to_clear.end(); ++iter)
{
P |= (1 << ((*iter) - 1));
}
P = ~P;
generateID();
std::string O;
std::string U;
QPDF::compute_encryption_O_U(
user_password, owner_password, V, R, key_len, P,
this->encrypt_metadata, this->id1, O, U);
setEncryptionParametersInternal(
V, R, key_len, P, O, U, this->id1, user_password);
}
void
QPDFWriter::copyEncryptionParameters()
{
generateID();
QPDFObjectHandle trailer = this->pdf.getTrailer();
if (trailer.hasKey("/Encrypt"))
{
QPDFObjectHandle encrypt = trailer.getKey("/Encrypt");
int V = encrypt.getKey("/V").getIntValue();
int key_len = 5;
if (V > 1)
{
key_len = encrypt.getKey("/Length").getIntValue() / 8;
}
if (encrypt.hasKey("/EncryptMetadata") &&
encrypt.getKey("/EncryptMetadata").isBool())
{
this->encrypt_metadata =
encrypt.getKey("/EncryptMetadata").getBoolValue();
}
QTC::TC("qpdf", "QPDFWriter copy encrypt metadata",
this->encrypt_metadata ? 0 : 1);
setEncryptionParametersInternal(
V,
encrypt.getKey("/R").getIntValue(),
key_len,
encrypt.getKey("/P").getIntValue(),
encrypt.getKey("/O").getStringValue(),
encrypt.getKey("/U").getStringValue(),
this->id1, // this->id1 == the other file's id1
pdf.getPaddedUserPassword());
}
}
void
QPDFWriter::disableIncompatibleEncryption(int major, int minor)
{
if (! this->encrypted)
{
return;
}
bool disable = false;
if (compareVersions(major, minor, 1, 3) < 0)
{
disable = true;
}
else
{
int V = atoi(encryption_dictionary["/V"].c_str());
int R = atoi(encryption_dictionary["/R"].c_str());
if (compareVersions(major, minor, 1, 4) < 0)
{
if ((V > 1) || (R > 2))
{
disable = true;
}
}
else if (compareVersions(major, minor, 1, 5) < 0)
{
if ((V > 2) || (R > 3))
{
disable = true;
}
}
else if (compareVersions(major, minor, 1, 6) < 0)
{
if (this->encrypt_use_aes)
{
disable = true;
}
}
}
if (disable)
{
QTC::TC("qpdf", "QPDFWriter forced version disabled encryption");
this->encrypted = false;
}
}
void
QPDFWriter::parseVersion(std::string const& version,
int& major, int& minor) const
{
major = atoi(version.c_str());
minor = 0;
size_t p = version.find('.');
if ((p != std::string::npos) && (version.length() > p))
{
minor = atoi(version.substr(p + 1).c_str());
}
std::string tmp = QUtil::int_to_string(major) + "." +
QUtil::int_to_string(minor);
if (tmp != version)
{
throw std::logic_error(
"INTERNAL ERROR: QPDFWriter::parseVersion"
" called with invalid version number " + version);
}
}
int
QPDFWriter::compareVersions(int major1, int minor1,
int major2, int minor2) const
{
if (major1 < major2)
{
return -1;
}
else if (major1 > major2)
{
return 1;
}
else if (minor1 < minor2)
{
return -1;
}
else if (minor1 > minor2)
{
return 1;
}
else
{
return 0;
}
}
void
QPDFWriter::setEncryptionParametersInternal(
int V, int R, int key_len, long P,
std::string const& O, std::string const& U,
std::string const& id1, std::string const& user_password)
{
encryption_dictionary["/Filter"] = "/Standard";
encryption_dictionary["/V"] = QUtil::int_to_string(V);
encryption_dictionary["/Length"] = QUtil::int_to_string(key_len * 8);
encryption_dictionary["/R"] = QUtil::int_to_string(R);
encryption_dictionary["/P"] = QUtil::int_to_string(P);
encryption_dictionary["/O"] = QPDF_String(O).unparse(true);
encryption_dictionary["/U"] = QPDF_String(U).unparse(true);
if ((R >= 4) && (! encrypt_metadata))
{
encryption_dictionary["/EncryptMetadata"] = "false";
}
if (V == 4)
{
// The spec says the value for the crypt filter key can be
// anything, and xpdf seems to agree. However, Adobe Reader
// won't open our files unless we use /StdCF.
encryption_dictionary["/StmF"] = "/StdCF";
encryption_dictionary["/StrF"] = "/StdCF";
std::string method = (this->encrypt_use_aes ? "/AESV2" : "/V2");
encryption_dictionary["/CF"] =
"<< /StdCF << /AuthEvent /DocOpen /CFM " + method + " >> >>";
}
this->encrypted = true;
QPDF::EncryptionData encryption_data(
V, R, key_len, P, O, U, this->id1, this->encrypt_metadata);
this->encryption_key = QPDF::compute_encryption_key(
user_password, encryption_data);
}
void
QPDFWriter::setDataKey(int objid)
{
this->cur_data_key = QPDF::compute_data_key(
this->encryption_key, objid, 0, this->encrypt_use_aes);
}
int
QPDFWriter::bytesNeeded(unsigned long n)
{
int bytes = 0;
while (n)
{
++bytes;
n >>= 8;
}
return bytes;
}
void
QPDFWriter::writeBinary(unsigned long val, unsigned int bytes)
{
assert(bytes <= sizeof(unsigned long));
unsigned char data[sizeof(unsigned long)];
for (unsigned int i = 0; i < bytes; ++i)
{
data[bytes - i - 1] = (unsigned char)(val & 0xff);
val >>= 8;
}
this->pipeline->write(data, bytes);
}
void
QPDFWriter::writeString(std::string const& str)
{
this->pipeline->write((unsigned char*)str.c_str(), str.length());
}
void
QPDFWriter::writeBuffer(PointerHolder<Buffer>& b)
{
this->pipeline->write(b->getBuffer(), b->getSize());
}
void
QPDFWriter::writeStringQDF(std::string const& str)
{
if (this->qdf_mode)
{
writeString(str);
}
}
void
QPDFWriter::writeStringNoQDF(std::string const& str)
{
if (! this->qdf_mode)
{
writeString(str);
}
}
void
QPDFWriter::writePad(int nspaces)
{
for (int i = 0; i < nspaces; ++i)
{
writeString(" ");
}
}
Pipeline*
QPDFWriter::pushPipeline(Pipeline* p)
{
assert(dynamic_cast<Pl_Count*>(p) == 0);
this->pipeline_stack.push_back(p);
return p;
}
void
QPDFWriter::initializePipelineStack(Pipeline *p)
{
this->pipeline = new Pl_Count("qpdf count", p);
to_delete.push_back(this->pipeline);
this->pipeline_stack.push_back(this->pipeline);
}
void
QPDFWriter::activatePipelineStack()
{
Pl_Count* c = new Pl_Count("count", this->pipeline_stack.back());
this->pipeline_stack.push_back(c);
this->pipeline = c;
}
void
QPDFWriter::popPipelineStack(PointerHolder<Buffer>* bp)
{
assert(this->pipeline_stack.size() >= 2);
this->pipeline->finish();
assert(dynamic_cast<Pl_Count*>(this->pipeline_stack.back()) ==
this->pipeline);
delete this->pipeline_stack.back();
this->pipeline_stack.pop_back();
while (dynamic_cast<Pl_Count*>(this->pipeline_stack.back()) == 0)
{
Pipeline* p = this->pipeline_stack.back();
this->pipeline_stack.pop_back();
Pl_Buffer* buf = dynamic_cast<Pl_Buffer*>(p);
if (bp && buf)
{
*bp = buf->getBuffer();
}
delete p;
}
this->pipeline = dynamic_cast<Pl_Count*>(this->pipeline_stack.back());
}
void
QPDFWriter::adjustAESStreamLength(size_t& length)
{
if (this->encrypted && (! this->cur_data_key.empty()) &&
this->encrypt_use_aes)
{
// Stream length will be padded with 1 to 16 bytes to end up
// as a multiple of 16. It will also be prepended by 16 bits
// of random data.
length += 32 - (length & 0xf);
}
}
void
QPDFWriter::pushEncryptionFilter()
{
if (this->encrypted && (! this->cur_data_key.empty()))
{
Pipeline* p = 0;
if (this->encrypt_use_aes)
{
p = new Pl_AES_PDF(
"aes stream encryption", this->pipeline, true,
(unsigned char*) this->cur_data_key.c_str());
}
else
{
p = new Pl_RC4("rc4 stream encryption", this->pipeline,
(unsigned char*) this->cur_data_key.c_str(),
(int)this->cur_data_key.length());
}
pushPipeline(p);
}
// Must call this unconditionally so we can call popPipelineStack
// to balance pushEncryptionFilter().
activatePipelineStack();
}
void
QPDFWriter::pushDiscardFilter()
{
pushPipeline(new Pl_Discard());
activatePipelineStack();
}
int
QPDFWriter::openObject(int objid)
{
if (objid == 0)
{
objid = this->next_objid++;
}
this->xref[objid] = QPDFXRefEntry(1, pipeline->getCount(), 0);
writeString(QUtil::int_to_string(objid));
writeString(" 0 obj\n");
return objid;
}
void
QPDFWriter::closeObject(int objid)
{
// Write a newline before endobj as it makes the file easier to
// repair.
writeString("\nendobj\n");
writeStringQDF("\n");
this->lengths[objid] = pipeline->getCount() - this->xref[objid].getOffset();
}
void
QPDFWriter::assignCompressedObjectNumbers(int objid)
{
if (this->object_stream_to_objects.count(objid) == 0)
{
return;
}
// Reserve numbers for the objects that belong to this object
// stream.
for (std::set<int>::iterator iter =
this->object_stream_to_objects[objid].begin();
iter != this->object_stream_to_objects[objid].end();
++iter)
{
obj_renumber[*iter] = next_objid++;
}
}
void
QPDFWriter::enqueueObject(QPDFObjectHandle object)
{
if (object.isIndirect())
{
if (object.isNull())
{
// This is a place-holder object for an object stream
}
else if (object.isScalar())
{
// flattenScalarReferences is supposed to have removed all
// indirect scalars.
throw std::logic_error(
"INTERNAL ERROR: QPDFWriter::enqueueObject: indirect scalar: " +
std::string(this->filename) + " " +
QUtil::int_to_string(object.getObjectID()) + " " +
QUtil::int_to_string(object.getGeneration()));
}
int objid = object.getObjectID();
if (obj_renumber.count(objid) == 0)
{
if (this->object_to_object_stream.count(objid))
{
// This is in an object stream. Don't process it
// here. Instead, enqueue the object stream.
int stream_id = this->object_to_object_stream[objid];
enqueueObject(this->pdf.getObjectByID(stream_id, 0));
}
else
{
object_queue.push_back(object);
obj_renumber[objid] = next_objid++;
if (this->object_stream_to_objects.count(objid))
{
// For linearized files, uncompressed objects go
// at end, and we take care of assigning numbers
// to them elsewhere.
if (! this->linearized)
{
assignCompressedObjectNumbers(objid);
}
}
else if ((! this->direct_stream_lengths) && object.isStream())
{
// reserve next object ID for length
++next_objid;
}
}
}
}
else if (object.isArray())
{
int n = object.getArrayNItems();
for (int i = 0; i < n; ++i)
{
if (! this->linearized)
{
enqueueObject(object.getArrayItem(i));
}
}
}
else if (object.isDictionary())
{
std::set<std::string> keys = object.getKeys();
for (std::set<std::string>::iterator iter = keys.begin();
iter != keys.end(); ++iter)
{
if (! this->linearized)
{
enqueueObject(object.getKey(*iter));
}
}
}
else
{
// ignore
}
}
void
QPDFWriter::unparseChild(QPDFObjectHandle child, int level, int flags)
{
if (! this->linearized)
{
enqueueObject(child);
}
if (child.isIndirect())
{
if (child.isScalar())
{
// flattenScalarReferences is supposed to have removed all
// indirect scalars.
throw std::logic_error(
"INTERNAL ERROR: QPDFWriter::unparseChild: indirect scalar: " +
QUtil::int_to_string(child.getObjectID()) + " " +
QUtil::int_to_string(child.getGeneration()));
}
int old_id = child.getObjectID();
int new_id = obj_renumber[old_id];
writeString(QUtil::int_to_string(new_id));
writeString(" 0 R");
}
else
{
unparseObject(child, level, flags);
}
}
void
QPDFWriter::writeTrailer(trailer_e which, int size, bool xref_stream, int prev)
{
QPDFObjectHandle trailer = pdf.getTrailer();
if (! xref_stream)
{
writeString("trailer <<");
}
writeStringQDF("\n");
if (which == t_lin_second)
{
writeString(" /Size ");
writeString(QUtil::int_to_string(size));
}
else
{
std::set<std::string> keys = trailer.getKeys();
for (std::set<std::string>::iterator iter = keys.begin();
iter != keys.end(); ++iter)
{
std::string const& key = *iter;
writeStringQDF(" ");
writeStringNoQDF(" ");
writeString(QPDF_Name::normalizeName(key));
writeString(" ");
if (key == "/Size")
{
writeString(QUtil::int_to_string(size));
if (which == t_lin_first)
{
writeString(" /Prev ");
off_t pos = this->pipeline->getCount();
writeString(QUtil::int_to_string(prev));
int nspaces = (int)(pos - this->pipeline->getCount() + 11);
assert(nspaces >= 0);
writePad(nspaces);
}
}
else
{
unparseChild(trailer.getKey(key), 1, 0);
}
writeStringQDF("\n");
}
}
// Write ID
writeStringQDF(" ");
writeString(" /ID [");
writeString(QPDF_String(this->id1).unparse(true));
writeString(QPDF_String(this->id2).unparse(true));
writeString("]");
if (which != t_lin_second)
{
// Write reference to encryption dictionary
if (this->encrypted)
{
writeString(" /Encrypt ");
writeString(QUtil::int_to_string(this->encryption_dict_objid));
writeString(" 0 R");
}
}
writeStringQDF("\n");
writeStringNoQDF(" ");
writeString(">>");
}
void
QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
unsigned int flags)
{
unparseObject(object, level, flags, 0, false);
}
void
QPDFWriter::unparseObject(QPDFObjectHandle object, int level,
unsigned int flags, size_t stream_length,
bool compress)
{
unsigned int child_flags = flags & ~f_stream;
std::string indent;
for (int i = 0; i < level; ++i)
{
indent += " ";
}
if (object.isArray())
{
// Note: PDF spec 1.4 implementation note 121 states that
// Acrobat requires a space after the [ in the /H key of the
// linearization parameter dictionary. We'll do this
// unconditionally for all arrays because it looks nicer and
// doesn't make the files that much bigger.
writeString("[");
writeStringQDF("\n");
int n = object.getArrayNItems();
for (int i = 0; i < n; ++i)
{
writeStringQDF(indent);
writeStringQDF(" ");
writeStringNoQDF(" ");
unparseChild(object.getArrayItem(i), level + 1, child_flags);
writeStringQDF("\n");
}
writeStringQDF(indent);
writeStringNoQDF(" ");
writeString("]");
}
else if (object.isDictionary())
{
writeString("<<");
writeStringQDF("\n");
std::set<std::string> keys = object.getKeys();
for (std::set<std::string>::iterator iter = keys.begin();
iter != keys.end(); ++iter)
{
// I'm not fully clear on /Crypt keys in /DecodeParms. If
// one is found, we refuse to filter, so we should be
// safe.
std::string const& key = *iter;
if ((flags & f_filtered) &&
((key == "/Filter") ||
(key == "/DecodeParms")))
{
continue;
}
if ((flags & f_stream) && (key == "/Length"))
{
continue;
}
writeStringQDF(indent);
writeStringQDF(" ");
writeStringNoQDF(" ");
writeString(QPDF_Name::normalizeName(key));
writeString(" ");
unparseChild(object.getKey(key), level + 1, child_flags);
writeStringQDF("\n");
}
if (flags & f_stream)
{
writeStringQDF(indent);
writeStringQDF(" ");
writeString(" /Length ");
if (this->direct_stream_lengths)
{
writeString(QUtil::int_to_string(stream_length));
}
else
{
writeString(
QUtil::int_to_string(this->cur_stream_length_id));
writeString(" 0 R");
}
writeStringQDF("\n");
if (compress && (flags & f_filtered))
{
writeStringQDF(indent);
writeStringQDF(" ");
writeString(" /Filter /FlateDecode");
writeStringQDF("\n");
}
}
writeStringQDF(indent);
writeStringNoQDF(" ");
writeString(">>");
}
else if (object.isStream())
{
// Write stream data to a buffer.
int old_id = object.getObjectID();
int new_id = obj_renumber[old_id];
if (! this->direct_stream_lengths)
{
this->cur_stream_length_id = new_id + 1;
}
QPDFObjectHandle stream_dict = object.getDict();
bool is_metadata = false;
if (stream_dict.getKey("/Type").isName() &&
(stream_dict.getKey("/Type").getName() == "/Metadata"))
{
is_metadata = true;
}
bool filter = (this->stream_data_mode != qpdf_s_preserve);
if (this->stream_data_mode == qpdf_s_compress)
{
// Don't filter if the stream is already compressed with
// FlateDecode. We don't want to make it worse by getting
// rid of a predictor or otherwise messing with it. We
// should also avoid messing with anything that's
// compressed with a lossy compression scheme, but we
// don't support any of those right now.
QPDFObjectHandle filter_obj = stream_dict.getKey("/Filter");
if (filter_obj.isName() &&
((filter_obj.getName() == "/FlateDecode") ||
(filter_obj.getName() == "/Fl")))
{
QTC::TC("qpdf", "QPDFWriter not recompressing /FlateDecode");
filter = false;
}
}
bool normalize = false;
bool compress = false;
if (is_metadata &&
((! this->encrypted) || (this->encrypt_metadata == false)))
{
QTC::TC("qpdf", "QPDFWriter not compressing metadata");
filter = true;
compress = false;
}
else if (this->normalize_content && normalized_streams.count(old_id))
{
normalize = true;
filter = true;
}
else if (filter && (this->stream_data_mode == qpdf_s_compress))
{
compress = true;
QTC::TC("qpdf", "QPDFWriter compressing uncompressed stream");
}
flags |= f_stream;
pushPipeline(new Pl_Buffer("stream data"));
activatePipelineStack();
bool filtered =
object.pipeStreamData(this->pipeline, filter, normalize, compress);
PointerHolder<Buffer> stream_data;
popPipelineStack(&stream_data);
if (filtered)
{
flags |= f_filtered;
}
else
{
compress = false;
}
this->cur_stream_length = stream_data->getSize();
if (is_metadata && this->encrypted && (! this->encrypt_metadata))
{
// Don't encrypt stream data for the metadata stream
this->cur_data_key.clear();
}
adjustAESStreamLength(this->cur_stream_length);
unparseObject(stream_dict, 0, flags, this->cur_stream_length, compress);
writeString("\nstream\n");
pushEncryptionFilter();
writeBuffer(stream_data);
char last_char = this->pipeline->getLastChar();
popPipelineStack();
if (this->qdf_mode)
{
if (last_char != '\n')
{
writeString("\n");
this->added_newline = true;
}
else
{
this->added_newline = false;
}
}
writeString("endstream");
}
else if (object.isString())
{
std::string val;
if (this->encrypted &&
(! (flags & f_in_ostream)) &&
(! this->cur_data_key.empty()))
{
val = object.getStringValue();
if (this->encrypt_use_aes)
{
Pl_Buffer bufpl("encrypted string");
Pl_AES_PDF pl("aes encrypt string", &bufpl, true,
(unsigned char const*)this->cur_data_key.c_str());
pl.write((unsigned char*) val.c_str(), val.length());
pl.finish();
Buffer* buf = bufpl.getBuffer();
val = QPDF_String(
std::string((char*)buf->getBuffer(),
buf->getSize())).unparse(true);
delete buf;
}
else
{
char* tmp = QUtil::copy_string(val);
size_t vlen = val.length();
RC4 rc4((unsigned char const*)this->cur_data_key.c_str(),
(int)this->cur_data_key.length());
rc4.process((unsigned char*)tmp, (int)vlen);
val = QPDF_String(std::string(tmp, vlen)).unparse();
delete [] tmp;
}
}
else
{
val = object.unparseResolved();
}
writeString(val);
}
else
{
writeString(object.unparseResolved());
}
}
void
QPDFWriter::writeObjectStreamOffsets(std::vector<off_t>& offsets,
int first_obj)
{
for (unsigned int i = 0; i < offsets.size(); ++i)
{
if (i != 0)
{
writeStringQDF("\n");
writeStringNoQDF(" ");
}
writeString(QUtil::int_to_string(i + first_obj));
writeString(" ");
writeString(QUtil::int_to_string(offsets[i]));
}
writeString("\n");
}
void
QPDFWriter::writeObjectStream(QPDFObjectHandle object)
{
// Note: object might be null if this is a place-holder for an
// object stream that we are generating from scratch.
int old_id = object.getObjectID();
int new_id = obj_renumber[old_id];
std::vector<off_t> offsets;
off_t first = 0;
// Generate stream itself. We have to do this in two passes so we
// can calculate offsets in the first pass.
PointerHolder<Buffer> stream_buffer;
int first_obj = -1;
bool compressed = false;
for (int pass = 1; pass <= 2; ++pass)
{
if (pass == 1)
{
pushDiscardFilter();
}
else
{
// Adjust offsets to skip over comment before first object
first = offsets[0];
for (std::vector<off_t>::iterator iter = offsets.begin();
iter != offsets.end(); ++iter)
{
*iter -= first;
}
// Take one pass at writing pairs of numbers so we can get
// their size information
pushDiscardFilter();
writeObjectStreamOffsets(offsets, first_obj);
first += this->pipeline->getCount();
popPipelineStack();
// Set up a stream to write the stream data into a buffer.
Pipeline* next = pushPipeline(new Pl_Buffer("object stream"));
if (! ((this->stream_data_mode == qpdf_s_uncompress) ||
this->qdf_mode))
{
compressed = true;
next = pushPipeline(
new Pl_Flate("compress object stream", next,
Pl_Flate::a_deflate));
}
activatePipelineStack();
writeObjectStreamOffsets(offsets, first_obj);
}
int count = 0;
for (std::set<int>::iterator iter =
this->object_stream_to_objects[old_id].begin();
iter != this->object_stream_to_objects[old_id].end();
++iter, ++count)
{
int obj = *iter;
int new_obj = this->obj_renumber[obj];
if (first_obj == -1)
{
first_obj = new_obj;
}
if (this->qdf_mode)
{
writeString("%% Object stream: object " +
QUtil::int_to_string(new_obj) + ", index " +
QUtil::int_to_string(count));
if (! this->suppress_original_object_ids)
{
writeString("; original object ID: " +
QUtil::int_to_string(obj));
}
writeString("\n");
}
if (pass == 1)
{
offsets.push_back(this->pipeline->getCount());
}
writeObject(this->pdf.getObjectByID(obj, 0), count);
this->xref[new_obj] = QPDFXRefEntry(2, new_id, count);
}
// stream_buffer will be initialized only for pass 2
popPipelineStack(&stream_buffer);
}
// Write the object
openObject(new_id);
setDataKey(new_id);
writeString("<<");
writeStringQDF("\n ");
writeString(" /Type /ObjStm");
writeStringQDF("\n ");
size_t length = stream_buffer->getSize();
adjustAESStreamLength(length);
writeString(" /Length " + QUtil::int_to_string(length));
writeStringQDF("\n ");
if (compressed)
{
writeString(" /Filter /FlateDecode");
}
writeString(" /N " + QUtil::int_to_string(offsets.size()));
writeStringQDF("\n ");
writeString(" /First " + QUtil::int_to_string(first));
if (! object.isNull())
{
// If the original object has an /Extends key, preserve it.
QPDFObjectHandle dict = object.getDict();
QPDFObjectHandle extends = dict.getKey("/Extends");
if (extends.isIndirect())
{
QTC::TC("qpdf", "QPDFWriter copy Extends");
writeStringQDF("\n ");
writeString(" /Extends ");
unparseChild(extends, 1, f_in_ostream);
}
}
writeStringQDF("\n");
writeStringNoQDF(" ");
writeString(">>\nstream\n");
if (this->encrypted)
{
QTC::TC("qpdf", "QPDFWriter encrypt object stream");
}
pushEncryptionFilter();
writeBuffer(stream_buffer);
popPipelineStack();
writeString("endstream");
this->cur_data_key.clear();
closeObject(new_id);
}
void
QPDFWriter::writeObject(QPDFObjectHandle object, int object_stream_index)
{
int old_id = object.getObjectID();
if ((object_stream_index == -1) &&
(this->object_stream_to_objects.count(old_id)))
{
writeObjectStream(object);
return;
}
int new_id = obj_renumber[old_id];
if (this->qdf_mode)
{
if (this->page_object_to_seq.count(old_id))
{
writeString("%% Page ");
writeString(
QUtil::int_to_string(
this->page_object_to_seq[old_id]));
writeString("\n");
}
if (this->contents_to_page_seq.count(old_id))
{
writeString("%% Contents for page ");
writeString(
QUtil::int_to_string(
this->contents_to_page_seq[old_id]));
writeString("\n");
}
}
if (object_stream_index == -1)
{
if (this->qdf_mode && (! this->suppress_original_object_ids))
{
writeString("%% Original object ID: " +
QUtil::int_to_string(object.getObjectID()) + " " +
QUtil::int_to_string(object.getGeneration()) + "\n");
}
openObject(new_id);
setDataKey(new_id);
unparseObject(object, 0, 0);
this->cur_data_key.clear();
closeObject(new_id);
}
else
{
unparseObject(object, 0, f_in_ostream);
writeString("\n");
}
if ((! this->direct_stream_lengths) && object.isStream())
{
if (this->qdf_mode)
{
if (this->added_newline)
{
writeString("%QDF: ignore_newline\n");
}
}
openObject(new_id + 1);
writeString(QUtil::int_to_string(this->cur_stream_length));
closeObject(new_id + 1);
}
}
void
QPDFWriter::generateID()
{
// Note: we can't call generateID() at the time of construction
// since the caller hasn't yet had a chance to call setStaticID(),
// but we need to generate it before computing encryption
// dictionary parameters. This is why we call this function both
// from setEncryptionParameters() and from write() and return
// immediately if the ID has already been generated.
if (! this->id2.empty())
{
return;
}
QPDFObjectHandle trailer = pdf.getTrailer();
std::string result;
if (this->static_id)
{
// For test suite use only...
static unsigned char tmp[] = {0x31, 0x41, 0x59, 0x26,
0x53, 0x58, 0x97, 0x93,
0x23, 0x84, 0x62, 0x64,
0x33, 0x83, 0x27, 0x95,
0x00};
result = (char*)tmp;
}
else
{
// The PDF specification has guidelines for creating IDs, but it
// states clearly that the only thing that's really important is
// that it is very likely to be unique. We can't really follow
// the guidelines in the spec exactly because we haven't written
// the file yet. This scheme should be fine though.
std::string seed;
seed += QUtil::int_to_string((int)QUtil::get_current_time());
seed += " QPDF ";
seed += filename;
seed += " ";
if (trailer.hasKey("/Info"))
{
std::set<std::string> keys = trailer.getKeys();
for (std::set<std::string>::iterator iter = keys.begin();
iter != keys.end(); ++iter)
{
QPDFObjectHandle obj = trailer.getKey(*iter);
if (obj.isString())
{
seed += " ";
seed += obj.getStringValue();
}
}
}
MD5 m;
m.encodeString(seed.c_str());
MD5::Digest digest;
m.digest(digest);
result = std::string((char*)digest, sizeof(MD5::Digest));
}
// If /ID already exists, follow the spec: use the original first
// word and generate a new second word. Otherwise, we'll use the
// generated ID for both.
this->id2 = result;
if (trailer.hasKey("/ID"))
{
// Note: keep /ID from old file even if --static-id was given.
this->id1 = trailer.getKey("/ID").getArrayItem(0).getStringValue();
}
else
{
this->id1 = this->id2;
}
}
void
QPDFWriter::initializeSpecialStreams()
{
// Mark all page content streams in case we are filtering or
// normalizing.
std::vector<QPDFObjectHandle> pages = pdf.getAllPages();
int num = 0;
for (std::vector<QPDFObjectHandle>::iterator iter = pages.begin();
iter != pages.end(); ++iter)
{
QPDFObjectHandle& page = *iter;
this->page_object_to_seq[page.getObjectID()] = ++num;
QPDFObjectHandle contents = page.getKey("/Contents");
std::vector<int> contents_objects;
if (contents.isArray())
{
int n = contents.getArrayNItems();
for (int i = 0; i < n; ++i)
{
contents_objects.push_back(
contents.getArrayItem(i).getObjectID());
}
}
else if (contents.isStream())
{
contents_objects.push_back(contents.getObjectID());
}
for (std::vector<int>::iterator iter = contents_objects.begin();
iter != contents_objects.end(); ++iter)
{
this->contents_to_page_seq[*iter] = num;
this->normalized_streams.insert(*iter);
}
}
}
void
QPDFWriter::preserveObjectStreams()
{
this->pdf.getObjectStreamData(this->object_to_object_stream);
}
void
QPDFWriter::generateObjectStreams()
{
// Basic strategy: make a list of objects that can go into an
// object stream. Then figure out how many object streams are
// needed so that we can distribute objects approximately evenly
// without having any object stream exceed 100 members. We don't
// have to worry about linearized files here -- if the file is
// linearized, we take care of excluding things that aren't
// allowed here later.
// This code doesn't do anything with /Extends.
std::vector<int> const& eligible = this->pdf.getCompressibleObjects();
unsigned int n_object_streams =
(unsigned int)((eligible.size() + 99) / 100);
unsigned int n_per = (unsigned int)(eligible.size() / n_object_streams);
if (n_per * n_object_streams < eligible.size())
{
++n_per;
}
unsigned int n = 0;
int cur_ostream = 0;
for (std::vector<int>::const_iterator iter = eligible.begin();
iter != eligible.end(); ++iter)
{
if ((n % n_per) == 0)
{
if (n > 0)
{
QTC::TC("qpdf", "QPDFWriter generate >1 ostream");
}
n = 0;
}
if (n == 0)
{
// Construct a new null object as the "original" object
// stream. The rest of the code knows that this means
// we're creating the object stream from scratch.
cur_ostream = this->pdf.makeIndirectObject(
QPDFObjectHandle::newNull()).getObjectID();
}
this->object_to_object_stream[*iter] = cur_ostream;
++n;
}
}
void
QPDFWriter::write()
{
// Do preliminary setup
if (this->linearized)
{
this->qdf_mode = false;
}
if (this->qdf_mode)
{
if (! this->normalize_content_set)
{
this->normalize_content = true;
}
if (! this->stream_data_mode_set)
{
this->stream_data_mode = qpdf_s_uncompress;
}
}
if (this->encrypted)
{
// Encryption has been explicitly set
this->preserve_encryption = false;
}
else if (this->normalize_content ||
(this->stream_data_mode == qpdf_s_uncompress) ||
this->qdf_mode)
{
// Encryption makes looking at contents pretty useless. If
// the user explicitly encrypted though, we still obey that.
this->preserve_encryption = false;
}
if (preserve_encryption)
{
copyEncryptionParameters();
}
if (! this->forced_pdf_version.empty())
{
int major = 0;
int minor = 0;
parseVersion(this->forced_pdf_version, major, minor);
disableIncompatibleEncryption(major, minor);
if (compareVersions(major, minor, 1, 5) < 0)
{
QTC::TC("qpdf", "QPDFWriter forcing object stream disable");
this->object_stream_mode = qpdf_o_disable;
}
}
if (this->qdf_mode || this->normalize_content ||
(this->stream_data_mode == qpdf_s_uncompress))
{
initializeSpecialStreams();
}
if (this->qdf_mode)
{
// Generate indirect stream lengths for qdf mode since fix-qdf
// uses them for storing recomputed stream length data.
// Certain streams such as object streams, xref streams, and
// hint streams always get direct stream lengths.
this->direct_stream_lengths = false;
}
switch (this->object_stream_mode)
{
case qpdf_o_disable:
// no action required
break;
case qpdf_o_preserve:
preserveObjectStreams();
break;
case qpdf_o_generate:
generateObjectStreams();
break;
// no default so gcc will warn for missing case tag
}
if (this->linearized)
{
// Page dictionaries are not allowed to be compressed objects.
std::vector<QPDFObjectHandle> pages = pdf.getAllPages();
for (std::vector<QPDFObjectHandle>::iterator iter = pages.begin();
iter != pages.end(); ++iter)
{
QPDFObjectHandle& page = *iter;
int objid = page.getObjectID();
if (this->object_to_object_stream.count(objid))
{
QTC::TC("qpdf", "QPDFWriter uncompressing page dictionary");
this->object_to_object_stream.erase(objid);
}
}
}
if (this->linearized || this->encrypted)
{
// The document catalog is not allowed to be compressed in
// linearized files either. It also appears that Adobe Reader
// 8.0.0 has a bug that prevents it from being able to handle
// encrypted files with compressed document catalogs, so we
// disable them in that case as well.
int objid = pdf.getRoot().getObjectID();
if (this->object_to_object_stream.count(objid))
{
QTC::TC("qpdf", "QPDFWriter uncompressing root");
this->object_to_object_stream.erase(objid);
}
}
// Generate reverse mapping from object stream to objects
for (std::map<int, int>::iterator iter =
this->object_to_object_stream.begin();
iter != this->object_to_object_stream.end(); ++iter)
{
int obj = (*iter).first;
int stream = (*iter).second;
this->object_stream_to_objects[stream].insert(obj);
this->max_ostream_index =
std::max(this->max_ostream_index,
(int)this->object_stream_to_objects[stream].size() - 1);
}
if (! this->object_stream_to_objects.empty())
{
setMinimumPDFVersion("1.5");
}
generateID();
pdf.trimTrailerForWrite();
pdf.flattenScalarReferences();
if (this->linearized)
{
writeLinearized();
}
else
{
writeStandard();
}
this->pipeline->finish();
if (this->close_file)
{
fclose(this->file);
}
this->file = 0;
if (this->buffer_pipeline)
{
this->output_buffer = this->buffer_pipeline->getBuffer();
this->buffer_pipeline = 0;
}
}
void
QPDFWriter::enqueuePart(std::vector<QPDFObjectHandle>& part)
{
for (std::vector<QPDFObjectHandle>::iterator iter = part.begin();
iter != part.end(); ++iter)
{
enqueueObject(*iter);
}
}
void
QPDFWriter::writeEncryptionDictionary()
{
this->encryption_dict_objid = openObject(this->encryption_dict_objid);
writeString("<<");
for (std::map<std::string, std::string>::iterator iter =
this->encryption_dictionary.begin();
iter != this->encryption_dictionary.end(); ++iter)
{
writeString(" ");
writeString((*iter).first);
writeString(" ");
writeString((*iter).second);
}
writeString(" >>");
closeObject(this->encryption_dict_objid);
}
void
QPDFWriter::writeHeader()
{
setMinimumPDFVersion(pdf.getPDFVersion());
std::string version = this->min_pdf_version;
if (! this->forced_pdf_version.empty())
{
QTC::TC("qpdf", "QPDFWriter using forced PDF version");
version = this->forced_pdf_version;
}
writeString("%PDF-");
writeString(version);
// This string of binary characters would not be valid UTF-8, so
// it really should be treated as binary.
writeString("\n%<25><><EFBFBD><EFBFBD>\n");
writeStringQDF("%QDF-1.0\n\n");
}
void
QPDFWriter::writeHintStream(int hint_id)
{
PointerHolder<Buffer> hint_buffer;
int S = 0;
int O = 0;
pdf.generateHintStream(
this->xref, this->lengths, this->obj_renumber, hint_buffer, S, O);
openObject(hint_id);
setDataKey(hint_id);
size_t hlen = hint_buffer->getSize();
writeString("<< /Filter /FlateDecode /S ");
writeString(QUtil::int_to_string(S));
if (O)
{
writeString(" /O ");
writeString(QUtil::int_to_string(O));
}
writeString(" /Length ");
adjustAESStreamLength(hlen);
writeString(QUtil::int_to_string(hlen));
writeString(" >>\nstream\n");
if (this->encrypted)
{
QTC::TC("qpdf", "QPDFWriter encrypted hint stream");
}
pushEncryptionFilter();
writeBuffer(hint_buffer);
char last_char = this->pipeline->getLastChar();
popPipelineStack();
if (last_char != '\n')
{
writeString("\n");
}
writeString("endstream");
closeObject(hint_id);
}
int
QPDFWriter::writeXRefTable(trailer_e which, int first, int last, int size)
{
return writeXRefTable(which, first, last, size, 0, false, 0, 0, 0);
}
int
QPDFWriter::writeXRefTable(trailer_e which, int first, int last, int size,
int prev, bool suppress_offsets,
int hint_id, off_t hint_offset, off_t hint_length)
{
writeString("xref\n");
writeString(QUtil::int_to_string(first));
writeString(" ");
writeString(QUtil::int_to_string(last - first + 1));
off_t space_before_zero = this->pipeline->getCount();
writeString("\n");
for (int i = first; i <= last; ++i)
{
if (i == 0)
{
writeString("0000000000 65535 f \n");
}
else
{
int offset = 0;
if (! suppress_offsets)
{
offset = this->xref[i].getOffset();
if ((hint_id != 0) &&
(i != hint_id) &&
(offset >= hint_offset))
{
offset += hint_length;
}
}
writeString(QUtil::int_to_string(offset, 10));
writeString(" 00000 n \n");
}
}
writeTrailer(which, size, false, prev);
writeString("\n");
return space_before_zero;
}
int
QPDFWriter::writeXRefStream(int objid, int max_id, int max_offset,
trailer_e which, int first, int last, int size)
{
return writeXRefStream(objid, max_id, max_offset,
which, first, last, size, 0, 0, 0, 0, false);
}
int
QPDFWriter::writeXRefStream(int xref_id, int max_id, int max_offset,
trailer_e which, int first, int last, int size,
int prev, int hint_id,
off_t hint_offset, off_t hint_length,
bool skip_compression)
{
off_t xref_offset = this->pipeline->getCount();
int space_before_zero = xref_offset - 1;
// field 1 contains offsets and object stream identifiers
int f1_size = std::max(bytesNeeded(max_offset),
bytesNeeded(max_id));
// field 2 contains object stream indices
int f2_size = bytesNeeded(this->max_ostream_index);
unsigned int esize = 1 + f1_size + f2_size;
// Must store in xref table in advance of writing the actual data
// rather than waiting for openObject to do it.
this->xref[xref_id] = QPDFXRefEntry(1, pipeline->getCount(), 0);
Pipeline* p = pushPipeline(new Pl_Buffer("xref stream"));
bool compressed = false;
if (! ((this->stream_data_mode == qpdf_s_uncompress) || this->qdf_mode))
{
compressed = true;
if (! skip_compression)
{
// Write the stream dictionary for compression but don't
// actually compress. This helps us with computation of
// padding for pass 1 of linearization.
p = pushPipeline(
new Pl_Flate("compress xref", p, Pl_Flate::a_deflate));
}
p = pushPipeline(
new Pl_PNGFilter(
"pngify xref", p, Pl_PNGFilter::a_encode, esize, 0));
}
activatePipelineStack();
for (int i = first; i <= last; ++i)
{
QPDFXRefEntry& e = this->xref[i];
switch (e.getType())
{
case 0:
writeBinary(0, 1);
writeBinary(0, f1_size);
writeBinary(0, f2_size);
break;
case 1:
{
int offset = e.getOffset();
if ((hint_id != 0) &&
(i != hint_id) &&
(offset >= hint_offset))
{
offset += hint_length;
}
writeBinary(1, 1);
writeBinary(offset, f1_size);
writeBinary(0, f2_size);
}
break;
case 2:
writeBinary(2, 1);
writeBinary(e.getObjStreamNumber(), f1_size);
writeBinary(e.getObjStreamIndex(), f2_size);
break;
default:
throw std::logic_error("invalid type writing xref stream");
break;
}
}
PointerHolder<Buffer> xref_data;
popPipelineStack(&xref_data);
openObject(xref_id);
writeString("<<");
writeStringQDF("\n ");
writeString(" /Type /XRef");
writeStringQDF("\n ");
writeString(" /Length " + QUtil::int_to_string(xref_data->getSize()));
if (compressed)
{
writeStringQDF("\n ");
writeString(" /Filter /FlateDecode");
writeStringQDF("\n ");
writeString(" /DecodeParms << /Columns " +
QUtil::int_to_string(esize) + " /Predictor 12 >>");
}
writeStringQDF("\n ");
writeString(" /W [ 1 " +
QUtil::int_to_string(f1_size) + " " +
QUtil::int_to_string(f2_size) + " ]");
if (! ((first == 0) && (last == size - 1)))
{
writeString(" /Index [ " +
QUtil::int_to_string(first) + " " +
QUtil::int_to_string(last - first + 1) + " ]");
}
writeTrailer(which, size, true, prev);
writeString("\nstream\n");
writeBuffer(xref_data);
writeString("\nendstream");
closeObject(xref_id);
return space_before_zero;
}
int
QPDFWriter::calculateXrefStreamPadding(int xref_bytes)
{
// This routine is called right after a linearization first pass
// xref stream has been written without compression. Calculate
// the amount of padding that would be required in the worst case,
// assuming the number of uncompressed bytes remains the same.
// The worst case for zlib is that the output is larger than the
// input by 6 bytes plus 5 bytes per 16K, and then we'll add 10
// extra bytes for number length increases.
return 16 + (5 * ((xref_bytes + 16383) / 16384));
}
void
QPDFWriter::writeLinearized()
{
// Optimize file and enqueue objects in order
bool need_xref_stream = (! this->object_to_object_stream.empty());
pdf.optimize(this->object_to_object_stream);
std::vector<QPDFObjectHandle> part4;
std::vector<QPDFObjectHandle> part6;
std::vector<QPDFObjectHandle> part7;
std::vector<QPDFObjectHandle> part8;
std::vector<QPDFObjectHandle> part9;
pdf.getLinearizedParts(this->object_to_object_stream,
part4, part6, part7, part8, part9);
// Object number sequence:
//
// second half
// second half uncompressed objects
// second half xref stream, if any
// second half compressed objects
// first half
// linearization dictionary
// first half xref stream, if any
// part 4 uncompresesd objects
// encryption dictionary, if any
// hint stream
// part 6 uncompressed objects
// first half compressed objects
//
// Second half objects
int second_half_uncompressed =
(int)(part7.size() + part8.size() + part9.size());
int second_half_first_obj = 1;
int after_second_half = 1 + second_half_uncompressed;
this->next_objid = after_second_half;
int second_half_xref = 0;
if (need_xref_stream)
{
second_half_xref = this->next_objid++;
}
// Assign numbers to all compressed objects in the second half.
std::vector<QPDFObjectHandle>* vecs2[] = {&part7, &part8, &part9};
for (int i = 0; i < 3; ++i)
{
for (std::vector<QPDFObjectHandle>::iterator iter = (*vecs2[i]).begin();
iter != (*vecs2[i]).end(); ++iter)
{
assignCompressedObjectNumbers((*iter).getObjectID());
}
}
int second_half_end = this->next_objid - 1;
int second_trailer_size = this->next_objid;
// First half objects
int first_half_start = this->next_objid;
int lindict_id = this->next_objid++;
int first_half_xref = 0;
if (need_xref_stream)
{
first_half_xref = this->next_objid++;
}
int part4_first_obj = this->next_objid;
this->next_objid += part4.size();
int after_part4 = this->next_objid;
if (this->encrypted)
{
this->encryption_dict_objid = this->next_objid++;
}
int hint_id = this->next_objid++;
int part6_first_obj = this->next_objid;
this->next_objid += part6.size();
int after_part6 = this->next_objid;
// Assign numbers to all compressed objects in the first half
std::vector<QPDFObjectHandle>* vecs1[] = {&part4, &part6};
for (int i = 0; i < 2; ++i)
{
for (std::vector<QPDFObjectHandle>::iterator iter = (*vecs1[i]).begin();
iter != (*vecs1[i]).end(); ++iter)
{
assignCompressedObjectNumbers((*iter).getObjectID());
}
}
int first_half_end = this->next_objid - 1;
int first_trailer_size = this->next_objid;
int part4_end_marker = part4.back().getObjectID();
int part6_end_marker = part6.back().getObjectID();
off_t space_before_zero = 0;
off_t file_size = 0;
off_t part6_end_offset = 0;
off_t first_half_max_obj_offset = 0;
off_t second_xref_offset = 0;
off_t first_xref_end = 0;
off_t second_xref_end = 0;
this->next_objid = part4_first_obj;
enqueuePart(part4);
assert(this->next_objid = after_part4);
this->next_objid = part6_first_obj;
enqueuePart(part6);
assert(this->next_objid == after_part6);
this->next_objid = second_half_first_obj;
enqueuePart(part7);
enqueuePart(part8);
enqueuePart(part9);
assert(this->next_objid == after_second_half);
off_t hint_length = 0;
PointerHolder<Buffer> hint_buffer;
// Write file in two passes. Part numbers refer to PDF spec 1.4.
for (int pass = 1; pass <= 2; ++pass)
{
if (pass == 1)
{
pushDiscardFilter();
}
// Part 1: header
writeHeader();
// Part 2: linearization parameter dictionary. Save enough
// space to write real dictionary. 150 characters is enough
// space if all numerical values in the parameter dictionary
// are 10 digits long plus a few extra characters for safety.
off_t pos = this->pipeline->getCount();
openObject(lindict_id);
writeString("<<");
if (pass == 2)
{
std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
int first_page_object = obj_renumber[pages[0].getObjectID()];
int npages = (int)pages.size();
writeString(" /Linearized 1 /L ");
writeString(QUtil::int_to_string(file_size + hint_length));
// Implementation note 121 states that a space is
// mandatory after this open bracket.
writeString(" /H [ ");
writeString(QUtil::int_to_string(this->xref[hint_id].getOffset()));
writeString(" ");
writeString(QUtil::int_to_string(hint_length));
writeString(" ] /O ");
writeString(QUtil::int_to_string(first_page_object));
writeString(" /E ");
writeString(QUtil::int_to_string(part6_end_offset + hint_length));
writeString(" /N ");
writeString(QUtil::int_to_string(npages));
writeString(" /T ");
writeString(QUtil::int_to_string(space_before_zero + hint_length));
}
writeString(" >>");
closeObject(lindict_id);
static int const pad = 150;
int spaces = (pos - this->pipeline->getCount() + pad);
assert(spaces >= 0);
writePad(spaces);
writeString("\n");
// Part 3: first page cross reference table and trailer.
off_t first_xref_offset = this->pipeline->getCount();
off_t hint_offset = 0;
if (pass == 2)
{
hint_offset = this->xref[hint_id].getOffset();
}
if (need_xref_stream)
{
// Must pad here too.
if (pass == 1)
{
// Set first_half_max_obj_offset to a value large
// enough to force four bytes to be reserved for each
// file offset. This would provide adequate space for
// the xref stream as long as the last object in page
// 1 starts with in the first 4 GB of the file, which
// is extremely likely. In the second pass, we will
// know the actual value for this, but it's okay if
// it's smaller.
first_half_max_obj_offset = 1 << 25;
}
pos = this->pipeline->getCount();
writeXRefStream(first_half_xref, first_half_end,
first_half_max_obj_offset,
t_lin_first, first_half_start, first_half_end,
first_trailer_size,
hint_length + second_xref_offset,
hint_id, hint_offset, hint_length,
(pass == 1));
off_t endpos = this->pipeline->getCount();
if (pass == 1)
{
// Pad so we have enough room for the real xref
// stream.
writePad(calculateXrefStreamPadding(endpos - pos));
first_xref_end = this->pipeline->getCount();
}
else
{
// Pad so that the next object starts at the same
// place as in pass 1.
writePad(first_xref_end - endpos);
// A failure of this insertion means we didn't allow
// enough padding for the first pass xref stream.
assert(this->pipeline->getCount() == first_xref_end);
}
writeString("\n");
}
else
{
writeXRefTable(t_lin_first, first_half_start, first_half_end,
first_trailer_size, hint_length + second_xref_offset,
(pass == 1), hint_id, hint_offset, hint_length);
writeString("startxref\n0\n%%EOF\n");
}
// Parts 4 through 9
for (std::list<QPDFObjectHandle>::iterator iter =
this->object_queue.begin();
iter != this->object_queue.end(); ++iter)
{
QPDFObjectHandle cur_object = (*iter);
if (cur_object.getObjectID() == part6_end_marker)
{
first_half_max_obj_offset = this->pipeline->getCount();
}
writeObject(cur_object);
if (cur_object.getObjectID() == part4_end_marker)
{
if (this->encrypted)
{
writeEncryptionDictionary();
}
if (pass == 1)
{
this->xref[hint_id] =
QPDFXRefEntry(1, this->pipeline->getCount(), 0);
}
else
{
// Part 5: hint stream
writeBuffer(hint_buffer);
}
}
if (cur_object.getObjectID() == part6_end_marker)
{
part6_end_offset = this->pipeline->getCount();
}
}
// Part 10: overflow hint stream -- not used
// Part 11: main cross reference table and trailer
second_xref_offset = this->pipeline->getCount();
if (need_xref_stream)
{
pos = this->pipeline->getCount();
space_before_zero =
writeXRefStream(second_half_xref,
second_half_end, second_xref_offset,
t_lin_second, 0, second_half_end,
second_trailer_size,
0, 0, 0, 0, (pass == 1));
off_t endpos = this->pipeline->getCount();
if (pass == 1)
{
// Pad so we have enough room for the real xref
// stream. See comments for previous xref stream on
// how we calculate the padding.
writePad(calculateXrefStreamPadding(endpos - pos));
writeString("\n");
second_xref_end = this->pipeline->getCount();
}
else
{
// Make the file size the same.
off_t pos = this->pipeline->getCount();
writePad(second_xref_end + hint_length - 1 - pos);
writeString("\n");
// If this assertion fails, maybe we didn't have
// enough padding above.
assert(this->pipeline->getCount() ==
(off_t)(second_xref_end + hint_length));
}
}
else
{
space_before_zero =
writeXRefTable(t_lin_second, 0, second_half_end,
second_trailer_size);
}
writeString("startxref\n");
writeString(QUtil::int_to_string(first_xref_offset));
writeString("\n%%EOF\n");
if (pass == 1)
{
// Close first pass pipeline
file_size = this->pipeline->getCount();
popPipelineStack();
// Save hint offset since it will be set to zero by
// calling openObject.
int hint_offset = this->xref[hint_id].getOffset();
// Write hint stream to a buffer
pushPipeline(new Pl_Buffer("hint buffer"));
activatePipelineStack();
writeHintStream(hint_id);
popPipelineStack(&hint_buffer);
hint_length = (off_t)hint_buffer->getSize();
// Restore hint offset
this->xref[hint_id] = QPDFXRefEntry(1, hint_offset, 0);
}
}
}
void
QPDFWriter::writeStandard()
{
// Start writing
writeHeader();
// Put root first on queue.
QPDFObjectHandle trailer = pdf.getTrailer();
enqueueObject(trailer.getKey("/Root"));
// Next place any other objects referenced from the trailer
// dictionary into the queue, handling direct objects recursively.
// Root is already there, so enqueuing it a second time is a
// no-op.
std::set<std::string> keys = trailer.getKeys();
for (std::set<std::string>::iterator iter = keys.begin();
iter != keys.end(); ++iter)
{
enqueueObject(trailer.getKey(*iter));
}
// Now start walking queue, output each object
while (this->object_queue.size())
{
QPDFObjectHandle cur_object = this->object_queue.front();
this->object_queue.pop_front();
writeObject(cur_object);
}
// Write out the encryption dictionary, if any
if (this->encrypted)
{
writeEncryptionDictionary();
}
// Now write out xref. next_objid is now the number of objects.
off_t xref_offset = this->pipeline->getCount();
if (this->object_stream_to_objects.empty())
{
// Write regular cross-reference table
// Write regular cross-reference table
writeXRefTable(t_normal, 0, this->next_objid - 1, this->next_objid);
}
else
{
// Write cross-reference stream.
int xref_id = this->next_objid++;
writeXRefStream(xref_id, xref_id, xref_offset, t_normal,
0, this->next_objid - 1, this->next_objid);
}
writeString("startxref\n");
writeString(QUtil::int_to_string(xref_offset));
writeString("\n%%EOF\n");
}