Commit 83144b29a9408b42b2cda4ad3eda64689cc3a7ff

Authored by m-holger
1 parent f7e07240

Refactor QPDFWriter pipeline stack logic for improved encapsulation

Encapsulated pipeline stack management into a dedicated `Pl_stack` class within `QPDFWriter::Members`. Reorganized related methods to streamline and simplify initialization and activation of pipelines, removing redundant code.
include/qpdf/QPDFWriter.hh
... ... @@ -447,6 +447,7 @@ class QPDFWriter
447 447 struct NewObject;
448 448 class ObjTable;
449 449 class NewObjTable;
  450 + class PipelinePopper;
450 451  
451 452 private:
452 453 // flags used by unparseObject
... ... @@ -458,28 +459,6 @@ class QPDFWriter
458 459  
459 460 enum trailer_e { t_normal, t_lin_first, t_lin_second };
460 461  
461   - // An reference to a PipelinePopper instance is passed into activatePipelineStack. When the
462   - // PipelinePopper goes out of scope, the pipeline stack is popped. PipelinePopper's destructor
463   - // calls finish on the current pipeline and pops the pipeline stack until the top of stack is a
464   - // previous active top of stack, and restores the pipeline to that point. It deletes any
465   - // pipelines that it pops. If the bp argument is non-null and any of the stack items are of type
466   - // Pl_Buffer, the buffer is retrieved.
467   - class PipelinePopper
468   - {
469   - friend class QPDFWriter;
470   -
471   - public:
472   - PipelinePopper(QPDFWriter* qw) :
473   - qw(qw)
474   - {
475   - }
476   - ~PipelinePopper();
477   -
478   - private:
479   - QPDFWriter* qw{nullptr};
480   - unsigned long stack_id{0};
481   - };
482   -
483 462 unsigned int bytesNeeded(long long n);
484 463 void writeBinary(unsigned long long val, unsigned int bytes);
485 464 QPDFWriter& write(std::string_view str);
... ... @@ -588,15 +567,6 @@ class QPDFWriter
588 567 // When filtering subsections, push additional pipelines to the stack. When ready to switch,
589 568 // activate the pipeline stack. When the passed in PipelinePopper goes out of scope, the stack
590 569 // is popped.
591   - Pipeline* pushPipeline(Pipeline*);
592   - void activatePipelineStack(PipelinePopper& pp, std::string& str);
593   - void activatePipelineStack(PipelinePopper& pp, std::unique_ptr<qpdf::pl::Link> link);
594   - void activatePipelineStack(
595   - PipelinePopper& pp,
596   - bool discard = false,
597   - std::string* str = nullptr,
598   - std::unique_ptr<qpdf::pl::Link> link = nullptr);
599   - void initializePipelineStack(Pipeline*);
600 570  
601 571 void adjustAESStreamLength(size_t& length);
602 572 void pushEncryptionFilter(PipelinePopper&);
... ...
libqpdf/QPDFWriter.cc
... ... @@ -47,9 +47,137 @@ QPDFWriter::FunctionProgressReporter::~FunctionProgressReporter() // NOLINT
47 47 void
48 48 QPDFWriter::FunctionProgressReporter::reportProgress(int progress)
49 49 {
50   - this->handler(progress);
  50 + handler(progress);
51 51 }
52 52  
  53 +// An reference to a PipelinePopper instance is passed into activatePipelineStack. When the
  54 +// PipelinePopper goes out of scope, the pipeline stack is popped. PipelinePopper's destructor
  55 +// calls finish on the current pipeline and pops the pipeline stack until the top of stack is a
  56 +// previous active top of stack, and restores the pipeline to that point. It deletes any
  57 +// pipelines that it pops. If the bp argument is non-null and any of the stack items are of type
  58 +// Pl_Buffer, the buffer is retrieved.
  59 +class QPDFWriter::PipelinePopper
  60 +{
  61 + public:
  62 + PipelinePopper(QPDFWriter* qw) :
  63 + qw(qw)
  64 + {
  65 + }
  66 + ~PipelinePopper();
  67 +
  68 + QPDFWriter* qw{nullptr};
  69 + unsigned long stack_id{0};
  70 +};
  71 +
  72 +namespace
  73 +{
  74 + class Pl_stack
  75 + {
  76 + using PP = QPDFWriter::PipelinePopper;
  77 +
  78 + public:
  79 + Pl_stack(pl::Count*& top, Pl_MD5*& md5_pipeline) :
  80 + top(top),
  81 + md5_pipeline(md5_pipeline)
  82 + {
  83 + }
  84 +
  85 + void
  86 + initialize(Pipeline* p)
  87 + {
  88 + top = new pl::Count(1, p);
  89 + to_delete.emplace_back(std::unique_ptr<Pipeline>(top));
  90 + stack.emplace_back(top);
  91 + }
  92 +
  93 + void
  94 + activate(PP& pp, std::string& str)
  95 + {
  96 + activate(pp, false, &str, nullptr);
  97 + }
  98 +
  99 + void
  100 + activate(PP& pp, std::unique_ptr<pl::Link> link)
  101 + {
  102 + count_buffer.clear();
  103 + activate(pp, false, &count_buffer, std::move(link));
  104 + }
  105 +
  106 + void
  107 + activate(
  108 + PP& pp,
  109 + bool discard = false,
  110 + std::string* str = nullptr,
  111 + std::unique_ptr<pl::Link> link = nullptr)
  112 + {
  113 + pl::Count* c;
  114 + if (link) {
  115 + c = new pl::Count(next_stack_id, count_buffer, std::move(link));
  116 + } else if (discard) {
  117 + c = new pl::Count(next_stack_id, nullptr);
  118 + } else if (!str) {
  119 + c = new pl::Count(next_stack_id, stack.back());
  120 + } else {
  121 + c = new pl::Count(next_stack_id, *str);
  122 + }
  123 + pp.stack_id = next_stack_id;
  124 + stack.emplace_back(c);
  125 + top = c;
  126 + ++next_stack_id;
  127 + }
  128 + Pipeline*
  129 + push(Pipeline* p)
  130 + {
  131 + qpdf_assert_debug(!dynamic_cast<pl::Count*>(p));
  132 + stack.emplace_back(p);
  133 + return p;
  134 + }
  135 +
  136 + void
  137 + pop(unsigned long stack_id)
  138 +
  139 + {
  140 + if (!stack_id) {
  141 + return;
  142 + }
  143 + qpdf_assert_debug(stack.size() >= 2);
  144 + top->finish();
  145 + qpdf_assert_debug(dynamic_cast<pl::Count*>(stack.back()) == top);
  146 + // It might be possible for this assertion to fail if writeLinearized exits by exception
  147 + // when deterministic ID, but I don't think so. As of this writing, this is the only
  148 + // case in which two dynamically allocated PipelinePopper objects ever exist at the same
  149 + // time, so the assertion will fail if they get popped out of order from automatic
  150 + // destruction.
  151 + qpdf_assert_debug(top->id() == stack_id);
  152 + delete stack.back();
  153 + stack.pop_back();
  154 + while (!dynamic_cast<pl::Count*>(stack.back())) {
  155 + Pipeline* p = stack.back();
  156 + if (dynamic_cast<Pl_MD5*>(p) == md5_pipeline) {
  157 + md5_pipeline = nullptr;
  158 + }
  159 + stack.pop_back();
  160 + delete p;
  161 + }
  162 + top = dynamic_cast<pl::Count*>(stack.back());
  163 + }
  164 +
  165 + void
  166 + clear_buffer()
  167 + {
  168 + count_buffer.clear();
  169 + }
  170 +
  171 + private:
  172 + std::vector<Pipeline*> stack;
  173 + pl::Count*& top;
  174 + Pl_MD5*& md5_pipeline;
  175 + std::vector<std::unique_ptr<Pipeline>> to_delete;
  176 + unsigned long next_stack_id{2};
  177 + std::string count_buffer;
  178 + };
  179 +} // namespace
  180 +
