Commit f7e07240c7080e6350aaacf374027de0f8fc3d33

Authored by m-holger
Committed by GitHub
2 parents 5c6cc0e6 e3fdb578

Merge pull request #1490 from m-holger/writer

Modernize QPDFWriter write methods
include/qpdf/QPDFWriter.hh
@@ -24,6 +24,7 @@ @@ -24,6 +24,7 @@
24 #include <qpdf/Types.h> 24 #include <qpdf/Types.h>
25 25
26 #include <bitset> 26 #include <bitset>
  27 +#include <concepts>
27 #include <cstdio> 28 #include <cstdio>
28 #include <functional> 29 #include <functional>
29 #include <list> 30 #include <list>
@@ -481,10 +482,16 @@ class QPDFWriter @@ -481,10 +482,16 @@ class QPDFWriter
481 482
482 unsigned int bytesNeeded(long long n); 483 unsigned int bytesNeeded(long long n);
483 void writeBinary(unsigned long long val, unsigned int bytes); 484 void writeBinary(unsigned long long val, unsigned int bytes);
484 - void writeString(std::string_view str);  
485 - void writeStringQDF(std::string_view str);  
486 - void writeStringNoQDF(std::string_view str);  
487 - void writePad(size_t nspaces); 485 + QPDFWriter& write(std::string_view str);
  486 + QPDFWriter& write(size_t count, char c);
  487 + QPDFWriter& write(std::integral auto val);
  488 + QPDFWriter& write_name(std::string const& str);
  489 + QPDFWriter& write_string(std::string const& str, bool force_binary = false);
  490 +
  491 + template <typename... Args>
  492 + QPDFWriter& write_qdf(Args&&... args);
  493 + template <typename... Args>
  494 + QPDFWriter& write_no_qdf(Args&&... args);
488 void assignCompressedObjectNumbers(QPDFObjGen og); 495 void assignCompressedObjectNumbers(QPDFObjGen og);
489 void enqueueObject(QPDFObjectHandle object); 496 void enqueueObject(QPDFObjectHandle object);
490 void writeObjectStreamOffsets(std::vector<qpdf_offset_t>& offsets, int first_obj); 497 void writeObjectStreamOffsets(std::vector<qpdf_offset_t>& offsets, int first_obj);
libqpdf.pc.in
@@ -8,4 +8,4 @@ Description: PDF transformation library @@ -8,4 +8,4 @@ Description: PDF transformation library
8 Version: @PROJECT_VERSION@ 8 Version: @PROJECT_VERSION@
9 Requires.private: zlib, libjpeg@CRYPTO_PKG@ 9 Requires.private: zlib, libjpeg@CRYPTO_PKG@
10 Libs: -L${libdir} -lqpdf 10 Libs: -L${libdir} -lqpdf
11 -Cflags: -I${includedir} 11 +Cflags: -I${includedir} -std=c++20
libqpdf/QPDFWriter.cc
@@ -832,32 +832,59 @@ QPDFWriter::writeBinary(unsigned long long val, unsigned int bytes) @@ -832,32 +832,59 @@ QPDFWriter::writeBinary(unsigned long long val, unsigned int bytes)
832 m->pipeline->write(data, bytes); 832 m->pipeline->write(data, bytes);
833 } 833 }
834 834
835 -void  
836 -QPDFWriter::writeString(std::string_view str) 835 +QPDFWriter&
  836 +QPDFWriter::write(std::string_view str)
837 { 837 {
838 - m->pipeline->write(reinterpret_cast<unsigned char const*>(str.data()), str.size()); 838 + m->pipeline->write(str);
  839 + return *this;
839 } 840 }
840 841
841 -void  
842 -QPDFWriter::writeStringQDF(std::string_view str) 842 +QPDFWriter&
  843 +QPDFWriter::write(std::integral auto val)
843 { 844 {
844 - if (m->qdf_mode) {  
845 - m->pipeline->write(reinterpret_cast<unsigned char const*>(str.data()), str.size());  
846 - } 845 + m->pipeline->write(std::to_string(val));
  846 + return *this;
847 } 847 }
848 848
849 -void  
850 -QPDFWriter::writeStringNoQDF(std::string_view str) 849 +QPDFWriter&
  850 +QPDFWriter::write(size_t count, char c)
851 { 851 {
852 - if (!m->qdf_mode) {  
853 - m->pipeline->write(reinterpret_cast<unsigned char const*>(str.data()), str.size()); 852 + m->pipeline->write(count, c);
  853 + return *this;
  854 +}
  855 +
  856 +QPDFWriter&
  857 +QPDFWriter::write_name(std::string const& str)
  858 +{
  859 + m->pipeline->write(Name::normalize(str));
  860 + return *this;
  861 +}
  862 +
  863 +QPDFWriter&
  864 +QPDFWriter::write_string(std::string const& str, bool force_binary)
  865 +{
  866 + m->pipeline->write(QPDF_String(str).unparse(force_binary));
  867 + return *this;
  868 +}
  869 +
  870 +template <typename... Args>
  871 +QPDFWriter&
  872 +QPDFWriter::write_qdf(Args&&... args)
  873 +{
  874 + if (m->qdf_mode) {
  875 + m->pipeline->write(std::forward<Args>(args)...);
854 } 876 }
  877 + return *this;
855 } 878 }
856 879
857 -void  
858 -QPDFWriter::writePad(size_t nspaces) 880 +template <typename... Args>
  881 +QPDFWriter&
  882 +QPDFWriter::write_no_qdf(Args&&... args)
