Commit 485399c71403f49f4becd625b0df6e17ce59f439

Authored by m-holger
1 parent 7d1ba7da

Refactor `QPDFWriter` to consolidate `write*` methods into parameterized `write`…

… overloads, streamline QDF handling, and simplify string and trailer writes.
include/qpdf/QPDFWriter.hh
... ... @@ -24,6 +24,7 @@
24 24 #include <qpdf/Types.h>
25 25  
26 26 #include <bitset>
  27 +#include <concepts>
27 28 #include <cstdio>
28 29 #include <functional>
29 30 #include <list>
... ... @@ -481,10 +482,13 @@ class QPDFWriter
481 482  
482 483 unsigned int bytesNeeded(long long n);
483 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 + template <typename... Args>
  489 + QPDFWriter& write_qdf(Args&&... args);
  490 + template <typename... Args>
  491 + QPDFWriter& write_no_qdf(Args&&... args);
488 492 void assignCompressedObjectNumbers(QPDFObjGen og);
489 493 void enqueueObject(QPDFObjectHandle object);
490 494 void writeObjectStreamOffsets(std::vector<qpdf_offset_t>& offsets, int first_obj);
... ...
libqpdf/QPDFWriter.cc
... ... @@ -832,32 +832,45 @@ QPDFWriter::writeBinary(unsigned long long val, unsigned int bytes)
832 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 +template <typename... Args>
  857 +QPDFWriter&
  858 +QPDFWriter::write_qdf(Args&&... args)
  859 +{
  860 + if (m->qdf_mode) {
  861 + m->pipeline->write(std::forward<Args>(args)...);
854 862 }
  863 + return *this;
855 864 }
856 865  
857   -void
858   -QPDFWriter::writePad(size_t nspaces)
  866 +template <typename... Args>
  867 +QPDFWriter&
  868 +QPDFWriter::write_no_qdf(Args&&... args)
859 869 {
860   - writeString(std::string(nspaces, ' '));
  870 + if (!m->qdf_mode) {
  871 + m->pipeline->write(std::forward<Args>(args)...);
  872 + }
  873 + return *this;
861 874 }
862 875  
863 876 Pipeline*
... ... @@ -1005,8 +1018,7 @@ QPDFWriter::openObject(int objid)
1005 1018 objid = m->next_objid++;
1006 1019 }
1007 1020 m->new_obj[objid].xref = QPDFXRefEntry(m->pipeline->getCount());
1008   - writeString(std::to_string(objid));
1009   - writeString(" 0 obj\n");
  1021 + write(objid).write(" 0 obj\n");
