Commit 314bd3ebe03280a2cc22eba7527e09b2a179373d

Authored by m-holger
Committed by GitHub
2 parents 65e7e3db 2033b376

Merge pull request #1406 from m-holger/count

Tune QPDFWriter writes to Pl_Count
include/qpdf/QPDF.hh
... ... @@ -879,7 +879,7 @@ class QPDF
879 879 void generateHintStream(
880 880 QPDFWriter::NewObjTable const& new_obj,
881 881 QPDFWriter::ObjTable const& obj,
882   - std::shared_ptr<Buffer>& hint_stream,
  882 + std::string& hint_stream,
883 883 int& S,
884 884 int& O,
885 885 bool compressed);
... ...
include/qpdf/QPDFWriter.hh
... ... @@ -43,6 +43,11 @@
43 43 #include <qpdf/QPDFObjectHandle.hh>
44 44 #include <qpdf/QPDFXRefEntry.hh>
45 45  
  46 +namespace qpdf::pl
  47 +{
  48 + struct Link;
  49 +}
  50 +
46 51 class QPDF;
47 52 class Pl_Count;
48 53 class Pl_MD5;
... ... @@ -462,23 +467,20 @@ class QPDFWriter
462 467 friend class QPDFWriter;
463 468  
464 469 public:
465   - PipelinePopper(QPDFWriter* qw, std::shared_ptr<Buffer>* bp = nullptr) :
466   - qw(qw),
467   - bp(bp)
  470 + PipelinePopper(QPDFWriter* qw) :
  471 + qw(qw)
468 472 {
469 473 }
470 474 ~PipelinePopper();
471 475  
472 476 private:
473   - QPDFWriter* qw;
474   - std::shared_ptr<Buffer>* bp;
475   - std::string stack_id;
  477 + QPDFWriter* qw{nullptr};
  478 + unsigned long stack_id{0};
476 479 };
477 480  
478 481 unsigned int bytesNeeded(long long n);
479 482 void writeBinary(unsigned long long val, unsigned int bytes);
480 483 void writeString(std::string_view str);
481   - void writeBuffer(std::shared_ptr<Buffer>&);
482 484 void writeStringQDF(std::string_view str);
483 485 void writeStringNoQDF(std::string_view str);
484 486 void writePad(size_t nspaces);
... ... @@ -493,7 +495,7 @@ class QPDFWriter
493 495 QPDFObjectHandle stream,
494 496 bool& compress_stream,
495 497 bool& is_metadata,
496   - std::shared_ptr<Buffer>* stream_data);
  498 + std::string* stream_data);
