diff --git a/include/qpdf/QPDFJob.hh b/include/qpdf/QPDFJob.hh index 45efafe..6039ca6 100644 --- a/include/qpdf/QPDFJob.hh +++ b/include/qpdf/QPDFJob.hh @@ -490,7 +490,7 @@ class QPDFJob // Output generation void doSplitPages(QPDF& pdf); - void setWriterOptions(QPDFWriter&); + void setWriterOptions(qpdf::Writer&); void setEncryptionOptions(QPDFWriter&); void maybeFixWritePassword(int R, std::string& password); void writeOutfile(QPDF& pdf); diff --git a/include/qpdf/QPDFWriter.hh b/include/qpdf/QPDFWriter.hh index c393f0d..9f884d7 100644 --- a/include/qpdf/QPDFWriter.hh +++ b/include/qpdf/QPDFWriter.hh @@ -43,6 +43,11 @@ #include #include +namespace qpdf +{ + class Writer; +} + class QPDF; // This class implements a simple writer for saving QPDF objects to new PDF files. See comments @@ -440,6 +445,8 @@ class QPDFWriter class NewObjTable; private: + friend class qpdf::Writer; + class Members; std::shared_ptr m; diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc index 3a96465..c9669b5 100644 --- a/libqpdf/QPDF.cc +++ b/libqpdf/QPDF.cc @@ -27,9 +27,8 @@ using namespace qpdf; using namespace std::literals; -using QDoc = QPDF::Doc; -using Common = QDoc::Common; -using Objects = QDoc::Objects; +using Common = impl::Doc::Common; +using Objects = impl::Doc::Objects; using Foreign = Objects::Foreign; using Streams = Objects::Streams; @@ -724,11 +723,11 @@ QPDF::getRoot() std::map QPDF::getXRefTable() { - return m->objects.getXRefTableInternal(); + return m->objects.xref_table(); } std::map const& -Objects::getXRefTableInternal() +Objects::xref_table() { if (!m->parsed) { throw std::logic_error("QPDF::getXRefTable called before parsing."); diff --git a/libqpdf/QPDFJob.cc b/libqpdf/QPDFJob.cc index d920ab0..30c0bf2 100644 --- a/libqpdf/QPDFJob.cc +++ b/libqpdf/QPDFJob.cc @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include #include @@ -30,8 +30,7 @@ using namespace qpdf; -using QDoc = QPDF::Doc; -using Pages = QDoc::Pages; +using Pages = impl::Doc::Pages; namespace { @@ -477,7 +476,7 @@ QPDFJob::writeQPDF(QPDF& pdf) if (!pdf.getWarnings().empty()) { m->warnings = true; } - if (m->warnings && !m->qcf.suppress_warnings()) { + if (m->warnings && !m->d_cfg.suppress_warnings()) { if (createsOutput()) { *m->log->getWarn() << m->message_prefix @@ -745,7 +744,8 @@ QPDFJob::doCheck(QPDF& pdf) // Write the file to nowhere, uncompressing streams. This causes full file traversal and // decoding of all streams we can decode. - QPDFWriter w(pdf); + Writer::Config cfg; + Writer w(pdf, cfg); Pl_Discard discard; w.setOutputPipeline(&discard); w.setDecodeLevel(qpdf_dl_all); @@ -804,7 +804,7 @@ QPDFJob::doShowObj(QPDF& pdf) m->log->saveToStandardOutput(true); obj.pipeStreamData( m->log->getSave().get(), - (filter && m->normalize) ? qpdf_ef_normalize : 0, + filter && m->w_cfg.normalize_content() ? qpdf_ef_normalize : 0, filter ? qpdf_dl_all : qpdf_dl_none); } } else { @@ -974,7 +974,7 @@ QPDFJob::doJSONObjects(Pipeline* p, bool& first, QPDF& pdf) p, false, first, - m->decode_level, + m->w_cfg.decode_level(), m->json_stream_data, m->json_stream_prefix, json_objects); @@ -1052,7 +1052,7 @@ QPDFJob::doJSONPages(Pipeline* p, bool& first, QPDF& pdf) j_image.addDictionaryMember("decodeparms", dp_array.getJSON(m->json_version)); j_image.addDictionaryMember( "filterable", - JSON::makeBool(image.pipeStreamData(nullptr, 0, m->decode_level, true))); + JSON::makeBool(image.pipeStreamData(nullptr, 0, m->w_cfg.decode_level(), true))); } j_page.addDictionaryMember("images", j_images); JSON j_contents = j_page.addDictionaryMember("contents", JSON::makeArray()); @@ -1569,7 +1569,7 @@ QPDFJob::doJSON(QPDF& pdf, Pipeline* p) JSON::writeDictionaryItem(p, first, "version", JSON::makeInt(m->json_version), 1); JSON j_params = JSON::makeDictionary(); std::string decode_level_str; - switch (m->decode_level) { + switch (m->w_cfg.decode_level()) { case qpdf_dl_none: decode_level_str = "none"; break; @@ -1709,7 +1709,7 @@ QPDFJob::doProcessOnce( bool main_input) { pdf = std::make_unique(); - pdf->doc().config(m->qcf.log(m->log)); + pdf->doc().config(m->d_cfg.log(m->log)); if (empty) { pdf->emptyPDF(); } else if (main_input && m->json_input) { @@ -1739,7 +1739,7 @@ QPDFJob::doProcess( // was incorrectly encoded, there's a good chance we'd succeed here. std::string ptemp; - if (password && !m->qcf.password_is_hex_key()) { + if (password && !m->d_cfg.password_is_hex_key()) { if (m->password_mode == QPDFJob::pm_hex_bytes) { // Special case: handle --password-mode=hex-bytes for input password as well as output // password @@ -1747,7 +1747,7 @@ QPDFJob::doProcess( password = ptemp.c_str(); } } - if (!password || empty || m->qcf.password_is_hex_key() || m->suppress_password_recovery) { + if (!password || empty || m->d_cfg.password_is_hex_key() || m->suppress_password_recovery) { // There is no password, or we're not doing recovery, so just do the normal processing with // the supplied password. doProcessOnce(pdf, fn, password, empty, used_for_input, main_input); @@ -2879,50 +2879,17 @@ parse_version(std::string const& full_version_string, std::string& version, int& } void -QPDFJob::setWriterOptions(QPDFWriter& w) +QPDFJob::setWriterOptions(Writer& w) { if (m->compression_level >= 0) { Pl_Flate::setCompressionLevel(m->compression_level); } - if (m->qdf_mode) { - w.setQDFMode(true); - } - if (m->preserve_unreferenced_objects) { - w.setPreserveUnreferencedObjects(true); - } - if (m->newline_before_endstream) { - w.setNewlineBeforeEndstream(true); - } - if (m->normalize_set) { - w.setContentNormalization(m->normalize); - } - if (m->stream_data_set) { - w.setStreamDataMode(m->stream_data_mode); - } - if (m->compress_streams_set) { - w.setCompressStreams(m->compress_streams); - } - if (m->recompress_flate_set) { - w.setRecompressFlate(m->recompress_flate); - } - if (m->decode_level_set) { - w.setDecodeLevel(m->decode_level); - } if (m->decrypt) { w.setPreserveEncryption(false); } - if (m->deterministic_id) { - w.setDeterministicID(true); - } - if (m->static_id) { - w.setStaticID(true); - } if (m->static_aes_iv) { w.setStaticAesIV(true); } - if (m->suppress_original_object_id) { - w.setSuppressOriginalObjectIDs(true); - } if (m->copy_encryption) { std::unique_ptr encryption_pdf; processFile( @@ -2936,15 +2903,6 @@ QPDFJob::setWriterOptions(QPDFWriter& w) if (m->encrypt) { setEncryptionOptions(w); } - if (m->linearize) { - w.setLinearization(true); - } - if (!m->linearize_pass1.empty()) { - w.setLinearizationPass1Filename(m->linearize_pass1); - } - if (m->object_stream_set) { - w.setObjectStreamMode(m->object_stream_mode); - } w.setMinimumPDFVersion(m->max_input_version); if (!m->min_version.empty()) { std::string version; @@ -2961,15 +2919,13 @@ QPDFJob::setWriterOptions(QPDFWriter& w) if (m->progress) { if (m->progress_handler) { w.registerProgressReporter( - std::shared_ptr( - new QPDFWriter::FunctionProgressReporter(m->progress_handler))); + std::make_shared(m->progress_handler)); } else { char const* outfilename = !m->outfilename.empty() ? m->outfilename.data() : "standard output"; w.registerProgressReporter( - std::shared_ptr( - // line-break - new ProgressReporter(*m->log->getInfo(), m->message_prefix, outfilename))); + std::make_shared( + *m->log->getInfo(), m->message_prefix, outfilename)); } } } @@ -3014,7 +2970,7 @@ QPDFJob::doSplitPages(QPDF& pdf) last = num_pages; } QPDF outpdf; - outpdf.doc().config(m->qcf); + outpdf.doc().config(m->d_cfg); outpdf.emptyPDF(); QPDFAcroFormDocumentHelper* out_afdh = afdh.hasAcroForm() ? &outpdf.doc().acroform() : nullptr; @@ -3052,7 +3008,8 @@ QPDFJob::doSplitPages(QPDF& pdf) if (QUtil::same_file(m->infile_nm(), outfile.data())) { throw std::runtime_error("split pages would overwrite input file with " + outfile); } - QPDFWriter w(outpdf, outfile.c_str()); + Writer w(outpdf, m->w_cfg); + w.setOutputFilename(outfile.data()); setWriterOptions(w); w.write(); doIfVerbose([&](Pipeline& v, std::string const& prefix) { @@ -3077,9 +3034,8 @@ QPDFJob::writeOutfile(QPDF& pdf) if (m->json_version) { writeJSON(pdf); } else { - // QPDFWriter must have block scope so the output file will be closed after write() - // finishes. - QPDFWriter w(pdf); + // Writer must have block scope so the output file will be closed after write() finishes. + Writer w(pdf, m->w_cfg); if (!m->outfilename.empty()) { w.setOutputFilename(m->outfilename.data()); } else { diff --git a/libqpdf/QPDFJob_config.cc b/libqpdf/QPDFJob_config.cc index de8c0a6..5dd8e29 100644 --- a/libqpdf/QPDFJob_config.cc +++ b/libqpdf/QPDFJob_config.cc @@ -68,7 +68,7 @@ QPDFJob::Config* QPDFJob::Config::check() { o.m->check = true; - o.m->qcf.check_mode(true); + o.m->d_cfg.check_mode(true); o.m->require_outfile = false; return this; } @@ -124,8 +124,7 @@ QPDFJob::Config::collate(std::string const& parameter) QPDFJob::Config* QPDFJob::Config::compressStreams(std::string const& parameter) { - o.m->compress_streams_set = true; - o.m->compress_streams = (parameter == "y"); + o.m->w_cfg.compress_streams(parameter == "y"); return this; } @@ -146,7 +145,7 @@ QPDFJob::Config::jpegQuality(std::string const& parameter) QPDFJob::Config* QPDFJob::Config::copyEncryption(std::string const& parameter) { - if (o.m->deterministic_id) { + if (o.m->w_cfg.deterministic_id()) { usage("the deterministic-id option is incompatible with encrypted output files"); } o.m->inputs.encryption_file = parameter; @@ -171,7 +170,7 @@ QPDFJob::Config::deterministicId() if (o.m->encrypt || o.m->copy_encryption) { usage("the deterministic-id option is incompatible with encrypted output files"); } - o.m->deterministic_id = true; + o.m->w_cfg.deterministic_id(true); return this; } @@ -234,7 +233,7 @@ QPDFJob::Config::generateAppearances() QPDFJob::Config* QPDFJob::Config::ignoreXrefStreams() { - o.m->qcf.ignore_xref_streams(true); + o.m->d_cfg.ignore_xref_streams(true); return this; } @@ -327,9 +326,7 @@ QPDFJob::Config::jsonOutput(std::string const& parameter) // No need to set json_stream_data_set -- that indicates explicit use of --json-stream-data. o.m->json_stream_data = qpdf_sj_inline; } - if (!o.m->decode_level_set) { - o.m->decode_level = qpdf_dl_none; - } + o.m->w_cfg.default_decode_level(qpdf_dl_none); o.m->json_keys.insert("qpdf"); return this; } @@ -373,14 +370,14 @@ QPDFJob::Config::keepInlineImages() QPDFJob::Config* QPDFJob::Config::linearize() { - o.m->linearize = true; + o.m->w_cfg.linearize(true); return this; } QPDFJob::Config* QPDFJob::Config::linearizePass1(std::string const& parameter) { - o.m->linearize_pass1 = parameter; + o.m->w_cfg.linearize_pass1(parameter); return this; } @@ -402,29 +399,28 @@ QPDFJob::Config::minVersion(std::string const& parameter) QPDFJob::Config* QPDFJob::Config::newlineBeforeEndstream() { - o.m->newline_before_endstream = true; + o.m->w_cfg.newline_before_endstream(true); return this; } QPDFJob::Config* QPDFJob::Config::noOriginalObjectIds() { - o.m->suppress_original_object_id = true; + o.m->w_cfg.no_original_object_ids(true); return this; } QPDFJob::Config* QPDFJob::Config::noWarn() { - o.m->qcf.suppress_warnings(true); + o.m->d_cfg.suppress_warnings(true); return this; } QPDFJob::Config* QPDFJob::Config::normalizeContent(std::string const& parameter) { - o.m->normalize_set = true; - o.m->normalize = (parameter == "y"); + o.m->w_cfg.normalize_content(parameter == "y"); return this; } @@ -466,14 +462,14 @@ QPDFJob::Config::password(std::string const& parameter) QPDFJob::Config* QPDFJob::Config::passwordIsHexKey() { - o.m->qcf.password_is_hex_key(true); + o.m->d_cfg.password_is_hex_key(true); return this; } QPDFJob::Config* QPDFJob::Config::preserveUnreferenced() { - o.m->preserve_unreferenced_objects = true; + o.m->w_cfg.preserve_unreferenced(true); return this; } @@ -494,7 +490,7 @@ QPDFJob::Config::progress() QPDFJob::Config* QPDFJob::Config::qdf() { - o.m->qdf_mode = true; + o.m->w_cfg.qdf(true); return this; } @@ -508,8 +504,7 @@ QPDFJob::Config::rawStreamData() QPDFJob::Config* QPDFJob::Config::recompressFlate() { - o.m->recompress_flate_set = true; - o.m->recompress_flate = true; + o.m->w_cfg.recompress_flate(true); return this; } @@ -649,7 +644,7 @@ QPDFJob::Config::staticAesIv() QPDFJob::Config* QPDFJob::Config::staticId() { - o.m->static_id = true; + o.m->w_cfg.static_id(true); return this; } @@ -663,7 +658,7 @@ QPDFJob::Config::suppressPasswordRecovery() QPDFJob::Config* QPDFJob::Config::suppressRecovery() { - o.m->qcf.surpress_recovery(true); + o.m->d_cfg.surpress_recovery(true); return this; } @@ -731,13 +726,12 @@ QPDFJob::Config::passwordMode(std::string const& parameter) QPDFJob::Config* QPDFJob::Config::streamData(std::string const& parameter) { - o.m->stream_data_set = true; if (parameter == "compress") { - o.m->stream_data_mode = qpdf_s_compress; + o.m->w_cfg.stream_data(qpdf_s_compress); } else if (parameter == "preserve") { - o.m->stream_data_mode = qpdf_s_preserve; + o.m->w_cfg.stream_data(qpdf_s_preserve); } else if (parameter == "uncompress") { - o.m->stream_data_mode = qpdf_s_uncompress; + o.m->w_cfg.stream_data(qpdf_s_uncompress); } else { usage("invalid stream-data option"); } @@ -747,15 +741,14 @@ QPDFJob::Config::streamData(std::string const& parameter) QPDFJob::Config* QPDFJob::Config::decodeLevel(std::string const& parameter) { - o.m->decode_level_set = true; if (parameter == "none") { - o.m->decode_level = qpdf_dl_none; + o.m->w_cfg.decode_level(qpdf_dl_none); } else if (parameter == "generalized") { - o.m->decode_level = qpdf_dl_generalized; + o.m->w_cfg.decode_level(qpdf_dl_generalized); } else if (parameter == "specialized") { - o.m->decode_level = qpdf_dl_specialized; + o.m->w_cfg.decode_level(qpdf_dl_specialized); } else if (parameter == "all") { - o.m->decode_level = qpdf_dl_all; + o.m->w_cfg.decode_level(qpdf_dl_all); } else { usage("invalid option"); } @@ -765,13 +758,12 @@ QPDFJob::Config::decodeLevel(std::string const& parameter) QPDFJob::Config* QPDFJob::Config::objectStreams(std::string const& parameter) { - o.m->object_stream_set = true; if (parameter == "disable") { - o.m->object_stream_mode = qpdf_o_disable; + o.m->w_cfg.object_streams(qpdf_o_disable); } else if (parameter == "preserve") { - o.m->object_stream_mode = qpdf_o_preserve; + o.m->w_cfg.object_streams(qpdf_o_preserve); } else if (parameter == "generate") { - o.m->object_stream_mode = qpdf_o_generate; + o.m->w_cfg.object_streams(qpdf_o_generate); } else { usage("invalid object stream mode"); } @@ -1111,7 +1103,7 @@ std::shared_ptr QPDFJob::Config::encrypt( int keylen, std::string const& user_password, std::string const& owner_password) { - if (o.m->deterministic_id) { + if (o.m->w_cfg.deterministic_id()) { usage("the deterministic-id option is incompatible with encrypted output files"); } o.m->keylen = keylen; diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc index 5c9c6e6..65571b8 100644 --- a/libqpdf/QPDFWriter.cc +++ b/libqpdf/QPDFWriter.cc @@ -27,8 +27,8 @@ using namespace std::literals; using namespace qpdf; -using QDoc = QPDF::Doc; -using Encryption = QDoc::Encryption; +using Encryption = impl::Doc::Encryption; +using Config = Writer::Config; QPDFWriter::ProgressReporter::~ProgressReporter() // NOLINT (modernize-use-equals-default) { @@ -262,300 +262,231 @@ Pl_stack::Popper::pop() stack = nullptr; } -// Writer class is restricted to QPDFWriter so that only it can call certain methods. -class QPDF::Doc::Writer: QPDF::Doc::Common +namespace qpdf::impl { - friend class QPDFWriter; - Writer(QPDF& qpdf) : - Common(qpdf, qpdf.doc().m), - lin(m->lin), - objects(m->objects) - { - } - - protected: - void - optimize( - QPDFWriter::ObjTable const& obj, - std::function skip_stream_parameters) - { - lin.optimize(obj, skip_stream_parameters); - } - - void - getLinearizedParts( - QPDFWriter::ObjTable const& obj, - std::vector& part4, - std::vector& part6, - std::vector& part7, - std::vector& part8, - std::vector& part9) - { - lin.getLinearizedParts(obj, part4, part6, part7, part8, part9); - } - - void - generateHintStream( - QPDFWriter::NewObjTable const& new_obj, - QPDFWriter::ObjTable const& obj, - std::string& hint_stream, - int& S, - int& O, - bool compressed) - { - lin.generateHintStream(new_obj, obj, hint_stream, S, O, compressed); - } - - std::vector - getCompressibleObjGens() - { - return objects.getCompressibleObjVector(); - } - - std::vector - getCompressibleObjSet() - { - return objects.getCompressibleObjSet(); - } - - std::map const& - getXRefTable() - { - return objects.getXRefTableInternal(); - } - - size_t - tableSize() + // Writer class is restricted to QPDFWriter so that only it can call certain methods. + class Writer: protected Doc::Common { - return qpdf.m->objects.tableSize(); - } - - QPDF::Doc::Linearization& lin; - QPDF::Doc::Objects& objects; -}; + public: + // flags used by unparseObject + static int const f_stream = 1 << 0; + static int const f_filtered = 1 << 1; + static int const f_in_ostream = 1 << 2; + static int const f_hex_string = 1 << 3; + static int const f_no_encryption = 1 << 4; + + enum trailer_e { t_normal, t_lin_first, t_lin_second }; + + Writer() = delete; + Writer(Writer const&) = delete; + Writer(Writer&&) = delete; + Writer& operator=(Writer const&) = delete; + Writer& operator=(Writer&&) = delete; + ~Writer() + { + if (file && close_file) { + fclose(file); + } + delete output_buffer; + } + Writer(QPDF& qpdf, QPDFWriter& w) : + Common(qpdf.doc()), + lin(qpdf.doc().linearization()), + cfg(true), + root_og(qpdf.getRoot().indirect() ? qpdf.getRoot().id_gen() : QPDFObjGen(-1, 0)), + pipeline_stack(pipeline) + { + } -class QPDFWriter::Members: QPDF::Doc::Writer + void write(); + std::map getWrittenXRefTable(); + void setMinimumPDFVersion(std::string const& version, int extension_level = 0); + void copyEncryptionParameters(QPDF&); + void doWriteSetup(); + void prepareFileForWrite(); + + void disableIncompatibleEncryption(int major, int minor, int extension_level); + void interpretR3EncryptionParameters( + bool allow_accessibility, + bool allow_extract, + bool allow_assemble, + bool allow_annotate_and_form, + bool allow_form_filling, + bool allow_modify_other, + qpdf_r3_print_e print, + qpdf_r3_modify_e modify); + void setEncryptionParameters(char const* user_password, char const* owner_password); + void setEncryptionMinimumVersion(); + void parseVersion(std::string const& version, int& major, int& minor) const; + int compareVersions(int major1, int minor1, int major2, int minor2) const; + void generateID(bool encrypted); + std::string getOriginalID1(); + void initializeTables(size_t extra = 0); + void preserveObjectStreams(); + void generateObjectStreams(); + void initializeSpecialStreams(); + void enqueue(QPDFObjectHandle const& object); + void enqueueObjectsStandard(); + void enqueueObjectsPCLm(); + void enqueuePart(std::vector& part); + void assignCompressedObjectNumbers(QPDFObjGen og); + Dictionary trimmed_trailer(); + + // Returns tuple + std::tuple + will_filter_stream(QPDFObjectHandle stream, std::string* stream_data); + + // Test whether stream would be filtered if it were written. + bool will_filter_stream(QPDFObjectHandle stream); + unsigned int bytesNeeded(long long n); + void writeBinary(unsigned long long val, unsigned int bytes); + Writer& write(std::string_view str); + Writer& write(size_t count, char c); + Writer& write(std::integral auto val); + Writer& write_name(std::string const& str); + Writer& write_string(std::string const& str, bool force_binary = false); + Writer& write_encrypted(std::string_view str); + + template + Writer& write_qdf(Args&&... args); + template + Writer& write_no_qdf(Args&&... args); + void writeObjectStreamOffsets(std::vector& offsets, int first_obj); + void writeObjectStream(QPDFObjectHandle object); + void writeObject(QPDFObjectHandle object, int object_stream_index = -1); + void writeTrailer( + trailer_e which, + int size, + bool xref_stream, + qpdf_offset_t prev, + int linearization_pass); + void unparseObject( + QPDFObjectHandle object, + size_t level, + int flags, + // for stream dictionaries + size_t stream_length = 0, + bool compress = false); + void unparseChild(QPDFObjectHandle const& child, size_t level, int flags); + int openObject(int objid = 0); + void closeObject(int objid); + void writeStandard(); + void writeLinearized(); + void writeEncryptionDictionary(); + void writeHeader(); + void writeHintStream(int hint_id); + qpdf_offset_t writeXRefTable(trailer_e which, int first, int last, int size); + qpdf_offset_t writeXRefTable( + trailer_e which, + int first, + int last, + int size, + // for linearization + qpdf_offset_t prev, + bool suppress_offsets, + int hint_id, + qpdf_offset_t hint_offset, + qpdf_offset_t hint_length, + int linearization_pass); + qpdf_offset_t writeXRefStream( + int objid, + int max_id, + qpdf_offset_t max_offset, + trailer_e which, + int first, + int last, + int size); + qpdf_offset_t writeXRefStream( + int objid, + int max_id, + qpdf_offset_t max_offset, + trailer_e which, + int first, + int last, + int size, + // for linearization + qpdf_offset_t prev, + int hint_id, + qpdf_offset_t hint_offset, + qpdf_offset_t hint_length, + bool skip_compression, + int linearization_pass); + + void setDataKey(int objid); + void indicateProgress(bool decrement, bool finished); + size_t calculateXrefStreamPadding(qpdf_offset_t xref_bytes); + + void adjustAESStreamLength(size_t& length); + void computeDeterministicIDData(); + + protected: + Doc::Linearization& lin; + + qpdf::Writer::Config cfg; + + QPDFObjGen root_og{-1, 0}; + char const* filename{"unspecified"}; + FILE* file{nullptr}; + bool close_file{false}; + std::unique_ptr buffer_pipeline{nullptr}; + Buffer* output_buffer{nullptr}; + + std::unique_ptr encryption; + std::string encryption_key; + + std::string id1; // for /ID key of + std::string id2; // trailer dictionary + std::string final_pdf_version; + int final_extension_level{0}; + std::string min_pdf_version; + int min_extension_level{0}; + int encryption_dict_objid{0}; + std::string cur_data_key; + std::unique_ptr file_pl; + qpdf::pl::Count* pipeline{nullptr}; + std::vector object_queue; + size_t object_queue_front{0}; + QPDFWriter::ObjTable obj; + QPDFWriter::NewObjTable new_obj; + int next_objid{1}; + int cur_stream_length_id{0}; + size_t cur_stream_length{0}; + bool added_newline{false}; + size_t max_ostream_index{0}; + std::set normalized_streams; + std::map page_object_to_seq; + std::map contents_to_page_seq; + std::map> object_stream_to_objects; + Pl_stack pipeline_stack; + std::string deterministic_id_data; + bool did_write_setup{false}; + + // For progress reporting + std::shared_ptr progress_reporter; + int events_expected{0}; + int events_seen{0}; + int next_progress_report{0}; + }; // class qpdf::impl::Writer + +} // namespace qpdf::impl + +class QPDFWriter::Members: impl::Writer { friend class QPDFWriter; + friend class qpdf::Writer; public: - // flags used by unparseObject - static int const f_stream = 1 << 0; - static int const f_filtered = 1 << 1; - static int const f_in_ostream = 1 << 2; - static int const f_hex_string = 1 << 3; - static int const f_no_encryption = 1 << 4; - - enum trailer_e { t_normal, t_lin_first, t_lin_second }; - - Members(QPDFWriter& w, QPDF& pdf) : - QPDF::Doc::Writer(pdf), - w(w), - root_og( - qpdf.getRoot().getObjGen().isIndirect() ? qpdf.getRoot().getObjGen() - : QPDFObjGen(-1, 0)), - pipeline_stack(pipeline) + Members(QPDFWriter& w, QPDF& qpdf) : + impl::Writer(qpdf, w) { } - - Members(Members const&) = delete; - - ~Members() - { - if (file && close_file) { - fclose(file); - } - delete output_buffer; - } - - void write(); - std::map getWrittenXRefTable(); - void setMinimumPDFVersion(std::string const& version, int extension_level); - void copyEncryptionParameters(QPDF&); - void doWriteSetup(); - void prepareFileForWrite(); - - void disableIncompatibleEncryption(int major, int minor, int extension_level); - void interpretR3EncryptionParameters( - bool allow_accessibility, - bool allow_extract, - bool allow_assemble, - bool allow_annotate_and_form, - bool allow_form_filling, - bool allow_modify_other, - qpdf_r3_print_e print, - qpdf_r3_modify_e modify); - void setEncryptionParameters(char const* user_password, char const* owner_password); - void setEncryptionMinimumVersion(); - void parseVersion(std::string const& version, int& major, int& minor) const; - int compareVersions(int major1, int minor1, int major2, int minor2) const; - void generateID(bool encrypted); - std::string getOriginalID1(); - void initializeTables(size_t extra = 0); - void preserveObjectStreams(); - void generateObjectStreams(); - void initializeSpecialStreams(); - void enqueue(QPDFObjectHandle const& object); - void enqueueObjectsStandard(); - void enqueueObjectsPCLm(); - void enqueuePart(std::vector& part); - void assignCompressedObjectNumbers(QPDFObjGen og); - Dictionary trimmed_trailer(); - - // Returns tuple - std::tuple - will_filter_stream(QPDFObjectHandle stream, std::string* stream_data); - - // Test whether stream would be filtered if it were written. - bool will_filter_stream(QPDFObjectHandle stream); - unsigned int bytesNeeded(long long n); - void writeBinary(unsigned long long val, unsigned int bytes); - Members& write(std::string_view str); - Members& write(size_t count, char c); - Members& write(std::integral auto val); - Members& write_name(std::string const& str); - Members& write_string(std::string const& str, bool force_binary = false); - Members& write_encrypted(std::string_view str); - - template - Members& write_qdf(Args&&... args); - template - Members& write_no_qdf(Args&&... args); - void writeObjectStreamOffsets(std::vector& offsets, int first_obj); - void writeObjectStream(QPDFObjectHandle object); - void writeObject(QPDFObjectHandle object, int object_stream_index = -1); - void writeTrailer( - trailer_e which, int size, bool xref_stream, qpdf_offset_t prev, int linearization_pass); - void unparseObject( - QPDFObjectHandle object, - size_t level, - int flags, - // for stream dictionaries - size_t stream_length = 0, - bool compress = false); - void unparseChild(QPDFObjectHandle const& child, size_t level, int flags); - int openObject(int objid = 0); - void closeObject(int objid); - void writeStandard(); - void writeLinearized(); - void writeEncryptionDictionary(); - void writeHeader(); - void writeHintStream(int hint_id); - qpdf_offset_t writeXRefTable(trailer_e which, int first, int last, int size); - qpdf_offset_t writeXRefTable( - trailer_e which, - int first, - int last, - int size, - // for linearization - qpdf_offset_t prev, - bool suppress_offsets, - int hint_id, - qpdf_offset_t hint_offset, - qpdf_offset_t hint_length, - int linearization_pass); - qpdf_offset_t writeXRefStream( - int objid, - int max_id, - qpdf_offset_t max_offset, - trailer_e which, - int first, - int last, - int size); - qpdf_offset_t writeXRefStream( - int objid, - int max_id, - qpdf_offset_t max_offset, - trailer_e which, - int first, - int last, - int size, - // for linearization - qpdf_offset_t prev, - int hint_id, - qpdf_offset_t hint_offset, - qpdf_offset_t hint_length, - bool skip_compression, - int linearization_pass); - - void setDataKey(int objid); - void indicateProgress(bool decrement, bool finished); - size_t calculateXrefStreamPadding(qpdf_offset_t xref_bytes); - - void adjustAESStreamLength(size_t& length); - void computeDeterministicIDData(); - - private: - QPDFWriter& w; - QPDFObjGen root_og{-1, 0}; - char const* filename{"unspecified"}; - FILE* file{nullptr}; - bool close_file{false}; - std::unique_ptr buffer_pipeline{nullptr}; - Buffer* output_buffer{nullptr}; - bool normalize_content_set{false}; - bool normalize_content{false}; - bool compress_streams{true}; - bool compress_streams_set{false}; - qpdf_stream_decode_level_e stream_decode_level{qpdf_dl_generalized}; - bool stream_decode_level_set{false}; - bool recompress_flate{false}; - bool qdf_mode{false}; - bool preserve_unreferenced_objects{false}; - bool newline_before_endstream{false}; - bool static_id{false}; - bool suppress_original_object_ids{false}; - bool direct_stream_lengths{true}; - bool preserve_encryption{true}; - bool linearized{false}; - bool pclm{false}; - qpdf_object_stream_e object_stream_mode{qpdf_o_preserve}; - - std::unique_ptr encryption; - std::string encryption_key; - bool encrypt_use_aes{false}; - - std::string id1; // for /ID key of - std::string id2; // trailer dictionary - std::string final_pdf_version; - int final_extension_level{0}; - std::string min_pdf_version; - int min_extension_level{0}; - std::string forced_pdf_version; - int forced_extension_level{0}; - std::string extra_header_text; - int encryption_dict_objid{0}; - std::string cur_data_key; - std::unique_ptr file_pl; - qpdf::pl::Count* pipeline{nullptr}; - std::vector object_queue; - size_t object_queue_front{0}; - QPDFWriter::ObjTable obj; - QPDFWriter::NewObjTable new_obj; - int next_objid{1}; - int cur_stream_length_id{0}; - size_t cur_stream_length{0}; - bool added_newline{false}; - size_t max_ostream_index{0}; - std::set normalized_streams; - std::map page_object_to_seq; - std::map contents_to_page_seq; - std::map> object_stream_to_objects; - Pl_stack pipeline_stack; - bool deterministic_id{false}; - std::string deterministic_id_data; - bool did_write_setup{false}; - - // For linearization only - std::string lin_pass1_filename; - - // For progress reporting - std::shared_ptr progress_reporter; - int events_expected{0}; - int events_seen{0}; - int next_progress_report{0}; }; +qpdf::Writer::Writer(QPDF& qpdf, Config cfg) : + QPDFWriter(qpdf) +{ + m->cfg = cfg; +} QPDFWriter::QPDFWriter(QPDF& pdf) : m(std::make_shared(*this, pdf)) { @@ -632,75 +563,129 @@ QPDFWriter::setOutputPipeline(Pipeline* p) void QPDFWriter::setObjectStreamMode(qpdf_object_stream_e mode) { - m->object_stream_mode = mode; + m->cfg.object_streams(mode); } void QPDFWriter::setStreamDataMode(qpdf_stream_data_e mode) { + m->cfg.stream_data(mode); +} + +Config& +Config::stream_data(qpdf_stream_data_e mode) +{ switch (mode) { case qpdf_s_uncompress: - m->stream_decode_level = std::max(qpdf_dl_generalized, m->stream_decode_level); - m->compress_streams = false; - break; + decode_level(std::max(qpdf_dl_generalized, decode_level_)); + compress_streams(false); + return *this; case qpdf_s_preserve: - m->stream_decode_level = qpdf_dl_none; - m->compress_streams = false; - break; + decode_level(qpdf_dl_none); + compress_streams(false); + return *this; case qpdf_s_compress: - m->stream_decode_level = std::max(qpdf_dl_generalized, m->stream_decode_level); - m->compress_streams = true; - break; + decode_level(std::max(qpdf_dl_generalized, decode_level_)); + compress_streams(true); } - m->stream_decode_level_set = true; - m->compress_streams_set = true; + return *this; } void QPDFWriter::setCompressStreams(bool val) { - m->compress_streams = val; - m->compress_streams_set = true; + m->cfg.compress_streams(val); +} + +Config& +Config::compress_streams(bool val) +{ + if (pclm_) { + usage("compress_streams cannot be set when pclm is set"); + return *this; + } + compress_streams_set_ = true; + compress_streams_ = val; + return *this; } void QPDFWriter::setDecodeLevel(qpdf_stream_decode_level_e val) { - m->stream_decode_level = val; - m->stream_decode_level_set = true; + m->cfg.decode_level(val); +} + +Config& +Config::decode_level(qpdf_stream_decode_level_e val) +{ + if (pclm_) { + usage("stream_decode_level cannot be set when pclm is set"); + return *this; + } + decode_level_set_ = true; + decode_level_ = val; + return *this; } void QPDFWriter::setRecompressFlate(bool val) { - m->recompress_flate = val; + m->cfg.recompress_flate(val); } void QPDFWriter::setContentNormalization(bool val) { - m->normalize_content_set = true; - m->normalize_content = val; + m->cfg.normalize_content(val); } void QPDFWriter::setQDFMode(bool val) { - m->qdf_mode = val; + m->cfg.qdf(val); +} + +Config& +Config::qdf(bool val) +{ + if (pclm_ || linearize_) { + usage("qdf cannot be set when linearize or pclm are set"); + } + if (preserve_encryption_) { + usage("preserve_encryption cannot be set when qdf is set"); + } + qdf_ = val; + if (val) { + if (!normalize_content_set_) { + normalize_content(true); + } + if (!compress_streams_set_) { + compress_streams(false); + } + if (!decode_level_set_) { + decode_level(qpdf_dl_generalized); + } + preserve_encryption_ = false; + // Generate indirect stream lengths for qdf mode since fix-qdf uses them for storing + // recomputed stream length data. Certain streams such as object streams, xref streams, and + // hint streams always get direct stream lengths. + direct_stream_lengths_ = false; + } + return *this; } void QPDFWriter::setPreserveUnreferencedObjects(bool val) { - m->preserve_unreferenced_objects = val; + m->cfg.preserve_unreferenced(val); } void QPDFWriter::setNewlineBeforeEndstream(bool val) { - m->newline_before_endstream = val; + m->cfg.newline_before_endstream(val); } void @@ -710,7 +695,7 @@ QPDFWriter::setMinimumPDFVersion(std::string const& version, int extension_level } void -QPDFWriter::Members::setMinimumPDFVersion(std::string const& version, int extension_level) +impl::Writer::setMinimumPDFVersion(std::string const& version, int extension_level) { bool set_version = false; bool set_extension_level = false; @@ -756,31 +741,37 @@ QPDFWriter::setMinimumPDFVersion(PDFVersion const& v) void QPDFWriter::forcePDFVersion(std::string const& version, int extension_level) { - m->forced_pdf_version = version; - m->forced_extension_level = extension_level; + m->cfg.forced_pdf_version(version, extension_level); } void QPDFWriter::setExtraHeaderText(std::string const& text) { - m->extra_header_text = text; - if (!m->extra_header_text.empty() && *m->extra_header_text.rbegin() != '\n') { - m->extra_header_text += "\n"; + m->cfg.extra_header_text(text); +} + +Config& +Config::extra_header_text(std::string const& val) +{ + extra_header_text_ = val; + if (!extra_header_text_.empty() && extra_header_text_.back() != '\n') { + extra_header_text_ += "\n"; } else { QTC::TC("qpdf", "QPDFWriter extra header text no newline"); } + return *this; } void QPDFWriter::setStaticID(bool val) { - m->static_id = val; + m->cfg.static_id(val); } void QPDFWriter::setDeterministicID(bool val) { - m->deterministic_id = val; + m->cfg.deterministic_id(val); } void @@ -794,37 +785,61 @@ QPDFWriter::setStaticAesIV(bool val) void QPDFWriter::setSuppressOriginalObjectIDs(bool val) { - m->suppress_original_object_ids = val; + m->cfg.no_original_object_ids(val); } void QPDFWriter::setPreserveEncryption(bool val) { - m->preserve_encryption = val; + m->cfg.preserve_encryption(val); } void QPDFWriter::setLinearization(bool val) { - m->linearized = val; - if (val) { - m->pclm = false; + m->cfg.linearize(val); +} + +Config& +Config::linearize(bool val) +{ + if (pclm_ || qdf_) { + usage("linearize cannot be set when qdf or pclm are set"); + return *this; } + linearize_ = val; + return *this; } void QPDFWriter::setLinearizationPass1Filename(std::string const& filename) { - m->lin_pass1_filename = filename; + m->cfg.linearize_pass1(filename); } void QPDFWriter::setPCLm(bool val) { - m->pclm = val; + m->cfg.pclm(val); +} + +Config& +Config::pclm(bool val) +{ + if (decode_level_set_ || compress_streams_set_ || linearize_) { + usage( + "pclm cannot be set when stream_decode_level, compress_streams, linearize or qdf are " + "set"); + return *this; + } + pclm_ = val; if (val) { - m->linearized = false; + decode_level_ = qpdf_dl_none; + compress_streams_ = false; + linearize_ = false; } + + return *this; } void @@ -892,7 +907,7 @@ QPDFWriter::setR4EncryptionParametersInsecure( bool use_aes) { m->encryption = std::make_unique(4, 4, 16, encrypt_metadata); - m->encrypt_use_aes = use_aes; + m->cfg.encrypt_use_aes(use_aes); m->interpretR3EncryptionParameters( allow_accessibility, allow_extract, @@ -919,7 +934,7 @@ QPDFWriter::setR5EncryptionParameters( bool encrypt_metadata) { m->encryption = std::make_unique(5, 5, 32, encrypt_metadata); - m->encrypt_use_aes = true; + m->cfg.encrypt_use_aes(true); m->interpretR3EncryptionParameters( allow_accessibility, allow_extract, @@ -955,12 +970,12 @@ QPDFWriter::setR6EncryptionParameters( allow_modify_other, print, qpdf_r3m_all); - m->encrypt_use_aes = true; + m->cfg.encrypt_use_aes(true); m->setEncryptionParameters(user_password, owner_password); } void -QPDFWriter::Members::interpretR3EncryptionParameters( +impl::Writer::interpretR3EncryptionParameters( bool allow_accessibility, bool allow_extract, bool allow_assemble, @@ -1059,7 +1074,7 @@ QPDFWriter::Members::interpretR3EncryptionParameters( } void -QPDFWriter::Members::setEncryptionParameters(char const* user_password, char const* owner_password) +impl::Writer::setEncryptionParameters(char const* user_password, char const* owner_password) { generateID(true); encryption->setId1(id1); @@ -1074,9 +1089,9 @@ QPDFWriter::copyEncryptionParameters(QPDF& qpdf) } void -QPDFWriter::Members::copyEncryptionParameters(QPDF& qpdf) +impl::Writer::copyEncryptionParameters(QPDF& qpdf) { - preserve_encryption = false; + cfg.preserve_encryption(false); QPDFObjectHandle trailer = qpdf.getTrailer(); if (trailer.hasKey("/Encrypt")) { generateID(true); @@ -1096,10 +1111,10 @@ QPDFWriter::Members::copyEncryptionParameters(QPDF& qpdf) // Acrobat doesn't create files with V >= 4 that don't use AES, and the logic of // figuring out whether AES is used or not is complicated with /StmF, /StrF, and /EFF // all potentially having different values. - encrypt_use_aes = true; + cfg.encrypt_use_aes(true); } QTC::TC("qpdf", "QPDFWriter copy encrypt metadata", encrypt_metadata ? 0 : 1); - QTC::TC("qpdf", "QPDFWriter copy use_aes", encrypt_use_aes ? 0 : 1); + QTC::TC("qpdf", "QPDFWriter copy use_aes", cfg.encrypt_use_aes() ? 0 : 1); encryption = std::make_unique( V, @@ -1120,7 +1135,7 @@ QPDFWriter::Members::copyEncryptionParameters(QPDF& qpdf) } void -QPDFWriter::Members::disableIncompatibleEncryption(int major, int minor, int extension_level) +impl::Writer::disableIncompatibleEncryption(int major, int minor, int extension_level) { if (!encryption) { return; @@ -1140,7 +1155,7 @@ QPDFWriter::Members::disableIncompatibleEncryption(int major, int minor, int ext encryption = nullptr; } } else if (compareVersions(major, minor, 1, 6) < 0) { - if (encrypt_use_aes) { + if (cfg.encrypt_use_aes()) { encryption = nullptr; } } else if ( @@ -1157,7 +1172,7 @@ QPDFWriter::Members::disableIncompatibleEncryption(int major, int minor, int ext } void -QPDFWriter::Members::parseVersion(std::string const& version, int& major, int& minor) const +impl::Writer::parseVersion(std::string const& version, int& major, int& minor) const { major = QUtil::string_to_int(version.c_str()); minor = 0; @@ -1174,7 +1189,7 @@ QPDFWriter::Members::parseVersion(std::string const& version, int& major, int& m } int -QPDFWriter::Members::compareVersions(int major1, int minor1, int major2, int minor2) const +impl::Writer::compareVersions(int major1, int minor1, int major2, int minor2) const { if (major1 < major2) { return -1; @@ -1189,33 +1204,38 @@ QPDFWriter::Members::compareVersions(int major1, int minor1, int major2, int min } void -QPDFWriter::Members::setEncryptionMinimumVersion() +impl::Writer::setEncryptionMinimumVersion() { auto const R = encryption->getR(); if (R >= 6) { - w.setMinimumPDFVersion("1.7", 8); + setMinimumPDFVersion("1.7", 8); } else if (R == 5) { - w.setMinimumPDFVersion("1.7", 3); + setMinimumPDFVersion("1.7", 3); } else if (R == 4) { - w.setMinimumPDFVersion(encrypt_use_aes ? "1.6" : "1.5"); + setMinimumPDFVersion(cfg.encrypt_use_aes() ? "1.6" : "1.5"); } else if (R == 3) { - w.setMinimumPDFVersion("1.4"); + setMinimumPDFVersion("1.4"); } else { - w.setMinimumPDFVersion("1.3"); + setMinimumPDFVersion("1.3"); } } void -QPDFWriter::Members::setDataKey(int objid) +impl::Writer::setDataKey(int objid) { if (encryption) { cur_data_key = QPDF::compute_data_key( - encryption_key, objid, 0, encrypt_use_aes, encryption->getV(), encryption->getR()); + encryption_key, + objid, + 0, + cfg.encrypt_use_aes(), + encryption->getV(), + encryption->getR()); } } unsigned int -QPDFWriter::Members::bytesNeeded(long long n) +impl::Writer::bytesNeeded(long long n) { unsigned int bytes = 0; while (n) { @@ -1226,7 +1246,7 @@ QPDFWriter::Members::bytesNeeded(long long n) } void -QPDFWriter::Members::writeBinary(unsigned long long val, unsigned int bytes) +impl::Writer::writeBinary(unsigned long long val, unsigned int bytes) { if (bytes > sizeof(unsigned long long)) { throw std::logic_error("QPDFWriter::writeBinary called with too many bytes"); @@ -1239,77 +1259,77 @@ QPDFWriter::Members::writeBinary(unsigned long long val, unsigned int bytes) pipeline->write(data, bytes); } -QPDFWriter::Members& -QPDFWriter::Members::write(std::string_view str) +impl::Writer& +impl::Writer::write(std::string_view str) { pipeline->write(str); return *this; } -QPDFWriter::Members& -QPDFWriter::Members::write(std::integral auto val) +impl::Writer& +impl::Writer::write(std::integral auto val) { pipeline->write(std::to_string(val)); return *this; } -QPDFWriter::Members& -QPDFWriter::Members::write(size_t count, char c) +impl::Writer& +impl::Writer::write(size_t count, char c) { pipeline->write(count, c); return *this; } -QPDFWriter::Members& -QPDFWriter::Members::write_name(std::string const& str) +impl::Writer& +impl::Writer::write_name(std::string const& str) { pipeline->write(Name::normalize(str)); return *this; } -QPDFWriter::Members& -QPDFWriter::Members::write_string(std::string const& str, bool force_binary) +impl::Writer& +impl::Writer::write_string(std::string const& str, bool force_binary) { pipeline->write(QPDF_String(str).unparse(force_binary)); return *this; } template -QPDFWriter::Members& -QPDFWriter::Members::write_qdf(Args&&... args) +impl::Writer& +impl::Writer::write_qdf(Args&&... args) { - if (qdf_mode) { + if (cfg.qdf()) { pipeline->write(std::forward(args)...); } return *this; } template -QPDFWriter::Members& -QPDFWriter::Members::write_no_qdf(Args&&... args) +impl::Writer& +impl::Writer::write_no_qdf(Args&&... args) { - if (!qdf_mode) { + if (!cfg.qdf()) { pipeline->write(std::forward(args)...); } return *this; } void -QPDFWriter::Members::adjustAESStreamLength(size_t& length) +impl::Writer::adjustAESStreamLength(size_t& length) { - if (encryption && !cur_data_key.empty() && encrypt_use_aes) { + if (encryption && !cur_data_key.empty() && cfg.encrypt_use_aes()) { // Stream length will be padded with 1 to 16 bytes to end up as a multiple of 16. It will // also be prepended by 16 bits of random data. length += 32 - (length & 0xf); } } -QPDFWriter::Members& -QPDFWriter::Members::write_encrypted(std::string_view str) +impl::Writer& +impl::Writer::write_encrypted(std::string_view str) { if (!(encryption && !cur_data_key.empty())) { write(str); - } else if (encrypt_use_aes) { + } else if (cfg.encrypt_use_aes()) { write(pl::pipe(str, true, cur_data_key)); } else { write(pl::pipe(str, cur_data_key)); @@ -1319,7 +1339,7 @@ QPDFWriter::Members::write_encrypted(std::string_view str) } void -QPDFWriter::Members::computeDeterministicIDData() +impl::Writer::computeDeterministicIDData() { if (!id2.empty()) { // Can't happen in the code @@ -1331,7 +1351,7 @@ QPDFWriter::Members::computeDeterministicIDData() } int -QPDFWriter::Members::openObject(int objid) +impl::Writer::openObject(int objid) { if (objid == 0) { objid = next_objid++; @@ -1342,7 +1362,7 @@ QPDFWriter::Members::openObject(int objid) } void -QPDFWriter::Members::closeObject(int objid) +impl::Writer::closeObject(int objid) { // Write a newline before endobj as it makes the file easier to repair. write("\nendobj\n").write_qdf("\n"); @@ -1351,7 +1371,7 @@ QPDFWriter::Members::closeObject(int objid) } void -QPDFWriter::Members::assignCompressedObjectNumbers(QPDFObjGen og) +impl::Writer::assignCompressedObjectNumbers(QPDFObjGen og) { int objid = og.getObj(); if (og.getGen() != 0 || !object_stream_to_objects.contains(objid)) { @@ -1366,7 +1386,7 @@ QPDFWriter::Members::assignCompressedObjectNumbers(QPDFObjGen og) } void -QPDFWriter::Members::enqueue(QPDFObjectHandle const& object) +impl::Writer::enqueue(QPDFObjectHandle const& object) { if (object.indirect()) { util::assertion( @@ -1380,7 +1400,7 @@ QPDFWriter::Members::enqueue(QPDFObjectHandle const& object) "Use QPDF::copyForeignObject to add objects from another file." // ); - if (qdf_mode && object.isStreamOfType("/XRef")) { + if (cfg.qdf() && object.isStreamOfType("/XRef")) { // As a special case, do not output any extraneous XRef streams in QDF mode. Doing so // will confuse fix-qdf, which expects to see only one XRef stream at the end of the // file. This case can occur when creating a QDF from a file with object streams when @@ -1406,10 +1426,10 @@ QPDFWriter::Members::enqueue(QPDFObjectHandle const& object) if (og.getGen() == 0 && object_stream_to_objects.contains(og.getObj())) { // For linearized files, uncompressed objects go at end, and we take care of // assigning numbers to them elsewhere. - if (!linearized) { + if (!cfg.linearize()) { assignCompressedObjectNumbers(og); } - } else if (!direct_stream_lengths && object.isStream()) { + } else if (!cfg.direct_stream_lengths() && object.isStream()) { // reserve next object ID for length ++next_objid; } @@ -1418,7 +1438,7 @@ QPDFWriter::Members::enqueue(QPDFObjectHandle const& object) return; } - if (linearized) { + if (cfg.linearize()) { return; } @@ -1437,9 +1457,9 @@ QPDFWriter::Members::enqueue(QPDFObjectHandle const& object) } void -QPDFWriter::Members::unparseChild(QPDFObjectHandle const& child, size_t level, int flags) +impl::Writer::unparseChild(QPDFObjectHandle const& child, size_t level, int flags) { - if (!linearized) { + if (!cfg.linearize()) { enqueue(child); } if (child.indirect()) { @@ -1450,7 +1470,7 @@ QPDFWriter::Members::unparseChild(QPDFObjectHandle const& child, size_t level, i } void -QPDFWriter::Members::writeTrailer( +impl::Writer::writeTrailer( trailer_e which, int size, bool xref_stream, qpdf_offset_t prev, int linearization_pass) { auto trailer = trimmed_trailer(); @@ -1498,7 +1518,7 @@ QPDFWriter::Members::writeTrailer( } write("<00000000000000000000000000000000>"); } else { - if (linearization_pass == 0 && deterministic_id) { + if (linearization_pass == 0 && cfg.deterministic_id()) { computeDeterministicIDData(); } generateID(encryption.get()); @@ -1517,7 +1537,7 @@ QPDFWriter::Members::writeTrailer( } bool -QPDFWriter::Members::will_filter_stream(QPDFObjectHandle stream) +impl::Writer::will_filter_stream(QPDFObjectHandle stream) { std::string s; [[maybe_unused]] auto [filter, ignore1, ignore2] = will_filter_stream(stream, &s); @@ -1525,23 +1545,23 @@ QPDFWriter::Members::will_filter_stream(QPDFObjectHandle stream) } std::tuple -QPDFWriter::Members::will_filter_stream(QPDFObjectHandle stream, std::string* stream_data) +impl::Writer::will_filter_stream(QPDFObjectHandle stream, std::string* stream_data) { const bool is_root_metadata = stream.isRootMetadata(); bool filter = false; - auto decode_level = stream_decode_level; + auto decode_level = cfg.decode_level(); int encode_flags = 0; Dictionary stream_dict = stream.getDict(); if (stream.getFilterOnWrite()) { - filter = stream.isDataModified() || compress_streams || decode_level != qpdf_dl_none; - if (compress_streams) { + filter = stream.isDataModified() || cfg.compress_streams() || decode_level != qpdf_dl_none; + if (cfg.compress_streams()) { // Don't filter if the stream is already compressed with FlateDecode. This way we don't // make it worse if the original file used a better Flate algorithm, and we don't spend // time and CPU cycles uncompressing and recompressing stuff. This can be overridden // with setRecompressFlate(true). Name Filter = stream_dict["/Filter"]; - if (Filter && !recompress_flate && !stream.isDataModified() && + if (Filter && !cfg.recompress_flate() && !stream.isDataModified() && (Filter == "/FlateDecode" || Filter == "/Fl")) { filter = false; } @@ -1549,10 +1569,10 @@ QPDFWriter::Members::will_filter_stream(QPDFObjectHandle stream, std::string* st if (is_root_metadata && (!encryption || !encryption->getEncryptMetadata())) { filter = true; decode_level = qpdf_dl_all; - } else if (normalize_content && normalized_streams.contains(stream)) { + } else if (cfg.normalize_content() && normalized_streams.contains(stream)) { encode_flags = qpdf_ef_normalize; filter = true; - } else if (filter && compress_streams) { + } else if (filter && cfg.compress_streams()) { encode_flags = qpdf_ef_compress; } } @@ -1598,7 +1618,7 @@ QPDFWriter::Members::will_filter_stream(QPDFObjectHandle stream, std::string* st } void -QPDFWriter::Members::unparseObject( +impl::Writer::unparseObject( QPDFObjectHandle object, size_t level, int flags, size_t stream_length, bool compress) { QPDFObjGen old_og = object.getObjGen(); @@ -1606,11 +1626,11 @@ QPDFWriter::Members::unparseObject( // For non-qdf, "indent" and "indent_large" are a single space between tokens. For qdf, they // include the preceding newline. std::string indent_large = " "; - if (qdf_mode) { + if (cfg.qdf()) { indent_large.append(2 * (level + 1), ' '); indent_large[0] = '\n'; } - std::string_view indent{indent_large.data(), qdf_mode ? indent_large.size() - 2 : 1}; + std::string_view indent{indent_large.data(), cfg.qdf() ? indent_large.size() - 2 : 1}; if (auto const tc = object.getTypeCode(); tc == ::ot_array) { // Note: PDF spec 1.4 implementation note 121 states that Acrobat requires a space after the @@ -1666,7 +1686,7 @@ QPDFWriter::Members::unparseObject( if (need_extensions_adbe) { if (!(have_extensions_other || have_extensions_adbe)) { // We need Extensions and don't have it. Create it here. - QTC::TC("qpdf", "QPDFWriter create Extensions", qdf_mode ? 0 : 1); + QTC::TC("qpdf", "QPDFWriter create Extensions", cfg.qdf() ? 0 : 1); extensions = object.replaceKeyAndGetNew( "/Extensions", QPDFObjectHandle::newDictionary()); } @@ -1771,7 +1791,7 @@ QPDFWriter::Members::unparseObject( if (flags & f_stream) { write(indent_large).write("/Length "); - if (direct_stream_lengths) { + if (cfg.direct_stream_lengths()) { write(stream_length); } else { write(cur_stream_length_id).write(" 0 R"); @@ -1784,7 +1804,7 @@ QPDFWriter::Members::unparseObject( write(indent).write(">>"); } else if (tc == ::ot_stream) { // Write stream data to a buffer. - if (!direct_stream_lengths) { + if (!cfg.direct_stream_lengths()) { cur_stream_length_id = obj[old_og].renumber + 1; } @@ -1805,14 +1825,14 @@ QPDFWriter::Members::unparseObject( unparseObject(stream_dict, 0, flags, cur_stream_length, compress_stream); char last_char = stream_data.empty() ? '\0' : stream_data.back(); write("\nstream\n").write_encrypted(stream_data); - added_newline = newline_before_endstream || (qdf_mode && last_char != '\n'); + added_newline = cfg.newline_before_endstream() || (cfg.qdf() && last_char != '\n'); write(added_newline ? "\nendstream" : "endstream"); } else if (tc == ::ot_string) { std::string val; if (encryption && !(flags & f_in_ostream) && !(flags & f_no_encryption) && !cur_data_key.empty()) { val = object.getStringValue(); - if (encrypt_use_aes) { + if (cfg.encrypt_use_aes()) { Pl_Buffer bufpl("encrypted string"); Pl_AES_PDF pl("aes encrypt string", &bufpl, true, cur_data_key); pl.writeString(val); @@ -1841,7 +1861,7 @@ QPDFWriter::Members::unparseObject( } void -QPDFWriter::Members::writeObjectStreamOffsets(std::vector& offsets, int first_obj) +impl::Writer::writeObjectStreamOffsets(std::vector& offsets, int first_obj) { qpdf_assert_debug(first_obj > 0); bool is_first = true; @@ -1860,7 +1880,7 @@ QPDFWriter::Members::writeObjectStreamOffsets(std::vector& offset } void -QPDFWriter::Members::writeObjectStream(QPDFObjectHandle object) +impl::Writer::writeObjectStream(QPDFObjectHandle object) { // Note: object might be null if this is a place-holder for an object stream that we are // generating from scratch. @@ -1878,7 +1898,7 @@ QPDFWriter::Members::writeObjectStream(QPDFObjectHandle object) std::string stream_buffer_pass1; std::string stream_buffer_pass2; int first_obj = -1; - const bool compressed = compress_streams && !qdf_mode; + const bool compressed = cfg.compress_streams() && !cfg.qdf(); { // Pass 1 auto pp_ostream_pass1 = pipeline_stack.activate(stream_buffer_pass1); @@ -1890,9 +1910,9 @@ QPDFWriter::Members::writeObjectStream(QPDFObjectHandle object) if (first_obj == -1) { first_obj = new_o; } - if (qdf_mode) { + if (cfg.qdf()) { write("%% Object stream: object ").write(new_o).write(", index ").write(count); - if (!suppress_original_object_ids) { + if (!cfg.no_original_object_ids()) { write("; original object ID: ").write(og.getObj()); // For compatibility, only write the generation if non-zero. While object // streams only allow objects with generation 0, if we are generating object @@ -1968,16 +1988,15 @@ QPDFWriter::Members::writeObjectStream(QPDFObjectHandle object) } } write_qdf("\n").write_no_qdf(" ").write(">>\nstream\n").write_encrypted(stream_buffer_pass2); + write(cfg.newline_before_endstream() ? "\nendstream" : "endstream"); if (encryption) { - QTC::TC("qpdf", "QPDFWriter encrypt object stream"); + cur_data_key.clear(); } - write(newline_before_endstream ? "\nendstream" : "endstream"); - cur_data_key.clear(); closeObject(new_stream_id); } void -QPDFWriter::Members::writeObject(QPDFObjectHandle object, int object_stream_index) +impl::Writer::writeObject(QPDFObjectHandle object, int object_stream_index) { QPDFObjGen old_og = object.getObjGen(); @@ -1989,7 +2008,7 @@ QPDFWriter::Members::writeObject(QPDFObjectHandle object, int object_stream_inde indicateProgress(false, false); auto new_id = obj[old_og].renumber; - if (qdf_mode) { + if (cfg.qdf()) { if (page_object_to_seq.contains(old_og)) { write("%% Page ").write(page_object_to_seq[old_og]).write("\n"); } @@ -1998,7 +2017,7 @@ QPDFWriter::Members::writeObject(QPDFObjectHandle object, int object_stream_inde } } if (object_stream_index == -1) { - if (qdf_mode && !suppress_original_object_ids) { + if (cfg.qdf() && !cfg.no_original_object_ids()) { write("%% Original object ID: ").write(object.getObjGen().unparse(' ')).write("\n"); } openObject(new_id); @@ -2011,8 +2030,8 @@ QPDFWriter::Members::writeObject(QPDFObjectHandle object, int object_stream_inde write("\n"); } - if (!direct_stream_lengths && object.isStream()) { - if (qdf_mode) { + if (!cfg.direct_stream_lengths() && object.isStream()) { + if (cfg.qdf()) { if (added_newline) { write("%QDF: ignore_newline\n"); } @@ -2024,7 +2043,7 @@ QPDFWriter::Members::writeObject(QPDFObjectHandle object, int object_stream_inde } std::string -QPDFWriter::Members::getOriginalID1() +impl::Writer::getOriginalID1() { QPDFObjectHandle trailer = qpdf.getTrailer(); if (trailer.hasKey("/ID")) { @@ -2035,7 +2054,7 @@ QPDFWriter::Members::getOriginalID1() } void -QPDFWriter::Members::generateID(bool encrypted) +impl::Writer::generateID(bool encrypted) { // Generate the ID lazily so that we can handle the user's preference to use static or // deterministic ID generation. @@ -2048,7 +2067,7 @@ QPDFWriter::Members::generateID(bool encrypted) std::string result; - if (static_id) { + if (cfg.static_id()) { // For test suite use only... static unsigned char tmp[] = { 0x31, @@ -2080,7 +2099,7 @@ QPDFWriter::Members::generateID(bool encrypted) // that case, would have the same ID regardless of the output file's name. std::string seed; - if (deterministic_id) { + if (cfg.deterministic_id()) { if (encrypted) { throw std::runtime_error( "QPDFWriter: unable to generated a deterministic ID because the file to be " @@ -2125,7 +2144,7 @@ QPDFWriter::Members::generateID(bool encrypted) } void -QPDFWriter::Members::initializeSpecialStreams() +impl::Writer::initializeSpecialStreams() { // Mark all page content streams in case we are filtering or normalizing. int num = 0; @@ -2150,9 +2169,9 @@ QPDFWriter::Members::initializeSpecialStreams() } void -QPDFWriter::Members::preserveObjectStreams() +impl::Writer::preserveObjectStreams() { - auto const& xref = getXRefTable(); + auto const& xref = objects.xref_table(); // Our object_to_object_stream map has to map ObjGen -> ObjGen since we may be generating object // streams out of old objects that have generation numbers greater than zero. However in an // existing PDF, all object stream objects and all objects in them must have generation 0 @@ -2162,7 +2181,7 @@ QPDFWriter::Members::preserveObjectStreams() // objects from being included. auto end = xref.cend(); obj.streams_empty = true; - if (preserve_unreferenced_objects) { + if (cfg.preserve_unreferenced()) { for (auto iter = xref.cbegin(); iter != end; ++iter) { if (iter->second.getType() == 2) { // Pdf contains object streams. @@ -2177,9 +2196,9 @@ QPDFWriter::Members::preserveObjectStreams() if (iter->second.getType() == 2) { // Pdf contains object streams. obj.streams_empty = false; - auto eligible = getCompressibleObjSet(); + auto eligible = objects.compressible_set(); // The object pointed to by iter may be a previous generation, in which case it is - // removed by getCompressibleObjSet. We need to restart the loop (while the object + // removed by compressible_set. We need to restart the loop (while the object // table may contain multiple generations of an object). for (iter = xref.cbegin(); iter != end; ++iter) { if (iter->second.getType() == 2) { @@ -2198,7 +2217,7 @@ QPDFWriter::Members::preserveObjectStreams() } void -QPDFWriter::Members::generateObjectStreams() +impl::Writer::generateObjectStreams() { // Basic strategy: make a list of objects that can go into an object stream. Then figure out // how many object streams are needed so that we can distribute objects approximately evenly @@ -2208,7 +2227,7 @@ QPDFWriter::Members::generateObjectStreams() // This code doesn't do anything with /Extends. - std::vector eligible = getCompressibleObjGens(); + auto eligible = objects.compressible_vector(); size_t n_object_streams = (eligible.size() + 99U) / 100U; initializeTables(2U * n_object_streams); @@ -2237,7 +2256,7 @@ QPDFWriter::Members::generateObjectStreams() } Dictionary -QPDFWriter::Members::trimmed_trailer() +impl::Writer::trimmed_trailer() { // Remove keys from the trailer that necessarily have to be replaced when writing the file. @@ -2264,7 +2283,7 @@ QPDFWriter::Members::trimmed_trailer() // Make document extension level information direct as required by the spec. void -QPDFWriter::Members::prepareFileForWrite() +impl::Writer::prepareFileForWrite() { qpdf.fixDanglingReferences(); auto root = qpdf.getRoot(); @@ -2287,15 +2306,15 @@ QPDFWriter::Members::prepareFileForWrite() } void -QPDFWriter::Members::initializeTables(size_t extra) +impl::Writer::initializeTables(size_t extra) { - auto size = QIntC::to_size(tableSize() + 100) + extra; + auto size = objects.table_size() + 100u + extra; obj.resize(size); new_obj.resize(size); } void -QPDFWriter::Members::doWriteSetup() +impl::Writer::doWriteSetup() { if (did_write_setup) { return; @@ -2304,63 +2323,42 @@ QPDFWriter::Members::doWriteSetup() // Do preliminary setup - if (linearized) { - qdf_mode = false; + if (cfg.linearize()) { + cfg.qdf(false); } - if (pclm) { - stream_decode_level = qpdf_dl_none; - compress_streams = false; + if (cfg.pclm()) { encryption = nullptr; } - if (qdf_mode) { - if (!normalize_content_set) { - normalize_content = true; - } - if (!compress_streams_set) { - compress_streams = false; - } - if (!stream_decode_level_set) { - stream_decode_level = qpdf_dl_generalized; - } - } - if (encryption) { // Encryption has been explicitly set - preserve_encryption = false; - } else if (normalize_content || pclm || qdf_mode) { + cfg.preserve_encryption(false); + } else if (cfg.normalize_content() || cfg.pclm()) { // Encryption makes looking at contents pretty useless. If the user explicitly encrypted // though, we still obey that. - preserve_encryption = false; + cfg.preserve_encryption(false); } - if (preserve_encryption) { + if (cfg.preserve_encryption()) { copyEncryptionParameters(qpdf); } - if (!forced_pdf_version.empty()) { + if (!cfg.forced_pdf_version().empty()) { int major = 0; int minor = 0; - parseVersion(forced_pdf_version, major, minor); - disableIncompatibleEncryption(major, minor, forced_extension_level); + parseVersion(cfg.forced_pdf_version(), major, minor); + disableIncompatibleEncryption(major, minor, cfg.forced_extension_level()); if (compareVersions(major, minor, 1, 5) < 0) { - object_stream_mode = qpdf_o_disable; + cfg.object_streams(qpdf_o_disable); } } - if (qdf_mode || normalize_content) { + if (cfg.qdf() || cfg.normalize_content()) { initializeSpecialStreams(); } - if (qdf_mode) { - // Generate indirect stream lengths for qdf mode since fix-qdf uses them for storing - // recomputed stream length data. Certain streams such as object streams, xref streams, and - // hint streams always get direct stream lengths. - direct_stream_lengths = false; - } - - switch (object_stream_mode) { + switch (cfg.object_streams()) { case qpdf_o_disable: initializeTables(); obj.streams_empty = true; @@ -2374,12 +2372,10 @@ QPDFWriter::Members::doWriteSetup() case qpdf_o_generate: generateObjectStreams(); break; - - // no default so gcc will warn for missing case tag } if (!obj.streams_empty) { - if (linearized) { + if (cfg.linearize()) { // Page dictionaries are not allowed to be compressed objects. for (auto& page: pages) { if (obj[page].object_stream > 0) { @@ -2388,9 +2384,9 @@ QPDFWriter::Members::doWriteSetup() } } - if (linearized || encryption) { - // The document catalog is not allowed to be compressed in linearized files either. It - // also appears that Adobe Reader 8.0.0 has a bug that prevents it from being able to + if (cfg.linearize() || encryption) { + // The document catalog is not allowed to be compressed in cfg.linearized_ files either. + // It also appears that Adobe Reader 8.0.0 has a bug that prevents it from being able to // handle encrypted files with compressed document catalogs, so we disable them in that // case as well. if (obj[root_og].object_stream > 0) { @@ -2413,16 +2409,16 @@ QPDFWriter::Members::doWriteSetup() if (object_stream_to_objects.empty()) { obj.streams_empty = true; } else { - w.setMinimumPDFVersion("1.5"); + setMinimumPDFVersion("1.5"); } } setMinimumPDFVersion(qpdf.getPDFVersion(), qpdf.getExtensionLevel()); final_pdf_version = min_pdf_version; final_extension_level = min_extension_level; - if (!forced_pdf_version.empty()) { - final_pdf_version = forced_pdf_version; - final_extension_level = forced_extension_level; + if (!cfg.forced_pdf_version().empty()) { + final_pdf_version = cfg.forced_pdf_version(); + final_extension_level = cfg.forced_extension_level(); } } @@ -2433,17 +2429,17 @@ QPDFWriter::write() } void -QPDFWriter::Members::write() +impl::Writer::write() { doWriteSetup(); // Set up progress reporting. 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. - events_expected = QIntC::to_int(qpdf.getObjectCount() * (linearized ? 2 : 1)); + events_expected = QIntC::to_int(qpdf.getObjectCount() * (cfg.linearize() ? 2 : 1)); prepareFileForWrite(); - if (linearized) { + if (cfg.linearize()) { writeLinearized(); } else { writeStandard(); @@ -2474,7 +2470,7 @@ QPDFWriter::getWrittenXRefTable() } std::map -QPDFWriter::Members::getWrittenXRefTable() +impl::Writer::getWrittenXRefTable() { std::map result; @@ -2488,7 +2484,7 @@ QPDFWriter::Members::getWrittenXRefTable() } void -QPDFWriter::Members::enqueuePart(std::vector& part) +impl::Writer::enqueuePart(std::vector& part) { for (auto const& oh: part) { enqueue(oh); @@ -2496,7 +2492,7 @@ QPDFWriter::Members::enqueuePart(std::vector& part) } void -QPDFWriter::Members::writeEncryptionDictionary() +impl::Writer::writeEncryptionDictionary() { encryption_dict_objid = openObject(encryption_dict_objid); auto& enc = *encryption; @@ -2505,10 +2501,10 @@ QPDFWriter::Members::writeEncryptionDictionary() write("<<"); if (V >= 4) { write(" /CF << /StdCF << /AuthEvent /DocOpen /CFM "); - write(encrypt_use_aes ? (V < 5 ? "/AESV2" : "/AESV3") : "/V2"); + write(cfg.encrypt_use_aes() ? (V < 5 ? "/AESV2" : "/AESV3") : "/V2"); // The PDF spec says the /Length key is optional, but the PDF previewer on some versions of // MacOS won't open encrypted files without it. - write((V < 5) ? " /Length 16 >> >>" : " /Length 32 >> >>"); + write(V < 5 ? " /Length 16 >> >>" : " /Length 32 >> >>"); if (!encryption->getEncryptMetadata()) { write(" /EncryptMetadata false"); } @@ -2543,10 +2539,10 @@ QPDFWriter::getFinalVersion() } void -QPDFWriter::Members::writeHeader() +impl::Writer::writeHeader() { write("%PDF-").write(final_pdf_version); - if (pclm) { + if (cfg.pclm()) { // PCLm version write("\n%PCLm 1.0\n"); } else { @@ -2563,13 +2559,13 @@ QPDFWriter::Members::writeHeader() } void -QPDFWriter::Members::writeHintStream(int hint_id) +impl::Writer::writeHintStream(int hint_id) { std::string hint_buffer; int S = 0; int O = 0; - bool compressed = compress_streams; - generateHintStream(new_obj, obj, hint_buffer, S, O, compressed); + bool compressed = cfg.compress_streams(); + lin.generateHintStream(new_obj, obj, hint_buffer, S, O, compressed); openObject(hint_id); setDataKey(hint_id); @@ -2597,7 +2593,7 @@ QPDFWriter::Members::writeHintStream(int hint_id) } qpdf_offset_t -QPDFWriter::Members::writeXRefTable(trailer_e which, int first, int last, int size) +impl::Writer::writeXRefTable(trailer_e which, int first, int last, int size) { // There are too many extra arguments to replace overloaded function with defaults in the header // file...too much risk of leaving something off. @@ -2605,7 +2601,7 @@ QPDFWriter::Members::writeXRefTable(trailer_e which, int first, int last, int si } qpdf_offset_t -QPDFWriter::Members::writeXRefTable( +impl::Writer::writeXRefTable( trailer_e which, int first, int last, @@ -2640,7 +2636,7 @@ QPDFWriter::Members::writeXRefTable( } qpdf_offset_t -QPDFWriter::Members::writeXRefStream( +impl::Writer::writeXRefStream( int objid, int max_id, qpdf_offset_t max_offset, trailer_e which, int first, int last, int size) { // There are too many extra arguments to replace overloaded function with defaults in the header @@ -2650,7 +2646,7 @@ QPDFWriter::Members::writeXRefStream( } qpdf_offset_t -QPDFWriter::Members::writeXRefStream( +impl::Writer::writeXRefStream( int xref_id, int max_id, qpdf_offset_t max_offset, @@ -2681,7 +2677,7 @@ QPDFWriter::Members::writeXRefStream( new_obj[xref_id].xref = QPDFXRefEntry(pipeline->getCount()); std::string xref_data; - const bool compressed = compress_streams && !qdf_mode; + const bool compressed = cfg.compress_streams() && !cfg.qdf(); { auto pp_xref = pipeline_stack.activate(xref_data); @@ -2746,7 +2742,7 @@ QPDFWriter::Members::writeXRefStream( } size_t -QPDFWriter::Members::calculateXrefStreamPadding(qpdf_offset_t xref_bytes) +impl::Writer::calculateXrefStreamPadding(qpdf_offset_t xref_bytes) { // This routine is called right after a linearization first pass xref stream has been written // without compression. Calculate the amount of padding that would be required in the worst @@ -2758,7 +2754,7 @@ QPDFWriter::Members::calculateXrefStreamPadding(qpdf_offset_t xref_bytes) } void -QPDFWriter::Members::writeLinearized() +impl::Writer::writeLinearized() { // Optimize file and enqueue objects in order @@ -2772,14 +2768,14 @@ QPDFWriter::Members::writeLinearized() } }; - optimize(obj, skip_stream_parameters); + lin.optimize(obj, skip_stream_parameters); std::vector part4; std::vector part6; std::vector part7; std::vector part8; std::vector part9; - getLinearizedParts(obj, part4, part6, part7, part8, part9); + lin.parts(obj, part4, part6, part7, part8, part9); // Object number sequence: // @@ -2871,7 +2867,7 @@ QPDFWriter::Members::writeLinearized() enqueuePart(part8); enqueuePart(part9); if (next_objid != after_second_half) { - throw std::runtime_error("error encountered after writing part 9 of linearized data"); + throw std::runtime_error("error encountered after writing part 9 of cfg.linearized_ data"); } qpdf_offset_t hint_length = 0; @@ -2884,15 +2880,15 @@ QPDFWriter::Members::writeLinearized() auto pp_md5 = pipeline_stack.popper(); for (int pass: {1, 2}) { if (pass == 1) { - if (!lin_pass1_filename.empty()) { - lin_pass1_file = QUtil::safe_fopen(lin_pass1_filename.c_str(), "wb"); + if (!cfg.linearize_pass1().empty()) { + lin_pass1_file = QUtil::safe_fopen(cfg.linearize_pass1().data(), "wb"); pipeline_stack.activate( pp_pass1, std::make_unique("linearization pass1", lin_pass1_file)); } else { pipeline_stack.activate(pp_pass1, true); } - if (deterministic_id) { + if (cfg.deterministic_id()) { pipeline_stack.activate_md5(pp_md5); } } @@ -2927,7 +2923,7 @@ QPDFWriter::Members::writeLinearized() // If the user supplied any additional header text, write it here after the linearization // parameter dictionary. - write(extra_header_text); + write(cfg.extra_header_text()); // Part 3: first page cross reference table and trailer. @@ -3062,7 +3058,7 @@ QPDFWriter::Members::writeLinearized() write("startxref\n").write(first_xref_offset).write("\n%%EOF\n"); if (pass == 1) { - if (deterministic_id) { + if (cfg.deterministic_id()) { QTC::TC("qpdf", "QPDFWriter linearized deterministic ID", need_xref_stream ? 0 : 1); computeDeterministicIDData(); pp_md5.pop(); @@ -3105,9 +3101,9 @@ QPDFWriter::Members::writeLinearized() } void -QPDFWriter::Members::enqueueObjectsStandard() +impl::Writer::enqueueObjectsStandard() { - if (preserve_unreferenced_objects) { + if (cfg.preserve_unreferenced()) { for (auto const& oh: qpdf.getAllObjects()) { enqueue(oh); } @@ -3127,7 +3123,7 @@ QPDFWriter::Members::enqueueObjectsStandard() } void -QPDFWriter::Members::enqueueObjectsPCLm() +impl::Writer::enqueueObjectsPCLm() { // Image transform stream content for page strip images. Each of this new stream has to come // after every page image strip written in the pclm file. @@ -3151,7 +3147,7 @@ QPDFWriter::Members::enqueueObjectsPCLm() } void -QPDFWriter::Members::indicateProgress(bool decrement, bool finished) +impl::Writer::indicateProgress(bool decrement, bool finished) { if (decrement) { --events_seen; @@ -3185,19 +3181,19 @@ QPDFWriter::registerProgressReporter(std::shared_ptr pr) } void -QPDFWriter::Members::writeStandard() +impl::Writer::writeStandard() { auto pp_md5 = pipeline_stack.popper(); - if (deterministic_id) { + if (cfg.deterministic_id()) { pipeline_stack.activate_md5(pp_md5); } // Start writing writeHeader(); - write(extra_header_text); + write(cfg.extra_header_text()); - if (pclm) { + if (cfg.pclm()) { enqueueObjectsPCLm(); } else { enqueueObjectsStandard(); @@ -3227,7 +3223,7 @@ QPDFWriter::Members::writeStandard() } write("startxref\n").write(xref_offset).write("\n%%EOF\n"); - if (deterministic_id) { + if (cfg.deterministic_id()) { QTC::TC( "qpdf", "QPDFWriter standard deterministic ID", diff --git a/libqpdf/QPDF_linearization.cc b/libqpdf/QPDF_linearization.cc index dfeb2dc..d5522ad 100644 --- a/libqpdf/QPDF_linearization.cc +++ b/libqpdf/QPDF_linearization.cc @@ -1681,7 +1681,7 @@ Lin::pushOutlinesToPart( } void -Lin::getLinearizedParts( +Lin::parts( QPDFWriter::ObjTable const& obj, std::vector& part4, std::vector& part6, diff --git a/libqpdf/QPDF_objects.cc b/libqpdf/QPDF_objects.cc index cd8a849..b8c43dc 100644 --- a/libqpdf/QPDF_objects.cc +++ b/libqpdf/QPDF_objects.cc @@ -1916,7 +1916,7 @@ QPDF::swapObjects(QPDFObjGen og1, QPDFObjGen og2) } size_t -Objects::tableSize() +Objects::table_size() { // If obj_cache is dense, accommodate all object in tables,else accommodate only original // objects. @@ -1936,20 +1936,20 @@ Objects::tableSize() } std::vector -Objects::getCompressibleObjVector() +Objects::compressible_vector() { - return getCompressibleObjGens(); + return compressible(); } std::vector -Objects::getCompressibleObjSet() +Objects::compressible_set() { - return getCompressibleObjGens(); + return compressible(); } template std::vector -Objects::getCompressibleObjGens() +Objects::compressible() { // Return a list of objects that are allowed to be in object streams. Walk through the objects // by traversing the document from the root, including a traversal of the pages tree. This @@ -1969,10 +1969,9 @@ Objects::getCompressibleObjGens() std::vector result; if constexpr (std::is_same_v) { result.reserve(m->obj_cache.size()); - } else if constexpr (std::is_same_v) { - result.resize(max_obj + 1U, false); } else { - throw std::logic_error("Unsupported type in QPDF::getCompressibleObjGens"); + qpdf_static_expect(std::is_same_v); + result.resize(max_obj + 1U, false); } while (!queue.empty()) { auto obj = queue.back(); diff --git a/libqpdf/qpdf/ObjTable.hh b/libqpdf/qpdf/ObjTable.hh index 3a36208..3cac753 100644 --- a/libqpdf/qpdf/ObjTable.hh +++ b/libqpdf/qpdf/ObjTable.hh @@ -75,7 +75,6 @@ class ObjTable: public std::vector return contains(static_cast(oh.getObjectID())); } - protected: inline T& operator[](int id) { diff --git a/libqpdf/qpdf/QPDFJob_private.hh b/libqpdf/qpdf/QPDFJob_private.hh index 8fe9be0..adbcfb5 100644 --- a/libqpdf/qpdf/QPDFJob_private.hh +++ b/libqpdf/qpdf/QPDFJob_private.hh @@ -5,6 +5,7 @@ #include #include +#include #include // A selection of pages from a single input PDF to be included in the output. This corresponds to a @@ -150,7 +151,7 @@ class QPDFJob::Members public: Members(QPDFJob& job) : - log(qcf.log()), + log(d_cfg.log()), inputs(job) { } @@ -168,14 +169,14 @@ class QPDFJob::Members static int constexpr DEFAULT_OI_MIN_AREA = 16384; static int constexpr DEFAULT_II_MIN_BYTES = 1024; - qpdf::Doc::Config qcf; + qpdf::Doc::Config d_cfg; + qpdf::Writer::Config w_cfg; std::shared_ptr log; std::string message_prefix{"qpdf"}; bool warnings{false}; unsigned long encryption_status{0}; bool verbose{false}; std::string password; - bool linearize{false}; bool decrypt{false}; bool remove_restrictions{false}; int split_pages{0}; @@ -206,25 +207,9 @@ class QPDFJob::Members bool force_R5{false}; bool cleartext_metadata{false}; bool use_aes{false}; - bool stream_data_set{false}; - qpdf_stream_data_e stream_data_mode{qpdf_s_compress}; - bool compress_streams{true}; - bool compress_streams_set{false}; - bool recompress_flate{false}; - bool recompress_flate_set{false}; int compression_level{-1}; int jpeg_quality{-1}; - qpdf_stream_decode_level_e decode_level{qpdf_dl_generalized}; - bool decode_level_set{false}; - bool normalize_set{false}; - bool normalize{false}; - bool object_stream_set{false}; - qpdf_object_stream_e object_stream_mode{qpdf_o_preserve}; - bool qdf_mode{false}; - bool preserve_unreferenced_objects{false}; remove_unref_e remove_unreferenced_page_resources{re_auto}; - bool newline_before_endstream{false}; - std::string linearize_pass1; bool coalesce_contents{false}; bool flatten_annotations{false}; int flatten_annotations_required{0}; @@ -234,10 +219,7 @@ class QPDFJob::Members std::string min_version; std::string force_version; bool show_npages{false}; - bool deterministic_id{false}; - bool static_id{false}; bool static_aes_iv{false}; - bool suppress_original_object_id{false}; bool show_encryption{false}; bool show_encryption_key{false}; bool check_linearization{false}; diff --git a/libqpdf/qpdf/QPDFObject_private.hh b/libqpdf/qpdf/QPDFObject_private.hh index 6de7158..70ec885 100644 --- a/libqpdf/qpdf/QPDFObject_private.hh +++ b/libqpdf/qpdf/QPDFObject_private.hh @@ -30,6 +30,11 @@ namespace qpdf class Integer; class Name; class Stream; + + namespace impl + { + class Writer; + } } // namespace qpdf class QPDF_Array final @@ -256,7 +261,7 @@ class QPDF_String final { friend class QPDFObject; friend class qpdf::BaseHandle; - friend class QPDFWriter; + friend class qpdf::impl::Writer; public: static std::shared_ptr create_utf16(std::string const& utf8_val); diff --git a/libqpdf/qpdf/QPDFWriter_private.hh b/libqpdf/qpdf/QPDFWriter_private.hh index b3973b5..f3a9587 100644 --- a/libqpdf/qpdf/QPDFWriter_private.hh +++ b/libqpdf/qpdf/QPDFWriter_private.hh @@ -5,10 +5,332 @@ #include #include +#include +#include // This file is intended for inclusion by QPDFWriter, QPDF, QPDF_optimization and QPDF_linearization // only. +namespace qpdf +{ + namespace impl + { + class Writer; + } + + class Writer: public QPDFWriter + { + public: + class Config + { + public: + Config() = default; + Config(Config const&) = default; + Config(Config&&) = delete; + Config& operator=(Config const&) = default; + Config& operator=(Config&&) = delete; + ~Config() = default; + + Config(bool permissive) : + permissive_(permissive) + { + } + + bool + linearize() const + { + return linearize_; + } + + Config& linearize(bool val); + + std::string const& + linearize_pass1() const + { + return linearize_pass1_; + } + + Config& + linearize_pass1(std::string const& val) + { + linearize_pass1_ = val; + return *this; + } + + bool + preserve_encryption() const + { + return preserve_encryption_; + } + + Config& + preserve_encryption(bool val) + { + preserve_encryption_ = val; + return *this; + } + + bool + encrypt_use_aes() const + { + return encrypt_use_aes_; + } + + Config& + encrypt_use_aes(bool val) + { + encrypt_use_aes_ = val; + return *this; + } + + Config& + default_decode_level(qpdf_stream_decode_level_e val) + { + if (!decode_level_set_) { + decode_level_ = val; + } + return *this; + } + + qpdf_stream_decode_level_e + decode_level() const + { + return decode_level_; + } + + Config& decode_level(qpdf_stream_decode_level_e val); + + qpdf_object_stream_e + object_streams() const + { + return object_streams_; + } + + Config& + object_streams(qpdf_object_stream_e val) + { + object_streams_ = val; + return *this; + } + + bool + compress_streams() const + { + return compress_streams_; + } + + Config& compress_streams(bool val); + + bool + direct_stream_lengths() const + { + return direct_stream_lengths_; + } + + bool + newline_before_endstream() const + { + return newline_before_endstream_; + } + + Config& + newline_before_endstream(bool val) + { + newline_before_endstream_ = val; + return *this; + } + + bool + recompress_flate() const + { + return recompress_flate_; + } + + Config& + recompress_flate(bool val) + { + recompress_flate_ = val; + return *this; + } + + Config& stream_data(qpdf_stream_data_e val); + + std::string const& + forced_pdf_version() const + { + return forced_pdf_version_; + } + + Config& + forced_pdf_version(std::string const& val) + { + forced_pdf_version_ = val; + return *this; + } + + Config& + forced_pdf_version(std::string const& val, int ext) + { + forced_pdf_version_ = val; + forced_extension_level_ = ext; + return *this; + } + + int + forced_extension_level() const + { + return forced_extension_level_; + } + + Config& + forced_extension_level(int val) + { + forced_extension_level_ = val; + return *this; + } + + std::string const& + extra_header_text() + { + return extra_header_text_; + } + + Config& extra_header_text(std::string const& val); + + bool + preserve_unreferenced() const + { + return preserve_unreferenced_; + } + + Config& + preserve_unreferenced(bool val) + { + preserve_unreferenced_ = val; + return *this; + } + + bool + no_original_object_ids() const + { + return no_original_object_ids_; + } + + Config& + no_original_object_ids(bool val) + { + no_original_object_ids_ = val; + return *this; + } + + bool + qdf() const + { + return qdf_; + } + + Config& qdf(bool val); + + bool + normalize_content() const + { + return normalize_content_; + } + + Config& + normalize_content(bool val) + { + normalize_content_ = val; + normalize_content_set_ = true; + return *this; + } + + bool + deterministic_id() const + { + return deterministic_id_; + } + + Config& + deterministic_id(bool val) + { + deterministic_id_ = val; + return *this; + } + + bool + static_id() const + { + return static_id_; + } + + Config& + static_id(bool val) + { + static_id_ = val; + return *this; + } + + bool + pclm() const + { + return pclm_; + } + + Config& pclm(bool val); + + private: + void + usage(std::string const& msg) const + { + if (!permissive_) { + throw QPDFUsage(msg); + } + } + + std::string forced_pdf_version_; + std::string extra_header_text_; + // For linearization only + std::string linearize_pass1_; + + qpdf_object_stream_e object_streams_{qpdf_o_preserve}; + qpdf_stream_decode_level_e decode_level_{qpdf_dl_generalized}; + + int forced_extension_level_{0}; + + bool normalize_content_set_{false}; + bool normalize_content_{false}; + bool compress_streams_{true}; + bool compress_streams_set_{false}; + bool decode_level_set_{false}; + bool recompress_flate_{false}; + bool qdf_{false}; + bool preserve_unreferenced_{false}; + bool newline_before_endstream_{false}; + bool deterministic_id_{false}; + bool static_id_{false}; + bool no_original_object_ids_{false}; + bool direct_stream_lengths_{true}; + bool preserve_encryption_{true}; + bool linearize_{false}; + bool pclm_{false}; + bool encrypt_use_aes_{false}; + + bool permissive_{true}; + }; // class Writer::Config + + Writer() = delete; + Writer(Writer const&) = delete; + Writer(Writer&&) = delete; + Writer& operator=(Writer const&) = delete; + Writer& operator=(Writer&&) = delete; + ~Writer() = default; + + Writer(QPDF& qpdf, Config cfg); + + }; // class Writer +} // namespace qpdf + struct QPDFWriter::Object { int renumber{0}; @@ -24,7 +346,7 @@ struct QPDFWriter::NewObject class QPDFWriter::ObjTable: public ::ObjTable { - friend class QPDFWriter; + friend class qpdf::impl::Writer; public: bool diff --git a/libqpdf/qpdf/QPDF_private.hh b/libqpdf/qpdf/QPDF_private.hh index 79efe70..895b06f 100644 --- a/libqpdf/qpdf/QPDF_private.hh +++ b/libqpdf/qpdf/QPDF_private.hh @@ -22,6 +22,11 @@ namespace qpdf class OffsetBuffer; } // namespace is + namespace impl + { + using Doc = QPDF::Doc; + } + class Doc: public QPDF { public: @@ -271,7 +276,6 @@ class QPDF::Doc class Pages; class ParseGuard; class Resolver; - class Writer; // This is the common base-class for all document components. It is used by the other document // components to access common functionality. It is not meant to be used directly by the user. @@ -286,6 +290,7 @@ class QPDF::Doc ~Common() = default; inline Common(QPDF& qpdf, QPDF::Members* m); + inline Common(Doc& doc); void stopOnError(std::string const& message); void warn(QPDFExc const& e); @@ -546,7 +551,7 @@ class QPDF::Doc::Linearization: Common ~Linearization() = default; Linearization(Doc& doc) : - Common(doc.qpdf, doc.m) + Common(doc) { } @@ -567,7 +572,7 @@ class QPDF::Doc::Linearization: Common // Get lists of all objects in order according to the part of a linearized file that they // belong to. - void getLinearizedParts( + void parts( QPDFWriter::ObjTable const& obj, std::vector& part4, std::vector& part6, @@ -940,7 +945,7 @@ class QPDF::Doc::Objects: Common ~Objects() = default; Objects(Doc& doc) : - Common(doc.qpdf, doc.m), + Common(doc), foreign_(*this), streams_(*this) { @@ -987,18 +992,19 @@ class QPDF::Doc::Objects: Common QPDFObjectHandle makeIndirectFromQPDFObject(std::shared_ptr const& obj); std::shared_ptr getObjectForParser(int id, int gen, bool parse_pdf); std::shared_ptr getObjectForJSON(int id, int gen); - size_t tableSize(); + size_t table_size(); // For QPDFWriter: - std::map const& getXRefTableInternal(); + std::map const& xref_table(); + std::vector compressible_vector(); + std::vector compressible_set(); + + private: // Get a list of objects that would be permitted in an object stream. template - std::vector getCompressibleObjGens(); - std::vector getCompressibleObjVector(); - std::vector getCompressibleObjSet(); + std::vector compressible(); - private: void setTrailer(QPDFObjectHandle obj); void reconstruct_xref(QPDFExc& e, bool found_startxref = true); void read_xref(qpdf_offset_t offset, bool in_stream_recovery = false); @@ -1060,7 +1066,7 @@ class QPDF::Doc::Pages: Common ~Pages() = default; Pages(Doc& doc) : - Common(doc.qpdf, doc.m) + Common(doc) { } @@ -1214,6 +1220,11 @@ inline QPDF::Doc::Common::Common(QPDF& qpdf, QPDF::Members* m) : { } +inline QPDF::Doc::Common::Common(Doc& doc) : + Common(doc.qpdf, doc.m) +{ +} + inline QPDF::Doc::Linearization& QPDF::Doc::linearization() { @@ -1245,7 +1256,7 @@ QPDF::doc() } inline QPDF::Doc::Objects::Foreign::Copier::Copier(QPDF& qpdf) : - Common(qpdf, qpdf.doc().m) + Common(qpdf.doc()) { } diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index 9d7fee2..167e4ea 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -46,7 +46,6 @@ QPDFObjectHandle copy stream 1 QPDF ignoring XRefStm in trailer 0 SF_FlateLzwDecode PNG filter 0 QPDF xref /Index is array 1 -QPDFWriter encrypt object stream 0 QPDF exclude indirect length 0 QPDF exclude encryption dictionary 0 QPDF_Stream pipeStreamData with null pipeline 0