859 { 883 {
860 - writeString(std::string(nspaces, ' ')); 884 + if (!m->qdf_mode) {
  885 + m->pipeline->write(std::forward<Args>(args)...);
  886 + }
  887 + return *this;
861 } 888 }
862 889
863 Pipeline* 890 Pipeline*
@@ -1005,8 +1032,7 @@ QPDFWriter::openObject(int objid) @@ -1005,8 +1032,7 @@ QPDFWriter::openObject(int objid)
1005 objid = m->next_objid++; 1032 objid = m->next_objid++;
1006 } 1033 }
1007 m->new_obj[objid].xref = QPDFXRefEntry(m->pipeline->getCount()); 1034 m->new_obj[objid].xref = QPDFXRefEntry(m->pipeline->getCount());
1008 - writeString(std::to_string(objid));  
1009 - writeString(" 0 obj\n"); 1035 + write(objid).write(" 0 obj\n");
1010 return objid; 1036 return objid;
1011 } 1037 }
1012 1038
@@ -1014,8 +1040,7 @@ void @@ -1014,8 +1040,7 @@ void
1014 QPDFWriter::closeObject(int objid) 1040 QPDFWriter::closeObject(int objid)
1015 { 1041 {
1016 // Write a newline before endobj as it makes the file easier to repair. 1042 // Write a newline before endobj as it makes the file easier to repair.
1017 - writeString("\nendobj\n");  
1018 - writeStringQDF("\n"); 1043 + write("\nendobj\n").write_qdf("\n");
1019 auto& new_obj = m->new_obj[objid]; 1044 auto& new_obj = m->new_obj[objid];
1020 new_obj.length = m->pipeline->getCount() - new_obj.xref.getOffset(); 1045 new_obj.length = m->pipeline->getCount() - new_obj.xref.getOffset();
1021 } 1046 }
@@ -1114,8 +1139,7 @@ QPDFWriter::unparseChild(QPDFObjectHandle child, int level, int flags) @@ -1114,8 +1139,7 @@ QPDFWriter::unparseChild(QPDFObjectHandle child, int level, int flags)
1114 enqueueObject(child); 1139 enqueueObject(child);
1115 } 1140 }
1116 if (child.isIndirect()) { 1141 if (child.isIndirect()) {
1117 - writeString(std::to_string(m->obj[child].renumber));  
1118 - writeString(" 0 R"); 1142 + write(m->obj[child].renumber).write(" 0 R");
1119 } else { 1143 } else {
1120 unparseObject(child, level, flags); 1144 unparseObject(child, level, flags);
1121 } 1145 }
@@ -1129,78 +1153,63 @@ QPDFWriter::writeTrailer( @@ -1129,78 +1153,63 @@ QPDFWriter::writeTrailer(
1129 if (xref_stream) { 1153 if (xref_stream) {
1130 m->cur_data_key.clear(); 1154 m->cur_data_key.clear();
1131 } else { 1155 } else {
1132 - writeString("trailer <<"); 1156 + write("trailer <<");
1133 } 1157 }
1134 - writeStringQDF("\n"); 1158 + write_qdf("\n");
1135 if (which == t_lin_second) { 1159 if (which == t_lin_second) {
1136 - writeString(" /Size ");  
1137 - writeString(std::to_string(size)); 1160 + write(" /Size ").write(size);
1138 } else { 1161 } else {
1139 for (auto const& [key, value]: trailer.as_dictionary()) { 1162 for (auto const& [key, value]: trailer.as_dictionary()) {
1140 if (value.null()) { 1163 if (value.null()) {
1141 continue; 1164 continue;
1142 } 1165 }
1143 - writeStringQDF(" ");  
1144 - writeStringNoQDF(" ");  
1145 - writeString(Name::normalize(key));  
1146 - writeString(" "); 1166 + write_qdf(" ").write_no_qdf(" ").write_name(key).write(" ");
1147 if (key == "/Size") { 1167 if (key == "/Size") {
1148 - writeString(std::to_string(size)); 1168 + write(size);
1149 if (which == t_lin_first) { 1169 if (which == t_lin_first) {
1150 - writeString(" /Prev "); 1170 + write(" /Prev ");
1151 qpdf_offset_t pos = m->pipeline->getCount(); 1171 qpdf_offset_t pos = m->pipeline->getCount();
1152 - writeString(std::to_string(prev));  
1153 - writePad(QIntC::to_size(pos - m->pipeline->getCount() + 21)); 1172 + write(prev).write(QIntC::to_size(pos - m->pipeline->getCount() + 21), ' ');
1154 } 1173 }
1155 } else { 1174 } else {
1156 unparseChild(value, 1, 0); 1175 unparseChild(value, 1, 0);
1157 } 1176 }
1158 - writeStringQDF("\n"); 1177 + write_qdf("\n");
1159 } 1178 }
1160 } 1179 }
1161 1180
1162 // Write ID 1181 // Write ID
1163 - writeStringQDF(" ");  
1164 - writeString(" /ID ["); 1182 + write_qdf(" ").write(" /ID [");
1165 if (linearization_pass == 1) { 1183 if (linearization_pass == 1) {
1166 std::string original_id1 = getOriginalID1(); 1184 std::string original_id1 = getOriginalID1();
1167 if (original_id1.empty()) { 1185 if (original_id1.empty()) {
1168 - writeString("<00000000000000000000000000000000>"); 1186 + write("<00000000000000000000000000000000>");
1169 } else { 1187 } else {
1170 // Write a string of zeroes equal in length to the representation of the original ID. 1188 // Write a string of zeroes equal in length to the representation of the original ID.
1171 // While writing the original ID would have the same number of bytes, it would cause a 1189 // While writing the original ID would have the same number of bytes, it would cause a
1172 // change to the deterministic ID generated by older versions of the software that 1190 // change to the deterministic ID generated by older versions of the software that
1173 // hard-coded the length of the ID to 16 bytes. 1191 // hard-coded the length of the ID to 16 bytes.
1174 - writeString("<");  
1175 size_t len = QPDF_String(original_id1).unparse(true).length() - 2; 1192 size_t len = QPDF_String(original_id1).unparse(true).length() - 2;
1176 - for (size_t i = 0; i < len; ++i) {  
1177 - writeString("0");  
1178 - }  
1179 - writeString(">"); 1193 + write("<").write(len, '0').write(">");
1180 } 1194 }
1181 - writeString("<00000000000000000000000000000000>"); 1195 + write("<00000000000000000000000000000000>");
1182 } else { 1196 } else {
1183 - if ((linearization_pass == 0) && (m->deterministic_id)) { 1197 + if (linearization_pass == 0 && m->deterministic_id) {
1184 computeDeterministicIDData(); 1198 computeDeterministicIDData();
1185 } 1199 }
1186 generateID(); 1200 generateID();
1187 - writeString(QPDF_String(m->id1).unparse(true));  
1188 - writeString(QPDF_String(m->id2).unparse(true)); 1201 + write_string(m->id1, true).write_string(m->id2, true);
1189 } 1202 }
1190 - writeString("]"); 1203 + write("]");
1191 1204
1192 if (which != t_lin_second) { 1205 if (which != t_lin_second) {
1193 // Write reference to encryption dictionary 1206 // Write reference to encryption dictionary
1194 if (m->encryption) { 1207 if (m->encryption) {
1195 - writeString(" /Encrypt ");  
1196 - writeString(std::to_string(m->encryption_dict_objid));  
1197 - writeString(" 0 R"); 1208 + write(" /Encrypt ").write(m->encryption_dict_objid).write(" 0 R");
1198 } 1209 }
1199 } 1210 }
1200 1211
1201 - writeStringQDF("\n");  
1202 - writeStringNoQDF(" ");  
1203 - writeString(">>"); 1212 + write_qdf("\n>>").write_no_qdf(" >>");
1204 } 1213 }
1205 1214
1206 bool 1215 bool
@@ -1314,26 +1323,25 @@ QPDFWriter::unparseObject( @@ -1314,26 +1323,25 @@ QPDFWriter::unparseObject(
1314 if (level < 0) { 1323 if (level < 0) {
1315 throw std::logic_error("invalid level in QPDFWriter::unparseObject"); 1324 throw std::logic_error("invalid level in QPDFWriter::unparseObject");
1316 } 1325 }
1317 - // For non-qdf, "indent" is a single space between tokens. For qdf, indent includes the  
1318 - // preceding newline.  
1319 - std::string indent = " "; 1326 + // For non-qdf, "indent" and "indent_large" are a single space between tokens. For qdf, they
  1327 + // include the preceding newline.
  1328 + std::string indent_large = " ";
1320 if (m->qdf_mode) { 1329 if (m->qdf_mode) {
1321 - indent.append(static_cast<size_t>(2 * level), ' ');  
1322 - indent[0] = '\n'; 1330 + indent_large.append(static_cast<size_t>(2 * (level + 1)), ' ');
  1331 + indent_large[0] = '\n';
1323 } 1332 }
  1333 + std::string_view indent{indent_large.data(), m->qdf_mode ? indent_large.size() - 2 : 1};
1324 1334
1325 if (auto const tc = object.getTypeCode(); tc == ::ot_array) { 1335 if (auto const tc = object.getTypeCode(); tc == ::ot_array) {
1326 // Note: PDF spec 1.4 implementation note 121 states that Acrobat requires a space after the 1336 // Note: PDF spec 1.4 implementation note 121 states that Acrobat requires a space after the
1327 // [ in the /H key of the linearization parameter dictionary. We'll do this unconditionally 1337 // [ in the /H key of the linearization parameter dictionary. We'll do this unconditionally
1328 // for all arrays because it looks nicer and doesn't make the files that much bigger. 1338 // for all arrays because it looks nicer and doesn't make the files that much bigger.
1329 - writeString("["); 1339 + write("[");
1330 for (auto const& item: object.as_array()) { 1340 for (auto const& item: object.as_array()) {
1331 - writeString(indent);  
1332 - writeStringQDF(" "); 1341 + write(indent_large);
1333 unparseChild(item, level + 1, child_flags); 1342 unparseChild(item, level + 1, child_flags);
1334 } 1343 }
1335 - writeString(indent);  
1336 - writeString("]"); 1344 + write(indent).write("]");
1337 } else if (tc == ::ot_dictionary) { 1345 } else if (tc == ::ot_dictionary) {
1338 // Handle special cases for specific dictionaries. 1346 // Handle special cases for specific dictionaries.
1339 1347
@@ -1474,14 +1482,11 @@ QPDFWriter::unparseObject( @@ -1474,14 +1482,11 @@ QPDFWriter::unparseObject(
1474 } 1482 }
1475 } 1483 }
1476 1484
1477 - writeString("<<"); 1485 + write("<<");
1478 1486
1479 for (auto const& [key, value]: object.as_dictionary()) { 1487 for (auto const& [key, value]: object.as_dictionary()) {
1480 if (!value.null()) { 1488 if (!value.null()) {
1481 - writeString(indent);  
1482 - writeStringQDF(" ");  
1483 - writeString(Name::normalize(key));  
1484 - writeString(" "); 1489 + write(indent_large).write_name(key).write(" ");
1485 if (key == "/Contents" && object.isDictionaryOfType("/Sig") && 1490 if (key == "/Contents" && object.isDictionaryOfType("/Sig") &&
1486 object.hasKey("/ByteRange")) { 1491 object.hasKey("/ByteRange")) {
1487 QTC::TC("qpdf", "QPDFWriter no encryption sig contents"); 1492 QTC::TC("qpdf", "QPDFWriter no encryption sig contents");
@@ -1493,25 +1498,19 @@ QPDFWriter::unparseObject( @@ -1493,25 +1498,19 @@ QPDFWriter::unparseObject(
1493 } 1498 }
1494 1499
1495 if (flags & f_stream) { 1500 if (flags & f_stream) {
1496 - writeString(indent);  
1497 - writeStringQDF(" ");  
1498 - writeString("/Length "); 1501 + write(indent_large).write("/Length ");
1499 1502
1500 if (m->direct_stream_lengths) { 1503 if (m->direct_stream_lengths) {
1501 - writeString(std::to_string(stream_length)); 1504 + write(stream_length);
1502 } else { 1505 } else {
1503 - writeString(std::to_string(m->cur_stream_length_id));  
1504 - writeString(" 0 R"); 1506 + write(m->cur_stream_length_id).write(" 0 R");
1505 } 1507 }
1506 if (compress && (flags & f_filtered)) { 1508 if (compress && (flags & f_filtered)) {
1507 - writeString(indent);  
1508 - writeStringQDF(" ");  
1509 - writeString("/Filter /FlateDecode"); 1509 + write(indent_large).write("/Filter /FlateDecode");
1510 } 1510 }
1511 } 1511 }
1512 1512
1513 - writeString(indent);  
1514 - writeString(">>"); 1513 + write(indent).write(">>");
1515 } else if (tc == ::ot_stream) { 1514 } else if (tc == ::ot_stream) {
1516 // Write stream data to a buffer. 1515 // Write stream data to a buffer.
1517 if (!m->direct_stream_lengths) { 1516 if (!m->direct_stream_lengths) {
@@ -1535,18 +1534,18 @@ QPDFWriter::unparseObject( @@ -1535,18 +1534,18 @@ QPDFWriter::unparseObject(
1535 adjustAESStreamLength(m->cur_stream_length); 1534 adjustAESStreamLength(m->cur_stream_length);
1536 unparseObject(stream_dict, 0, flags, m->cur_stream_length, compress_stream); 1535 unparseObject(stream_dict, 0, flags, m->cur_stream_length, compress_stream);
1537 char last_char = stream_data.empty() ? '\0' : stream_data.back(); 1536 char last_char = stream_data.empty() ? '\0' : stream_data.back();
1538 - writeString("\nstream\n"); 1537 + write("\nstream\n");
1539 { 1538 {
1540 PipelinePopper pp_enc(this); 1539 PipelinePopper pp_enc(this);
1541 pushEncryptionFilter(pp_enc); 1540 pushEncryptionFilter(pp_enc);
1542 - writeString(stream_data); 1541 + write(stream_data);
1543 } 1542 }
1544 1543
1545 if ((m->added_newline = 1544 if ((m->added_newline =
1546 m->newline_before_endstream || (m->qdf_mode && last_char != '\n'))) { 1545 m->newline_before_endstream || (m->qdf_mode && last_char != '\n'))) {
1547 - writeString("\nendstream"); 1546 + write("\nendstream");
1548 } else { 1547 } else {
1549 - writeString("endstream"); 1548 + write("endstream");
1550 } 1549 }
1551 } else if (tc == ::ot_string) { 1550 } else if (tc == ::ot_string) {
1552 std::string val; 1551 std::string val;
@@ -1580,9 +1579,9 @@ QPDFWriter::unparseObject( @@ -1580,9 +1579,9 @@ QPDFWriter::unparseObject(
1580 } else { 1579 } else {
1581 val = object.unparseResolved(); 1580 val = object.unparseResolved();
1582 } 1581 }
1583 - writeString(val); 1582 + write(val);
1584 } else { 1583 } else {
1585 - writeString(object.unparseResolved()); 1584 + write(object.unparseResolved());
1586 } 1585 }
1587 } 1586 }
1588 1587
@@ -1596,14 +1595,13 @@ QPDFWriter::writeObjectStreamOffsets(std::vector&lt;qpdf_offset_t&gt;&amp; offsets, int fi @@ -1596,14 +1595,13 @@ QPDFWriter::writeObjectStreamOffsets(std::vector&lt;qpdf_offset_t&gt;&amp; offsets, int fi
1596 if (is_first) { 1595 if (is_first) {
1597 is_first = false; 1596 is_first = false;
1598 } else { 1597 } else {
1599 - writeStringQDF("\n");  
1600 - writeStringNoQDF(" "); 1598 + write_qdf("\n").write_no_qdf(" ");
1601 } 1599 }
1602 - writeString(id); 1600 + write(id);
1603 util::increment(id, 1); 1601 util::increment(id, 1);
1604 - writeString(std::to_string(offset)); 1602 + write(offset);
1605 } 1603 }
1606 - writeString("\n"); 1604 + write("\n");
1607 } 1605 }
1608 1606
1609 void 1607 void
@@ -1639,20 +1637,18 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object) @@ -1639,20 +1637,18 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1639 first_obj = new_obj; 1637 first_obj = new_obj;
1640 } 1638 }
1641 if (m->qdf_mode) { 1639 if (m->qdf_mode) {
1642 - writeString(  
1643 - "%% Object stream: object " + std::to_string(new_obj) + ", index " +  
1644 - std::to_string(count)); 1640 + write("%% Object stream: object ").write(new_obj).write(", index ").write(count);
1645 if (!m->suppress_original_object_ids) { 1641 if (!m->suppress_original_object_ids) {
1646 - writeString("; original object ID: " + std::to_string(obj.getObj())); 1642 + write("; original object ID: ").write(obj.getObj());
1647 // For compatibility, only write the generation if non-zero. While object 1643 // For compatibility, only write the generation if non-zero. While object
1648 // streams only allow objects with generation 0, if we are generating object 1644 // streams only allow objects with generation 0, if we are generating object
1649 // streams, the old object could have a non-zero generation. 1645 // streams, the old object could have a non-zero generation.
1650 if (obj.getGen() != 0) { 1646 if (obj.getGen() != 0) {
1651 QTC::TC("qpdf", "QPDFWriter original obj non-zero gen"); 1647 QTC::TC("qpdf", "QPDFWriter original obj non-zero gen");
1652 - writeString(" " + std::to_string(obj.getGen())); 1648 + write(" ").write(obj.getGen());
1653 } 1649 }
1654 } 1650 }
1655 - writeString("\n"); 1651 + write("\n");
1656 } 1652 }
1657 1653
1658 offsets.push_back(m->pipeline->getCount()); 1654 offsets.push_back(m->pipeline->getCount());
@@ -1698,7 +1694,7 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object) @@ -1698,7 +1694,7 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1698 activatePipelineStack(pp_ostream, stream_buffer_pass2); 1694 activatePipelineStack(pp_ostream, stream_buffer_pass2);
1699 } 1695 }
1700 writeObjectStreamOffsets(offsets, first_obj); 1696 writeObjectStreamOffsets(offsets, first_obj);
1701 - writeString(stream_buffer_pass1); 1697 + write(stream_buffer_pass1);
1702 stream_buffer_pass1.clear(); 1698 stream_buffer_pass1.clear();
1703 stream_buffer_pass1.shrink_to_fit(); 1699 stream_buffer_pass1.shrink_to_fit();
1704 } 1700 }
@@ -1706,46 +1702,37 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object) @@ -1706,46 +1702,37 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1706 // Write the object 1702 // Write the object
1707 openObject(new_stream_id); 1703 openObject(new_stream_id);
1708 setDataKey(new_stream_id); 1704 setDataKey(new_stream_id);
1709 - writeString("<<");  
1710 - writeStringQDF("\n ");  
1711 - writeString(" /Type /ObjStm");  
1712 - writeStringQDF("\n "); 1705 + write("<<").write_qdf("\n ").write(" /Type /ObjStm").write_qdf("\n ");
1713 size_t length = stream_buffer_pass2.size(); 1706 size_t length = stream_buffer_pass2.size();
1714 adjustAESStreamLength(length); 1707 adjustAESStreamLength(length);
1715 - writeString(" /Length " + std::to_string(length));  
1716 - writeStringQDF("\n "); 1708 + write(" /Length ").write(length).write_qdf("\n ");
1717 if (compressed) { 1709 if (compressed) {
1718 - writeString(" /Filter /FlateDecode"); 1710 + write(" /Filter /FlateDecode");
1719 } 1711 }
1720 - writeString(" /N " + std::to_string(offsets.size()));  
1721 - writeStringQDF("\n ");  
1722 - writeString(" /First " + std::to_string(first)); 1712 + write(" /N ").write(offsets.size()).write_qdf("\n ").write(" /First ").write(first);
1723 if (!object.isNull()) { 1713 if (!object.isNull()) {
1724 // If the original object has an /Extends key, preserve it. 1714 // If the original object has an /Extends key, preserve it.
1725 QPDFObjectHandle dict = object.getDict(); 1715 QPDFObjectHandle dict = object.getDict();
1726 QPDFObjectHandle extends = dict.getKey("/Extends"); 1716 QPDFObjectHandle extends = dict.getKey("/Extends");
1727 if (extends.isIndirect()) { 1717 if (extends.isIndirect()) {
1728 QTC::TC("qpdf", "QPDFWriter copy Extends"); 1718 QTC::TC("qpdf", "QPDFWriter copy Extends");
1729 - writeStringQDF("\n ");  
1730 - writeString(" /Extends "); 1719 + write_qdf("\n ").write(" /Extends ");
1731 unparseChild(extends, 1, f_in_ostream); 1720 unparseChild(extends, 1, f_in_ostream);
1732 } 1721 }
1733 } 1722 }
1734 - writeStringQDF("\n");  
1735 - writeStringNoQDF(" ");  
1736 - writeString(">>\nstream\n"); 1723 + write_qdf("\n").write_no_qdf(" ").write(">>\nstream\n");
1737 if (m->encryption) { 1724 if (m->encryption) {
1738 QTC::TC("qpdf", "QPDFWriter encrypt object stream"); 1725 QTC::TC("qpdf", "QPDFWriter encrypt object stream");
1739 } 1726 }
1740 { 1727 {
1741 PipelinePopper pp_enc(this); 1728 PipelinePopper pp_enc(this);
1742 pushEncryptionFilter(pp_enc); 1729 pushEncryptionFilter(pp_enc);
1743 - writeString(stream_buffer_pass2); 1730 + write(stream_buffer_pass2);
1744 } 1731 }
1745 if (m->newline_before_endstream) { 1732 if (m->newline_before_endstream) {
1746 - writeString("\n"); 1733 + write("\n");
1747 } 1734 }
1748 - writeString("endstream"); 1735 + write("endstream");
1749 m->cur_data_key.clear(); 1736 m->cur_data_key.clear();
1750 closeObject(new_stream_id); 1737 closeObject(new_stream_id);
1751 } 1738 }
@@ -1755,8 +1742,8 @@ QPDFWriter::writeObject(QPDFObjectHandle object, int object_stream_index) @@ -1755,8 +1742,8 @@ QPDFWriter::writeObject(QPDFObjectHandle object, int object_stream_index)
1755 { 1742 {
1756 QPDFObjGen old_og = object.getObjGen(); 1743 QPDFObjGen old_og = object.getObjGen();
1757 1744
1758 - if ((object_stream_index == -1) && (old_og.getGen() == 0) &&  
1759 - (m->object_stream_to_objects.count(old_og.getObj()))) { 1745 + if (object_stream_index == -1 && old_og.getGen() == 0 &&
  1746 + m->object_stream_to_objects.contains(old_og.getObj())) {
1760 writeObjectStream(object); 1747 writeObjectStream(object);
1761 return; 1748 return;
1762 } 1749 }
@@ -1765,19 +1752,15 @@ QPDFWriter::writeObject(QPDFObjectHandle object, int object_stream_index) @@ -1765,19 +1752,15 @@ QPDFWriter::writeObject(QPDFObjectHandle object, int object_stream_index)
1765 auto new_id = m->obj[old_og].renumber; 1752 auto new_id = m->obj[old_og].renumber;
1766 if (m->qdf_mode) { 1753 if (m->qdf_mode) {
1767 if (m->page_object_to_seq.contains(old_og)) { 1754 if (m->page_object_to_seq.contains(old_og)) {
1768 - writeString("%% Page ");  
1769 - writeString(std::to_string(m->page_object_to_seq[old_og]));  
1770 - writeString("\n"); 1755 + write("%% Page ").write(m->page_object_to_seq[old_og]).write("\n");
1771 } 1756 }
1772 if (m->contents_to_page_seq.contains(old_og)) { 1757 if (m->contents_to_page_seq.contains(old_og)) {
1773 - writeString("%% Contents for page ");  
1774 - writeString(std::to_string(m->contents_to_page_seq[old_og]));  
1775 - writeString("\n"); 1758 + write("%% Contents for page ").write(m->contents_to_page_seq[old_og]).write("\n");
1776 } 1759 }
1777 } 1760 }
1778 if (object_stream_index == -1) { 1761 if (object_stream_index == -1) {
1779 if (m->qdf_mode && (!m->suppress_original_object_ids)) { 1762 if (m->qdf_mode && (!m->suppress_original_object_ids)) {
1780 - writeString("%% Original object ID: " + object.getObjGen().unparse(' ') + "\n"); 1763 + write("%% Original object ID: ").write(object.getObjGen().unparse(' ')).write("\n");
1781 } 1764 }
1782 openObject(new_id); 1765 openObject(new_id);
1783 setDataKey(new_id); 1766 setDataKey(new_id);
@@ -1786,17 +1769,17 @@ QPDFWriter::writeObject(QPDFObjectHandle object, int object_stream_index) @@ -1786,17 +1769,17 @@ QPDFWriter::writeObject(QPDFObjectHandle object, int object_stream_index)
1786 closeObject(new_id); 1769 closeObject(new_id);
1787 } else { 1770 } else {
1788 unparseObject(object, 0, f_in_ostream); 1771 unparseObject(object, 0, f_in_ostream);
1789 - writeString("\n"); 1772 + write("\n");
1790 } 1773 }
1791 1774
1792 - if ((!m->direct_stream_lengths) && object.isStream()) { 1775 + if (!m->direct_stream_lengths && object.isStream()) {
1793 if (m->qdf_mode) { 1776 if (m->qdf_mode) {
1794 if (m->added_newline) { 1777 if (m->added_newline) {
1795 - writeString("%QDF: ignore_newline\n"); 1778 + write("%QDF: ignore_newline\n");
1796 } 1779 }
1797 } 1780 }
1798 openObject(new_id + 1); 1781 openObject(new_id + 1);
1799 - writeString(std::to_string(m->cur_stream_length)); 1782 + write(m->cur_stream_length);
1800 closeObject(new_id + 1); 1783 closeObject(new_id + 1);
1801 } 1784 }
1802 } 1785 }
@@ -2274,46 +2257,36 @@ QPDFWriter::writeEncryptionDictionary() @@ -2274,46 +2257,36 @@ QPDFWriter::writeEncryptionDictionary()
2274 auto& enc = *m->encryption; 2257 auto& enc = *m->encryption;
2275 auto const V = enc.getV(); 2258 auto const V = enc.getV();
2276 2259
2277 - writeString("<<"); 2260 + write("<<");
2278 if (V >= 4) { 2261 if (V >= 4) {
2279 - writeString(" /CF << /StdCF << /AuthEvent /DocOpen /CFM ");  
2280 - writeString(m->encrypt_use_aes ? ((V < 5) ? "/AESV2" : "/AESV3") : "/V2"); 2262 + write(" /CF << /StdCF << /AuthEvent /DocOpen /CFM ");
  2263 + write(m->encrypt_use_aes ? ((V < 5) ? "/AESV2" : "/AESV3") : "/V2");
2281 // The PDF spec says the /Length key is optional, but the PDF previewer on some versions of 2264 // The PDF spec says the /Length key is optional, but the PDF previewer on some versions of
2282 // MacOS won't open encrypted files without it. 2265 // MacOS won't open encrypted files without it.
2283 - writeString((V < 5) ? " /Length 16 >> >>" : " /Length 32 >> >>"); 2266 + write((V < 5) ? " /Length 16 >> >>" : " /Length 32 >> >>");
2284 if (!m->encryption->getEncryptMetadata()) { 2267 if (!m->encryption->getEncryptMetadata()) {
2285 - writeString(" /EncryptMetadata false"); 2268 + write(" /EncryptMetadata false");
2286 } 2269 }
2287 } 2270 }
2288 - writeString(" /Filter /Standard /Length ");  
2289 - writeString(std::to_string(enc.getLengthBytes() * 8));  
2290 - writeString(" /O ");  
2291 - writeString(QPDF_String(enc.getO()).unparse(true)); 2271 + write(" /Filter /Standard /Length ").write(enc.getLengthBytes() * 8);
  2272 + write(" /O ").write_string(enc.getO(), true);