497 499 void unparseObject(
498 500 QPDFObjectHandle object,
499 501 int level,
... ... @@ -600,12 +602,17 @@ class QPDFWriter
600 602 // activate the pipeline stack. When the passed in PipelinePopper goes out of scope, the stack
601 603 // is popped.
602 604 Pipeline* pushPipeline(Pipeline*);
603   - void activatePipelineStack(PipelinePopper&);
  605 + void activatePipelineStack(PipelinePopper& pp, std::string& str);
  606 + void activatePipelineStack(PipelinePopper& pp, std::unique_ptr<qpdf::pl::Link> link);
  607 + void activatePipelineStack(
  608 + PipelinePopper& pp,
  609 + bool discard = false,
  610 + std::string* str = nullptr,
  611 + std::unique_ptr<qpdf::pl::Link> link = nullptr);
604 612 void initializePipelineStack(Pipeline*);
605 613  
606 614 void adjustAESStreamLength(size_t& length);
607 615 void pushEncryptionFilter(PipelinePopper&);
608   - void pushDiscardFilter(PipelinePopper&);
609 616 void pushMD5Pipeline(PipelinePopper&);
610 617 void computeDeterministicIDData();
611 618  
... ...
libqpdf/QPDFWriter.cc
... ... @@ -6,13 +6,12 @@
6 6  
7 7 #include <qpdf/MD5.hh>
8 8 #include <qpdf/Pl_AES_PDF.hh>
9   -#include <qpdf/Pl_Count.hh>
10   -#include <qpdf/Pl_Discard.hh>
11 9 #include <qpdf/Pl_Flate.hh>
12 10 #include <qpdf/Pl_MD5.hh>
13 11 #include <qpdf/Pl_PNGFilter.hh>
14 12 #include <qpdf/Pl_RC4.hh>
15 13 #include <qpdf/Pl_StdioFile.hh>
  14 +#include <qpdf/Pl_String.hh>
16 15 #include <qpdf/QIntC.hh>
17 16 #include <qpdf/QPDFObjectHandle_private.hh>
18 17 #include <qpdf/QPDFObject_private.hh>
... ... @@ -880,12 +879,6 @@ QPDFWriter::writeString(std::string_view str)
880 879 }
881 880  
882 881 void
883   -QPDFWriter::writeBuffer(std::shared_ptr<Buffer>& b)
884   -{
885   - m->pipeline->write(b->getBuffer(), b->getSize());
886   -}
887   -
888   -void
889 882 QPDFWriter::writeStringQDF(std::string_view str)
890 883 {
891 884 if (m->qdf_mode) {
... ... @@ -910,58 +903,76 @@ QPDFWriter::writePad(size_t nspaces)
910 903 Pipeline*
911 904 QPDFWriter::pushPipeline(Pipeline* p)
912 905 {
913   - qpdf_assert_debug(dynamic_cast<Pl_Count*>(p) == nullptr);
914   - m->pipeline_stack.push_back(p);
  906 + qpdf_assert_debug(!dynamic_cast<pl::Count*>(p));
  907 + m->pipeline_stack.emplace_back(p);
915 908 return p;
916 909 }
917 910  
918 911 void
919 912 QPDFWriter::initializePipelineStack(Pipeline* p)
920 913 {
921   - m->pipeline = new Pl_Count("pipeline stack base", p);
922   - m->to_delete.push_back(std::shared_ptr<Pipeline>(m->pipeline));
923   - m->pipeline_stack.push_back(m->pipeline);
  914 + m->pipeline = new pl::Count(1, p);
  915 + m->to_delete.emplace_back(std::shared_ptr<Pipeline>(m->pipeline));
  916 + m->pipeline_stack.emplace_back(m->pipeline);
924 917 }
925 918  
926 919 void
927   -QPDFWriter::activatePipelineStack(PipelinePopper& pp)
  920 +QPDFWriter::activatePipelineStack(PipelinePopper& pp, std::string& str)
928 921 {
929   - std::string stack_id("stack " + std::to_string(m->next_stack_id));
930   - auto* c = new Pl_Count(stack_id.c_str(), m->pipeline_stack.back());
931   - ++m->next_stack_id;
932   - m->pipeline_stack.push_back(c);
  922 + activatePipelineStack(pp, false, &str, nullptr);
  923 +}
  924 +
  925 +void
  926 +QPDFWriter::activatePipelineStack(PipelinePopper& pp, std::unique_ptr<pl::Link> link)
  927 +{
  928 + m->count_buffer.clear();
  929 + activatePipelineStack(pp, false, &m->count_buffer, std::move(link));
  930 +}
  931 +
  932 +void
  933 +QPDFWriter::activatePipelineStack(
  934 + PipelinePopper& pp, bool discard, std::string* str, std::unique_ptr<pl::Link> link)
  935 +{
  936 + pl::Count* c;
  937 + if (link) {
  938 + c = new pl::Count(m->next_stack_id, m->count_buffer, std::move(link));
  939 + } else if (discard) {
  940 + c = new pl::Count(m->next_stack_id, nullptr);
  941 + } else if (!str) {
  942 + c = new pl::Count(m->next_stack_id, m->pipeline_stack.back());
  943 + } else {
  944 + c = new pl::Count(m->next_stack_id, *str);
  945 + }
  946 + pp.stack_id = m->next_stack_id;
  947 + m->pipeline_stack.emplace_back(c);
933 948 m->pipeline = c;
934   - pp.stack_id = stack_id;
  949 + ++m->next_stack_id;
935 950 }
936 951  
937 952 QPDFWriter::PipelinePopper::~PipelinePopper()
938 953 {
939   - if (stack_id.empty()) {
  954 + if (!stack_id) {
940 955 return;
941 956 }
942 957 qpdf_assert_debug(qw->m->pipeline_stack.size() >= 2);
943 958 qw->m->pipeline->finish();
944   - qpdf_assert_debug(dynamic_cast<Pl_Count*>(qw->m->pipeline_stack.back()) == qw->m->pipeline);
  959 + qpdf_assert_debug(dynamic_cast<pl::Count*>(qw->m->pipeline_stack.back()) == qw->m->pipeline);
945 960 // It might be possible for this assertion to fail if writeLinearized exits by exception when
946 961 // deterministic ID, but I don't think so. As of this writing, this is the only case in which
947 962 // two dynamically allocated PipelinePopper objects ever exist at the same time, so the
948 963 // assertion will fail if they get popped out of order from automatic destruction.
949   - qpdf_assert_debug(qw->m->pipeline->getIdentifier() == stack_id);
  964 + qpdf_assert_debug(qw->m->pipeline->id() == stack_id);
950 965 delete qw->m->pipeline_stack.back();
951 966 qw->m->pipeline_stack.pop_back();
952   - while (dynamic_cast<Pl_Count*>(qw->m->pipeline_stack.back()) == nullptr) {
  967 + while (!dynamic_cast<pl::Count*>(qw->m->pipeline_stack.back())) {
953 968 Pipeline* p = qw->m->pipeline_stack.back();
954 969 if (dynamic_cast<Pl_MD5*>(p) == qw->m->md5_pipeline) {
955 970 qw->m->md5_pipeline = nullptr;
956 971 }
957 972 qw->m->pipeline_stack.pop_back();
958   - auto* buf = dynamic_cast<Pl_Buffer*>(p);
959   - if (bp && buf) {
960   - *bp = buf->getBufferSharedPointer();
961   - }
962 973 delete p;
963 974 }
964   - qw->m->pipeline = dynamic_cast<Pl_Count*>(qw->m->pipeline_stack.back());
  975 + qw->m->pipeline = dynamic_cast<pl::Count*>(qw->m->pipeline_stack.back());
965 976 }
966 977  
967 978 void
... ... @@ -1001,13 +1012,6 @@ QPDFWriter::pushEncryptionFilter(PipelinePopper&amp; pp)
1001 1012 }
1002 1013  
1003 1014 void
1004   -QPDFWriter::pushDiscardFilter(PipelinePopper& pp)
1005   -{
1006   - pushPipeline(new Pl_Discard());
1007   - activatePipelineStack(pp);
1008   -}
1009   -
1010   -void
1011 1015 QPDFWriter::pushMD5Pipeline(PipelinePopper& pp)
1012 1016 {
1013 1017 if (!m->id2.empty()) {
... ... @@ -1244,7 +1248,7 @@ QPDFWriter::willFilterStream(
1244 1248 QPDFObjectHandle stream,
1245 1249 bool& compress_stream, // out only
1246 1250 bool& is_metadata, // out only
1247   - std::shared_ptr<Buffer>* stream_data)
  1251 + std::string* stream_data)
1248 1252 {
1249 1253 compress_stream = false;
1250 1254 is_metadata = false;
... ... @@ -1290,9 +1294,12 @@ QPDFWriter::willFilterStream(
1290 1294  
1291 1295 bool filtered = false;
1292 1296 for (bool first_attempt: {true, false}) {
1293   - pushPipeline(new Pl_Buffer("stream data"));
1294   - PipelinePopper pp_stream_data(this, stream_data);
1295   - activatePipelineStack(pp_stream_data);
  1297 + PipelinePopper pp_stream_data(this);
  1298 + if (stream_data != nullptr) {
  1299 + activatePipelineStack(pp_stream_data, *stream_data);
  1300 + } else {
  1301 + activatePipelineStack(pp_stream_data, true);
  1302 + }
1296 1303 try {
1297 1304 filtered = stream.pipeStreamData(
1298 1305 m->pipeline,
... ... @@ -1320,6 +1327,9 @@ QPDFWriter::willFilterStream(
1320 1327 throw std::runtime_error(
1321 1328 "error while getting stream data for " + stream.unparse() + ": " + e.what());
1322 1329 }
  1330 + if (stream_data) {
  1331 + stream_data->clear();
  1332 + }
1323 1333 }
1324 1334 if (!filtered) {
1325 1335 compress_stream = false;
... ... @@ -1545,29 +1555,28 @@ QPDFWriter::unparseObject(
1545 1555 flags |= f_stream;
1546 1556 bool compress_stream = false;
1547 1557 bool is_metadata = false;
1548   - std::shared_ptr<Buffer> stream_data;
  1558 + std::string stream_data;
1549 1559 if (willFilterStream(object, compress_stream, is_metadata, &stream_data)) {
1550 1560 flags |= f_filtered;
1551 1561 }
1552 1562 QPDFObjectHandle stream_dict = object.getDict();
1553 1563  
1554   - m->cur_stream_length = stream_data->getSize();
  1564 + m->cur_stream_length = stream_data.size();
1555 1565 if (is_metadata && m->encrypted && (!m->encrypt_metadata)) {
1556 1566 // Don't encrypt stream data for the metadata stream
1557 1567 m->cur_data_key.clear();
1558 1568 }
1559 1569 adjustAESStreamLength(m->cur_stream_length);
1560 1570 unparseObject(stream_dict, 0, flags, m->cur_stream_length, compress_stream);
1561   - unsigned char last_char = '\0';
  1571 + char last_char = stream_data.empty() ? '\0' : stream_data.back();
1562 1572 writeString("\nstream\n");
1563 1573 {
1564 1574 PipelinePopper pp_enc(this);
1565 1575 pushEncryptionFilter(pp_enc);
1566   - writeBuffer(stream_data);
1567   - last_char = m->pipeline->getLastChar();
  1576 + writeString(stream_data);
1568 1577 }
1569 1578  
1570   - if (m->newline_before_endstream || (m->qdf_mode && (last_char != '\n'))) {
  1579 + if (m->newline_before_endstream || (m->qdf_mode && last_char != '\n')) {
1571 1580 writeString("\n");
1572 1581 m->added_newline = true;
1573 1582 } else {
... ... @@ -1643,15 +1652,14 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1643 1652  
1644 1653 // Generate stream itself. We have to do this in two passes so we can calculate offsets in the
1645 1654 // first pass.
1646   - std::shared_ptr<Buffer> stream_buffer;
  1655 + std::string stream_buffer_pass1;
  1656 + std::string stream_buffer_pass2;
1647 1657 int first_obj = -1;
1648   - bool compressed = false;
  1658 + const bool compressed = m->compress_streams && !m->qdf_mode;
1649 1659 {
1650 1660 // Pass 1
1651   - PipelinePopper pp_ostream_pass1(this, &stream_buffer);
1652   -
1653   - pushPipeline(new Pl_Buffer("object stream"));
1654   - activatePipelineStack(pp_ostream_pass1);
  1661 + PipelinePopper pp_ostream_pass1(this);
  1662 + activatePipelineStack(pp_ostream_pass1, stream_buffer_pass1);
1655 1663  
1656 1664 int count = -1;
1657 1665 for (auto const& obj: m->object_stream_to_objects[old_id]) {
... ... @@ -1695,7 +1703,7 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1695 1703 }
1696 1704 }
1697 1705 {
1698   - PipelinePopper pp_ostream(this, &stream_buffer);
  1706 + PipelinePopper pp_ostream(this);
1699 1707 // Adjust offsets to skip over comment before first object
1700 1708 first = offsets.at(0);
1701 1709 for (auto& iter: offsets) {
... ... @@ -1705,20 +1713,24 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1705 1713 // Take one pass at writing pairs of numbers so we can get their size information
1706 1714 {
1707 1715 PipelinePopper pp_discard(this);
1708   - pushDiscardFilter(pp_discard);
  1716 + activatePipelineStack(pp_discard, true);
1709 1717 writeObjectStreamOffsets(offsets, first_obj);
1710 1718 first += m->pipeline->getCount();
1711 1719 }
1712 1720  
1713 1721 // Set up a stream to write the stream data into a buffer.
1714   - Pipeline* next = pushPipeline(new Pl_Buffer("object stream"));
1715   - if (m->compress_streams && !m->qdf_mode) {
1716   - compressed = true;
1717   - next = pushPipeline(new Pl_Flate("compress object stream", next, Pl_Flate::a_deflate));
  1722 + if (compressed) {
  1723 + activatePipelineStack(
  1724 + pp_ostream,
  1725 + pl::create<Pl_Flate>(
  1726 + pl::create<pl::String>(stream_buffer_pass2), Pl_Flate::a_deflate));
  1727 + } else {
  1728 + activatePipelineStack(pp_ostream, stream_buffer_pass2);
1718 1729 }
1719   - activatePipelineStack(pp_ostream);
1720 1730 writeObjectStreamOffsets(offsets, first_obj);
1721   - writeBuffer(stream_buffer);
  1731 + writeString(stream_buffer_pass1);
  1732 + stream_buffer_pass1.clear();
  1733 + stream_buffer_pass1.shrink_to_fit();
1722 1734 }
1723 1735  
1724 1736 // Write the object
... ... @@ -1728,7 +1740,7 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1728 1740 writeStringQDF("\n ");
1729 1741 writeString(" /Type /ObjStm");
1730 1742 writeStringQDF("\n ");
1731   - size_t length = stream_buffer->getSize();
  1743 + size_t length = stream_buffer_pass2.size();
1732 1744 adjustAESStreamLength(length);
1733 1745 writeString(" /Length " + std::to_string(length));
1734 1746 writeStringQDF("\n ");
... ... @@ -1758,7 +1770,7 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1758 1770 {
1759 1771 PipelinePopper pp_enc(this);
1760 1772 pushEncryptionFilter(pp_enc);
1761   - writeBuffer(stream_buffer);
  1773 + writeString(stream_buffer_pass2);
1762 1774 }
1763 1775 if (m->newline_before_endstream) {
1764 1776 writeString("\n");
... ... @@ -2331,7 +2343,7 @@ QPDFWriter::writeHeader()
2331 2343 void
2332 2344 QPDFWriter::writeHintStream(int hint_id)
2333 2345 {
2334   - std::shared_ptr<Buffer> hint_buffer;
  2346 + std::string hint_buffer;
2335 2347 int S = 0;
2336 2348 int O = 0;
2337 2349 bool compressed = (m->compress_streams && !m->qdf_mode);
... ... @@ -2340,7 +2352,7 @@ QPDFWriter::writeHintStream(int hint_id)
2340 2352 openObject(hint_id);
2341 2353 setDataKey(hint_id);
2342 2354  
2343   - size_t hlen = hint_buffer->getSize();
  2355 + size_t hlen = hint_buffer.size();
2344 2356  
2345 2357 writeString("<< ");
2346 2358 if (compressed) {
... ... @@ -2360,12 +2372,11 @@ QPDFWriter::writeHintStream(int hint_id)
2360 2372 if (m->encrypted) {
2361 2373 QTC::TC("qpdf", "QPDFWriter encrypted hint stream");
2362 2374 }
2363   - unsigned char last_char = '\0';
  2375 + char last_char = hint_buffer.empty() ? '\0' : hint_buffer.back();
2364 2376 {
2365 2377 PipelinePopper pp_enc(this);
2366 2378 pushEncryptionFilter(pp_enc);
2367   - writeBuffer(hint_buffer);
2368   - last_char = m->pipeline->getLastChar();
  2379 + writeString(hint_buffer);
2369 2380 }
2370 2381  
2371 2382 if (last_char != '\n') {
... ... @@ -2463,21 +2474,24 @@ QPDFWriter::writeXRefStream(
2463 2474 // openObject to do it.
2464 2475 m->new_obj[xref_id].xref = QPDFXRefEntry(m->pipeline->getCount());
2465 2476  
2466   - Pipeline* p = pushPipeline(new Pl_Buffer("xref stream"));
2467   - bool compressed = false;
2468   - if (m->compress_streams && !m->qdf_mode) {
2469   - compressed = true;
2470   - if (!skip_compression) {
2471   - // Write the stream dictionary for compression but don't actually compress. This helps
2472   - // us with computation of padding for pass 1 of linearization.
2473   - p = pushPipeline(new Pl_Flate("compress xref", p, Pl_Flate::a_deflate));
2474   - }
2475   - p = pushPipeline(new Pl_PNGFilter("pngify xref", p, Pl_PNGFilter::a_encode, esize));
2476   - }
2477   - std::shared_ptr<Buffer> xref_data;
  2477 + std::string xref_data;
  2478 + const bool compressed = m->compress_streams && !m->qdf_mode;
2478 2479 {
2479   - PipelinePopper pp_xref(this, &xref_data);
2480   - activatePipelineStack(pp_xref);
  2480 + PipelinePopper pp_xref(this);
  2481 + if (compressed) {
  2482 + m->count_buffer.clear();
  2483 + auto link = pl::create<pl::String>(xref_data);
  2484 + if (!skip_compression) {
  2485 + // Write the stream dictionary for compression but don't actually compress. This
  2486 + // helps us with computation of padding for pass 1 of linearization.
  2487 + link = pl::create<Pl_Flate>(std::move(link), Pl_Flate::a_deflate);
  2488 + }
  2489 + activatePipelineStack(
  2490 + pp_xref, pl::create<Pl_PNGFilter>(std::move(link), Pl_PNGFilter::a_encode, esize));
  2491 + } else {
  2492 + activatePipelineStack(pp_xref, xref_data);
  2493 + }
  2494 +
2481 2495 for (int i = first; i <= last; ++i) {
2482 2496 QPDFXRefEntry& e = m->new_obj[i].xref;
2483 2497 switch (e.getType()) {
... ... @@ -2517,7 +2531,7 @@ QPDFWriter::writeXRefStream(
2517 2531 writeStringQDF("\n ");
2518 2532 writeString(" /Type /XRef");
2519 2533 writeStringQDF("\n ");
2520   - writeString(" /Length " + std::to_string(xref_data->getSize()));
  2534 + writeString(" /Length " + std::to_string(xref_data.size()));
2521 2535 if (compressed) {
2522 2536 writeStringQDF("\n ");
2523 2537 writeString(" /Filter /FlateDecode");
... ... @@ -2532,7 +2546,7 @@ QPDFWriter::writeXRefStream(
2532 2546 }
2533 2547 writeTrailer(which, size, true, prev, linearization_pass);
2534 2548 writeString("\nstream\n");
2535   - writeBuffer(xref_data);
  2549 + writeString(xref_data);
2536 2550 writeString("\nendstream");
2537 2551 closeObject(xref_id);
2538 2552 return space_before_zero;
... ... @@ -2674,21 +2688,21 @@ QPDFWriter::writeLinearized()
2674 2688 }
2675 2689  
2676 2690 qpdf_offset_t hint_length = 0;
2677   - std::shared_ptr<Buffer> hint_buffer;
  2691 + std::string hint_buffer;
2678 2692  
2679 2693 // Write file in two passes. Part numbers refer to PDF spec 1.4.
2680 2694  
2681 2695 FILE* lin_pass1_file = nullptr;
2682   - auto pp_pass1 = std::make_shared<PipelinePopper>(this);
2683   - auto pp_md5 = std::make_shared<PipelinePopper>(this);
2684   - for (int pass = 1; pass <= 2; ++pass) {
  2696 + auto pp_pass1 = std::make_unique<PipelinePopper>(this);
  2697 + auto pp_md5 = std::make_unique<PipelinePopper>(this);
  2698 + for (int pass: {1, 2}) {
2685 2699 if (pass == 1) {
2686 2700 if (!m->lin_pass1_filename.empty()) {
2687 2701 lin_pass1_file = QUtil::safe_fopen(m->lin_pass1_filename.c_str(), "wb");
2688 2702 pushPipeline(new Pl_StdioFile("linearization pass1", lin_pass1_file));
2689 2703 activatePipelineStack(*pp_pass1);
2690 2704 } else {
2691   - pushDiscardFilter(*pp_pass1);
  2705 + activatePipelineStack(*pp_pass1, true);
2692 2706 }
2693 2707 if (m->deterministic_id) {
2694 2708 pushMD5Pipeline(*pp_md5);
... ... @@ -2818,7 +2832,7 @@ QPDFWriter::writeLinearized()
2818 2832 m->new_obj[hint_id].xref = QPDFXRefEntry(m->pipeline->getCount());
2819 2833 } else {
2820 2834 // Part 5: hint stream
2821   - writeBuffer(hint_buffer);
  2835 + writeString(hint_buffer);
2822 2836 }
2823 2837 }
2824 2838 if (cur_object.getObjectID() == part6_end_marker) {
... ... @@ -2892,12 +2906,11 @@ QPDFWriter::writeLinearized()
2892 2906  
2893 2907 // Write hint stream to a buffer
2894 2908 {
2895   - pushPipeline(new Pl_Buffer("hint buffer"));
2896   - PipelinePopper pp_hint(this, &hint_buffer);
2897   - activatePipelineStack(pp_hint);
  2909 + PipelinePopper pp_hint(this);
  2910 + activatePipelineStack(pp_hint, hint_buffer);
2898 2911 writeHintStream(hint_id);
2899 2912 }
2900   - hint_length = QIntC::to_offset(hint_buffer->getSize());
  2913 + hint_length = QIntC::to_offset(hint_buffer.size());
2901 2914  
2902 2915 // Restore hint offset
2903 2916 m->new_obj[hint_id].xref = QPDFXRefEntry(hint_offset1);
... ... @@ -3012,9 +3025,9 @@ QPDFWriter::registerProgressReporter(std::shared_ptr&lt;ProgressReporter&gt; pr)
3012 3025 void
3013 3026 QPDFWriter::writeStandard()
3014 3027 {
3015   - auto pp_md5 = std::make_shared<PipelinePopper>(this);
  3028 + auto pp_md5 = PipelinePopper(this);
3016 3029 if (m->deterministic_id) {
3017   - pushMD5Pipeline(*pp_md5);
  3030 + pushMD5Pipeline(pp_md5);
3018 3031 }
3019 3032  
3020 3033 // Start writing
... ... @@ -3060,7 +3073,5 @@ QPDFWriter::writeStandard()
3060 3073 "qpdf",
3061 3074 "QPDFWriter standard deterministic ID",
3062 3075 m->object_stream_to_objects.empty() ? 0 : 1);
3063   - pp_md5 = nullptr;
3064   - qpdf_assert_debug(m->md5_pipeline == nullptr);
3065 3076 }
3066 3077 }
... ...
libqpdf/QPDF_linearization.cc
... ... @@ -5,9 +5,10 @@
5 5 #include <qpdf/BitStream.hh>
6 6 #include <qpdf/BitWriter.hh>
7 7 #include <qpdf/InputSource_private.hh>
  8 +#include <qpdf/Pipeline_private.hh>
8 9 #include <qpdf/Pl_Buffer.hh>
9   -#include <qpdf/Pl_Count.hh>
10 10 #include <qpdf/Pl_Flate.hh>
  11 +#include <qpdf/Pl_String.hh>
11 12 #include <qpdf/QPDFExc.hh>
12 13 #include <qpdf/QPDFLogger.hh>
13 14 #include <qpdf/QPDFWriter_private.hh>
... ... @@ -1742,7 +1743,7 @@ void
1742 1743 QPDF::generateHintStream(
1743 1744 QPDFWriter::NewObjTable const& new_obj,
1744 1745 QPDFWriter::ObjTable const& obj,
1745   - std::shared_ptr<Buffer>& hint_buffer,
  1746 + std::string& hint_buffer,
1746 1747 int& S,
1747 1748 int& O,
1748 1749 bool compressed)
... ... @@ -1754,26 +1755,21 @@ QPDF::generateHintStream(
1754 1755  
1755 1756 // Write the hint stream itself into a compressed memory buffer. Write through a counter so we
1756 1757 // can get offsets.
1757   - Pl_Buffer hint_stream("hint stream");
1758   - Pipeline* next = &hint_stream;
1759   - std::shared_ptr<Pipeline> flate;
1760   - if (compressed) {
1761   - flate =
1762   - std::make_shared<Pl_Flate>("compress hint stream", &hint_stream, Pl_Flate::a_deflate);
1763   - next = flate.get();
1764   - }
1765   - Pl_Count c("count", next);
1766   - BitWriter w(&c);
  1758 + std::string b;
  1759 + auto c = compressed
  1760 + ? std::make_unique<pl::Count>(
  1761 + 0, b, pl::create<Pl_Flate>(pl::create<pl::String>(hint_buffer), Pl_Flate::a_deflate))
  1762 + : std::make_unique<pl::Count>(0, hint_buffer);
  1763 +
  1764 + BitWriter w(c.get());
1767 1765  
1768 1766 writeHPageOffset(w);
1769   - S = toI(c.getCount());
  1767 + S = toI(c->getCount());
1770 1768 writeHSharedObject(w);
1771 1769 O = 0;
1772 1770 if (m->outline_hints.nobjects > 0) {
1773   - O = toI(c.getCount());
  1771 + O = toI(c->getCount());
1774 1772 writeHGeneric(w, m->outline_hints);
1775 1773 }
1776   - c.finish();
1777   -
1778   - hint_buffer = hint_stream.getBufferSharedPointer();
  1774 + c->finish();
1779 1775 }
... ...
libqpdf/qpdf/Pipeline_private.hh 0 โ†’ 100644
  1 +#ifndef PIPELINE_PRIVATE_HH
  2 +#define PIPELINE_PRIVATE_HH
  3 +
  4 +#include <qpdf/Pipeline.hh>
  5 +
  6 +#include <qpdf/Pl_Flate.hh>
  7 +
  8 +namespace qpdf::pl
  9 +{
  10 + struct Link
  11 + {
  12 + Link(std::unique_ptr<Link> next_link, std::unique_ptr<Pipeline> next_pl) :
  13 + next_link(std::move(next_link)),
  14 + next_pl(std::move(next_pl))
  15 + {
  16 + }
  17 +
  18 + std::unique_ptr<Link> next_link{nullptr};
  19 + std::unique_ptr<Pipeline> next_pl{nullptr};
  20 + };
  21 +
  22 + template <typename P, typename... Args>
  23 + std::unique_ptr<Link>
  24 + create(Args&&... args)
  25 + {
  26 + return std::make_unique<Link>(
  27 + nullptr, std::make_unique<P>("", nullptr, std::forward<Args>(args)...));
  28 + }
  29 +
  30 + template <typename P, typename... Args>
  31 + std::unique_ptr<Link>
  32 + create(std::unique_ptr<Link> link, Args&&... args)
  33 + {
  34 + auto* next = link->next_pl.get();
  35 + return std::make_unique<Link>(
  36 + std::move(link), std::make_unique<P>("", next, std::forward<Args>(args)...));
  37 + }
  38 +
  39 + class String final: public Pipeline
  40 + {
  41 + public:
  42 + String(char const* identifier, Pipeline*, std::string& str) :
  43 + Pipeline(identifier, nullptr),
  44 + str(str)
  45 + {
  46 + }
  47 +
  48 + ~String() final = default;
  49 +
  50 + void
  51 + write(unsigned char const* buf, size_t len) final
  52 + {
  53 + if (len) {
  54 + str.append(reinterpret_cast<char const*>(buf), len);
  55 + }
  56 + }
  57 +
  58 + void
  59 + finish() final
  60 + {
  61 + }
  62 +
  63 + private:
  64 + std::string& str;
  65 + };
  66 +
  67 + class Count final: public Pipeline
  68 + {
  69 + public:
  70 + // Count the number of characters written. If 'next' is not set, the content written will be
  71 + // discarded.
  72 + Count(unsigned long id, Pipeline* next = nullptr) :
  73 + Pipeline("", next),
  74 + id_(id),
  75 + pass_immediately_to_next(next)
  76 + {
  77 + }
  78 +
  79 + // Count the number of characters written. If 'next' is not set, the content written will be
  80 + // discarded.
  81 + Count(unsigned long id, std::unique_ptr<Link> link) :
  82 + Pipeline("", link ? link->next_pl.get() : nullptr),
  83 + link(std::move(link)),
  84 + id_(id),
  85 + pass_immediately_to_next(link)
  86 + {
  87 + }
  88 +
  89 + // Write to 'str'. If 'next' is set, 'str' will be written to 'next' when 'finish' is
  90 + // called.
  91 + Count(unsigned long id, std::string& str, std::unique_ptr<Link> link = nullptr) :
  92 + Pipeline("", link ? link->next_pl.get() : nullptr),
  93 + str(&str),
  94 + link(std::move(link)),
  95 + id_(id)
  96 + {
  97 + }
  98 +
  99 + ~Count() final = default;
  100 +
  101 + void
  102 + write(unsigned char const* buf, size_t len) final
  103 + {
  104 + if (len) {
  105 + if (str) {
  106 + str->append(reinterpret_cast<char const*>(buf), len);
  107 + return;
  108 + }
  109 + count += static_cast<qpdf_offset_t>(len);
  110 + if (pass_immediately_to_next) {
  111 + next()->write(buf, len);
  112 + }
  113 + }
  114 + }
  115 +
  116 + void
  117 + finish() final
  118 + {
  119 + if (next()) {
  120 + if (!pass_immediately_to_next) {
  121 + next()->write(reinterpret_cast<unsigned char const*>(str->data()), str->size());
  122 + }
  123 + next()->finish();
  124 + }
  125 + }
  126 +
  127 + qpdf_offset_t
  128 + getCount() const
  129 + {
  130 + return str ? static_cast<qpdf_offset_t>(str->size()) : count;
  131 + }
  132 +
  133 + unsigned long long
  134 + id() const
  135 + {
  136 + return id_;
  137 + }
  138 +
  139 + private:
  140 + qpdf_offset_t count{0};
  141 + std::string* str{nullptr};
  142 + std::unique_ptr<Link> link{nullptr};
  143 + unsigned long id_{0};
  144 + bool pass_immediately_to_next{false};
  145 + };
  146 +} // namespace qpdf::pl
  147 +
  148 +#endif // PIPELINE_PRIVATE_HH
... ...
libqpdf/qpdf/QPDFWriter_private.hh
... ... @@ -4,6 +4,7 @@
4 4 #include <qpdf/QPDFWriter.hh>
5 5  
6 6 #include <qpdf/ObjTable.hh>
  7 +#include <qpdf/Pipeline_private.hh>
7 8  
8 9 // This file is intended for inclusion by QPDFWriter, QPDF, QPDF_optimization and QPDF_linearization
9 10 // only.
... ... @@ -98,7 +99,7 @@ class QPDFWriter::Members
98 99 int encryption_dict_objid{0};
99 100 std::string cur_data_key;
100 101 std::list<std::shared_ptr<Pipeline>> to_delete;
101   - Pl_Count* pipeline{nullptr};
  102 + qpdf::pl::Count* pipeline{nullptr};
102 103 std::vector<QPDFObjectHandle> object_queue;
103 104 size_t object_queue_front{0};
104 105 QPDFWriter::ObjTable obj;
... ... @@ -112,8 +113,9 @@ class QPDFWriter::Members
112 113 std::map<QPDFObjGen, int> page_object_to_seq;
113 114 std::map<QPDFObjGen, int> contents_to_page_seq;
114 115 std::map<int, std::vector<QPDFObjGen>> object_stream_to_objects;
115   - std::list<Pipeline*> pipeline_stack;
116   - unsigned long long next_stack_id{0};
  116 + std::vector<Pipeline*> pipeline_stack;
  117 + unsigned long next_stack_id{2};
  118 + std::string count_buffer;
117 119 bool deterministic_id{false};
118 120 Pl_MD5* md5_pipeline{nullptr};
119 121 std::string deterministic_id_data;
... ...
libqpdf/qpdf/QPDF_private.hh
... ... @@ -39,7 +39,7 @@ class QPDF::Writer
39 39 QPDF& qpdf,
40 40 QPDFWriter::NewObjTable const& new_obj,
41 41 QPDFWriter::ObjTable const& obj,
42   - std::shared_ptr<Buffer>& hint_stream,
  42 + std::string& hint_stream,
43 43 int& S,
44 44 int& O,
45 45 bool compressed)
... ...
manual/release-notes.rst
... ... @@ -21,7 +21,7 @@ more detail.
21 21 integer object. Previously the method returned false if the first
22 22 dictionary object was not a linearization parameter dictionary.
23 23  
24   - = Fix parsing of object streams containing objects not seperated by
  24 + - Fix parsing of object streams containing objects not seperated by
25 25 white-space. Pre-2020 editions of the PDF specification incorrectly
26 26 stated that white-space was required between objects. qpdf relied on this
27 27 when parsing object streams.
... ... @@ -40,8 +40,8 @@ more detail.
40 40 messages and object descriptions has been refactored with some
41 41 improvement both in runtime and memory usage.
42 42  
43   - - There has been some refactoring of how object streams are written with
44   - some performance improvement.
  43 + - There has been some refactoring of QPDFWriter including how object
  44 + streams are written with some performance improvement.
45 45  
46 46 .. cSpell:ignore substract
47 47  
... ...