diff --git a/ChangeLog b/ChangeLog index be0163cb..8474f68d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 2010-10-01 Jay Berkenbilt + * include/qpdf/QPDF.hh: Add setOutputStreams method to allow + redirection of library-generated output/error to alternative + streams. + * include/qpdf/QPDF.hh: Add processMemoryFile method for processing a PDF file from a memory buffer instead of a file. diff --git a/TODO b/TODO index d3c25152..1542fbe7 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,7 @@ -General -======= +2.2.1 +===== + + * Remove cerr and cout from QPDF*.cc * QPDF::checkLinearization writes things to std::cout, which makes it hard for GUIs that want to display the result. Go through all @@ -12,6 +14,10 @@ General rethrow if needed. The old version should call the ostream version with std::cout. + +General +======= + * In general, take a fresh look at private methods to see which, if any, should be protected. diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh index 241a45de..a6ccc409 100644 --- a/include/qpdf/QPDF.hh +++ b/include/qpdf/QPDF.hh @@ -12,6 +12,7 @@ #include #include #include +#include #include @@ -61,6 +62,21 @@ class QPDF // Parameter settings + // By default, warning messages are issued to std::cerr and output + // messages printed by certain check calls are issued to + // std::cout. This method allows you to specify alternative + // streams for this purpose. Note that no normal QPDF operations + // generate output to std::cout, so for applications that just + // wish to avoid creating output and don't call any check + // functions, calling setSuppressWarnings(true) is sufficient. + // Applications that wish to present check or warning information + // to users may replace the output and error streams to capture + // the output and errors for other use. A null value for either + // stream will cause QPDF to use std::cout or std::cerr as + // appropriate. + QPDF_DLL + void setOutputStreams(std::ostream* out_stream, std::ostream* err_stream); + // If true, ignore any cross-reference streams in a hybrid file // (one that contains both cross-reference streams and // cross-reference tables). This can be useful for testing to @@ -68,7 +84,8 @@ class QPDF QPDF_DLL void setIgnoreXRefStreams(bool); - // By default, any warnings are issued to stderr as they are + // By default, any warnings are issued to std::cerr or the error + // stream specified in a call to setOutputStreams as they are // encountered. If this is called with a true value, reporting of // warnings is suppressed. You may still retrieve warnings by // calling getWarnings. @@ -88,7 +105,7 @@ class QPDF // clear the list. This method may be called even if processFile // throws an exception. Note that if setSuppressWarnings was not // called or was called with a false value, any warnings retrieved - // here will have already been issued to stderr. + // here will have already been output. QPDF_DLL std::vector getWarnings(); @@ -204,12 +221,14 @@ class QPDF // Performs various sanity checks on a linearized file. Return // true if no errors or warnings. Otherwise, return false and - // output errors and warnings to stdout. + // output errors and warnings to std::cout or the output stream + // specified in a call to setOutputStreams. QPDF_DLL bool checkLinearization(); // Calls checkLinearization() and, if possible, prints normalized - // contents of some of the hints tables to stdout. Normalization + // contents of some of the hints tables to std::cout or the output + // stream specified in a call to setOutputStreams. Normalization // includes adding min values to delta values and adjusting // offsets based on the location and size of the primary hint // stream. @@ -803,6 +822,8 @@ class QPDF bool encryption_initialized; bool ignore_xref_streams; bool suppress_warnings; + std::ostream* out_stream; + std::ostream* err_stream; bool attempt_recovery; int encryption_V; bool encrypt_metadata; diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc index 3ea0f813..122b0bdb 100644 --- a/libqpdf/QPDF.cc +++ b/libqpdf/QPDF.cc @@ -267,6 +267,8 @@ QPDF::QPDF() : encryption_initialized(false), ignore_xref_streams(false), suppress_warnings(false), + out_stream(&std::cout), + err_stream(&std::cerr), attempt_recovery(true), encryption_V(0), encrypt_metadata(true), @@ -332,6 +334,13 @@ QPDF::setIgnoreXRefStreams(bool val) this->ignore_xref_streams = val; } +void +QPDF::setOutputStreams(std::ostream* out, std::ostream* err) +{ + this->out_stream = out ? out : &std::cout; + this->err_stream = err ? err : &std::cerr; +} + void QPDF::setSuppressWarnings(bool val) { @@ -449,7 +458,8 @@ QPDF::warn(QPDFExc const& e) this->warnings.push_back(e); if (! this->suppress_warnings) { - std::cerr << "WARNING: " << this->warnings.back().what() << std::endl; + *err_stream << "WARNING: " + << this->warnings.back().what() << std::endl; } } @@ -1045,16 +1055,16 @@ QPDF::showXRefTable() { ObjGen const& og = (*iter).first; QPDFXRefEntry const& entry = (*iter).second; - std::cout << og.obj << "/" << og.gen << ": "; + *out_stream << og.obj << "/" << og.gen << ": "; switch (entry.getType()) { case 1: - std::cout << "uncompressed; offset = " << entry.getOffset(); + *out_stream << "uncompressed; offset = " << entry.getOffset(); break; case 2: - std::cout << "compressed; stream = " << entry.getObjStreamNumber() - << ", index = " << entry.getObjStreamIndex(); + *out_stream << "compressed; stream = " << entry.getObjStreamNumber() + << ", index = " << entry.getObjStreamIndex(); break; default: @@ -1062,7 +1072,7 @@ QPDF::showXRefTable() " showing xref_table"); break; } - std::cout << std::endl; + *out_stream << std::endl; } } diff --git a/libqpdf/QPDF_linearization.cc b/libqpdf/QPDF_linearization.cc index f291c201..ca3fdbae 100644 --- a/libqpdf/QPDF_linearization.cc +++ b/libqpdf/QPDF_linearization.cc @@ -64,7 +64,7 @@ QPDF::checkLinearization() } catch (QPDFExc& e) { - std::cout << e.what() << std::endl; + *out_stream << e.what() << std::endl; } return result; } @@ -351,9 +351,9 @@ QPDF::readHintStream(Pipeline& pl, off_t offset, size_t length) if ((computed_end < min_end_offset) || (computed_end > max_end_offset)) { - std::cout << "expected = " << computed_end - << "; actual = " << min_end_offset << ".." - << max_end_offset << std::endl; + *out_stream << "expected = " << computed_end + << "; actual = " << min_end_offset << ".." + << max_end_offset << std::endl; throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(), "linearization dictionary", this->file->getLastOffset(), @@ -617,7 +617,7 @@ QPDF::checkLinearizationInternal() for (std::list::iterator iter = errors.begin(); iter != errors.end(); ++iter) { - std::cout << "ERROR: " << (*iter) << std::endl; + *out_stream << "ERROR: " << (*iter) << std::endl; } } @@ -627,7 +627,7 @@ QPDF::checkLinearizationInternal() for (std::list::iterator iter = warnings.begin(); iter != warnings.end(); ++iter) { - std::cout << "WARNING: " << (*iter) << std::endl; + *out_stream << "WARNING: " << (*iter) << std::endl; } } @@ -1008,17 +1008,17 @@ QPDF::showLinearizationData() } catch (QPDFExc& e) { - std::cout << e.what() << std::endl; + *out_stream << e.what() << std::endl; } } void QPDF::dumpLinearizationDataInternal() { - std::cout << this->file->getName() << ": linearization data:" << std::endl - << std::endl; + *out_stream << this->file->getName() << ": linearization data:" << std::endl + << std::endl; - std::cout + *out_stream << "file_size: " << this->linp.file_size << std::endl << "first_page_object: " << this->linp.first_page_object << std::endl << "first_page_end: " << this->linp.first_page_end << std::endl @@ -1029,19 +1029,19 @@ QPDF::dumpLinearizationDataInternal() << "H_length: " << this->linp.H_length << std::endl << std::endl; - std::cout << "Page Offsets Hint Table" << std::endl - << std::endl; + *out_stream << "Page Offsets Hint Table" << std::endl + << std::endl; dumpHPageOffset(); - std::cout << std::endl - << "Shared Objects Hint Table" << std::endl - << std::endl; + *out_stream << std::endl + << "Shared Objects Hint Table" << std::endl + << std::endl; dumpHSharedObject(); if (this->outline_hints.nobjects > 0) { - std::cout << std::endl - << "Outlines Hint Table" << std::endl - << std::endl; + *out_stream << std::endl + << "Outlines Hint Table" << std::endl + << std::endl; dumpHGeneric(this->outline_hints); } } @@ -1064,7 +1064,7 @@ void QPDF::dumpHPageOffset() { HPageOffset& t = this->page_offset_hints; - std::cout + *out_stream << "min_nobjects: " << t.min_nobjects << std::endl << "first_page_offset: " << adjusted_offset(t.first_page_offset) @@ -1095,7 +1095,7 @@ QPDF::dumpHPageOffset() for (int i1 = 0; i1 < this->linp.npages; ++i1) { HPageOffsetEntry& pe = t.entries[i1]; - std::cout + *out_stream << "Page " << i1 << ":" << std::endl << " nobjects: " << pe.delta_nobjects + t.min_nobjects << std::endl @@ -1109,10 +1109,10 @@ QPDF::dumpHPageOffset() << " nshared_objects: " << pe.nshared_objects << std::endl; for (int i2 = 0; i2 < pe.nshared_objects; ++i2) { - std::cout << " identifier " << i2 << ": " - << pe.shared_identifiers[i2] << std::endl; - std::cout << " numerator " << i2 << ": " - << pe.shared_numerators[i2] << std::endl; + *out_stream << " identifier " << i2 << ": " + << pe.shared_identifiers[i2] << std::endl; + *out_stream << " numerator " << i2 << ": " + << pe.shared_numerators[i2] << std::endl; } } } @@ -1121,7 +1121,7 @@ void QPDF::dumpHSharedObject() { HSharedObject& t = this->shared_object_hints; - std::cout + *out_stream << "first_shared_obj: " << t.first_shared_obj << std::endl << "first_shared_offset: " << adjusted_offset(t.first_shared_offset) @@ -1140,19 +1140,19 @@ QPDF::dumpHSharedObject() for (int i = 0; i < t.nshared_total; ++i) { HSharedObjectEntry& se = t.entries[i]; - std::cout << "Shared Object " << i << ":" << std::endl; - std::cout << " group length: " - << se.delta_group_length + t.min_group_length << std::endl; + *out_stream << "Shared Object " << i << ":" << std::endl; + *out_stream << " group length: " + << se.delta_group_length + t.min_group_length << std::endl; // PDF spec says signature present nobjects_minus_one are // always 0, so print them only if they have a non-zero value. if (se.signature_present) { - std::cout << " signature present" << std::endl; + *out_stream << " signature present" << std::endl; } if (se.nobjects_minus_one != 0) { - std::cout << " nobjects: " - << se.nobjects_minus_one + 1 << std::endl; + *out_stream << " nobjects: " + << se.nobjects_minus_one + 1 << std::endl; } } } @@ -1160,7 +1160,7 @@ QPDF::dumpHSharedObject() void QPDF::dumpHGeneric(HGeneric& t) { - std::cout + *out_stream << "first_object: " << t.first_object << std::endl << "first_object_offset: " << adjusted_offset(t.first_object_offset) diff --git a/manual/qpdf-manual.xml b/manual/qpdf-manual.xml index cab6740e..773a918e 100644 --- a/manual/qpdf-manual.xml +++ b/manual/qpdf-manual.xml @@ -2074,6 +2074,61 @@ print "\n"; ChangeLog in the source distribution. + + 2.2.1: XXX + + + + + Add new method QPDF::processMemoryFile + for operating on PDF files that are loaded into memory rather + than in a file on disk. + + + + + Add new method QPDF::setOutputStreams + to replace std::cout and + std::cerr with other streams for generation + of diagnostic messages and error messages. This can be useful + for GUIs or other applications that want to capture any output + generated by the library to present to the user in some other + way. Note that QPDF does not write to + std::cout (or the specified output stream) + except where explicitly mentioned in + QPDF.hh, and that the only use of the + error stream is for warnings. Note also that output of + warnings is suppressed when + setSuppressWarnings(true) is called. + + + + + Give a warning but otherwise ignore empty PDF objects by + treating them as null. Empty object are not permitted by the + PDF specification but have been known to appear in some actual + PDF files. + + + + + Handle inline image filter abbreviations when the appear as + stream filter abbreviations. The PDF specification does not + allow use of stream filter abbreviations in this way, but + Adobe Reader and some other PDF readers accept them since they + sometimes appear incorrectly in actual PDF files. + + + + + Implement miscellaneous enhancements to + PointerHolder and + Buffer to support other changes. + + + + + 2.2.0: August 14, 2010 diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test index fd787364..8257633d 100644 --- a/qpdf/qtest/qpdf.test +++ b/qpdf/qtest/qpdf.test @@ -111,7 +111,7 @@ $td->runtest("new stream", show_ntests(); # ---------- $td->notify("--- Miscellaneous Tests ---"); -$n_tests += 26; +$n_tests += 28; $td->runtest("qpdf version", {$td->COMMAND => "qpdf --version"}, @@ -245,6 +245,18 @@ $td->runtest("empty object", $td->EXIT_STATUS => 3}, $td->NORMALIZE_NEWLINES); +$td->runtest("error/output redirection to null", + {$td->COMMAND => "test_driver 12 linearized-and-warnings.pdf"}, + {$td->FILE => "linearized-and-warnings-1.out", + $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); + +$td->runtest("error/output redirection to strings", + {$td->COMMAND => "test_driver 13 linearized-and-warnings.pdf"}, + {$td->FILE => "linearized-and-warnings-2.out", + $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); + show_ntests(); # ---------- $td->notify("--- Error Condition Tests ---"); diff --git a/qpdf/qtest/qpdf/linearized-and-warnings-1.out b/qpdf/qtest/qpdf/linearized-and-warnings-1.out new file mode 100644 index 00000000..954181f6 --- /dev/null +++ b/qpdf/qtest/qpdf/linearized-and-warnings-1.out @@ -0,0 +1,52 @@ +WARNING: linearized-and-warnings.pdf (object 2 0, file position 1117): empty object treated as null +linearized-and-warnings.pdf: linearization data: + +file_size: 1310 +first_page_object: 6 +first_page_end: 1044 +npages: 1 +xref_zero_offset: 1132 +first_page: 0 +H_offset: 528 +H_length: 118 + +Page Offsets Hint Table + +min_nobjects: 4 +first_page_offset: 646 +nbits_delta_nobjects: 0 +min_page_length: 398 +nbits_delta_page_length: 0 +min_content_offset: 0 +nbits_delta_content_offset: 0 +min_content_length: 398 +nbits_delta_content_length: 0 +nbits_nshared_objects: 0 +nbits_shared_identifier: 3 +nbits_shared_numerator: 0 +shared_denominator: 4 +Page 0: + nobjects: 4 + length: 398 + content_offset: 0 + content_length: 398 + nshared_objects: 0 + +Shared Objects Hint Table + +first_shared_obj: 0 +first_shared_offset: 0 +nshared_first_page: 4 +nshared_total: 4 +nbits_nobjects: 0 +min_group_length: 30 +nbits_delta_group_length: 7 +Shared Object 0: + group length: 143 +Shared Object 1: + group length: 118 +Shared Object 2: + group length: 30 +Shared Object 3: + group length: 107 +test 12 done diff --git a/qpdf/qtest/qpdf/linearized-and-warnings-2.out b/qpdf/qtest/qpdf/linearized-and-warnings-2.out new file mode 100644 index 00000000..d8d17b7c --- /dev/null +++ b/qpdf/qtest/qpdf/linearized-and-warnings-2.out @@ -0,0 +1,54 @@ +---output--- +linearized-and-warnings.pdf: linearization data: + +file_size: 1310 +first_page_object: 6 +first_page_end: 1044 +npages: 1 +xref_zero_offset: 1132 +first_page: 0 +H_offset: 528 +H_length: 118 + +Page Offsets Hint Table + +min_nobjects: 4 +first_page_offset: 646 +nbits_delta_nobjects: 0 +min_page_length: 398 +nbits_delta_page_length: 0 +min_content_offset: 0 +nbits_delta_content_offset: 0 +min_content_length: 398 +nbits_delta_content_length: 0 +nbits_nshared_objects: 0 +nbits_shared_identifier: 3 +nbits_shared_numerator: 0 +shared_denominator: 4 +Page 0: + nobjects: 4 + length: 398 + content_offset: 0 + content_length: 398 + nshared_objects: 0 + +Shared Objects Hint Table + +first_shared_obj: 0 +first_shared_offset: 0 +nshared_first_page: 4 +nshared_total: 4 +nbits_nobjects: 0 +min_group_length: 30 +nbits_delta_group_length: 7 +Shared Object 0: + group length: 143 +Shared Object 1: + group length: 118 +Shared Object 2: + group length: 30 +Shared Object 3: + group length: 107 +---error--- +WARNING: linearized-and-warnings.pdf (object 2 0, file position 1117): empty object treated as null +test 13 done diff --git a/qpdf/qtest/qpdf/linearized-and-warnings.pdf b/qpdf/qtest/qpdf/linearized-and-warnings.pdf new file mode 100644 index 00000000..44037566 Binary files /dev/null and b/qpdf/qtest/qpdf/linearized-and-warnings.pdf differ diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc index 3127503a..c7b6a6b4 100644 --- a/qpdf/test_driver.cc +++ b/qpdf/test_driver.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -502,6 +503,22 @@ void runtest(int n, char const* filename) std::cout << "raw stream data okay" << std::endl; } } + else if (n == 12) + { + pdf.setOutputStreams(0, 0); + pdf.showLinearizationData(); + } + else if (n == 13) + { + std::ostringstream out; + std::ostringstream err; + pdf.setOutputStreams(&out, &err); + pdf.showLinearizationData(); + std::cout << "---output---" << std::endl + << out.str() + << "---error---" << std::endl + << err.str(); + } else { throw std::runtime_error(std::string("invalid test ") +