1010 1022 return objid;
1011 1023 }
1012 1024  
... ... @@ -1014,8 +1026,7 @@ void
1014 1026 QPDFWriter::closeObject(int objid)
1015 1027 {
1016 1028 // Write a newline before endobj as it makes the file easier to repair.
1017   - writeString("\nendobj\n");
1018   - writeStringQDF("\n");
  1029 + write("\nendobj\n").write_qdf("\n");
1019 1030 auto& new_obj = m->new_obj[objid];
1020 1031 new_obj.length = m->pipeline->getCount() - new_obj.xref.getOffset();
1021 1032 }
... ... @@ -1114,8 +1125,7 @@ QPDFWriter::unparseChild(QPDFObjectHandle child, int level, int flags)
1114 1125 enqueueObject(child);
1115 1126 }
1116 1127 if (child.isIndirect()) {
1117   - writeString(std::to_string(m->obj[child].renumber));
1118   - writeString(" 0 R");
  1128 + write(m->obj[child].renumber).write(" 0 R");
1119 1129 } else {
1120 1130 unparseObject(child, level, flags);
1121 1131 }
... ... @@ -1129,78 +1139,63 @@ QPDFWriter::writeTrailer(
1129 1139 if (xref_stream) {
1130 1140 m->cur_data_key.clear();
1131 1141 } else {
1132   - writeString("trailer <<");
  1142 + write("trailer <<");
1133 1143 }
1134   - writeStringQDF("\n");
  1144 + write_qdf("\n");
1135 1145 if (which == t_lin_second) {
1136   - writeString(" /Size ");
1137   - writeString(std::to_string(size));
  1146 + write(" /Size ").write(size);
1138 1147 } else {
1139 1148 for (auto const& [key, value]: trailer.as_dictionary()) {
1140 1149 if (value.null()) {
1141 1150 continue;
1142 1151 }
1143   - writeStringQDF(" ");
1144   - writeStringNoQDF(" ");
1145   - writeString(Name::normalize(key));
1146   - writeString(" ");
  1152 + write_qdf(" ").write_no_qdf(" ").write(Name::normalize(key)).write(" ");
1147 1153 if (key == "/Size") {
1148   - writeString(std::to_string(size));
  1154 + write(size);
1149 1155 if (which == t_lin_first) {
1150   - writeString(" /Prev ");
  1156 + write(" /Prev ");
1151 1157 qpdf_offset_t pos = m->pipeline->getCount();
1152   - writeString(std::to_string(prev));
1153   - writePad(QIntC::to_size(pos - m->pipeline->getCount() + 21));
  1158 + write(prev).write(QIntC::to_size(pos - m->pipeline->getCount() + 21), ' ');
1154 1159 }
1155 1160 } else {
1156 1161 unparseChild(value, 1, 0);
1157 1162 }
1158   - writeStringQDF("\n");
  1163 + write_qdf("\n");
1159 1164 }
1160 1165 }
1161 1166  
1162 1167 // Write ID
1163   - writeStringQDF(" ");
1164   - writeString(" /ID [");
  1168 + write_qdf(" ").write(" /ID [");
1165 1169 if (linearization_pass == 1) {
1166 1170 std::string original_id1 = getOriginalID1();
1167 1171 if (original_id1.empty()) {
1168   - writeString("<00000000000000000000000000000000>");
  1172 + write("<00000000000000000000000000000000>");
1169 1173 } else {
1170 1174 // Write a string of zeroes equal in length to the representation of the original ID.
1171 1175 // While writing the original ID would have the same number of bytes, it would cause a
1172 1176 // change to the deterministic ID generated by older versions of the software that
1173 1177 // hard-coded the length of the ID to 16 bytes.
1174   - writeString("<");
1175 1178 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(">");
  1179 + write("<").write(len, '0').write(">");
1180 1180 }
1181   - writeString("<00000000000000000000000000000000>");
  1181 + write("<00000000000000000000000000000000>");
1182 1182 } else {
1183   - if ((linearization_pass == 0) && (m->deterministic_id)) {
  1183 + if (linearization_pass == 0 && m->deterministic_id) {
1184 1184 computeDeterministicIDData();
1185 1185 }
1186 1186 generateID();
1187   - writeString(QPDF_String(m->id1).unparse(true));
1188   - writeString(QPDF_String(m->id2).unparse(true));
  1187 + write(QPDF_String(m->id1).unparse(true)).write(QPDF_String(m->id2).unparse(true));
1189 1188 }
1190   - writeString("]");
  1189 + write("]");
1191 1190  
1192 1191 if (which != t_lin_second) {
1193 1192 // Write reference to encryption dictionary
1194 1193 if (m->encryption) {
1195   - writeString(" /Encrypt ");
1196   - writeString(std::to_string(m->encryption_dict_objid));
1197   - writeString(" 0 R");
  1194 + write(" /Encrypt ").write(m->encryption_dict_objid).write(" 0 R");
1198 1195 }
1199 1196 }
1200 1197  
1201   - writeStringQDF("\n");
1202   - writeStringNoQDF(" ");
1203   - writeString(">>");
  1198 + write_qdf("\n>>").write_no_qdf(" >>");
1204 1199 }
1205 1200  
1206 1201 bool
... ... @@ -1326,14 +1321,12 @@ QPDFWriter::unparseObject(
1326 1321 // Note: PDF spec 1.4 implementation note 121 states that Acrobat requires a space after the
1327 1322 // [ in the /H key of the linearization parameter dictionary. We'll do this unconditionally
1328 1323 // for all arrays because it looks nicer and doesn't make the files that much bigger.
1329   - writeString("[");
  1324 + write("[");
1330 1325 for (auto const& item: object.as_array()) {
1331   - writeString(indent);
1332   - writeStringQDF(" ");
  1326 + write(indent).write_qdf(" ");
1333 1327 unparseChild(item, level + 1, child_flags);
1334 1328 }
1335   - writeString(indent);
1336   - writeString("]");
  1329 + write(indent).write("]");
1337 1330 } else if (tc == ::ot_dictionary) {
1338 1331 // Handle special cases for specific dictionaries.
1339 1332  
... ... @@ -1474,14 +1467,11 @@ QPDFWriter::unparseObject(
1474 1467 }
1475 1468 }
1476 1469  
1477   - writeString("<<");
  1470 + write("<<");
1478 1471  
1479 1472 for (auto const& [key, value]: object.as_dictionary()) {
1480 1473 if (!value.null()) {
1481   - writeString(indent);
1482   - writeStringQDF(" ");
1483   - writeString(Name::normalize(key));
1484   - writeString(" ");
  1474 + write(indent).write_qdf(" ").write(Name::normalize(key)).write(" ");
1485 1475 if (key == "/Contents" && object.isDictionaryOfType("/Sig") &&
1486 1476 object.hasKey("/ByteRange")) {
1487 1477 QTC::TC("qpdf", "QPDFWriter no encryption sig contents");
... ... @@ -1493,25 +1483,19 @@ QPDFWriter::unparseObject(
1493 1483 }
1494 1484  
1495 1485 if (flags & f_stream) {
1496   - writeString(indent);
1497   - writeStringQDF(" ");
1498   - writeString("/Length ");
  1486 + write(indent).write_qdf(" ").write("/Length ");
1499 1487  
1500 1488 if (m->direct_stream_lengths) {
1501   - writeString(std::to_string(stream_length));
  1489 + write(stream_length);
1502 1490 } else {
1503   - writeString(std::to_string(m->cur_stream_length_id));
1504   - writeString(" 0 R");
  1491 + write(m->cur_stream_length_id).write(" 0 R");
1505 1492 }
1506 1493 if (compress && (flags & f_filtered)) {
1507   - writeString(indent);
1508   - writeStringQDF(" ");
1509   - writeString("/Filter /FlateDecode");
  1494 + write(indent).write_qdf(" ").write("/Filter /FlateDecode");
1510 1495 }
1511 1496 }
1512 1497  
1513   - writeString(indent);
1514   - writeString(">>");
  1498 + write(indent).write(">>");
1515 1499 } else if (tc == ::ot_stream) {
1516 1500 // Write stream data to a buffer.
1517 1501 if (!m->direct_stream_lengths) {
... ... @@ -1535,18 +1519,18 @@ QPDFWriter::unparseObject(
1535 1519 adjustAESStreamLength(m->cur_stream_length);
1536 1520 unparseObject(stream_dict, 0, flags, m->cur_stream_length, compress_stream);
1537 1521 char last_char = stream_data.empty() ? '\0' : stream_data.back();
1538   - writeString("\nstream\n");
  1522 + write("\nstream\n");
1539 1523 {
1540 1524 PipelinePopper pp_enc(this);
1541 1525 pushEncryptionFilter(pp_enc);
1542   - writeString(stream_data);
  1526 + write(stream_data);
1543 1527 }
1544 1528  
1545 1529 if ((m->added_newline =
1546 1530 m->newline_before_endstream || (m->qdf_mode && last_char != '\n'))) {
1547   - writeString("\nendstream");
  1531 + write("\nendstream");
1548 1532 } else {
1549   - writeString("endstream");
  1533 + write("endstream");
1550 1534 }
1551 1535 } else if (tc == ::ot_string) {
1552 1536 std::string val;
... ... @@ -1580,9 +1564,9 @@ QPDFWriter::unparseObject(
1580 1564 } else {
1581 1565 val = object.unparseResolved();
1582 1566 }
1583   - writeString(val);
  1567 + write(val);
1584 1568 } else {
1585   - writeString(object.unparseResolved());
  1569 + write(object.unparseResolved());
1586 1570 }
1587 1571 }
1588 1572  
... ... @@ -1596,14 +1580,13 @@ QPDFWriter::writeObjectStreamOffsets(std::vector&lt;qpdf_offset_t&gt;&amp; offsets, int fi
1596 1580 if (is_first) {
1597 1581 is_first = false;
1598 1582 } else {
1599   - writeStringQDF("\n");
1600   - writeStringNoQDF(" ");
  1583 + write_qdf("\n").write_no_qdf(" ");
1601 1584 }
1602   - writeString(id);
  1585 + write(id);
1603 1586 util::increment(id, 1);
1604   - writeString(std::to_string(offset));
  1587 + write(offset);
1605 1588 }
1606   - writeString("\n");
  1589 + write("\n");
1607 1590 }
1608 1591  
1609 1592 void
... ... @@ -1639,20 +1622,18 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1639 1622 first_obj = new_obj;
1640 1623 }
1641 1624 if (m->qdf_mode) {
1642   - writeString(
1643   - "%% Object stream: object " + std::to_string(new_obj) + ", index " +
1644   - std::to_string(count));
  1625 + write("%% Object stream: object ").write(new_obj).write(", index ").write(count);
1645 1626 if (!m->suppress_original_object_ids) {
1646   - writeString("; original object ID: " + std::to_string(obj.getObj()));
  1627 + write("; original object ID: ").write(obj.getObj());
1647 1628 // For compatibility, only write the generation if non-zero. While object
1648 1629 // streams only allow objects with generation 0, if we are generating object
1649 1630 // streams, the old object could have a non-zero generation.
1650 1631 if (obj.getGen() != 0) {
1651 1632 QTC::TC("qpdf", "QPDFWriter original obj non-zero gen");
1652   - writeString(" " + std::to_string(obj.getGen()));
  1633 + write(" ").write(obj.getGen());
1653 1634 }
1654 1635 }
1655   - writeString("\n");
  1636 + write("\n");
1656 1637 }
1657 1638  
1658 1639 offsets.push_back(m->pipeline->getCount());
... ... @@ -1698,7 +1679,7 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1698 1679 activatePipelineStack(pp_ostream, stream_buffer_pass2);
1699 1680 }
1700 1681 writeObjectStreamOffsets(offsets, first_obj);
1701   - writeString(stream_buffer_pass1);
  1682 + write(stream_buffer_pass1);
1702 1683 stream_buffer_pass1.clear();
1703 1684 stream_buffer_pass1.shrink_to_fit();
1704 1685 }
... ... @@ -1706,46 +1687,37 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1706 1687 // Write the object
1707 1688 openObject(new_stream_id);
1708 1689 setDataKey(new_stream_id);
1709   - writeString("<<");
1710   - writeStringQDF("\n ");
1711   - writeString(" /Type /ObjStm");
1712   - writeStringQDF("\n ");
  1690 + write("<<").write_qdf("\n ").write(" /Type /ObjStm").write_qdf("\n ");