53 181 class QPDFWriter::Members
54 182 {
55 183 friend class QPDFWriter;
... ... @@ -116,9 +244,7 @@ class QPDFWriter::Members
116 244 std::map<QPDFObjGen, int> page_object_to_seq;
117 245 std::map<QPDFObjGen, int> contents_to_page_seq;
118 246 std::map<int, std::vector<QPDFObjGen>> object_stream_to_objects;
119   - std::vector<Pipeline*> pipeline_stack;
120   - unsigned long next_stack_id{2};
121   - std::string count_buffer;
  247 + Pl_stack pipeline_stack;
122 248 bool deterministic_id{false};
123 249 Pl_MD5* md5_pipeline{nullptr};
124 250 std::string deterministic_id_data;
... ... @@ -136,7 +262,8 @@ class QPDFWriter::Members
136 262  
137 263 QPDFWriter::Members::Members(QPDF& pdf) :
138 264 pdf(pdf),
139   - root_og(pdf.getRoot().getObjGen().isIndirect() ? pdf.getRoot().getObjGen() : QPDFObjGen(-1, 0))
  265 + root_og(pdf.getRoot().getObjGen().isIndirect() ? pdf.getRoot().getObjGen() : QPDFObjGen(-1, 0)),
  266 + pipeline_stack(pipeline, md5_pipeline)
140 267 {
141 268 }
142 269  
... ... @@ -192,7 +319,7 @@ QPDFWriter::setOutputFile(char const* description, FILE* file, bool close_file)
192 319 m->close_file = close_file;
193 320 std::shared_ptr<Pipeline> p = std::make_shared<Pl_StdioFile>("qpdf output", file);
194 321 m->to_delete.push_back(p);
195   - initializePipelineStack(p.get());
  322 + m->pipeline_stack.initialize(p.get());
196 323 }
197 324  
198 325 void
... ... @@ -201,7 +328,7 @@ QPDFWriter::setOutputMemory()
201 328 m->filename = "memory buffer";
202 329 m->buffer_pipeline = new Pl_Buffer("qpdf output");
203 330 m->to_delete.push_back(std::shared_ptr<Pipeline>(m->buffer_pipeline));
204   - initializePipelineStack(m->buffer_pipeline);
  331 + m->pipeline_stack.initialize(m->buffer_pipeline);