2292 if (V >= 4) { 2273 if (V >= 4) {
2293 - writeString(" /OE ");  
2294 - writeString(QPDF_String(enc.getOE()).unparse(true)); 2274 + write(" /OE ").write_string(enc.getOE(), true);
2295 } 2275 }
2296 - writeString(" /P ");  
2297 - writeString(std::to_string(enc.getP())); 2276 + write(" /P ").write(enc.getP());
2298 if (V >= 5) { 2277 if (V >= 5) {
2299 - writeString(" /Perms ");  
2300 - writeString(QPDF_String(enc.getPerms()).unparse(true)); 2278 + write(" /Perms ").write_string(enc.getPerms(), true);
2301 } 2279 }
2302 - writeString(" /R ");  
2303 - writeString(std::to_string(enc.getR())); 2280 + write(" /R ").write(enc.getR());
2304 2281
2305 if (V >= 4) { 2282 if (V >= 4) {
2306 - writeString(" /StmF /StdCF /StrF /StdCF"); 2283 + write(" /StmF /StdCF /StrF /StdCF");
2307 } 2284 }
2308 - writeString(" /U ");  
2309 - writeString(QPDF_String(enc.getU()).unparse(true)); 2285 + write(" /U ").write_string(enc.getU(), true);
2310 if (V >= 4) { 2286 if (V >= 4) {
2311 - writeString(" /UE ");  
2312 - writeString(QPDF_String(enc.getUE()).unparse(true)); 2287 + write(" /UE ").write_string(enc.getUE(), true);
2313 } 2288 }
2314 - writeString(" /V ");  
2315 - writeString(std::to_string(enc.getV()));  
2316 - writeString(" >>"); 2289 + write(" /V ").write(enc.getV()).write(" >>");
2317 closeObject(m->encryption_dict_objid); 2290 closeObject(m->encryption_dict_objid);
2318 } 2291 }
2319 2292
@@ -2327,17 +2300,16 @@ QPDFWriter::getFinalVersion() @@ -2327,17 +2300,16 @@ QPDFWriter::getFinalVersion()
2327 void 2300 void
2328 QPDFWriter::writeHeader() 2301 QPDFWriter::writeHeader()
2329 { 2302 {
2330 - writeString("%PDF-");  
2331 - writeString(m->final_pdf_version); 2303 + write("%PDF-").write(m->final_pdf_version);
2332 if (m->pclm) { 2304 if (m->pclm) {
2333 // PCLm version 2305 // PCLm version
2334 - writeString("\n%PCLm 1.0\n"); 2306 + write("\n%PCLm 1.0\n");
2335 } else { 2307 } else {
2336 // This string of binary characters would not be valid UTF-8, so it really should be treated 2308 // This string of binary characters would not be valid UTF-8, so it really should be treated
2337 // as binary. 2309 // as binary.
2338 - writeString("\n%\xbf\xf7\xa2\xfe\n"); 2310 + write("\n%\xbf\xf7\xa2\xfe\n");
2339 } 2311 }
2340 - writeStringQDF("%QDF-1.0\n\n"); 2312 + write_qdf("%QDF-1.0\n\n");
2341 2313
2342 // Note: do not write extra header text here. Linearized PDFs must include the entire 2314 // Note: do not write extra header text here. Linearized PDFs must include the entire
2343 // linearization parameter dictionary within the first 1024 characters of the PDF file, so for 2315 // linearization parameter dictionary within the first 1024 characters of the PDF file, so for
@@ -2351,7 +2323,7 @@ QPDFWriter::writeHintStream(int hint_id) @@ -2351,7 +2323,7 @@ QPDFWriter::writeHintStream(int hint_id)
2351 std::string hint_buffer; 2323 std::string hint_buffer;
2352 int S = 0; 2324 int S = 0;
2353 int O = 0; 2325 int O = 0;
2354 - bool compressed = (m->compress_streams && !m->qdf_mode); 2326 + bool compressed = m->compress_streams && !m->qdf_mode;
2355 QPDF::Writer::generateHintStream(m->pdf, m->new_obj, m->obj, hint_buffer, S, O, compressed); 2327 QPDF::Writer::generateHintStream(m->pdf, m->new_obj, m->obj, hint_buffer, S, O, compressed);
2356 2328
2357 openObject(hint_id); 2329 openObject(hint_id);
@@ -2359,20 +2331,17 @@ QPDFWriter::writeHintStream(int hint_id) @@ -2359,20 +2331,17 @@ QPDFWriter::writeHintStream(int hint_id)
2359 2331
2360 size_t hlen = hint_buffer.size(); 2332 size_t hlen = hint_buffer.size();
2361 2333
2362 - writeString("<< "); 2334 + write("<< ");
2363 if (compressed) { 2335 if (compressed) {
2364 - writeString("/Filter /FlateDecode "); 2336 + write("/Filter /FlateDecode ");
2365 } 2337 }
2366 - writeString("/S ");  
2367 - writeString(std::to_string(S)); 2338 + write("/S ").write(S);
2368 if (O) { 2339 if (O) {
2369 - writeString(" /O ");  
2370 - writeString(std::to_string(O)); 2340 + write(" /O ").write(O);
2371 } 2341 }
2372 - writeString(" /Length ");  
2373 adjustAESStreamLength(hlen); 2342 adjustAESStreamLength(hlen);
2374 - writeString(std::to_string(hlen));  
2375 - writeString(" >>\nstream\n"); 2343 + write(" /Length ").write(hlen);
  2344 + write(" >>\nstream\n");
2376 2345
2377 if (m->encryption) { 2346 if (m->encryption) {
2378 QTC::TC("qpdf", "QPDFWriter encrypted hint stream"); 2347 QTC::TC("qpdf", "QPDFWriter encrypted hint stream");
@@ -2381,13 +2350,13 @@ QPDFWriter::writeHintStream(int hint_id) @@ -2381,13 +2350,13 @@ QPDFWriter::writeHintStream(int hint_id)
2381 { 2350 {
2382 PipelinePopper pp_enc(this); 2351 PipelinePopper pp_enc(this);
2383 pushEncryptionFilter(pp_enc); 2352 pushEncryptionFilter(pp_enc);
2384 - writeString(hint_buffer); 2353 + write(hint_buffer);
2385 } 2354 }
2386 2355
2387 if (last_char != '\n') { 2356 if (last_char != '\n') {
2388 - writeString("\n"); 2357 + write("\n");
2389 } 2358 }
2390 - writeString("endstream"); 2359 + write("endstream");
2391 closeObject(hint_id); 2360 closeObject(hint_id);
2392 } 2361 }
2393 2362
@@ -2412,29 +2381,25 @@ QPDFWriter::writeXRefTable( @@ -2412,29 +2381,25 @@ QPDFWriter::writeXRefTable(
2412 qpdf_offset_t hint_length, 2381 qpdf_offset_t hint_length,
2413 int linearization_pass) 2382 int linearization_pass)
2414 { 2383 {
2415 - writeString("xref\n");  
2416 - writeString(std::to_string(first));  
2417 - writeString(" ");  
2418 - writeString(std::to_string(last - first + 1)); 2384 + write("xref\n").write(first).write(" ").write(last - first + 1);
2419 qpdf_offset_t space_before_zero = m->pipeline->getCount(); 2385 qpdf_offset_t space_before_zero = m->pipeline->getCount();
2420 - writeString("\n"); 2386 + write("\n");
  2387 + if (first == 0) {
  2388 + write("0000000000 65535 f \n");
  2389 + ++first;
  2390 + }
2421 for (int i = first; i <= last; ++i) { 2391 for (int i = first; i <= last; ++i) {
2422 - if (i == 0) {  
2423 - writeString("0000000000 65535 f \n");  
2424 - } else {  
2425 - qpdf_offset_t offset = 0;  
2426 - if (!suppress_offsets) {  
2427 - offset = m->new_obj[i].xref.getOffset();  
2428 - if ((hint_id != 0) && (i != hint_id) && (offset >= hint_offset)) {  
2429 - offset += hint_length;  
2430 - } 2392 + qpdf_offset_t offset = 0;
  2393 + if (!suppress_offsets) {
  2394 + offset = m->new_obj[i].xref.getOffset();
  2395 + if ((hint_id != 0) && (i != hint_id) && (offset >= hint_offset)) {
  2396 + offset += hint_length;
2431 } 2397 }
2432 - writeString(QUtil::int_to_string(offset, 10));  
2433 - writeString(" 00000 n \n");  
2434 } 2398 }
  2399 + write(QUtil::int_to_string(offset, 10)).write(" 00000 n \n");
2435 } 2400 }
2436 writeTrailer(which, size, false, prev, linearization_pass); 2401 writeTrailer(which, size, false, prev, linearization_pass);
2437 - writeString("\n"); 2402 + write("\n");
2438 return space_before_zero; 2403 return space_before_zero;
2439 } 2404 }
2440 2405
@@ -2532,27 +2497,18 @@ QPDFWriter::writeXRefStream( @@ -2532,27 +2497,18 @@ QPDFWriter::writeXRefStream(
2532 } 2497 }
2533 2498
2534 openObject(xref_id); 2499 openObject(xref_id);
2535 - writeString("<<");  
2536 - writeStringQDF("\n ");  
2537 - writeString(" /Type /XRef");  
2538 - writeStringQDF("\n ");  
2539 - writeString(" /Length " + std::to_string(xref_data.size())); 2500 + write("<<").write_qdf("\n ").write(" /Type /XRef").write_qdf("\n ");
  2501 + write(" /Length ").write(xref_data.size());
2540 if (compressed) { 2502 if (compressed) {
2541 - writeStringQDF("\n ");  
2542 - writeString(" /Filter /FlateDecode");  
2543 - writeStringQDF("\n ");  
2544 - writeString(" /DecodeParms << /Columns " + std::to_string(esize) + " /Predictor 12 >>"); 2503 + write_qdf("\n ").write(" /Filter /FlateDecode").write_qdf("\n ");
  2504 + write(" /DecodeParms << /Columns ").write(esize).write(" /Predictor 12 >>");
2545 } 2505 }
2546 - writeStringQDF("\n ");  
2547 - writeString(" /W [ 1 " + std::to_string(f1_size) + " " + std::to_string(f2_size) + " ]");  
2548 - if (!((first == 0) && (last == size - 1))) {  
2549 - writeString(  
2550 - " /Index [ " + std::to_string(first) + " " + std::to_string(last - first + 1) + " ]"); 2506 + write_qdf("\n ").write(" /W [ 1 ").write(f1_size).write(" ").write(f2_size).write(" ]");
  2507 + if (!(first == 0 && last == (size - 1))) {
  2508 + write(" /Index [ ").write(first).write(" ").write(last - first + 1).write(" ]");
2551 } 2509 }
2552 writeTrailer(which, size, true, prev, linearization_pass); 2510 writeTrailer(which, size, true, prev, linearization_pass);
2553 - writeString("\nstream\n");  
2554 - writeString(xref_data);  
2555 - writeString("\nendstream"); 2511 + write("\nstream\n").write(xref_data).write("\nendstream");
2556 closeObject(xref_id); 2512 closeObject(xref_id);
2557 return space_before_zero; 2513 return space_before_zero;
2558 } 2514 }
@@ -2726,37 +2682,28 @@ QPDFWriter::writeLinearized() @@ -2726,37 +2682,28 @@ QPDFWriter::writeLinearized()
2726 2682
2727 qpdf_offset_t pos = m->pipeline->getCount(); 2683 qpdf_offset_t pos = m->pipeline->getCount();
2728 openObject(lindict_id); 2684 openObject(lindict_id);
2729 - writeString("<<"); 2685 + write("<<");
2730 if (pass == 2) { 2686 if (pass == 2) {
2731 std::vector<QPDFObjectHandle> const& pages = m->pdf.getAllPages(); 2687 std::vector<QPDFObjectHandle> const& pages = m->pdf.getAllPages();
2732 int first_page_object = m->obj[pages.at(0)].renumber; 2688 int first_page_object = m->obj[pages.at(0)].renumber;
2733 - int npages = QIntC::to_int(pages.size());  
2734 2689
2735 - writeString(" /Linearized 1 /L ");  
2736 - writeString(std::to_string(file_size + hint_length)); 2690 + write(" /Linearized 1 /L ").write(file_size + hint_length);
2737 // Implementation note 121 states that a space is mandatory after this open bracket. 2691 // Implementation note 121 states that a space is mandatory after this open bracket.
2738 - writeString(" /H [ ");  
2739 - writeString(std::to_string(m->new_obj[hint_id].xref.getOffset()));  
2740 - writeString(" ");  
2741 - writeString(std::to_string(hint_length));  
2742 - writeString(" ] /O ");  
2743 - writeString(std::to_string(first_page_object));  
2744 - writeString(" /E ");  
2745 - writeString(std::to_string(part6_end_offset + hint_length));  
2746 - writeString(" /N ");  
2747 - writeString(std::to_string(npages));  
2748 - writeString(" /T ");  
2749 - writeString(std::to_string(space_before_zero + hint_length));  
2750 - }  
2751 - writeString(" >>"); 2692 + write(" /H [ ").write(m->new_obj[hint_id].xref.getOffset()).write(" ");
  2693 + write(hint_length);
  2694 + write(" ] /O ").write(first_page_object);
  2695 + write(" /E ").write(part6_end_offset + hint_length);
  2696 + write(" /N ").write(pages.size());
  2697 + write(" /T ").write(space_before_zero + hint_length);
  2698 + }
  2699 + write(" >>");