1713 1691 size_t length = stream_buffer_pass2.size();
1714 1692 adjustAESStreamLength(length);
1715   - writeString(" /Length " + std::to_string(length));
1716   - writeStringQDF("\n ");
  1693 + write(" /Length ").write(length).write_qdf("\n ");
1717 1694 if (compressed) {
1718   - writeString(" /Filter /FlateDecode");
  1695 + write(" /Filter /FlateDecode");
1719 1696 }
1720   - writeString(" /N " + std::to_string(offsets.size()));
1721   - writeStringQDF("\n ");
1722   - writeString(" /First " + std::to_string(first));
  1697 + write(" /N ").write(offsets.size()).write_qdf("\n ").write(" /First ").write(first);
1723 1698 if (!object.isNull()) {
1724 1699 // If the original object has an /Extends key, preserve it.
1725 1700 QPDFObjectHandle dict = object.getDict();
1726 1701 QPDFObjectHandle extends = dict.getKey("/Extends");
1727 1702 if (extends.isIndirect()) {
1728 1703 QTC::TC("qpdf", "QPDFWriter copy Extends");
1729   - writeStringQDF("\n ");
1730   - writeString(" /Extends ");
  1704 + write_qdf("\n ").write(" /Extends ");
1731 1705 unparseChild(extends, 1, f_in_ostream);
1732 1706 }
1733 1707 }
1734   - writeStringQDF("\n");
1735   - writeStringNoQDF(" ");
1736   - writeString(">>\nstream\n");
  1708 + write_qdf("\n").write_no_qdf(" ").write(">>\nstream\n");
