diff --git a/include/qpdf/QPDFWriter.hh b/include/qpdf/QPDFWriter.hh index b26be87..a3dccdd 100644 --- a/include/qpdf/QPDFWriter.hh +++ b/include/qpdf/QPDFWriter.hh @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -481,10 +482,16 @@ class QPDFWriter unsigned int bytesNeeded(long long n); void writeBinary(unsigned long long val, unsigned int bytes); - void writeString(std::string_view str); - void writeStringQDF(std::string_view str); - void writeStringNoQDF(std::string_view str); - void writePad(size_t nspaces); + QPDFWriter& write(std::string_view str); + QPDFWriter& write(size_t count, char c); + QPDFWriter& write(std::integral auto val); + QPDFWriter& write_name(std::string const& str); + QPDFWriter& write_string(std::string const& str, bool force_binary = false); + + template + QPDFWriter& write_qdf(Args&&... args); + template + QPDFWriter& write_no_qdf(Args&&... args); void assignCompressedObjectNumbers(QPDFObjGen og); void enqueueObject(QPDFObjectHandle object); void writeObjectStreamOffsets(std::vector& offsets, int first_obj); diff --git a/libqpdf.pc.in b/libqpdf.pc.in index 1a809f8..6f0ebe9 100644 --- a/libqpdf.pc.in +++ b/libqpdf.pc.in @@ -8,4 +8,4 @@ Description: PDF transformation library Version: @PROJECT_VERSION@ Requires.private: zlib, libjpeg@CRYPTO_PKG@ Libs: -L${libdir} -lqpdf -Cflags: -I${includedir} +Cflags: -I${includedir} -std=c++20 diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc index 68ab329..da675f7 100644 --- a/libqpdf/QPDFWriter.cc +++ b/libqpdf/QPDFWriter.cc @@ -832,32 +832,59 @@ QPDFWriter::writeBinary(unsigned long long val, unsigned int bytes) m->pipeline->write(data, bytes); } -void -QPDFWriter::writeString(std::string_view str) +QPDFWriter& +QPDFWriter::write(std::string_view str) { - m->pipeline->write(reinterpret_cast(str.data()), str.size()); + m->pipeline->write(str); + return *this; } -void -QPDFWriter::writeStringQDF(std::string_view str) +QPDFWriter& +QPDFWriter::write(std::integral auto val) { - if (m->qdf_mode) { - m->pipeline->write(reinterpret_cast(str.data()), str.size()); - } + m->pipeline->write(std::to_string(val)); + return *this; } -void -QPDFWriter::writeStringNoQDF(std::string_view str) +QPDFWriter& +QPDFWriter::write(size_t count, char c) { - if (!m->qdf_mode) { - m->pipeline->write(reinterpret_cast(str.data()), str.size()); + m->pipeline->write(count, c); + return *this; +} + +QPDFWriter& +QPDFWriter::write_name(std::string const& str) +{ + m->pipeline->write(Name::normalize(str)); + return *this; +} + +QPDFWriter& +QPDFWriter::write_string(std::string const& str, bool force_binary) +{ + m->pipeline->write(QPDF_String(str).unparse(force_binary)); + return *this; +} + +template +QPDFWriter& +QPDFWriter::write_qdf(Args&&... args) +{ + if (m->qdf_mode) { + m->pipeline->write(std::forward(args)...); } + return *this; } -void -QPDFWriter::writePad(size_t nspaces) +template +QPDFWriter& +QPDFWriter::write_no_qdf(Args&&... args) { - writeString(std::string(nspaces, ' ')); + if (!m->qdf_mode) { + m->pipeline->write(std::forward(args)...); + } + return *this; } Pipeline* @@ -1005,8 +1032,7 @@ QPDFWriter::openObject(int objid) objid = m->next_objid++; } m->new_obj[objid].xref = QPDFXRefEntry(m->pipeline->getCount()); - writeString(std::to_string(objid)); - writeString(" 0 obj\n"); + write(objid).write(" 0 obj\n"); return objid; } @@ -1014,8 +1040,7 @@ void QPDFWriter::closeObject(int objid) { // Write a newline before endobj as it makes the file easier to repair. - writeString("\nendobj\n"); - writeStringQDF("\n"); + write("\nendobj\n").write_qdf("\n"); auto& new_obj = m->new_obj[objid]; new_obj.length = m->pipeline->getCount() - new_obj.xref.getOffset(); } @@ -1114,8 +1139,7 @@ QPDFWriter::unparseChild(QPDFObjectHandle child, int level, int flags) enqueueObject(child); } if (child.isIndirect()) { - writeString(std::to_string(m->obj[child].renumber)); - writeString(" 0 R"); + write(m->obj[child].renumber).write(" 0 R"); } else { unparseObject(child, level, flags); } @@ -1129,78 +1153,63 @@ QPDFWriter::writeTrailer( if (xref_stream) { m->cur_data_key.clear(); } else { - writeString("trailer <<"); + write("trailer <<"); } - writeStringQDF("\n"); + write_qdf("\n"); if (which == t_lin_second) { - writeString(" /Size "); - writeString(std::to_string(size)); + write(" /Size ").write(size); } else { for (auto const& [key, value]: trailer.as_dictionary()) { if (value.null()) { continue; } - writeStringQDF(" "); - writeStringNoQDF(" "); - writeString(Name::normalize(key)); - writeString(" "); + write_qdf(" ").write_no_qdf(" ").write_name(key).write(" "); if (key == "/Size") { - writeString(std::to_string(size)); + write(size); if (which == t_lin_first) { - writeString(" /Prev "); + write(" /Prev "); qpdf_offset_t pos = m->pipeline->getCount(); - writeString(std::to_string(prev)); - writePad(QIntC::to_size(pos - m->pipeline->getCount() + 21)); + write(prev).write(QIntC::to_size(pos - m->pipeline->getCount() + 21), ' '); } } else { unparseChild(value, 1, 0); } - writeStringQDF("\n"); + write_qdf("\n"); } } // Write ID - writeStringQDF(" "); - writeString(" /ID ["); + write_qdf(" ").write(" /ID ["); if (linearization_pass == 1) { std::string original_id1 = getOriginalID1(); if (original_id1.empty()) { - writeString("<00000000000000000000000000000000>"); + write("<00000000000000000000000000000000>"); } else { // Write a string of zeroes equal in length to the representation of the original ID. // While writing the original ID would have the same number of bytes, it would cause a // change to the deterministic ID generated by older versions of the software that // hard-coded the length of the ID to 16 bytes. - writeString("<"); size_t len = QPDF_String(original_id1).unparse(true).length() - 2; - for (size_t i = 0; i < len; ++i) { - writeString("0"); - } - writeString(">"); + write("<").write(len, '0').write(">"); } - writeString("<00000000000000000000000000000000>"); + write("<00000000000000000000000000000000>"); } else { - if ((linearization_pass == 0) && (m->deterministic_id)) { + if (linearization_pass == 0 && m->deterministic_id) { computeDeterministicIDData(); } generateID(); - writeString(QPDF_String(m->id1).unparse(true)); - writeString(QPDF_String(m->id2).unparse(true)); + write_string(m->id1, true).write_string(m->id2, true); } - writeString("]"); + write("]"); if (which != t_lin_second) { // Write reference to encryption dictionary if (m->encryption) { - writeString(" /Encrypt "); - writeString(std::to_string(m->encryption_dict_objid)); - writeString(" 0 R"); + write(" /Encrypt ").write(m->encryption_dict_objid).write(" 0 R"); } } - writeStringQDF("\n"); - writeStringNoQDF(" "); - writeString(">>"); + write_qdf("\n>>").write_no_qdf(" >>"); } bool @@ -1314,26 +1323,25 @@ QPDFWriter::unparseObject( if (level < 0) { throw std::logic_error("invalid level in QPDFWriter::unparseObject"); } - // For non-qdf, "indent" is a single space between tokens. For qdf, indent includes the - // preceding newline. - std::string indent = " "; + // For non-qdf, "indent" and "indent_large" are a single space between tokens. For qdf, they + // include the preceding newline. + std::string indent_large = " "; if (m->qdf_mode) { - indent.append(static_cast(2 * level), ' '); - indent[0] = '\n'; + indent_large.append(static_cast(2 * (level + 1)), ' '); + indent_large[0] = '\n'; } + std::string_view indent{indent_large.data(), m->qdf_mode ? indent_large.size() - 2 : 1}; if (auto const tc = object.getTypeCode(); tc == ::ot_array) { // 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("["); + write("["); for (auto const& item: object.as_array()) { - writeString(indent); - writeStringQDF(" "); + write(indent_large); unparseChild(item, level + 1, child_flags); } - writeString(indent); - writeString("]"); + write(indent).write("]"); } else if (tc == ::ot_dictionary) { // Handle special cases for specific dictionaries. @@ -1474,14 +1482,11 @@ QPDFWriter::unparseObject( } } - writeString("<<"); + write("<<"); for (auto const& [key, value]: object.as_dictionary()) { if (!value.null()) { - writeString(indent); - writeStringQDF(" "); - writeString(Name::normalize(key)); - writeString(" "); + write(indent_large).write_name(key).write(" "); if (key == "/Contents" && object.isDictionaryOfType("/Sig") && object.hasKey("/ByteRange")) { QTC::TC("qpdf", "QPDFWriter no encryption sig contents"); @@ -1493,25 +1498,19 @@ QPDFWriter::unparseObject( } if (flags & f_stream) { - writeString(indent); - writeStringQDF(" "); - writeString("/Length "); + write(indent_large).write("/Length "); if (m->direct_stream_lengths) { - writeString(std::to_string(stream_length)); + write(stream_length); } else { - writeString(std::to_string(m->cur_stream_length_id)); - writeString(" 0 R"); + write(m->cur_stream_length_id).write(" 0 R"); } if (compress && (flags & f_filtered)) { - writeString(indent); - writeStringQDF(" "); - writeString("/Filter /FlateDecode"); + write(indent_large).write("/Filter /FlateDecode"); } } - writeString(indent); - writeString(">>"); + write(indent).write(">>"); } else if (tc == ::ot_stream) { // Write stream data to a buffer. if (!m->direct_stream_lengths) { @@ -1535,18 +1534,18 @@ QPDFWriter::unparseObject( adjustAESStreamLength(m->cur_stream_length); unparseObject(stream_dict, 0, flags, m->cur_stream_length, compress_stream); char last_char = stream_data.empty() ? '\0' : stream_data.back(); - writeString("\nstream\n"); + write("\nstream\n"); { PipelinePopper pp_enc(this); pushEncryptionFilter(pp_enc); - writeString(stream_data); + write(stream_data); } if ((m->added_newline = m->newline_before_endstream || (m->qdf_mode && last_char != '\n'))) { - writeString("\nendstream"); + write("\nendstream"); } else { - writeString("endstream"); + write("endstream"); } } else if (tc == ::ot_string) { std::string val; @@ -1580,9 +1579,9 @@ QPDFWriter::unparseObject( } else { val = object.unparseResolved(); } - writeString(val); + write(val); } else { - writeString(object.unparseResolved()); + write(object.unparseResolved()); } } @@ -1596,14 +1595,13 @@ QPDFWriter::writeObjectStreamOffsets(std::vector& offsets, int fi if (is_first) { is_first = false; } else { - writeStringQDF("\n"); - writeStringNoQDF(" "); + write_qdf("\n").write_no_qdf(" "); } - writeString(id); + write(id); util::increment(id, 1); - writeString(std::to_string(offset)); + write(offset); } - writeString("\n"); + write("\n"); } void @@ -1639,20 +1637,18 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object) first_obj = new_obj; } if (m->qdf_mode) { - writeString( - "%% Object stream: object " + std::to_string(new_obj) + ", index " + - std::to_string(count)); + write("%% Object stream: object ").write(new_obj).write(", index ").write(count); if (!m->suppress_original_object_ids) { - writeString("; original object ID: " + std::to_string(obj.getObj())); + write("; original object ID: ").write(obj.getObj()); // For compatibility, only write the generation if non-zero. While object // streams only allow objects with generation 0, if we are generating object // streams, the old object could have a non-zero generation. if (obj.getGen() != 0) { QTC::TC("qpdf", "QPDFWriter original obj non-zero gen"); - writeString(" " + std::to_string(obj.getGen())); + write(" ").write(obj.getGen()); } } - writeString("\n"); + write("\n"); } offsets.push_back(m->pipeline->getCount()); @@ -1698,7 +1694,7 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object) activatePipelineStack(pp_ostream, stream_buffer_pass2); } writeObjectStreamOffsets(offsets, first_obj); - writeString(stream_buffer_pass1); + write(stream_buffer_pass1); stream_buffer_pass1.clear(); stream_buffer_pass1.shrink_to_fit(); } @@ -1706,46 +1702,37 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object) // Write the object openObject(new_stream_id); setDataKey(new_stream_id); - writeString("<<"); - writeStringQDF("\n "); - writeString(" /Type /ObjStm"); - writeStringQDF("\n "); + write("<<").write_qdf("\n ").write(" /Type /ObjStm").write_qdf("\n "); size_t length = stream_buffer_pass2.size(); adjustAESStreamLength(length); - writeString(" /Length " + std::to_string(length)); - writeStringQDF("\n "); + write(" /Length ").write(length).write_qdf("\n "); if (compressed) { - writeString(" /Filter /FlateDecode"); + write(" /Filter /FlateDecode"); } - writeString(" /N " + std::to_string(offsets.size())); - writeStringQDF("\n "); - writeString(" /First " + std::to_string(first)); + write(" /N ").write(offsets.size()).write_qdf("\n ").write(" /First ").write(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 "); + write_qdf("\n ").write(" /Extends "); unparseChild(extends, 1, f_in_ostream); } } - writeStringQDF("\n"); - writeStringNoQDF(" "); - writeString(">>\nstream\n"); + write_qdf("\n").write_no_qdf(" ").write(">>\nstream\n"); if (m->encryption) { QTC::TC("qpdf", "QPDFWriter encrypt object stream"); } { PipelinePopper pp_enc(this); pushEncryptionFilter(pp_enc); - writeString(stream_buffer_pass2); + write(stream_buffer_pass2); } if (m->newline_before_endstream) { - writeString("\n"); + write("\n"); } - writeString("endstream"); + write("endstream"); m->cur_data_key.clear(); closeObject(new_stream_id); } @@ -1755,8 +1742,8 @@ QPDFWriter::writeObject(QPDFObjectHandle object, int object_stream_index) { QPDFObjGen old_og = object.getObjGen(); - if ((object_stream_index == -1) && (old_og.getGen() == 0) && - (m->object_stream_to_objects.count(old_og.getObj()))) { + if (object_stream_index == -1 && old_og.getGen() == 0 && + m->object_stream_to_objects.contains(old_og.getObj())) { writeObjectStream(object); return; } @@ -1765,19 +1752,15 @@ QPDFWriter::writeObject(QPDFObjectHandle object, int object_stream_index) auto new_id = m->obj[old_og].renumber; if (m->qdf_mode) { if (m->page_object_to_seq.contains(old_og)) { - writeString("%% Page "); - writeString(std::to_string(m->page_object_to_seq[old_og])); - writeString("\n"); + write("%% Page ").write(m->page_object_to_seq[old_og]).write("\n"); } if (m->contents_to_page_seq.contains(old_og)) { - writeString("%% Contents for page "); - writeString(std::to_string(m->contents_to_page_seq[old_og])); - writeString("\n"); + write("%% Contents for page ").write(m->contents_to_page_seq[old_og]).write("\n"); } } if (object_stream_index == -1) { if (m->qdf_mode && (!m->suppress_original_object_ids)) { - writeString("%% Original object ID: " + object.getObjGen().unparse(' ') + "\n"); + write("%% Original object ID: ").write(object.getObjGen().unparse(' ')).write("\n"); } openObject(new_id); setDataKey(new_id); @@ -1786,17 +1769,17 @@ QPDFWriter::writeObject(QPDFObjectHandle object, int object_stream_index) closeObject(new_id); } else { unparseObject(object, 0, f_in_ostream); - writeString("\n"); + write("\n"); } - if ((!m->direct_stream_lengths) && object.isStream()) { + if (!m->direct_stream_lengths && object.isStream()) { if (m->qdf_mode) { if (m->added_newline) { - writeString("%QDF: ignore_newline\n"); + write("%QDF: ignore_newline\n"); } } openObject(new_id + 1); - writeString(std::to_string(m->cur_stream_length)); + write(m->cur_stream_length); closeObject(new_id + 1); } } @@ -2274,46 +2257,36 @@ QPDFWriter::writeEncryptionDictionary() auto& enc = *m->encryption; auto const V = enc.getV(); - writeString("<<"); + write("<<"); if (V >= 4) { - writeString(" /CF << /StdCF << /AuthEvent /DocOpen /CFM "); - writeString(m->encrypt_use_aes ? ((V < 5) ? "/AESV2" : "/AESV3") : "/V2"); + write(" /CF << /StdCF << /AuthEvent /DocOpen /CFM "); + write(m->encrypt_use_aes ? ((V < 5) ? "/AESV2" : "/AESV3") : "/V2"); // The PDF spec says the /Length key is optional, but the PDF previewer on some versions of // MacOS won't open encrypted files without it. - writeString((V < 5) ? " /Length 16 >> >>" : " /Length 32 >> >>"); + write((V < 5) ? " /Length 16 >> >>" : " /Length 32 >> >>"); if (!m->encryption->getEncryptMetadata()) { - writeString(" /EncryptMetadata false"); + write(" /EncryptMetadata false"); } } - writeString(" /Filter /Standard /Length "); - writeString(std::to_string(enc.getLengthBytes() * 8)); - writeString(" /O "); - writeString(QPDF_String(enc.getO()).unparse(true)); + write(" /Filter /Standard /Length ").write(enc.getLengthBytes() * 8); + write(" /O ").write_string(enc.getO(), true); if (V >= 4) { - writeString(" /OE "); - writeString(QPDF_String(enc.getOE()).unparse(true)); + write(" /OE ").write_string(enc.getOE(), true); } - writeString(" /P "); - writeString(std::to_string(enc.getP())); + write(" /P ").write(enc.getP()); if (V >= 5) { - writeString(" /Perms "); - writeString(QPDF_String(enc.getPerms()).unparse(true)); + write(" /Perms ").write_string(enc.getPerms(), true); } - writeString(" /R "); - writeString(std::to_string(enc.getR())); + write(" /R ").write(enc.getR()); if (V >= 4) { - writeString(" /StmF /StdCF /StrF /StdCF"); + write(" /StmF /StdCF /StrF /StdCF"); } - writeString(" /U "); - writeString(QPDF_String(enc.getU()).unparse(true)); + write(" /U ").write_string(enc.getU(), true); if (V >= 4) { - writeString(" /UE "); - writeString(QPDF_String(enc.getUE()).unparse(true)); + write(" /UE ").write_string(enc.getUE(), true); } - writeString(" /V "); - writeString(std::to_string(enc.getV())); - writeString(" >>"); + write(" /V ").write(enc.getV()).write(" >>"); closeObject(m->encryption_dict_objid); } @@ -2327,17 +2300,16 @@ QPDFWriter::getFinalVersion() void QPDFWriter::writeHeader() { - writeString("%PDF-"); - writeString(m->final_pdf_version); + write("%PDF-").write(m->final_pdf_version); if (m->pclm) { // PCLm version - writeString("\n%PCLm 1.0\n"); + write("\n%PCLm 1.0\n"); } else { // This string of binary characters would not be valid UTF-8, so it really should be treated // as binary. - writeString("\n%\xbf\xf7\xa2\xfe\n"); + write("\n%\xbf\xf7\xa2\xfe\n"); } - writeStringQDF("%QDF-1.0\n\n"); + write_qdf("%QDF-1.0\n\n"); // Note: do not write extra header text here. Linearized PDFs must include the entire // linearization parameter dictionary within the first 1024 characters of the PDF file, so for @@ -2351,7 +2323,7 @@ QPDFWriter::writeHintStream(int hint_id) std::string hint_buffer; int S = 0; int O = 0; - bool compressed = (m->compress_streams && !m->qdf_mode); + bool compressed = m->compress_streams && !m->qdf_mode; QPDF::Writer::generateHintStream(m->pdf, m->new_obj, m->obj, hint_buffer, S, O, compressed); openObject(hint_id); @@ -2359,20 +2331,17 @@ QPDFWriter::writeHintStream(int hint_id) size_t hlen = hint_buffer.size(); - writeString("<< "); + write("<< "); if (compressed) { - writeString("/Filter /FlateDecode "); + write("/Filter /FlateDecode "); } - writeString("/S "); - writeString(std::to_string(S)); + write("/S ").write(S); if (O) { - writeString(" /O "); - writeString(std::to_string(O)); + write(" /O ").write(O); } - writeString(" /Length "); adjustAESStreamLength(hlen); - writeString(std::to_string(hlen)); - writeString(" >>\nstream\n"); + write(" /Length ").write(hlen); + write(" >>\nstream\n"); if (m->encryption) { QTC::TC("qpdf", "QPDFWriter encrypted hint stream"); @@ -2381,13 +2350,13 @@ QPDFWriter::writeHintStream(int hint_id) { PipelinePopper pp_enc(this); pushEncryptionFilter(pp_enc); - writeString(hint_buffer); + write(hint_buffer); } if (last_char != '\n') { - writeString("\n"); + write("\n"); } - writeString("endstream"); + write("endstream"); closeObject(hint_id); } @@ -2412,29 +2381,25 @@ QPDFWriter::writeXRefTable( qpdf_offset_t hint_length, int linearization_pass) { - writeString("xref\n"); - writeString(std::to_string(first)); - writeString(" "); - writeString(std::to_string(last - first + 1)); + write("xref\n").write(first).write(" ").write(last - first + 1); qpdf_offset_t space_before_zero = m->pipeline->getCount(); - writeString("\n"); + write("\n"); + if (first == 0) { + write("0000000000 65535 f \n"); + ++first; + } for (int i = first; i <= last; ++i) { - if (i == 0) { - writeString("0000000000 65535 f \n"); - } else { - qpdf_offset_t offset = 0; - if (!suppress_offsets) { - offset = m->new_obj[i].xref.getOffset(); - if ((hint_id != 0) && (i != hint_id) && (offset >= hint_offset)) { - offset += hint_length; - } + qpdf_offset_t offset = 0; + if (!suppress_offsets) { + offset = m->new_obj[i].xref.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"); } + write(QUtil::int_to_string(offset, 10)).write(" 00000 n \n"); } writeTrailer(which, size, false, prev, linearization_pass); - writeString("\n"); + write("\n"); return space_before_zero; } @@ -2532,27 +2497,18 @@ QPDFWriter::writeXRefStream( } openObject(xref_id); - writeString("<<"); - writeStringQDF("\n "); - writeString(" /Type /XRef"); - writeStringQDF("\n "); - writeString(" /Length " + std::to_string(xref_data.size())); + write("<<").write_qdf("\n ").write(" /Type /XRef").write_qdf("\n "); + write(" /Length ").write(xref_data.size()); if (compressed) { - writeStringQDF("\n "); - writeString(" /Filter /FlateDecode"); - writeStringQDF("\n "); - writeString(" /DecodeParms << /Columns " + std::to_string(esize) + " /Predictor 12 >>"); + write_qdf("\n ").write(" /Filter /FlateDecode").write_qdf("\n "); + write(" /DecodeParms << /Columns ").write(esize).write(" /Predictor 12 >>"); } - writeStringQDF("\n "); - writeString(" /W [ 1 " + std::to_string(f1_size) + " " + std::to_string(f2_size) + " ]"); - if (!((first == 0) && (last == size - 1))) { - writeString( - " /Index [ " + std::to_string(first) + " " + std::to_string(last - first + 1) + " ]"); + write_qdf("\n ").write(" /W [ 1 ").write(f1_size).write(" ").write(f2_size).write(" ]"); + if (!(first == 0 && last == (size - 1))) { + write(" /Index [ ").write(first).write(" ").write(last - first + 1).write(" ]"); } writeTrailer(which, size, true, prev, linearization_pass); - writeString("\nstream\n"); - writeString(xref_data); - writeString("\nendstream"); + write("\nstream\n").write(xref_data).write("\nendstream"); closeObject(xref_id); return space_before_zero; } @@ -2726,37 +2682,28 @@ QPDFWriter::writeLinearized() qpdf_offset_t pos = m->pipeline->getCount(); openObject(lindict_id); - writeString("<<"); + write("<<"); if (pass == 2) { std::vector const& pages = m->pdf.getAllPages(); int first_page_object = m->obj[pages.at(0)].renumber; - int npages = QIntC::to_int(pages.size()); - writeString(" /Linearized 1 /L "); - writeString(std::to_string(file_size + hint_length)); + write(" /Linearized 1 /L ").write(file_size + hint_length); // Implementation note 121 states that a space is mandatory after this open bracket. - writeString(" /H [ "); - writeString(std::to_string(m->new_obj[hint_id].xref.getOffset())); - writeString(" "); - writeString(std::to_string(hint_length)); - writeString(" ] /O "); - writeString(std::to_string(first_page_object)); - writeString(" /E "); - writeString(std::to_string(part6_end_offset + hint_length)); - writeString(" /N "); - writeString(std::to_string(npages)); - writeString(" /T "); - writeString(std::to_string(space_before_zero + hint_length)); - } - writeString(" >>"); + write(" /H [ ").write(m->new_obj[hint_id].xref.getOffset()).write(" "); + write(hint_length); + write(" ] /O ").write(first_page_object); + write(" /E ").write(part6_end_offset + hint_length); + write(" /N ").write(pages.size()); + write(" /T ").write(space_before_zero + hint_length); + } + write(" >>"); closeObject(lindict_id); static int const pad = 200; - writePad(QIntC::to_size(pos - m->pipeline->getCount() + pad)); - writeString("\n"); + write(QIntC::to_size(pos - m->pipeline->getCount() + pad), ' ').write("\n"); // If the user supplied any additional header text, write it here after the linearization // parameter dictionary. - writeString(m->extra_header_text); + write(m->extra_header_text); // Part 3: first page cross reference table and trailer. @@ -2793,20 +2740,19 @@ QPDFWriter::writeLinearized() qpdf_offset_t endpos = m->pipeline->getCount(); if (pass == 1) { // Pad so we have enough room for the real xref stream. - writePad(calculateXrefStreamPadding(endpos - pos)); + write(calculateXrefStreamPadding(endpos - pos), ' '); first_xref_end = m->pipeline->getCount(); } else { // Pad so that the next object starts at the same place as in pass 1. - writePad(QIntC::to_size(first_xref_end - endpos)); + write(QIntC::to_size(first_xref_end - endpos), ' '); if (m->pipeline->getCount() != first_xref_end) { throw std::logic_error( - "insufficient padding for first pass xref stream; " - "first_xref_end=" + + "insufficient padding for first pass xref stream; first_xref_end=" + std::to_string(first_xref_end) + "; endpos=" + std::to_string(endpos)); } } - writeString("\n"); + write("\n"); } else { writeXRefTable( t_lin_first, @@ -2819,7 +2765,7 @@ QPDFWriter::writeLinearized() hint_offset, hint_length, pass); - writeString("startxref\n0\n%%EOF\n"); + write("startxref\n0\n%%EOF\n"); } // Parts 4 through 9 @@ -2837,7 +2783,7 @@ QPDFWriter::writeLinearized() m->new_obj[hint_id].xref = QPDFXRefEntry(m->pipeline->getCount()); } else { // Part 5: hint stream - writeString(hint_buffer); + write(hint_buffer); } } if (cur_object.getObjectID() == part6_end_marker) { @@ -2871,14 +2817,13 @@ QPDFWriter::writeLinearized() 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"); + write(calculateXrefStreamPadding(endpos - pos), ' ').write("\n"); second_xref_end = m->pipeline->getCount(); } else { // Make the file size the same. - writePad( - QIntC::to_size(second_xref_end + hint_length - 1 - m->pipeline->getCount())); - writeString("\n"); + auto padding = + QIntC::to_size(second_xref_end + hint_length - 1 - m->pipeline->getCount()); + write(padding, ' ').write("\n"); // If this assertion fails, maybe we didn't have enough padding above. if (m->pipeline->getCount() != second_xref_end + hint_length) { @@ -2890,9 +2835,7 @@ QPDFWriter::writeLinearized() space_before_zero = writeXRefTable( t_lin_second, 0, second_half_end, second_trailer_size, 0, false, 0, 0, 0, pass); } - writeString("startxref\n"); - writeString(std::to_string(first_xref_offset)); - writeString("\n%%EOF\n"); + write("startxref\n").write(first_xref_offset).write("\n%%EOF\n"); if (pass == 1) { if (m->deterministic_id) { @@ -3038,7 +2981,7 @@ QPDFWriter::writeStandard() // Start writing writeHeader(); - writeString(m->extra_header_text); + write(m->extra_header_text); if (m->pclm) { enqueueObjectsPCLm(); @@ -3069,9 +3012,7 @@ QPDFWriter::writeStandard() writeXRefStream( xref_id, xref_id, xref_offset, t_normal, 0, m->next_objid - 1, m->next_objid); } - writeString("startxref\n"); - writeString(std::to_string(xref_offset)); - writeString("\n%%EOF\n"); + write("startxref\n").write(xref_offset).write("\n%%EOF\n"); if (m->deterministic_id) { QTC::TC( diff --git a/libqpdf/qpdf/Pipeline_private.hh b/libqpdf/qpdf/Pipeline_private.hh index 6ced77d..24825ce 100644 --- a/libqpdf/qpdf/Pipeline_private.hh +++ b/libqpdf/qpdf/Pipeline_private.hh @@ -109,13 +109,36 @@ namespace qpdf::pl write(unsigned char const* buf, size_t len) final { if (len) { + Count::write(std::string_view(reinterpret_cast(buf), len)); + } + } + + void + write(std::string_view sv) + { + if (sv.size()) { + if (str) { + str->append(sv); + return; + } + count += static_cast(sv.size()); + if (pass_immediately_to_next) { + next()->write(reinterpret_cast(sv.data()), sv.size()); + } + } + } + + void + write(size_t len, char c) + { + if (len) { if (str) { - str->append(reinterpret_cast(buf), len); + str->append(len, c); return; } count += static_cast(len); if (pass_immediately_to_next) { - next()->write(buf, len); + next()->writeString(std::string(len, c)); } } } diff --git a/pkg-test/CMakeLists.txt b/pkg-test/CMakeLists.txt index 36257f2..99c75ec 100644 --- a/pkg-test/CMakeLists.txt +++ b/pkg-test/CMakeLists.txt @@ -1,5 +1,6 @@ cmake_minimum_required(VERSION 3.10) project(qpdf-version LANGUAGES CXX) +set(CMAKE_CXX_STANDARD 20) find_package(qpdf) add_executable(qpdf-version qpdf-version.cc) target_link_libraries(qpdf-version qpdf::libqpdf)