From a433ed24f978d6e0ae1b87e997dcb511b001b12e Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Fri, 22 Jun 2018 15:22:29 -0400 Subject: [PATCH] Add progress reporting for QPDFWriter (fixes #200) --- ChangeLog | 6 ++++ include/qpdf/QPDFWriter.hh | 25 ++++++++++++++++ libqpdf/QPDFWriter.cc | 60 +++++++++++++++++++++++++++++++++++++- qpdf/qpdf.cc | 36 ++++++++++++++++++++++- 4 files changed, 125 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index dd19b0a2..018b9f59 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,11 @@ 2018-06-22 Jay Berkenbilt + * Add progress reporting to QPDFWriter. Programmatically, you can + register a progress reporter with registerProgressReporter(). From + the command line, passing --progress will give progress indicators + in increments of no less than 1% as output files are written. + Fixes #200. + * Add new method QPDF::getObjectCount(). This gives an approximate (upper bound) account of objects in the QPDF object. diff --git a/include/qpdf/QPDFWriter.hh b/include/qpdf/QPDFWriter.hh index cdd63e47..1802078a 100644 --- a/include/qpdf/QPDFWriter.hh +++ b/include/qpdf/QPDFWriter.hh @@ -76,6 +76,19 @@ class QPDFWriter QPDF_DLL ~QPDFWriter(); + class ProgressReporter + { + public: + virtual ~ProgressReporter() + { + } + + // This method is called with a value from 0 to 100 to + // indicate approximate progress through the write process. + // See registerProgressReporter. + virtual void reportProgress(int) = 0; + }; + // Setting Output. Output may be set only one time. If you don't // use the filename version of the QPDFWriter constructor, you // must call exactly one of these methods. @@ -386,6 +399,11 @@ class QPDFWriter QPDF_DLL void setPCLm(bool); + // If you want to be notified of progress, derive a class from + // ProgressReporter and override the reportProgress method. + QPDF_DLL + void registerProgressReporter(PointerHolder); + QPDF_DLL void write(); @@ -450,6 +468,7 @@ class QPDFWriter void prepareFileForWrite(); void enqueueObjectsStandard(); void enqueueObjectsPCLm(); + void indicateProgress(bool decrement, bool finished); void writeStandard(); void writeLinearized(); void enqueuePart(std::vector& part); @@ -583,6 +602,12 @@ class QPDFWriter std::string lin_pass1_filename; std::map obj_renumber_no_gen; std::map object_to_object_stream_no_gen; + + // For progress reporting + PointerHolder progress_reporter; + int events_expected; + int events_seen; + int next_progress_report; }; // Keep all member variables inside the Members object, which we diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc index 3dcff164..447b6627 100644 --- a/libqpdf/QPDFWriter.cc +++ b/libqpdf/QPDFWriter.cc @@ -62,7 +62,10 @@ QPDFWriter::Members::Members(QPDF& pdf) : added_newline(false), max_ostream_index(0), deterministic_id(false), - md5_pipeline(0) + md5_pipeline(0), + events_expected(0), + events_seen(0), + next_progress_report(0) { } @@ -1856,6 +1859,10 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object) if (pass == 1) { offsets.push_back(this->m->pipeline->getCount()); + // To avoid double-counting objects being written in + // object streams for progress reporting, decrement in + // pass 1. + indicateProgress(true, false); } writeObject(this->m->pdf.getObjectByObjGen(obj), count); @@ -1929,6 +1936,7 @@ QPDFWriter::writeObject(QPDFObjectHandle object, int object_stream_index) return; } + indicateProgress(false, false); int new_id = this->m->obj_renumber[old_og]; if (this->m->qdf_mode) { @@ -2253,6 +2261,7 @@ QPDFWriter::prepareFileForWrite() { continue; } + indicateProgress(false, false); visited.insert(node.getObjectID()); } @@ -2500,6 +2509,15 @@ QPDFWriter::write() setMinimumPDFVersion("1.5"); } + // Set up progress reporting. We spent about equal amounts of time + // preparing and writing one pass. To get a rough estimate of + // progress, we track handling of indirect objects. For linearized + // files, we write two passes. events_expected is an + // approximation, but it's good enough for progress reporting, + // which is mostly a guess anyway. + this->m->events_expected = ( + this->m->pdf.getObjectCount() * (this->m->linearized ? 3 : 2)); + prepareFileForWrite(); if (this->m->linearized) @@ -2522,6 +2540,7 @@ QPDFWriter::write() this->m->output_buffer = this->m->buffer_pipeline->getBuffer(); this->m->buffer_pipeline = 0; } + indicateProgress(false, true); } void @@ -3311,6 +3330,45 @@ QPDFWriter::enqueueObjectsPCLm() enqueueObject(trailer.getKey("/Root")); } +void +QPDFWriter::indicateProgress(bool decrement, bool finished) +{ + if (decrement) + { + --this->m->events_seen; + return; + } + + ++this->m->events_seen; + + if (! this->m->progress_reporter.getPointer()) + { + return; + } + + if (finished || (this->m->events_seen >= this->m->next_progress_report)) + { + int percentage = ( + finished + ? 100 + : this->m->next_progress_report == 0 + ? 0 + : std::min(99, 1 + ((100 * this->m->events_seen) / + this->m->events_expected))); + this->m->progress_reporter->reportProgress(percentage); + } + while (this->m->events_seen >= this->m->next_progress_report) + { + this->m->next_progress_report += (this->m->events_expected / 100); + } +} + +void +QPDFWriter::registerProgressReporter(PointerHolder pr) +{ + this->m->progress_reporter = pr; +} + void QPDFWriter::writeStandard() { diff --git a/qpdf/qpdf.cc b/qpdf/qpdf.cc index 783548ef..1684215a 100644 --- a/qpdf/qpdf.cc +++ b/qpdf/qpdf.cc @@ -60,6 +60,7 @@ struct Options decrypt(false), split_pages(0), verbose(false), + progress(false), copy_encryption(false), encryption_file(0), encryption_file_password(0), @@ -122,7 +123,8 @@ struct Options bool linearize; bool decrypt; int split_pages; - bool verbose; + bool verbose; + bool progress; bool copy_encryption; char const* encryption_file; char const* encryption_file_password; @@ -204,6 +206,29 @@ class DiscardContents: public QPDFObjectHandle::ParserCallbacks virtual void handleEOF() {} }; +class ProgressReporter: public QPDFWriter::ProgressReporter +{ + public: + ProgressReporter(char const* filename) : + filename(filename) + { + } + virtual ~ProgressReporter() + { + } + + virtual void reportProgress(int); + private: + std::string filename; +}; + +void +ProgressReporter::reportProgress(int percentage) +{ + std::cout << whoami << ": " << filename << ": write progress: " + << percentage << "%" << std::endl; +} + // Note: let's not be too noisy about documenting the fact that this // software purposely fails to enforce the distinction between user // and owner passwords. A user password is sufficient to gain full @@ -235,6 +260,7 @@ Basic Options\n\ --help show command-line argument help\n\ --password=password specify a password for accessing encrypted files\n\ --verbose provide additional informational output\n\ +--progress give progress indicators while writing output\n\ --linearize generated a linearized (web optimized) file\n\ --copy-encryption=file copy encryption parameters from specified file\n\ --encryption-file-password=password\n\ @@ -1642,6 +1668,10 @@ static void parse_options(int argc, char* argv[], Options& o) { o.verbose = true; } + else if (strcmp(arg, "progress") == 0) + { + o.progress = true; + } else if (strcmp(arg, "deterministic-id") == 0) { o.deterministic_id = true; @@ -2350,6 +2380,10 @@ static void set_writer_options(QPDF& pdf, Options& o, QPDFWriter& w) parse_version(o.force_version, version, extension_level); w.forcePDFVersion(version, extension_level); } + if (o.progress && o.outfilename) + { + w.registerProgressReporter(new ProgressReporter(o.outfilename)); + } } static void write_outfile(QPDF& pdf, Options& o)