1737 1709 if (m->encryption) {
1738 1710 QTC::TC("qpdf", "QPDFWriter encrypt object stream");
1739 1711 }
1740 1712 {
1741 1713 PipelinePopper pp_enc(this);
1742 1714 pushEncryptionFilter(pp_enc);
1743   - writeString(stream_buffer_pass2);
  1715 + write(stream_buffer_pass2);
1744 1716 }
1745 1717 if (m->newline_before_endstream) {
1746   - writeString("\n");
  1718 + write("\n");
1747 1719 }
1748   - writeString("endstream");
  1720 + write("endstream");
1749 1721 m->cur_data_key.clear();
1750 1722 closeObject(new_stream_id);
1751 1723 }
... ... @@ -1755,8 +1727,8 @@ QPDFWriter::writeObject(QPDFObjectHandle object, int object_stream_index)
1755 1727 {
1756 1728 QPDFObjGen old_og = object.getObjGen();
1757 1729  
1758   - if ((object_stream_index == -1) && (old_og.getGen() == 0) &&
1759   - (m->object_stream_to_objects.count(old_og.getObj()))) {
  1730 + if (object_stream_index == -1 && old_og.getGen() == 0 &&
  1731 + m->object_stream_to_objects.contains(old_og.getObj())) {
1760 1732 writeObjectStream(object);
1761 1733 return;
1762 1734 }
... ... @@ -1765,19 +1737,15 @@ QPDFWriter::writeObject(QPDFObjectHandle object, int object_stream_index)
1765 1737 auto new_id = m->obj[old_og].renumber;
1766 1738 if (m->qdf_mode) {
1767 1739 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");
  1740 + write("%% Page ").write(m->page_object_to_seq[old_og]).write("\n");
1771 1741 }
1772 1742 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");
  1743 + write("%% Contents for page ").write(m->contents_to_page_seq[old_og]).write("\n");
1776 1744 }
1777 1745 }
1778 1746 if (object_stream_index == -1) {
1779 1747 if (m->qdf_mode && (!m->suppress_original_object_ids)) {
1780   - writeString("%% Original object ID: " + object.getObjGen().unparse(' ') + "\n");
  1748 + write("%% Original object ID: ").write(object.getObjGen().unparse(' ')).write("\n");
1781 1749 }
1782 1750 openObject(new_id);
1783 1751 setDataKey(new_id);
... ... @@ -1786,17 +1754,17 @@ QPDFWriter::writeObject(QPDFObjectHandle object, int object_stream_index)
1786 1754 closeObject(new_id);
1787 1755 } else {
1788 1756 unparseObject(object, 0, f_in_ostream);
1789   - writeString("\n");
  1757 + write("\n");
1790 1758 }
1791 1759  
1792   - if ((!m->direct_stream_lengths) && object.isStream()) {
  1760 + if (!m->direct_stream_lengths && object.isStream()) {
1793 1761 if (m->qdf_mode) {
1794 1762 if (m->added_newline) {
1795   - writeString("%QDF: ignore_newline\n");
  1763 + write("%QDF: ignore_newline\n");
1796 1764 }
1797 1765 }
1798 1766 openObject(new_id + 1);
1799   - writeString(std::to_string(m->cur_stream_length));
  1767 + write(m->cur_stream_length);
1800 1768 closeObject(new_id + 1);
1801 1769 }
1802 1770 }
... ... @@ -2274,46 +2242,36 @@ QPDFWriter::writeEncryptionDictionary()
2274 2242 auto& enc = *m->encryption;
2275 2243 auto const V = enc.getV();
2276 2244  
2277   - writeString("<<");
  2245 + write("<<");