205 332 }
206 333  
207 334 Buffer*
... ... @@ -222,7 +349,7 @@ void
222 349 QPDFWriter::setOutputPipeline(Pipeline* p)
223 350 {
224 351 m->filename = "custom pipeline";
225   - initializePipelineStack(p);
  352 + m->pipeline_stack.initialize(p);
226 353 }
227 354  
228 355 void
... ... @@ -887,79 +1014,9 @@ QPDFWriter::write_no_qdf(Args&amp;&amp;... args)
887 1014 return *this;
888 1015 }
889 1016  
890   -Pipeline*
891   -QPDFWriter::pushPipeline(Pipeline* p)
892   -{
893   - qpdf_assert_debug(!dynamic_cast<pl::Count*>(p));
894   - m->pipeline_stack.emplace_back(p);
895   - return p;
896   -}
897   -
898   -void
899   -QPDFWriter::initializePipelineStack(Pipeline* p)
900   -{
901   - m->pipeline = new pl::Count(1, p);
902   - m->to_delete.emplace_back(std::shared_ptr<Pipeline>(m->pipeline));
903   - m->pipeline_stack.emplace_back(m->pipeline);
904   -}
905   -
906   -void
907   -QPDFWriter::activatePipelineStack(PipelinePopper& pp, std::string& str)
908   -{
909   - activatePipelineStack(pp, false, &str, nullptr);
910   -}
911   -
912   -void
913   -QPDFWriter::activatePipelineStack(PipelinePopper& pp, std::unique_ptr<pl::Link> link)
914   -{
915   - m->count_buffer.clear();
916   - activatePipelineStack(pp, false, &m->count_buffer, std::move(link));
917   -}
918   -
919   -void
920   -QPDFWriter::activatePipelineStack(
921   - PipelinePopper& pp, bool discard, std::string* str, std::unique_ptr<pl::Link> link)
922   -{
923   - pl::Count* c;
924   - if (link) {
925   - c = new pl::Count(m->next_stack_id, m->count_buffer, std::move(link));
926   - } else if (discard) {
927   - c = new pl::Count(m->next_stack_id, nullptr);
928   - } else if (!str) {
929   - c = new pl::Count(m->next_stack_id, m->pipeline_stack.back());
930   - } else {
931   - c = new pl::Count(m->next_stack_id, *str);
932   - }
933   - pp.stack_id = m->next_stack_id;
934   - m->pipeline_stack.emplace_back(c);
935   - m->pipeline = c;
936   - ++m->next_stack_id;
937   -}
938   -
939 1017 QPDFWriter::PipelinePopper::~PipelinePopper()
940 1018 {
941   - if (!stack_id) {
942   - return;
943   - }
944   - qpdf_assert_debug(qw->m->pipeline_stack.size() >= 2);
945   - qw->m->pipeline->finish();
946   - qpdf_assert_debug(dynamic_cast<pl::Count*>(qw->m->pipeline_stack.back()) == qw->m->pipeline);
947   - // It might be possible for this assertion to fail if writeLinearized exits by exception when
948   - // deterministic ID, but I don't think so. As of this writing, this is the only case in which
949   - // two dynamically allocated PipelinePopper objects ever exist at the same time, so the
950   - // assertion will fail if they get popped out of order from automatic destruction.
951   - qpdf_assert_debug(qw->m->pipeline->id() == stack_id);
952   - delete qw->m->pipeline_stack.back();
953   - qw->m->pipeline_stack.pop_back();
954   - while (!dynamic_cast<pl::Count*>(qw->m->pipeline_stack.back())) {
955   - Pipeline* p = qw->m->pipeline_stack.back();
956   - if (dynamic_cast<Pl_MD5*>(p) == qw->m->md5_pipeline) {
957   - qw->m->md5_pipeline = nullptr;
958   - }
959   - qw->m->pipeline_stack.pop_back();
960   - delete p;
961   - }
962   - qw->m->pipeline = dynamic_cast<pl::Count*>(qw->m->pipeline_stack.back());
  1019 + qw->m->pipeline_stack.pop(stack_id);
963 1020 }
964 1021  
965 1022 void
... ... @@ -991,11 +1048,11 @@ QPDFWriter::pushEncryptionFilter(PipelinePopper&amp; pp)
991 1048 QUtil::unsigned_char_pointer(m->cur_data_key),
992 1049 QIntC::to_int(m->cur_data_key.length()));
993 1050 }
994   - pushPipeline(p);
  1051 + m->pipeline_stack.push(p);