2752 closeObject(lindict_id); 2700 closeObject(lindict_id);
2753 static int const pad = 200; 2701 static int const pad = 200;
2754 - writePad(QIntC::to_size(pos - m->pipeline->getCount() + pad));  
2755 - writeString("\n"); 2702 + write(QIntC::to_size(pos - m->pipeline->getCount() + pad), ' ').write("\n");
2756 2703
2757 // If the user supplied any additional header text, write it here after the linearization 2704 // If the user supplied any additional header text, write it here after the linearization
2758 // parameter dictionary. 2705 // parameter dictionary.
2759 - writeString(m->extra_header_text); 2706 + write(m->extra_header_text);
2760 2707
2761 // Part 3: first page cross reference table and trailer. 2708 // Part 3: first page cross reference table and trailer.
2762 2709
@@ -2793,20 +2740,19 @@ QPDFWriter::writeLinearized() @@ -2793,20 +2740,19 @@ QPDFWriter::writeLinearized()
2793 qpdf_offset_t endpos = m->pipeline->getCount(); 2740 qpdf_offset_t endpos = m->pipeline->getCount();
2794 if (pass == 1) { 2741 if (pass == 1) {
2795 // Pad so we have enough room for the real xref stream. 2742 // Pad so we have enough room for the real xref stream.
2796 - writePad(calculateXrefStreamPadding(endpos - pos)); 2743 + write(calculateXrefStreamPadding(endpos - pos), ' ');
2797 first_xref_end = m->pipeline->getCount(); 2744 first_xref_end = m->pipeline->getCount();
2798 } else { 2745 } else {
2799 // Pad so that the next object starts at the same place as in pass 1. 2746 // Pad so that the next object starts at the same place as in pass 1.
2800 - writePad(QIntC::to_size(first_xref_end - endpos)); 2747 + write(QIntC::to_size(first_xref_end - endpos), ' ');
2801 2748
2802 if (m->pipeline->getCount() != first_xref_end) { 2749 if (m->pipeline->getCount() != first_xref_end) {
2803 throw std::logic_error( 2750 throw std::logic_error(
2804 - "insufficient padding for first pass xref stream; "  
2805 - "first_xref_end=" + 2751 + "insufficient padding for first pass xref stream; first_xref_end=" +
2806 std::to_string(first_xref_end) + "; endpos=" + std::to_string(endpos)); 2752 std::to_string(first_xref_end) + "; endpos=" + std::to_string(endpos));
2807 } 2753 }
2808 } 2754 }
2809 - writeString("\n"); 2755 + write("\n");
2810 } else { 2756 } else {
2811 writeXRefTable( 2757 writeXRefTable(
2812 t_lin_first, 2758 t_lin_first,
@@ -2819,7 +2765,7 @@ QPDFWriter::writeLinearized() @@ -2819,7 +2765,7 @@ QPDFWriter::writeLinearized()
2819 hint_offset, 2765 hint_offset,
2820 hint_length, 2766 hint_length,
2821 pass); 2767 pass);
2822 - writeString("startxref\n0\n%%EOF\n"); 2768 + write("startxref\n0\n%%EOF\n");
2823 } 2769 }
2824 2770
2825 // Parts 4 through 9 2771 // Parts 4 through 9
@@ -2837,7 +2783,7 @@ QPDFWriter::writeLinearized() @@ -2837,7 +2783,7 @@ QPDFWriter::writeLinearized()
2837 m->new_obj[hint_id].xref = QPDFXRefEntry(m->pipeline->getCount()); 2783 m->new_obj[hint_id].xref = QPDFXRefEntry(m->pipeline->getCount());
2838 } else { 2784 } else {
2839 // Part 5: hint stream 2785 // Part 5: hint stream
2840 - writeString(hint_buffer); 2786 + write(hint_buffer);
2841 } 2787 }
2842 } 2788 }
2843 if (cur_object.getObjectID() == part6_end_marker) { 2789 if (cur_object.getObjectID() == part6_end_marker) {
@@ -2871,14 +2817,13 @@ QPDFWriter::writeLinearized() @@ -2871,14 +2817,13 @@ QPDFWriter::writeLinearized()
2871 if (pass == 1) { 2817 if (pass == 1) {
2872 // Pad so we have enough room for the real xref stream. See comments for previous 2818 // Pad so we have enough room for the real xref stream. See comments for previous
2873 // xref stream on how we calculate the padding. 2819 // xref stream on how we calculate the padding.
2874 - writePad(calculateXrefStreamPadding(endpos - pos));  
2875 - writeString("\n"); 2820 + write(calculateXrefStreamPadding(endpos - pos), ' ').write("\n");
2876 second_xref_end = m->pipeline->getCount(); 2821 second_xref_end = m->pipeline->getCount();
2877 } else { 2822 } else {
2878 // Make the file size the same. 2823 // Make the file size the same.
2879 - writePad(  
2880 - QIntC::to_size(second_xref_end + hint_length - 1 - m->pipeline->getCount()));  
2881 - writeString("\n"); 2824 + auto padding =
  2825 + QIntC::to_size(second_xref_end + hint_length - 1 - m->pipeline->getCount());
  2826 + write(padding, ' ').write("\n");
2882 2827
2883 // If this assertion fails, maybe we didn't have enough padding above. 2828 // If this assertion fails, maybe we didn't have enough padding above.
2884 if (m->pipeline->getCount() != second_xref_end + hint_length) { 2829 if (m->pipeline->getCount() != second_xref_end + hint_length) {
@@ -2890,9 +2835,7 @@ QPDFWriter::writeLinearized() @@ -2890,9 +2835,7 @@ QPDFWriter::writeLinearized()
2890 space_before_zero = writeXRefTable( 2835 space_before_zero = writeXRefTable(
2891 t_lin_second, 0, second_half_end, second_trailer_size, 0, false, 0, 0, 0, pass); 2836 t_lin_second, 0, second_half_end, second_trailer_size, 0, false, 0, 0, 0, pass);
2892 } 2837 }
2893 - writeString("startxref\n");  
2894 - writeString(std::to_string(first_xref_offset));  
2895 - writeString("\n%%EOF\n"); 2838 + write("startxref\n").write(first_xref_offset).write("\n%%EOF\n");
2896 2839
2897 if (pass == 1) { 2840 if (pass == 1) {
2898 if (m->deterministic_id) { 2841 if (m->deterministic_id) {
@@ -3038,7 +2981,7 @@ QPDFWriter::writeStandard() @@ -3038,7 +2981,7 @@ QPDFWriter::writeStandard()
3038 // Start writing 2981 // Start writing
3039 2982
3040 writeHeader(); 2983 writeHeader();
3041 - writeString(m->extra_header_text); 2984 + write(m->extra_header_text);
3042 2985
3043 if (m->pclm) { 2986 if (m->pclm) {
3044 enqueueObjectsPCLm(); 2987 enqueueObjectsPCLm();
@@ -3069,9 +3012,7 @@ QPDFWriter::writeStandard() @@ -3069,9 +3012,7 @@ QPDFWriter::writeStandard()
3069 writeXRefStream( 3012 writeXRefStream(
3070 xref_id, xref_id, xref_offset, t_normal, 0, m->next_objid - 1, m->next_objid); 3013 xref_id, xref_id, xref_offset, t_normal, 0, m->next_objid - 1, m->next_objid);
3071 } 3014 }
3072 - writeString("startxref\n");  
3073 - writeString(std::to_string(xref_offset));  
3074 - writeString("\n%%EOF\n"); 3015 + write("startxref\n").write(xref_offset).write("\n%%EOF\n");
3075 3016
3076 if (m->deterministic_id) { 3017 if (m->deterministic_id) {
3077 QTC::TC( 3018 QTC::TC(
libqpdf/qpdf/Pipeline_private.hh
@@ -109,13 +109,36 @@ namespace qpdf::pl @@ -109,13 +109,36 @@ namespace qpdf::pl
109 write(unsigned char const* buf, size_t len) final 109 write(unsigned char const* buf, size_t len) final
110 { 110 {
111 if (len) { 111 if (len) {
  112 + Count::write(std::string_view(reinterpret_cast<char const*>(buf), len));
  113 + }
  114 + }
  115 +
  116 + void
  117 + write(std::string_view sv)
  118 + {
  119 + if (sv.size()) {
  120 + if (str) {
  121 + str->append(sv);
  122 + return;
  123 + }
  124 + count += static_cast<qpdf_offset_t>(sv.size());
  125 + if (pass_immediately_to_next) {
  126 + next()->write(reinterpret_cast<char const*>(sv.data()), sv.size());
  127 + }
  128 + }
  129 + }
  130 +
  131 + void
  132 + write(size_t len, char c)
  133 + {
  134 + if (len) {
112 if (str) { 135 if (str) {
113 - str->append(reinterpret_cast<char const*>(buf), len); 136 + str->append(len, c);
114 return; 137 return;
115 } 138 }
116 count += static_cast<qpdf_offset_t>(len); 139 count += static_cast<qpdf_offset_t>(len);
117 if (pass_immediately_to_next) { 140 if (pass_immediately_to_next) {
118 - next()->write(buf, len); 141 + next()->writeString(std::string(len, c));
119 } 142 }
120 } 143 }
121 } 144 }
pkg-test/CMakeLists.txt
1 cmake_minimum_required(VERSION 3.10) 1 cmake_minimum_required(VERSION 3.10)
2 project(qpdf-version LANGUAGES CXX) 2 project(qpdf-version LANGUAGES CXX)
  3 +set(CMAKE_CXX_STANDARD 20)
3 find_package(qpdf) 4 find_package(qpdf)
4 add_executable(qpdf-version qpdf-version.cc) 5 add_executable(qpdf-version qpdf-version.cc)
5 target_link_libraries(qpdf-version qpdf::libqpdf) 6 target_link_libraries(qpdf-version qpdf::libqpdf)