2278 2246 if (V >= 4) {
2279   - writeString(" /CF << /StdCF << /AuthEvent /DocOpen /CFM ");
2280   - writeString(m->encrypt_use_aes ? ((V < 5) ? "/AESV2" : "/AESV3") : "/V2");
  2247 + write(" /CF << /StdCF << /AuthEvent /DocOpen /CFM ");
  2248 + write(m->encrypt_use_aes ? ((V < 5) ? "/AESV2" : "/AESV3") : "/V2");
2281 2249 // The PDF spec says the /Length key is optional, but the PDF previewer on some versions of
2282 2250 // MacOS won't open encrypted files without it.
2283   - writeString((V < 5) ? " /Length 16 >> >>" : " /Length 32 >> >>");
  2251 + write((V < 5) ? " /Length 16 >> >>" : " /Length 32 >> >>");
2284 2252 if (!m->encryption->getEncryptMetadata()) {
2285   - writeString(" /EncryptMetadata false");
  2253 + write(" /EncryptMetadata false");
2286 2254 }
2287 2255 }
2288   - writeString(" /Filter /Standard /Length ");
2289   - writeString(std::to_string(enc.getLengthBytes() * 8));
2290   - writeString(" /O ");
2291   - writeString(QPDF_String(enc.getO()).unparse(true));
  2256 + write(" /Filter /Standard /Length ").write(enc.getLengthBytes() * 8);
  2257 + write(" /O ").write(QPDF_String(enc.getO()).unparse(true));
2292 2258 if (V >= 4) {
2293   - writeString(" /OE ");
2294   - writeString(QPDF_String(enc.getOE()).unparse(true));
  2259 + write(" /OE ").write(QPDF_String(enc.getOE()).unparse(true));
2295 2260 }
2296   - writeString(" /P ");
2297   - writeString(std::to_string(enc.getP()));
  2261 + write(" /P ").write(enc.getP());
2298 2262 if (V >= 5) {
2299   - writeString(" /Perms ");
2300   - writeString(QPDF_String(enc.getPerms()).unparse(true));
  2263 + write(" /Perms ").write(QPDF_String(enc.getPerms()).unparse(true));
2301 2264 }
2302   - writeString(" /R ");
2303   - writeString(std::to_string(enc.getR()));
  2265 + write(" /R ").write(enc.getR());
2304 2266  
2305 2267 if (V >= 4) {
2306   - writeString(" /StmF /StdCF /StrF /StdCF");
  2268 + write(" /StmF /StdCF /StrF /StdCF");
2307 2269 }
2308   - writeString(" /U ");
2309   - writeString(QPDF_String(enc.getU()).unparse(true));
  2270 + write(" /U ").write(QPDF_String(enc.getU()).unparse(true));
2310 2271 if (V >= 4) {
2311   - writeString(" /UE ");
2312   - writeString(QPDF_String(enc.getUE()).unparse(true));
  2272 + write(" /UE ").write(QPDF_String(enc.getUE()).unparse(true));
2313 2273 }
2314   - writeString(" /V ");
2315   - writeString(std::to_string(enc.getV()));
2316   - writeString(" >>");
  2274 + write(" /V ").write(enc.getV()).write(" >>");
