Add progress reporting for QPDFWriter (fixes #200)

This commit is contained in:
Jay Berkenbilt 2018-06-22 15:22:29 -04:00
parent 2a82f6e1e0
commit a433ed24f9
4 changed files with 125 additions and 2 deletions

View File

@ -1,5 +1,11 @@
2018-06-22 Jay Berkenbilt <ejb@ql.org>
* 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.

View File

@ -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<ProgressReporter>);
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<QPDFObjectHandle>& part);
@ -583,6 +602,12 @@ class QPDFWriter
std::string lin_pass1_filename;
std::map<int, int> obj_renumber_no_gen;
std::map<int, int> object_to_object_stream_no_gen;
// For progress reporting
PointerHolder<ProgressReporter> progress_reporter;
int events_expected;
int events_seen;
int next_progress_report;
};
// Keep all member variables inside the Members object, which we

View File

@ -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<ProgressReporter> pr)
{
this->m->progress_reporter = pr;
}
void
QPDFWriter::writeStandard()
{

View File

@ -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)