995 1052 }
996 1053 // Must call this unconditionally so we can call popPipelineStack to balance
997 1054 // pushEncryptionFilter().
998   - activatePipelineStack(pp);
  1055 + m->pipeline_stack.activate(pp);
999 1056 }
1000 1057  
1001 1058 void
... ... @@ -1012,8 +1069,8 @@ QPDFWriter::pushMD5Pipeline(PipelinePopper&amp; pp)
1012 1069 m->md5_pipeline = new Pl_MD5("qpdf md5", m->pipeline);
1013 1070 m->md5_pipeline->persistAcrossFinish(true);
1014 1071 // Special case code in popPipelineStack clears m->md5_pipeline upon deletion.
1015   - pushPipeline(m->md5_pipeline);
1016   - activatePipelineStack(pp);
  1072 + m->pipeline_stack.push(m->md5_pipeline);
  1073 + m->pipeline_stack.activate(pp);
1017 1074 }
1018 1075  
1019 1076 void
... ... @@ -1273,9 +1330,9 @@ QPDFWriter::willFilterStream(
1273 1330 for (bool first_attempt: {true, false}) {
1274 1331 PipelinePopper pp_stream_data(this);
1275 1332 if (stream_data != nullptr) {
1276   - activatePipelineStack(pp_stream_data, *stream_data);
  1333 + m->pipeline_stack.activate(pp_stream_data, *stream_data);
1277 1334 } else {
1278   - activatePipelineStack(pp_stream_data, true);
  1335 + m->pipeline_stack.activate(pp_stream_data, true);
1279 1336 }
1280 1337 try {
1281 1338 filtered = stream.pipeStreamData(
... ... @@ -1627,7 +1684,7 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1627 1684 {
1628 1685 // Pass 1
1629 1686 PipelinePopper pp_ostream_pass1(this);
1630   - activatePipelineStack(pp_ostream_pass1, stream_buffer_pass1);
  1687 + m->pipeline_stack.activate(pp_ostream_pass1, stream_buffer_pass1);
1631 1688  
1632 1689 int count = -1;
1633 1690 for (auto const& obj: m->object_stream_to_objects[old_id]) {
... ... @@ -1679,19 +1736,19 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object)
1679 1736 // Take one pass at writing pairs of numbers so we can get their size information
1680 1737 {
1681 1738 PipelinePopper pp_discard(this);
1682   - activatePipelineStack(pp_discard, true);
  1739 + m->pipeline_stack.activate(pp_discard, true);
1683 1740 writeObjectStreamOffsets(offsets, first_obj);
1684 1741 first += m->pipeline->getCount();
1685 1742 }
1686 1743  
1687 1744 // Set up a stream to write the stream data into a buffer.
1688 1745 if (compressed) {
1689   - activatePipelineStack(
  1746 + m->pipeline_stack.activate(
1690 1747 pp_ostream,
1691 1748 pl::create<Pl_Flate>(
1692 1749 pl::create<pl::String>(stream_buffer_pass2), Pl_Flate::a_deflate));
1693 1750 } else {
1694   - activatePipelineStack(pp_ostream, stream_buffer_pass2);
  1751 + m->pipeline_stack.activate(pp_ostream, stream_buffer_pass2);
1695 1752 }
1696 1753 writeObjectStreamOffsets(offsets, first_obj);
1697 1754 write(stream_buffer_pass1);
... ... @@ -2449,17 +2506,17 @@ QPDFWriter::writeXRefStream(
2449 2506 {
2450 2507 PipelinePopper pp_xref(this);
2451 2508 if (compressed) {
2452   - m->count_buffer.clear();
  2509 + m->pipeline_stack.clear_buffer();
2453 2510 auto link = pl::create<pl::String>(xref_data);
2454 2511 if (!skip_compression) {
2455 2512 // Write the stream dictionary for compression but don't actually compress. This
2456 2513 // helps us with computation of padding for pass 1 of linearization.
2457 2514 link = pl::create<Pl_Flate>(std::move(link), Pl_Flate::a_deflate);
2458 2515 }
2459   - activatePipelineStack(
  2516 + m->pipeline_stack.activate(
2460 2517 pp_xref, pl::create<Pl_PNGFilter>(std::move(link), Pl_PNGFilter::a_encode, esize));
2461 2518 } else {
2462   - activatePipelineStack(pp_xref, xref_data);
  2519 + m->pipeline_stack.activate(pp_xref, xref_data);
2463 2520 }
2464 2521  
2465 2522 for (int i = first; i <= last; ++i) {
... ... @@ -2660,10 +2717,10 @@ QPDFWriter::writeLinearized()
2660 2717 if (pass == 1) {
2661 2718 if (!m->lin_pass1_filename.empty()) {
2662 2719 lin_pass1_file = QUtil::safe_fopen(m->lin_pass1_filename.c_str(), "wb");
2663   - pushPipeline(new Pl_StdioFile("linearization pass1", lin_pass1_file));
2664   - activatePipelineStack(*pp_pass1);
  2720 + m->pipeline_stack.push(new Pl_StdioFile("linearization pass1", lin_pass1_file));
  2721 + m->pipeline_stack.activate(*pp_pass1);
2665 2722 } else {
2666   - activatePipelineStack(*pp_pass1, true);
  2723 + m->pipeline_stack.activate(*pp_pass1, true);
2667 2724 }
2668 2725 if (m->deterministic_id) {
2669 2726 pushMD5Pipeline(*pp_md5);
... ... @@ -2855,7 +2912,7 @@ QPDFWriter::writeLinearized()
2855 2912 // Write hint stream to a buffer
2856 2913 {
2857 2914 PipelinePopper pp_hint(this);
2858   - activatePipelineStack(pp_hint, hint_buffer);
  2915 + m->pipeline_stack.activate(pp_hint, hint_buffer);
2859 2916 writeHintStream(hint_id);
2860 2917 }
2861 2918 hint_length = QIntC::to_offset(hint_buffer.size());
... ...