2317 2275 closeObject(m->encryption_dict_objid);
2318 2276 }
2319 2277  
... ... @@ -2327,17 +2285,16 @@ QPDFWriter::getFinalVersion()
2327 2285 void
2328 2286 QPDFWriter::writeHeader()
2329 2287 {
2330   - writeString("%PDF-");
2331   - writeString(m->final_pdf_version);
  2288 + write("%PDF-").write(m->final_pdf_version);
2332 2289 if (m->pclm) {
2333 2290 // PCLm version
2334   - writeString("\n%PCLm 1.0\n");
  2291 + write("\n%PCLm 1.0\n");
2335 2292 } else {
2336 2293 // This string of binary characters would not be valid UTF-8, so it really should be treated
2337 2294 // as binary.
2338   - writeString("\n%\xbf\xf7\xa2\xfe\n");
  2295 + write("\n%\xbf\xf7\xa2\xfe\n");
2339 2296 }
2340   - writeStringQDF("%QDF-1.0\n\n");
  2297 + write_qdf("%QDF-1.0\n\n");
2341 2298  
2342 2299 // Note: do not write extra header text here. Linearized PDFs must include the entire
2343 2300 // linearization parameter dictionary within the first 1024 characters of the PDF file, so for
... ... @@ -2351,7 +2308,7 @@ QPDFWriter::writeHintStream(int hint_id)
2351 2308 std::string hint_buffer;
2352 2309 int S = 0;
2353 2310 int O = 0;
2354   - bool compressed = (m->compress_streams && !m->qdf_mode);
  2311 + bool compressed = m->compress_streams && !m->qdf_mode;
2355 2312 QPDF::Writer::generateHintStream(m->pdf, m->new_obj, m->obj, hint_buffer, S, O, compressed);
2356 2313  
2357 2314 openObject(hint_id);
... ... @@ -2359,20 +2316,17 @@ QPDFWriter::writeHintStream(int hint_id)
2359 2316  
2360 2317 size_t hlen = hint_buffer.size();
2361 2318  
2362   - writeString("<< ");
  2319 + write("<< ");
2363 2320 if (compressed) {
2364   - writeString("/Filter /FlateDecode ");
  2321 + write("/Filter /FlateDecode ");
2365 2322 }
2366   - writeString("/S ");
2367   - writeString(std::to_string(S));
  2323 + write("/S ").write(S);
2368 2324 if (O) {
2369   - writeString(" /O ");
2370   - writeString(std::to_string(O));
  2325 + write(" /O ").write(O);
2371 2326 }
2372   - writeString(" /Length ");
2373 2327 adjustAESStreamLength(hlen);
2374   - writeString(std::to_string(hlen));
2375   - writeString(" >>\nstream\n");
  2328 + write(" /Length ").write(hlen);
  2329 + write(" >>\nstream\n");
2376 2330  
2377 2331 if (m->encryption) {
2378 2332 QTC::TC("qpdf", "QPDFWriter encrypted hint stream");
... ... @@ -2381,13 +2335,13 @@ QPDFWriter::writeHintStream(int hint_id)
2381 2335 {
2382 2336 PipelinePopper pp_enc(this);
2383 2337 pushEncryptionFilter(pp_enc);
2384   - writeString(hint_buffer);
  2338 + write(hint_buffer);
2385 2339 }
2386 2340  
2387 2341 if (last_char != '\n') {
2388   - writeString("\n");
  2342 + write("\n");
2389 2343 }
2390   - writeString("endstream");
  2344 + write("endstream");
2391 2345 closeObject(hint_id);
2392 2346 }
2393 2347  
... ... @@ -2412,29 +2366,25 @@ QPDFWriter::writeXRefTable(
2412 2366 qpdf_offset_t hint_length,
2413 2367 int linearization_pass)
2414 2368 {
2415   - writeString("xref\n");
2416   - writeString(std::to_string(first));
2417   - writeString(" ");
2418   - writeString(std::to_string(last - first + 1));
  2369 + write("xref\n").write(first).write(" ").write(last - first + 1);
2419 2370 qpdf_offset_t space_before_zero = m->pipeline->getCount();
2420   - writeString("\n");
  2371 + write("\n");
  2372 + if (first == 0) {
  2373 + write("0000000000 65535 f \n");
  2374 + ++first;
  2375 + }
2421 2376 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   - }
  2377 + qpdf_offset_t offset = 0;
  2378 + if (!suppress_offsets) {
  2379 + offset = m->new_obj[i].xref.getOffset();
  2380 + if ((hint_id != 0) && (i != hint_id) && (offset >= hint_offset)) {
  2381 + offset += hint_length;
2431 2382 }
2432   - writeString(QUtil::int_to_string(offset, 10));
2433   - writeString(" 00000 n \n");
2434 2383 }
  2384 + write(QUtil::int_to_string(offset, 10)).write(" 00000 n \n");
2435 2385 }
2436 2386 writeTrailer(which, size, false, prev, linearization_pass);
2437   - writeString("\n");
  2387 + write("\n");
