aboutsummaryrefslogtreecommitdiffstats
path: root/libqpdf/QPDFWriter.cc
diff options
context:
space:
mode:
Diffstat (limited to 'libqpdf/QPDFWriter.cc')
-rw-r--r--libqpdf/QPDFWriter.cc2021
1 files changed, 2021 insertions, 0 deletions
diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc
new file mode 100644
index 00000000..0a611eb9
--- /dev/null
+++ b/libqpdf/QPDFWriter.cc
@@ -0,0 +1,2021 @@
+
+#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_Buffer.hh>
+#include <qpdf/Pl_RC4.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>
+
+QPDFWriter::QPDFWriter(QPDF& pdf, char const* filename) :
+ pdf(pdf),
+ filename(filename),
+ file(0),
+ close_file(false),
+ normalize_content_set(false),
+ normalize_content(false),
+ stream_data_mode_set(false),
+ stream_data_mode(s_compress),
+ qdf_mode(false),
+ static_id(false),
+ direct_stream_lengths(true),
+ encrypted(false),
+ preserve_encryption(true),
+ linearized(false),
+ object_stream_mode(o_preserve),
+ encryption_dict_objid(0),
+ next_objid(1),
+ cur_stream_length_id(0),
+ cur_stream_length(0),
+ added_newline(false),
+ max_ostream_index(0)
+{
+ if (filename == 0)
+ {
+ this->filename = "standard output";
+ QTC::TC("qpdf", "QPDFWriter write to stdout");
+ file = 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("qdf output", file);
+ to_delete.push_back(p);
+ pipeline = new Pl_Count("qdf count", p);
+ to_delete.push_back(pipeline);
+ pipeline_stack.push_back(pipeline);
+}
+
+QPDFWriter::~QPDFWriter()
+{
+ if (file)
+ {
+ fclose(file);
+ }
+}
+
+void
+QPDFWriter::setObjectStreamMode(object_stream_e mode)
+{
+ this->object_stream_mode = mode;
+}
+
+void
+QPDFWriter::setStreamDataMode(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::setStaticID(bool val)
+{
+ this->static_id = 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);
+ }
+
+ this->min_pdf_version = "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,
+ r3_print_e print, 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
+
+ std::set<int> clear;
+ 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 r3p_none:
+ clear.insert(3); // any printing
+
+ case r3p_low:
+ clear.insert(12); // high resolution printing
+
+ case r3p_full:
+ break;
+
+ // no default so gcc warns for missing cases
+ }
+
+ switch (modify)
+ {
+ case r3m_none:
+ clear.insert(11); // document essembly
+
+ case r3m_assembly:
+ clear.insert(9); // filling in form fields
+
+ case r3m_form:
+ clear.insert(6); // modify annotations, fill in form fields
+
+ case r3m_annotate:
+ clear.insert(4); // other modifications
+
+ case r3m_all:
+ break;
+
+ // no default so gcc warns for missing cases
+ }
+
+ this->min_pdf_version = "1.4";
+ setEncryptionParameters(user_password, owner_password, 2, 3, 16, clear);
+}
+
+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 requirse bits 1 and 2 to be cleared.
+ bits_to_clear.insert(1);
+ bits_to_clear.insert(2);
+
+ unsigned long 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->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;
+ }
+ 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.getUserPassword());
+ }
+}
+
+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);
+ this->encrypted = true;
+ QPDF::EncryptionData encryption_data(V, R, key_len, P, O, U, this->id1);
+ 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);
+}
+
+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.getPointer()->getBuffer(),
+ b.getPointer()->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);
+ }
+}
+
+Pipeline*
+QPDFWriter::pushPipeline(Pipeline* p)
+{
+ assert(dynamic_cast<Pl_Count*>(p) == 0);
+ this->pipeline_stack.push_back(p);
+ return p;
+}
+
+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::pushEncryptionFilter()
+{
+ if (this->encrypted && (! this->cur_data_key.empty()))
+ {
+ Pipeline* p =
+ new Pl_RC4("stream encryption", this->pipeline,
+ (unsigned char*) this->cur_data_key.c_str(),
+ 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())
+ {
+ throw QEXC::Internal(
+ "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())
+ {
+ throw QEXC::Internal(
+ "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 ");
+ int pos = this->pipeline->getCount();
+ writeString(QUtil::int_to_string(prev));
+ int nspaces = pos + 11 - this->pipeline->getCount();
+ assert(nspaces >= 0);
+ for (int i = 0; i < nspaces; ++i)
+ {
+ writeString(" ");
+ }
+ }
+ }
+ 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, int 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)
+ {
+ 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 filter = (this->stream_data_mode != s_preserve);
+ if (this->stream_data_mode == 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 otherwising 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"))
+ {
+ QTC::TC("qpdf", "QPDFWriter not recompressing /FlateDecode");
+ filter = false;
+ }
+ }
+ bool normalize = false;
+ bool compress = false;
+ if (this->normalize_content && normalized_streams.count(old_id))
+ {
+ normalize = true;
+ filter = true;
+ }
+ else if (filter && (this->stream_data_mode == 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.getPointer()->getSize();
+ unparseObject(stream_dict, 0, flags, this->cur_stream_length, compress);
+ writeString("\nstream\n");
+ pushEncryptionFilter();
+ writeBuffer(stream_data);
+ popPipelineStack();
+
+ if (this->qdf_mode)
+ {
+ if (this->pipeline->getLastChar() != '\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();
+ char* tmp = QUtil::copy_string(val);
+ unsigned int vlen = val.length();
+ RC4 rc4((unsigned char const*)this->cur_data_key.c_str(),
+ this->cur_data_key.length());
+ rc4.process((unsigned char*)tmp, 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<int>& 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<int> offsets;
+ int 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<int>::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 == 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) + "\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 ");
+ writeString(" /Length " +
+ QUtil::int_to_string(stream_buffer.getPointer()->getSize()));
+ 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)
+ {
+ 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 char tmp[] = {0x31, 0x41, 0x59, 0x26,
+ 0x53, 0x58, 0x97, 0x93,
+ 0x23, 0x84, 0x62, 0x64,
+ 0x33, 0x83, 0x27, 0x95,
+ 0x00};
+ result = 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)time(0));
+ 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 = (eligible.size() + 99) / 100;
+ unsigned int n_per = 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 = s_uncompress;
+ }
+ }
+
+ if (this->encrypted)
+ {
+ // Encryption has been explicitly set
+ this->preserve_encryption = false;
+ }
+ else if (this->normalize_content ||
+ (this->stream_data_mode == 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->qdf_mode || this->normalize_content ||
+ (this->stream_data_mode == 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 o_disable:
+ // no action required
+ break;
+
+ case o_preserve:
+ preserveObjectStreams();
+ break;
+
+ case 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())
+ {
+ this->min_pdf_version = "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;
+}
+
+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()
+{
+ std::string version = pdf.getPDFVersion();
+ if (! this->min_pdf_version.empty())
+ {
+ float ov = atof(version.c_str());
+ float mv = atof(this->min_pdf_version.c_str());
+ if (mv > ov)
+ {
+ version = this->min_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%¿÷¢þ\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);
+
+ unsigned char* hs = hint_buffer.getPointer()->getBuffer();
+ unsigned long hlen = hint_buffer.getPointer()->getSize();
+
+ writeString("<< /Filter /FlateDecode /S ");
+ writeString(QUtil::int_to_string(S));
+ if (O)
+ {
+ writeString(" /O ");
+ writeString(QUtil::int_to_string(O));
+ }
+ writeString(" /Length ");
+ writeString(QUtil::int_to_string(hlen));
+ writeString(" >>\nstream\n");
+
+ if (this->encrypted)
+ {
+ QTC::TC("qpdf", "QPDFWriter encrypted hint stream");
+ }
+ pushEncryptionFilter();
+ writeBuffer(hint_buffer);
+ popPipelineStack();
+
+ if (hs[hlen - 1] != '\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, int hint_offset, int hint_length)
+{
+ writeString("xref\n");
+ writeString(QUtil::int_to_string(first));
+ writeString(" ");
+ writeString(QUtil::int_to_string(last - first + 1));
+ int 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);
+}
+
+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,
+ int hint_offset, int hint_length)
+{
+ int 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 == s_uncompress) || this->qdf_mode))
+ {
+ compressed = true;
+ 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 QEXC::Internal("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.getPointer()->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;
+}
+
+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 = 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();
+ int space_before_zero = 0;
+ int file_size = 0;
+ int part6_end_offset = 0;
+ int first_half_max_obj_offset = 0;
+ int second_xref_offset = 0;
+ int first_xref_end = 0;
+ int 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);
+
+ int 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.
+
+ int 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 = 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 + pad - this->pipeline->getCount());
+ assert(spaces >= 0);
+ for (int i = 0; i < spaces; ++i)
+ {
+ writeString(" ");
+ }
+ writeString("\n");
+
+ // Part 3: first page cross reference table and trailer.
+
+ int first_xref_offset = this->pipeline->getCount();
+ int hint_offset = 0;
+ if (pass == 2)
+ {
+ hint_offset = this->xref[hint_id].getOffset();
+ }
+ if (need_xref_stream)
+ {
+ // Must pad here too.
+ if (pass == 1)
+ {
+ // first_half_max_obj_offset is very likely to fall
+ // within the first 64K of the document (thus
+ // requiring two bytes for offsets) since it is the
+ // offset of the last uncompressed object in page 1.
+ // We allow for it to do otherwise though.
+ first_half_max_obj_offset = 65535;
+ }
+ 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);
+ int endpos = this->pipeline->getCount();
+ if (pass == 1)
+ {
+ // Pad so we have enough room for the real xref
+ // stream. In an extremely unlikely worst case,
+ // first_half_max_obj_offset could be enough larger to
+ // require two extra bytes beyond what we calculated
+ // in pass 1. This means we need to save two extra
+ // bytes for each xref entry. To that, we'll add 10
+ // extra bytes for number length increases.
+ int possible_extra =
+ 10 + (2 * (first_half_end - first_half_start + 1));
+ for (int i = 0; i < possible_extra; ++i)
+ {
+ writeString(" ");
+ }
+ first_xref_end = this->pipeline->getCount();
+ }
+ else
+ {
+ // Pad so that the next object starts at the same
+ // place as in pass 1.
+ for (int i = 0; i < first_xref_end - endpos; ++i)
+ {
+ writeString(" ");
+ }
+ 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)
+ {
+ space_before_zero =
+ writeXRefStream(second_half_xref,
+ second_half_end, second_xref_offset,
+ t_lin_second, 0, second_half_end,
+ second_trailer_size);
+ if (pass == 1)
+ {
+ // Add some padding -- we need an accurate file_size
+ // number, and this could change if the pass 2 xref
+ // stream compresses differently. There shouldn't be
+ // much difference, so we'll just pad 100 characters.
+ // This is unscientific though, and may not always
+ // work. The only way we could really get around this
+ // would be to seek back to the beginning of the file
+ // and update /L in the linearization dictionary, but
+ // that would be the only thing in the design that
+ // would require the output file to be seekable.
+ for (int i = 0; i < 99; ++i)
+ {
+ writeString(" ");
+ }
+ writeString("\n");
+ second_xref_end = this->pipeline->getCount();
+ }
+ else
+ {
+ // Make the file size the same.
+ int pos = this->pipeline->getCount();
+ while (pos < second_xref_end + hint_length - 1)
+ {
+ ++pos;
+ writeString(" ");
+ }
+ writeString("\n");
+ // If this assertion fails, maybe we didn't have
+ // enough padding above.
+ assert(this->pipeline->getCount() ==
+ 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 = hint_buffer.getPointer()->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");
+}