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 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,16 @@ 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 + 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 495 void assignCompressedObjectNumbers(QPDFObjGen og);
489 496 void enqueueObject(QPDFObjectHandle object);
490 497 void writeObjectStreamOffsets(std::vector<qpdf_offset_t>& offsets, int first_obj);
... ...
libqpdf.pc.in
... ... @@ -8,4 +8,4 @@ Description: PDF transformation library
8 8 Version: @PROJECT_VERSION@
9 9 Requires.private: zlib, libjpeg@CRYPTO_PKG@
10 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 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 890 Pipeline*
... ... @@ -1005,8 +1032,7 @@ QPDFWriter::openObject(int objid)
1005 1032 objid = m->next_objid++;
1006 1033 }
1007 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 1036 return objid;
1011 1037 }
1012 1038  
... ... @@ -1014,8 +1040,7 @@ void
1014 1040 QPDFWriter::closeObject(int objid)
1015 1041 {
1016 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 1044 auto& new_obj = m->new_obj[objid];
1020 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 1139 enqueueObject(child);
1115 1140 }
1116 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 1143 } else {
1120 1144 unparseObject(child, level, flags);
1121 1145 }
... ... @@ -1129,78 +1153,63 @@ QPDFWriter::writeTrailer(
1129 1153 if (xref_stream) {
1130 1154 m->cur_data_key.clear();
1131 1155 } else {
1132   - writeString("trailer <<");
  1156 + write("trailer <<");
1133 1157 }
1134   - writeStringQDF("\n");
  1158 + write_qdf("\n");
1135 1159 if (which == t_lin_second) {
1136   - writeString(" /Size ");
1137   - writeString(std::to_string(size));
  1160 + write(" /Size ").write(size);
1138 1161 } else {
1139 1162 for (auto const& [key, value]: trailer.as_dictionary()) {
1140 1163 if (value.null()) {
1141 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 1167 if (key == "/Size") {
1148   - writeString(std::to_string(size));
  1168 + write(size);
1149 1169 if (which == t_lin_first) {
1150   - writeString(" /Prev ");
  1170 + write(" /Prev ");
1151 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 1174 } else {
1156 1175 unparseChild(value, 1, 0);
1157 1176 }
1158   - writeStringQDF("\n");
  1177 + write_qdf("\n");
1159 1178 }
1160 1179 }
1161 1180  
1162 1181 // Write ID
1163   - writeStringQDF(" ");
1164   - writeString(" /ID [");
  1182 + write_qdf(" ").write(" /ID [");
1165 1183 if (linearization_pass == 1) {
1166 1184 std::string original_id1 = getOriginalID1();
1167 1185 if (original_id1.empty()) {
1168   - writeString("<00000000000000000000000000000000>");
  1186 + write("<00000000000000000000000000000000>");
1169 1187 } else {
1170 1188 // Write a string of zeroes equal in length to the representation of the original ID.
1171 1189 // While writing the original ID would have the same number of bytes, it would cause a
1172 1190 // change to the deterministic ID generated by older versions of the software that
1173 1191 // hard-coded the length of the ID to 16 bytes.
1174   - writeString("<");
1175 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 1196 } else {
1183   - if ((linearization_pass == 0) && (m->deterministic_id)) {
  1197 + if (linearization_pass == 0 && m->deterministic_id) {
1184 1198 computeDeterministicIDData();
1185 1199 }
1186 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 1205 if (which != t_lin_second) {
1193 1206 // Write reference to encryption dictionary
1194 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 1215 bool
... ... @@ -1314,26 +1323,25 @@ QPDFWriter::unparseObject(
1314 1323 if (level < 0) {
1315 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 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 1335 if (auto const tc = object.getTypeCode(); tc == ::ot_array) {
1326 1336 // Note: PDF spec 1.4 implementation note 121 states that Acrobat requires a space after the
1327 1337 // [ in the /H key of the linearization parameter dictionary. We'll do this unconditionally
1328 1338 // for all arrays because it looks nicer and doesn't make the files that much bigger.
1329   - writeString("[");
  1339 + write("[");
1330 1340 for (auto const& item: object.as_array()) {
1331   - writeString(indent);
1332   - writeStringQDF(" ");
  1341 + write(indent_large);
1333 1342 unparseChild(item, level + 1, child_flags);
1334 1343 }
1335   - writeString(indent);
1336   - writeString("]");
  1344 + write(indent).write("]");
1337 1345 } else if (tc == ::ot_dictionary) {
1338 1346 // Handle special cases for specific dictionaries.
1339 1347  
... ... @@ -1474,14 +1482,11 @@ QPDFWriter::unparseObject(
1474 1482 }
1475 1483 }
1476 1484  
1477   - writeString("<<");
  1485 + write("<<");
1478 1486  
1479 1487 for (auto const& [key, value]: object.as_dictionary()) {
1480 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 1490 if (key == "/Contents" && object.isDictionaryOfType("/Sig") &&
1486 1491 object.hasKey("/ByteRange")) {
1487 1492 QTC::TC("qpdf", "QPDFWriter no encryption sig contents");
... ... @@ -1493,25 +1498,19 @@ QPDFWriter::unparseObject(
1493 1498 }
1494 1499  
1495 1500 if (flags & f_stream) {
1496   - writeString(indent);
1497   - writeStringQDF(" ");
1498   - writeString("/Length ");
  1501 + write(indent_large).write("/Length ");
1499 1502  
1500 1503 if (m->direct_stream_lengths) {
1501   - writeString(std::to_string(stream_length));
  1504 + write(stream_length);
1502 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 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 1514 } else if (tc == ::ot_stream) {
1516 1515 // Write stream data to a buffer.
1517 1516 if (!m->direct_stream_lengths) {
... ... @@ -1535,18 +1534,18 @@ QPDFWriter::unparseObject(
1535 1534 adjustAESStreamLength(m->cur_stream_length);
1536 1535 unparseObject(stream_dict, 0, flags, m->cur_stream_length, compress_stream);
1537 1536 char last_char = stream_data.empty() ? '\0' : stream_data.back();
1538   - writeString("\nstream\n");
  1537 + write("\nstream\n");
1539 1538 {
1540 1539 PipelinePopper pp_enc(this);
1541 1540 pushEncryptionFilter(pp_enc);
1542   - writeString(stream_data);
  1541 + write(stream_data);
1543 1542 }
1544 1543  
1545 1544 if ((m->added_newline =
1546 1545 m->newline_before_endstream || (m->qdf_mode && last_char != '\n'))) {
1547   - writeString("\nendstream");
  1546 + write("\nendstream");
1548 1547 } else {
1549   - writeString("endstream");
  1548 + write("endstream");
1550 1549 }
1551 1550 } else if (tc == ::ot_string) {
1552 1551 std::string val;
... ... @@ -1580,9 +1579,9 @@ QPDFWriter::unparseObject(
1580 1579 } else {
1581 1580 val = object.unparseResolved();
1582 1581 }
1583   - writeString(val);
  1582 + write(val);
1584 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 1595 if (is_first) {
1597 1596 is_first = false;
1598 1597 } else {
1599   - writeStringQDF("\n");
1600   - writeStringNoQDF(" ");
  1598 + write_qdf("\n").write_no_qdf(" ");
1601 1599 }
1602   - writeString(id);
  1600 + write(id);
1603 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 1607 void
... ... @@ -1639,20 +1637,18 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1639 1637 first_obj = new_obj;
1640 1638 }
1641 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 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 1643 // For compatibility, only write the generation if non-zero. While object
1648 1644 // streams only allow objects with generation 0, if we are generating object
1649 1645 // streams, the old object could have a non-zero generation.
1650 1646 if (obj.getGen() != 0) {
1651 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 1654 offsets.push_back(m->pipeline->getCount());
... ... @@ -1698,7 +1694,7 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1698 1694 activatePipelineStack(pp_ostream, stream_buffer_pass2);
1699 1695 }
1700 1696 writeObjectStreamOffsets(offsets, first_obj);
1701   - writeString(stream_buffer_pass1);
  1697 + write(stream_buffer_pass1);
1702 1698 stream_buffer_pass1.clear();
1703 1699 stream_buffer_pass1.shrink_to_fit();
1704 1700 }
... ... @@ -1706,46 +1702,37 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1706 1702 // Write the object
1707 1703 openObject(new_stream_id);
1708 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 1706 size_t length = stream_buffer_pass2.size();
1714 1707 adjustAESStreamLength(length);
1715   - writeString(" /Length " + std::to_string(length));
1716   - writeStringQDF("\n ");
  1708 + write(" /Length ").write(length).write_qdf("\n ");
1717 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 1713 if (!object.isNull()) {
1724 1714 // If the original object has an /Extends key, preserve it.
1725 1715 QPDFObjectHandle dict = object.getDict();
1726 1716 QPDFObjectHandle extends = dict.getKey("/Extends");
1727 1717 if (extends.isIndirect()) {
1728 1718 QTC::TC("qpdf", "QPDFWriter copy Extends");
1729   - writeStringQDF("\n ");
1730   - writeString(" /Extends ");
  1719 + write_qdf("\n ").write(" /Extends ");
1731 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 1724 if (m->encryption) {
1738 1725 QTC::TC("qpdf", "QPDFWriter encrypt object stream");
1739 1726 }
1740 1727 {
1741 1728 PipelinePopper pp_enc(this);
1742 1729 pushEncryptionFilter(pp_enc);
1743   - writeString(stream_buffer_pass2);
  1730 + write(stream_buffer_pass2);
1744 1731 }
1745 1732 if (m->newline_before_endstream) {
1746   - writeString("\n");
  1733 + write("\n");
1747 1734 }
1748   - writeString("endstream");
  1735 + write("endstream");
1749 1736 m->cur_data_key.clear();
1750 1737 closeObject(new_stream_id);
1751 1738 }
... ... @@ -1755,8 +1742,8 @@ QPDFWriter::writeObject(QPDFObjectHandle object, int object_stream_index)
1755 1742 {
1756 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 1747 writeObjectStream(object);
1761 1748 return;
1762 1749 }
... ... @@ -1765,19 +1752,15 @@ QPDFWriter::writeObject(QPDFObjectHandle object, int object_stream_index)
1765 1752 auto new_id = m->obj[old_og].renumber;
1766 1753 if (m->qdf_mode) {
1767 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 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 1761 if (object_stream_index == -1) {
1779 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 1765 openObject(new_id);
1783 1766 setDataKey(new_id);
... ... @@ -1786,17 +1769,17 @@ QPDFWriter::writeObject(QPDFObjectHandle object, int object_stream_index)
1786 1769 closeObject(new_id);
1787 1770 } else {
1788 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 1776 if (m->qdf_mode) {
1794 1777 if (m->added_newline) {
1795   - writeString("%QDF: ignore_newline\n");
  1778 + write("%QDF: ignore_newline\n");
1796 1779 }
1797 1780 }
1798 1781 openObject(new_id + 1);
1799   - writeString(std::to_string(m->cur_stream_length));
  1782 + write(m->cur_stream_length);
1800 1783 closeObject(new_id + 1);
1801 1784 }
1802 1785 }
... ... @@ -2274,46 +2257,36 @@ QPDFWriter::writeEncryptionDictionary()
2274 2257 auto& enc = *m->encryption;
2275 2258 auto const V = enc.getV();
2276 2259  
2277   - writeString("<<");
  2260 + write("<<");
2278 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 2264 // The PDF spec says the /Length key is optional, but the PDF previewer on some versions of
2282 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 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 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 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 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 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 2290 closeObject(m->encryption_dict_objid);
2318 2291 }
2319 2292  
... ... @@ -2327,17 +2300,16 @@ QPDFWriter::getFinalVersion()
2327 2300 void
2328 2301 QPDFWriter::writeHeader()
2329 2302 {
2330   - writeString("%PDF-");
2331   - writeString(m->final_pdf_version);
  2303 + write("%PDF-").write(m->final_pdf_version);
2332 2304 if (m->pclm) {
2333 2305 // PCLm version
2334   - writeString("\n%PCLm 1.0\n");
  2306 + write("\n%PCLm 1.0\n");
2335 2307 } else {
2336 2308 // This string of binary characters would not be valid UTF-8, so it really should be treated
2337 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 2314 // Note: do not write extra header text here. Linearized PDFs must include the entire
2343 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 2323 std::string hint_buffer;
2352 2324 int S = 0;
2353 2325 int O = 0;
2354   - bool compressed = (m->compress_streams && !m->qdf_mode);
  2326 + bool compressed = m->compress_streams && !m->qdf_mode;
2355 2327 QPDF::Writer::generateHintStream(m->pdf, m->new_obj, m->obj, hint_buffer, S, O, compressed);
2356 2328  
2357 2329 openObject(hint_id);
... ... @@ -2359,20 +2331,17 @@ QPDFWriter::writeHintStream(int hint_id)
2359 2331  
2360 2332 size_t hlen = hint_buffer.size();
2361 2333  
2362   - writeString("<< ");
  2334 + write("<< ");
2363 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 2339 if (O) {
2369   - writeString(" /O ");
2370   - writeString(std::to_string(O));
  2340 + write(" /O ").write(O);
2371 2341 }
2372   - writeString(" /Length ");
2373 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 2346 if (m->encryption) {
2378 2347 QTC::TC("qpdf", "QPDFWriter encrypted hint stream");
... ... @@ -2381,13 +2350,13 @@ QPDFWriter::writeHintStream(int hint_id)
2381 2350 {
2382 2351 PipelinePopper pp_enc(this);
2383 2352 pushEncryptionFilter(pp_enc);
2384   - writeString(hint_buffer);
  2353 + write(hint_buffer);
2385 2354 }
2386 2355  
2387 2356 if (last_char != '\n') {
2388   - writeString("\n");
  2357 + write("\n");
2389 2358 }
2390   - writeString("endstream");
  2359 + write("endstream");
2391 2360 closeObject(hint_id);
2392 2361 }
2393 2362  
... ... @@ -2412,29 +2381,25 @@ QPDFWriter::writeXRefTable(
2412 2381 qpdf_offset_t hint_length,
2413 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 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 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 2401 writeTrailer(which, size, false, prev, linearization_pass);
2437   - writeString("\n");
  2402 + write("\n");
2438 2403 return space_before_zero;
2439 2404 }
2440 2405  
... ... @@ -2532,27 +2497,18 @@ QPDFWriter::writeXRefStream(
2532 2497 }
2533 2498  
2534 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 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 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 2512 closeObject(xref_id);
2557 2513 return space_before_zero;
2558 2514 }
... ... @@ -2726,37 +2682,28 @@ QPDFWriter::writeLinearized()
2726 2682  
2727 2683 qpdf_offset_t pos = m->pipeline->getCount();
2728 2684 openObject(lindict_id);
2729   - writeString("<<");
  2685 + write("<<");
2730 2686 if (pass == 2) {
2731 2687 std::vector<QPDFObjectHandle> const& pages = m->pdf.getAllPages();
2732 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 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 2700 closeObject(lindict_id);
2753 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 2704 // If the user supplied any additional header text, write it here after the linearization
2758 2705 // parameter dictionary.
2759   - writeString(m->extra_header_text);
  2706 + write(m->extra_header_text);
2760 2707  
2761 2708 // Part 3: first page cross reference table and trailer.
2762 2709  
... ... @@ -2793,20 +2740,19 @@ QPDFWriter::writeLinearized()
2793 2740 qpdf_offset_t endpos = m->pipeline->getCount();
2794 2741 if (pass == 1) {
2795 2742 // Pad so we have enough room for the real xref stream.
2796   - writePad(calculateXrefStreamPadding(endpos - pos));
  2743 + write(calculateXrefStreamPadding(endpos - pos), ' ');
2797 2744 first_xref_end = m->pipeline->getCount();
2798 2745 } else {
2799 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 2749 if (m->pipeline->getCount() != first_xref_end) {
2803 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 2752 std::to_string(first_xref_end) + "; endpos=" + std::to_string(endpos));
2807 2753 }
2808 2754 }
2809   - writeString("\n");
  2755 + write("\n");
2810 2756 } else {
2811 2757 writeXRefTable(
2812 2758 t_lin_first,
... ... @@ -2819,7 +2765,7 @@ QPDFWriter::writeLinearized()
2819 2765 hint_offset,
2820 2766 hint_length,
2821 2767 pass);
2822   - writeString("startxref\n0\n%%EOF\n");
  2768 + write("startxref\n0\n%%EOF\n");
2823 2769 }
2824 2770  
2825 2771 // Parts 4 through 9
... ... @@ -2837,7 +2783,7 @@ QPDFWriter::writeLinearized()
2837 2783 m->new_obj[hint_id].xref = QPDFXRefEntry(m->pipeline->getCount());
2838 2784 } else {
2839 2785 // Part 5: hint stream
2840   - writeString(hint_buffer);
  2786 + write(hint_buffer);
2841 2787 }
2842 2788 }
2843 2789 if (cur_object.getObjectID() == part6_end_marker) {
... ... @@ -2871,14 +2817,13 @@ QPDFWriter::writeLinearized()
2871 2817 if (pass == 1) {
2872 2818 // Pad so we have enough room for the real xref stream. See comments for previous
2873 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 2821 second_xref_end = m->pipeline->getCount();
2877 2822 } else {
2878 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 2828 // If this assertion fails, maybe we didn't have enough padding above.
2884 2829 if (m->pipeline->getCount() != second_xref_end + hint_length) {
... ... @@ -2890,9 +2835,7 @@ QPDFWriter::writeLinearized()
2890 2835 space_before_zero = writeXRefTable(
2891 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 2840 if (pass == 1) {
2898 2841 if (m->deterministic_id) {
... ... @@ -3038,7 +2981,7 @@ QPDFWriter::writeStandard()
3038 2981 // Start writing
3039 2982  
3040 2983 writeHeader();
3041   - writeString(m->extra_header_text);
  2984 + write(m->extra_header_text);
3042 2985  
3043 2986 if (m->pclm) {
3044 2987 enqueueObjectsPCLm();
... ... @@ -3069,9 +3012,7 @@ QPDFWriter::writeStandard()
3069 3012 writeXRefStream(
3070 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 3017 if (m->deterministic_id) {
3077 3018 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 }
... ...
pkg-test/CMakeLists.txt
1 1 cmake_minimum_required(VERSION 3.10)
2 2 project(qpdf-version LANGUAGES CXX)
  3 +set(CMAKE_CXX_STANDARD 20)
3 4 find_package(qpdf)
4 5 add_executable(qpdf-version qpdf-version.cc)
5 6 target_link_libraries(qpdf-version qpdf::libqpdf)
... ...