2438 2388 return space_before_zero;
2439 2389 }
2440 2390  
... ... @@ -2532,27 +2482,18 @@ QPDFWriter::writeXRefStream(
2532 2482 }
2533 2483  
2534 2484 openObject(xref_id);
2535   - writeString("<<");
2536   - writeStringQDF("\n ");
2537   - writeString(" /Type /XRef");
2538   - writeStringQDF("\n ");
2539   - writeString(" /Length " + std::to_string(xref_data.size()));
  2485 + write("<<").write_qdf("\n ").write(" /Type /XRef").write_qdf("\n ");
  2486 + write(" /Length ").write(xref_data.size());
2540 2487 if (compressed) {
2541   - writeStringQDF("\n ");
2542   - writeString(" /Filter /FlateDecode");
2543   - writeStringQDF("\n ");
2544   - writeString(" /DecodeParms << /Columns " + std::to_string(esize) + " /Predictor 12 >>");
  2488 + write_qdf("\n ").write(" /Filter /FlateDecode").write_qdf("\n ");
  2489 + write(" /DecodeParms << /Columns ").write(esize).write(" /Predictor 12 >>");
2545 2490 }
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) + " ]");
  2491 + write_qdf("\n ").write(" /W [ 1 ").write(f1_size).write(" ").write(f2_size).write(" ]");
  2492 + if (!(first == 0 && last == (size - 1))) {
  2493 + write(" /Index [ ").write(first).write(" ").write(last - first + 1).write(" ]");
2551 2494 }
2552 2495 writeTrailer(which, size, true, prev, linearization_pass);
2553   - writeString("\nstream\n");
2554   - writeString(xref_data);
2555   - writeString("\nendstream");
  2496 + write("\nstream\n").write(xref_data).write("\nendstream");
2556 2497 closeObject(xref_id);
2557 2498 return space_before_zero;
2558 2499 }
... ... @@ -2726,37 +2667,28 @@ QPDFWriter::writeLinearized()
2726 2667  
2727 2668 qpdf_offset_t pos = m->pipeline->getCount();
2728 2669 openObject(lindict_id);
2729   - writeString("<<");
  2670 + write("<<");
2730 2671 if (pass == 2) {
2731 2672 std::vector<QPDFObjectHandle> const& pages = m->pdf.getAllPages();
2732 2673 int first_page_object = m->obj[pages.at(0)].renumber;
2733   - int npages = QIntC::to_int(pages.size());
2734 2674  
2735   - writeString(" /Linearized 1 /L ");
2736   - writeString(std::to_string(file_size + hint_length));
  2675 + write(" /Linearized 1 /L ").write(file_size + hint_length);
2737 2676 // 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(" >>");
  2677 + write(" /H [ ").write(m->new_obj[hint_id].xref.getOffset()).write(" ");
  2678 + write(hint_length);
  2679 + write(" ] /O ").write(first_page_object);
  2680 + write(" /E ").write(part6_end_offset + hint_length);
  2681 + write(" /N ").write(pages.size());
  2682 + write(" /T ").write(space_before_zero + hint_length);
  2683 + }
  2684 + write(" >>");
2752 2685 closeObject(lindict_id);
2753 2686 static int const pad = 200;
2754   - writePad(QIntC::to_size(pos - m->pipeline->getCount() + pad));
2755   - writeString("\n");
  2687 + write(QIntC::to_size(pos - m->pipeline->getCount() + pad), ' ').write("\n");
2756 2688  
2757 2689 // If the user supplied any additional header text, write it here after the linearization
2758 2690 // parameter dictionary.
2759   - writeString(m->extra_header_text);
  2691 + write(m->extra_header_text);
2760 2692  
2761 2693 // Part 3: first page cross reference table and trailer.
2762 2694  
... ... @@ -2793,20 +2725,19 @@ QPDFWriter::writeLinearized()
2793 2725 qpdf_offset_t endpos = m->pipeline->getCount();
2794 2726 if (pass == 1) {
2795 2727 // Pad so we have enough room for the real xref stream.
2796   - writePad(calculateXrefStreamPadding(endpos - pos));
  2728 + write(calculateXrefStreamPadding(endpos - pos), ' ');
2797 2729 first_xref_end = m->pipeline->getCount();
2798 2730 } else {
2799 2731 // Pad so that the next object starts at the same place as in pass 1.
2800   - writePad(QIntC::to_size(first_xref_end - endpos));
  2732 + write(QIntC::to_size(first_xref_end - endpos), ' ');
2801 2733  
2802 2734 if (m->pipeline->getCount() != first_xref_end) {
2803 2735 throw std::logic_error(
2804   - "insufficient padding for first pass xref stream; "
2805   - "first_xref_end=" +
  2736 + "insufficient padding for first pass xref stream; first_xref_end=" +
2806 2737 std::to_string(first_xref_end) + "; endpos=" + std::to_string(endpos));
2807 2738 }
2808 2739 }
2809   - writeString("\n");
  2740 + write("\n");
2810 2741 } else {
2811 2742 writeXRefTable(
2812 2743 t_lin_first,
... ... @@ -2819,7 +2750,7 @@ QPDFWriter::writeLinearized()
2819 2750 hint_offset,
2820 2751 hint_length,
2821 2752 pass);
2822   - writeString("startxref\n0\n%%EOF\n");
  2753 + write("startxref\n0\n%%EOF\n");
2823 2754 }
2824 2755  
2825 2756 // Parts 4 through 9
... ... @@ -2837,7 +2768,7 @@ QPDFWriter::writeLinearized()
2837 2768 m->new_obj[hint_id].xref = QPDFXRefEntry(m->pipeline->getCount());
2838 2769 } else {
2839 2770 // Part 5: hint stream
2840   - writeString(hint_buffer);
  2771 + write(hint_buffer);
2841 2772 }
2842 2773 }
2843 2774 if (cur_object.getObjectID() == part6_end_marker) {
... ... @@ -2871,14 +2802,13 @@ QPDFWriter::writeLinearized()
2871 2802 if (pass == 1) {
2872 2803 // Pad so we have enough room for the real xref stream. See comments for previous
2873 2804 // xref stream on how we calculate the padding.
2874   - writePad(calculateXrefStreamPadding(endpos - pos));
2875   - writeString("\n");
  2805 + write(calculateXrefStreamPadding(endpos - pos), ' ').write("\n");
2876 2806 second_xref_end = m->pipeline->getCount();
2877 2807 } else {
2878 2808 // Make the file size the same.
2879   - writePad(
2880   - QIntC::to_size(second_xref_end + hint_length - 1 - m->pipeline->getCount()));
2881   - writeString("\n");
  2809 + auto padding =
  2810 + QIntC::to_size(second_xref_end + hint_length - 1 - m->pipeline->getCount());
  2811 + write(padding, ' ').write("\n");
2882 2812  
2883 2813 // If this assertion fails, maybe we didn't have enough padding above.
2884 2814 if (m->pipeline->getCount() != second_xref_end + hint_length) {
... ... @@ -2890,9 +2820,7 @@ QPDFWriter::writeLinearized()
2890 2820 space_before_zero = writeXRefTable(
2891 2821 t_lin_second, 0, second_half_end, second_trailer_size, 0, false, 0, 0, 0, pass);
2892 2822 }
2893   - writeString("startxref\n");
2894   - writeString(std::to_string(first_xref_offset));
2895   - writeString("\n%%EOF\n");
  2823 + write("startxref\n").write(first_xref_offset).write("\n%%EOF\n");
2896 2824  
2897 2825 if (pass == 1) {
2898 2826 if (m->deterministic_id) {
... ... @@ -3038,7 +2966,7 @@ QPDFWriter::writeStandard()
3038 2966 // Start writing
3039 2967  
3040 2968 writeHeader();
3041   - writeString(m->extra_header_text);
  2969 + write(m->extra_header_text);
3042 2970  
3043 2971 if (m->pclm) {
3044 2972 enqueueObjectsPCLm();
... ... @@ -3069,9 +2997,7 @@ QPDFWriter::writeStandard()
3069 2997 writeXRefStream(
3070 2998 xref_id, xref_id, xref_offset, t_normal, 0, m->next_objid - 1, m->next_objid);
3071 2999 }
3072   - writeString("startxref\n");
3073   - writeString(std::to_string(xref_offset));
3074   - writeString("\n%%EOF\n");
  3000 + write("startxref\n").write(xref_offset).write("\n%%EOF\n");
3075 3001  
3076 3002 if (m->deterministic_id) {
3077 3003 QTC::TC(
... ...
libqpdf/qpdf/Pipeline_private.hh
... ... @@ -109,13 +109,36 @@ namespace qpdf::pl
109 109 write(unsigned char const* buf, size_t len) final
110 110 {
111 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 135 if (str) {
113   - str->append(reinterpret_cast<char const*>(buf), len);
  136 + str->append(len, c);
114 137 return;
115 138 }
116 139 count += static_cast<qpdf_offset_t>(len);
117 140 if (pass_immediately_to_next) {
118   - next()->write(buf, len);
  141 + next()->writeString(std::string(len, c));
119 142 }
120 143 }
121 144 }
... ...